diff --git a/README.md b/README.md index 234ff85ff..b54bc0487 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Scrawl-canvas Library -Version: `8.3.4 - 6 Jan 2021` +Version: `8.4.0 - 2 Feb 2021` Scrawl-canvas website: [scrawl-v8.rikweb.org.uk](https://scrawl-v8.rikweb.org.uk). @@ -46,7 +46,7 @@ There are three main ways to include Scrawl-canvas in your project: 2. Unzip the file to a folder in your project. 3. Import the library into the script code where you will be using it. -Alternatively, a zip package of the v8.3.4 files can be downloaded from this link: [scrawl.rikweb.org.uk/downloads/scrawl-canvas_8-3-4.zip](https://scrawl.rikweb.org.uk/downloads/scrawl-canvas_8-3-4.zip) - this package only includes the minified file. +Alternatively, a zip package of the v8.4.0 files can be downloaded from this link: [scrawl.rikweb.org.uk/downloads/scrawl-canvas_8-4-0.zip](https://scrawl.rikweb.org.uk/downloads/scrawl-canvas_8-4-0.zip) - this package only includes the minified file. ```html @@ -95,7 +95,7 @@ Alternatively, a zip package of the v8.3.4 files can be downloaded from this lin This will pull the requested npm package directly into your web page: ```html ``` diff --git a/demo/canvas-007.html b/demo/canvas-007.html index 3f7b8a5fc..b73313a8a 100644 --- a/demo/canvas-007.html +++ b/demo/canvas-007.html @@ -13,6 +13,9 @@ .controls { grid-template-columns: 1fr 2fr 1fr 2fr; } + img { + display: none; + } @@ -27,13 +30,20 @@

Apply filters at the entity, group and cell level

@@ -71,7 +85,7 @@

Apply filters at the entity, group and cell level

- +

@@ -95,6 +109,8 @@

Notes

Annotated code

+ + diff --git a/demo/canvas-007.js b/demo/canvas-007.js index 0265efa7f..a44e66962 100644 --- a/demo/canvas-007.js +++ b/demo/canvas-007.js @@ -8,9 +8,11 @@ import scrawl from '../source/scrawl.js' // #### Scene setup let canvas = scrawl.library.artefact.mycanvas; +scrawl.importDomImage('.map'); + canvas.set({ fit: 'fill', - backgroundColor: 'lightgray', + backgroundColor: 'beige', css: { border: '1px solid black' } @@ -105,8 +107,13 @@ let wheel2 = wheel1.clone({ // Define filters - need to test them all, plus some user-defined filters -// __Grayscale__ filter +// __Gray__ filter scrawl.makeFilter({ + name: 'gray', + method: 'gray', + +// __Grayscale__ filter +}).clone({ name: 'grayscale', method: 'grayscale', @@ -164,6 +171,25 @@ scrawl.makeFilter({ }).clone({ name: 'yellow', method: 'yellow', + +// __Edge detect__ filter +}).clone({ + name: 'edgeDetect', + method: 'edgeDetect', + +// __Sharpen__ filter +}).clone({ + name: 'sharpen', + method: 'sharpen', +}); + +// __Emboss__ filter +scrawl.makeFilter({ + name: 'emboss', + method: 'emboss', + angle: 225, + strength: 10, + tolerance: 50, }); // __Chroma__ (green screen) filter @@ -230,6 +256,25 @@ scrawl.makeFilter({ blueInBlue: 0.4, }); +// __Offset__ filter +scrawl.makeFilter({ + name: 'offset', + method: 'offset', + offsetX: 12, + offsetY: 12, + opacity: 0.5, +}); + +// __Offset Channels__ filter +scrawl.makeFilter({ + name: 'offsetChannels', + method: 'offsetChannels', + offsetRedX: -12, + offsetGreenY: 12, + offsetBlueX: 3, + offsetBlueY: -3, +}); + // __Pixellate__ filter scrawl.makeFilter({ name: 'pixelate', @@ -244,10 +289,21 @@ scrawl.makeFilter({ scrawl.makeFilter({ name: 'blur', method: 'blur', - radius: 20, - shrinkingRadius: true, - includeAlpha: true, - passes: 3, + radius: 6, + passes: 2, +}); + +// __AreaAlpha__ filter +scrawl.makeFilter({ + name: 'areaAlpha', + method: 'areaAlpha', + tileWidth: 20, + tileHeight: 20, + gutterWidth: 20, + gutterHeight: 20, + offsetX: 8, + offsetY: 8, + areaAlphaLevels: [255, 0, 0, 255], }); // __Matrix__ filter @@ -263,44 +319,111 @@ scrawl.makeFilter({ weights: [-1, -1, -1, -1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 1], }); -// First user-defined filter -let myUDF = scrawl.makeFilter({ - name: 'totalRed', - method: 'userDefined', - - userDefined: ` - for (let i = 0, iz = cache.length; i < iz; i++) { - - data[cache[i]] = 255; - }`, +// __ChannelLevels__ filter +scrawl.makeFilter({ + name: 'channelLevels', + method: 'channelLevels', + red: [50, 200], + green: [60, 220, 150], + blue: [40, 180], + alpha: [], }); -// Second user-defined filter (cloned) -myUDF.clone({ - name: 'venetianBlinds', - level: 9, - - userDefined: ` - let i, iz, j, jz, - level = filter.level || 6, - halfLevel = level / 2, - yw, transparent, pos; - - for (i = localY, iz = localY + localHeight; i < iz; i++) { - - transparent = (i % level > halfLevel) ? true : false; +scrawl.makeFilter({ + name: 'chromakey', + method: 'chromakey', + red: 0, + green: 127, + blue: 0, + opaqueAt: 0.7, + transparentAt: 0.5, +}); - if (transparent) { +scrawl.makeFilter({ + name: 'dropShadow', + actions: [ + { + action: 'blur', + lineIn: 'source-alpha', + lineOut: 'shadow', + radius: 2, + passes: 2, + includeRed: false, + includeGreen: false, + includeBlue: false, + includeAlpha: true, + }, + { + action: 'compose', + lineIn: 'source', + lineMix: 'shadow', + offsetX: 6, + offsetY: 6, + } + ], +}); - yw = (i * iWidth) + 3; - - for (j = localX, jz = localX + localWidth; j < jz; j ++) { +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', + } + ], +}); - pos = yw + (j * 4); - data[pos] = 0; - } - } - }`, +scrawl.makeFilter({ + name: 'noise', + actions: [ + { + action: 'process-image', + asset: 'perlin', + width: 500, + height: 500, + copyWidth: 500, + copyHeight: 500, + lineOut: 'map', + }, + { + action: 'displace', + lineMix: 'map', + scaleX: 20, + scaleY: 30, + transparentEdges: true, + } + ], }); diff --git a/demo/canvas-013.js b/demo/canvas-013.js index a6454387c..4aede97c6 100644 --- a/demo/canvas-013.js +++ b/demo/canvas-013.js @@ -108,26 +108,26 @@ scrawl.makeLine({ }).clone({ name: 'secondLine', - startY: '22%', - endY: '19.2%', + startY: '16.5%', + endY: '13.7%', }).clone({ name: 'thirdLine', startX: '20%', - startY: '18.5%', + startY: '14%', endX: '85%', - endY: '18.5%', + endY: '14%', }); // ##### makeQuadratic factory function scrawl.makeQuadratic({ name: 'firstQuad', startX: '5%', - startY: '30.5%', + startY: '20%', controlX: '50%', - controlY: '22%', + controlY: '15%', endX: '95%', - endY: '30.5%', + endY: '20%', lineWidth: 3, lineCap: 'round', strokeStyle: 'darkseagreen', @@ -138,28 +138,30 @@ scrawl.makeQuadratic({ }).clone({ name: 'secondQuad', - startX: '7%', - controlY: '18.5%', - endX: '93%', + startX: '12%', + endX: '88%', + startY: '21.5%', + endY: '21.5%', }).clone({ name: 'thirdQuad', - startX: '9%', - controlY: '15%', - endX: '91%', + startX: '19%', + endX: '81%', + startY: '23%', + endY: '23%', }); // ##### makeBezier factory function scrawl.makeBezier({ name: 'firstBezier', startX: '5%', - startY: '36%', + startY: '27%', startControlX: '40%', - startControlY: '31%', + startControlY: '22%', endControlX: '60%', - endControlY: '41%', + endControlY: '32%', endX: '95%', - endY: '36%', + endY: '27%', lineWidth: 3, lineCap: 'round', strokeStyle: 'linen', @@ -171,15 +173,15 @@ scrawl.makeBezier({ }).clone({ name: 'secondBezier', startX: '7%', - startControlY: '25%', - endControlY: '47%', + startControlY: '18%', + endControlY: '36%', endX: '93%', }).clone({ name: 'thirdBezier', startX: '9%', - startControlY: '19%', - endControlY: '53%', + startControlY: '14%', + endControlY: '40%', endX: '91%', }); diff --git a/demo/canvas-015.html b/demo/canvas-015.html index 1e5f896b9..8751b0e20 100644 --- a/demo/canvas-015.html +++ b/demo/canvas-015.html @@ -30,6 +30,7 @@

Test purpose

+

Note: this Demo will appear differently on webkit-based (for example: Safari) and other browsers because webkit engines have difficulty understanding font strings that include a bold keyword. To achieve boldness in these browsers, use a font face that already has boldness applied to it.

Annotated code

diff --git a/demo/canvas-016.html b/demo/canvas-016.html index 5ceff0531..d3aad8c08 100644 --- a/demo/canvas-016.html +++ b/demo/canvas-016.html @@ -13,6 +13,9 @@ .controls { grid-template-columns: 1fr 2fr 2fr 1fr 2fr 2fr; } + canvas { + font-size: 120%; + } @@ -152,6 +155,9 @@

Phrase entity position and font attributes; Block mimic functionality

+ + +
 
@@ -159,6 +165,25 @@

Phrase entity position and font attributes; Block mimic functionality

Font size
 
diff --git a/demo/canvas-016.js b/demo/canvas-016.js index 025f7dc77..d09749243 100644 --- a/demo/canvas-016.js +++ b/demo/canvas-016.js @@ -185,4 +185,4 @@ document.querySelector('#style').options.selectedIndex = 0; document.querySelector('#variant').options.selectedIndex = 0; document.querySelector('#family').options.selectedIndex = 0; document.querySelector('#size_px').value = 16; -document.querySelector('#size_string').options.selectedIndex = 4; +document.querySelector('#size_string').options.selectedIndex = 0; diff --git a/demo/canvas-017.html b/demo/canvas-017.html index 6a6d6a5c9..3e79dabee 100644 --- a/demo/canvas-017.html +++ b/demo/canvas-017.html @@ -61,14 +61,11 @@

Phrase entity - test lineHeight, letterSpacing and justify attributes
S
Font size
diff --git a/demo/canvas-017.js b/demo/canvas-017.js index d56ba69c1..6b73e8338 100644 --- a/demo/canvas-017.js +++ b/demo/canvas-017.js @@ -157,7 +157,7 @@ document.querySelector('#letterSpacing').value = 0; document.querySelector('#justify').options.selectedIndex = 0; document.querySelector('#family').options.selectedIndex = 0; document.querySelector('#size_px').value = 16; -document.querySelector('#size_string').options.selectedIndex = 4; +document.querySelector('#size_string').options.selectedIndex = 0; // #### Development and testing diff --git a/demo/canvas-019.html b/demo/canvas-019.html index 9f1d625ec..2d435827d 100644 --- a/demo/canvas-019.html +++ b/demo/canvas-019.html @@ -51,6 +51,7 @@

Test purpose

  • Check that all canvases display as expected, with in-focus areas of the image centred either on the mouse cursor, or in the middle of the canvas.
  • +

    Known issue: generating the blurred images from the originals takes a very long time in some browsers (for example: Safari). once the images are generated, then the Demo should be performant across all browsers.

    Annotated code

    diff --git a/demo/canvas-022.js b/demo/canvas-022.js index 36f7b3349..987d507bc 100644 --- a/demo/canvas-022.js +++ b/demo/canvas-022.js @@ -27,6 +27,7 @@ let cellGradient = scrawl.makeGradient({ let gridGradient = scrawl.makeGradient({ name: 'red-blue', endX: '100%', + endY: '100%', }) .updateColor(0, 'red') .updateColor(500, 'gold') diff --git a/demo/canvas-023.html b/demo/canvas-023.html index d765dc403..0ec4e0baa 100644 --- a/demo/canvas-023.html +++ b/demo/canvas-023.html @@ -192,6 +192,7 @@

    Test purpose

  • Add controls to manipulate the Grid's positioning (start, handle, offset)
  • Add controls to manipulate the Grid's roll, scale and flip attributes
  • +

    Known issue: in webkit-based browsers (for example: Safari), setting the Highlight fill or Grid line stroke to a video will lead to that video being displayed across the grid as if it was the Base fill

    Annotated code

    diff --git a/demo/canvas-025.html b/demo/canvas-025.html index 25478fc1e..7295fba50 100644 --- a/demo/canvas-025.html +++ b/demo/canvas-025.html @@ -59,7 +59,7 @@

    Test purpose

  • Check that when the browser selects a different srcset image, the canvas displays that image
  • Check that the canvas always displays the entire image, not a part of it
  • - +

    Known issue: Webkit based browsers (for example: Safari) will load an appropriately sized image initially, but does not respond by uploading additional images as the browser dimensiopns change.

    Annotated code

    diff --git a/demo/canvas-027.js b/demo/canvas-027.js index 7d9dd2190..3c442352b 100644 --- a/demo/canvas-027.js +++ b/demo/canvas-027.js @@ -170,13 +170,21 @@ scrawl.makePhrase({ // Turn the swans pink scrawl.makeFilter({ - name: 'red', - method: 'red', -}).clone({ - - name: 'chroma', - method: 'chroma', - ranges: [[0, 0, 0, 190, 190, 190]], + name: 'swan-mask', + + actions: [ + { + action: 'threshold', + level: 200, + low: [0, 0, 0], + high: [255, 0, 0], + }, + { + action: 'channels-to-alpha', + includeGreen: false, + includeBlue: false, + }, + ], }); scrawl.makePicture({ @@ -195,7 +203,7 @@ scrawl.makePicture({ copyStartY: '25%', - filters: ['chroma', 'red'], + filters: ['swan-mask'], globalAlpha: 0.01, @@ -434,7 +442,7 @@ let vtPhrase = scrawl.makePhrase({ name: 'test-video-time-phrase', family: 'monospace', - size: '2em', + size: '1em', weight: '700', startX: '1%', diff --git a/demo/canvas-029.html b/demo/canvas-029.html index db6b05e5e..5f4dd6da7 100644 --- a/demo/canvas-029.html +++ b/demo/canvas-029.html @@ -56,7 +56,7 @@

    Test purpose

  • Check that the gradient appears as expected when it is horizontal (left-right)
  • Check that the gradient appears as expected for both cell-locked and entity-locked gradients
  • - +

    Known issue: this Demo is excessively slow in Webkit-based browsers (for instance: Safari)

    Annotated code

    diff --git a/demo/canvas-029.js b/demo/canvas-029.js index bdf1da5c9..574d98754 100644 --- a/demo/canvas-029.js +++ b/demo/canvas-029.js @@ -19,7 +19,6 @@ let mygradient = scrawl.makeGradient({ name: 'gradient-1', endX: '100%', - // endY: '100%', paletteStart: 10, paletteEnd: 990, @@ -172,10 +171,7 @@ scrawl.makePhrase({ name: 'test-phrase-9', startX: '58%', - startY: '40%', - - width: '65%', - scale: 0.65, + scale: 0.8, letterSpacing: 3, lockFillStyleToEntity: true, diff --git a/demo/canvas-037.html b/demo/canvas-037.html index c2fa1d51d..42e45ed45 100644 --- a/demo/canvas-037.html +++ b/demo/canvas-037.html @@ -28,7 +28,7 @@

    Test purpose

  • Add event listeners that allow the user to zoom the image in/out within the Picture. Zooming out should be constrained so that no blank space appears at the Picture's edges: when the image edge zooms past the Picture edge, it sticks to the edge. Wherever possible (as constrained by edges), the zoom effect should center on the mouse cursor.
  • Add event listeners that allow the user to drag the image within the Picture; again, drag should be constrained so that no blank space appears at the Picture's edges
  • - +

    Known issue: the scroll functionality in Firefox is very much less responsive than in other browsers. This is not an issue with Scrawl-canvas, but rather with the coding of the Demo.

    Annotated code

    diff --git a/demo/canvas-040.html b/demo/canvas-040.html index beb0d5bc9..ed93913d5 100644 --- a/demo/canvas-040.html +++ b/demo/canvas-040.html @@ -47,6 +47,8 @@

    Trace out a Shape path over time

    Test purpose

    +

    Note that this functionality - for reasons unknown - does not work on Safari-based browsers.

    +

    Results from each test will appear in the console

    +

    Known issue: this Demo is far less responsive in Firefox than in other browsers.

    Annotated code

    diff --git a/demo/component-007.html b/demo/component-007.html index 03484612d..1c4ad528e 100644 --- a/demo/component-007.html +++ b/demo/component-007.html @@ -31,7 +31,10 @@

    Factory functions to create more complex, compound entitys

    Test purpose

    Annotated code

    diff --git a/demo/dom-002.html b/demo/dom-002.html index 217ad26d9..a9d5c869f 100644 --- a/demo/dom-002.html +++ b/demo/dom-002.html @@ -152,6 +152,7 @@

    Test purpose

  • Set a stack element to pivot to another element
  • Check the attributes that allow a pivoted element to take into account the host element's handle and rotation values
  • +

    Note: transform-style: preserve-3d issue? Safari displays differently from other browsers. See Things to Watch Out for When Working with CSS 3D.

    Annotated code

    diff --git a/demo/dom-008.html b/demo/dom-008.html index 11dc27c79..bd47e6f15 100644 --- a/demo/dom-008.html +++ b/demo/dom-008.html @@ -41,6 +41,7 @@

    Test purpose

  • Animate the cube by animating just the one element to which the others are pivoted
  • (Cube animations up to scrawl v7 relied on direct manipulation of quaternions in user code; this should all be hidden in v8, leaving the user able to animate the cube entirely via rotation attributes - pitch, yaw, roll)
  • +

    Note: transform-style: preserve-3d or backface-visibility: hidden issue? Safari displays differently from other browsers. See Things to Watch Out for When Working with CSS 3D.

    Annotated code

    diff --git a/demo/dom-013.html b/demo/dom-013.html index 9d7fd5476..3334a711a 100644 --- a/demo/dom-013.html +++ b/demo/dom-013.html @@ -79,8 +79,11 @@

    Test purpose

  • Position and rotate the canvas within the stack, checking repeatedly that both Wheel entitys continue to track the mouse cursor position
  • -

    Note that resizing the canvas will lead to positioning failures. This is a known (lower priority) bug which will be fixed in due course.

    - +

    Known issues

    +

    Annotated code

    diff --git a/demo/dom-015.html b/demo/dom-015.html index 0c8871c09..21c492618 100644 --- a/demo/dom-015.html +++ b/demo/dom-015.html @@ -82,10 +82,6 @@

    Test purpose

  • Drag and drop the element across the stack canvas, to check that element's sensors are correctly positioned and the artefact collision detection works as expected
  • Vary the element's attributes (handle, offset, 3d-rotation, scale) to make sure its sensors correctly account for these changes
  • - -

    Note: there is currently a very annoying, intermittent error which will often occur on first load. While the DOM element positions itself correctly (thanks to a temporary fix for a different bug), it fails get the message across to all the canvas entitys using it as a pivot that its position has corrected. This issue seems to be a consequence of the margin padding set on the Stack element (which has CSS styling "margin: 0 auto;") - when that is removed from the stack, the element and its pivoting entitys all align correctly on first display. Needs to be fixed.

    - -

    For details of the other persistent bug affecting this demo, see demo DOM-013.

    Annotated code

    diff --git a/demo/filters-001.html b/demo/filters-001.html index 37e9e46cf..fa9ce4c5b 100644 --- a/demo/filters-001.html +++ b/demo/filters-001.html @@ -22,37 +22,59 @@

    Parameters for: Blur filter

    Radius
    -
    +
    Passes
    -
    Shrinking radius
    -
    -
    + +
    Opacity
    +
    + +
    Include red
    +
    + +
    + +
    Include green
    +
    +
    -
    Include alpha
    +
    Include blue
    + +
    + +
    Include alpha
    +
    -
    Process horizontal
    -
    +
    Process horizontal
    +
    -
    Process vertical
    -
    +
    Process vertical
    +
    + +
    + - +

    @@ -31,6 +41,7 @@

    Test purpose

    Annotated code

    diff --git a/demo/filters-002.js b/demo/filters-002.js index b4dfefac7..5410f33da 100644 --- a/demo/filters-002.js +++ b/demo/filters-002.js @@ -6,7 +6,8 @@ import scrawl from '../source/scrawl.js'; // #### Scene setup -const canvas = scrawl.library.canvas.mycanvas; +const canvas = scrawl.library.canvas.mycanvas, + filter = scrawl.library.filter; scrawl.importDomImage('.flowers'); @@ -227,13 +228,16 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); testTime = testNow - testTicker; testTicker = testNow; - testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Opacity: ${opacity.value}`; }; }(); @@ -247,5 +251,31 @@ const demoAnimation = scrawl.makeRender({ }); +// #### User interaction +const myFilters = [ + filter.red, + filter.green, + filter.blue, + filter.cyan, + filter.magenta, + filter.yellow, + filter.notred, + filter.notgreen, + filter.notblue, + filter.grayscale, + filter.sepia, + filter.invert +]; + +scrawl.addNativeListener(['input', 'change'], (e) => { + + myFilters.forEach(f => f.set({ opacity: parseFloat(e.target.value) })); + +}, '#opacity'); + +// Setup form +document.querySelector('#opacity').value = 1; + + // #### Development and testing console.log(scrawl.library); diff --git a/demo/filters-003.html b/demo/filters-003.html index 8977bbfb8..9e87993d0 100644 --- a/demo/filters-003.html +++ b/demo/filters-003.html @@ -24,6 +24,9 @@

    Parameters for: brightness, saturation filters

    +
    Opacity
    +
    +
    Level
    diff --git a/demo/filters-003.js b/demo/filters-003.js index a478589db..f158e8c6c 100644 --- a/demo/filters-003.js +++ b/demo/filters-003.js @@ -79,6 +79,9 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let level = document.querySelector('#level'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -86,7 +89,8 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - level: ${filter.brightness.level}`; + Level: ${level.value} + Opacity: ${opacity.value}`; }; }(); @@ -101,13 +105,40 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -scrawl.addNativeListener(['input', 'change'], (e) => { +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: filter.brightness, + + useNativeListener: true, + preventDefault: true, + + updates: { + opacity: ['opacity', 'float'], + level: ['level', 'float'], + }, +}); + +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', - levelFilters.forEach(f => f.set({ level: parseFloat(e.target.value) })); + target: filter.saturation, -}, '#level') + useNativeListener: true, + preventDefault: true, + + updates: { + opacity: ['opacity', 'float'], + level: ['level', 'float'], + }, +}); // Setup form +document.querySelector('#opacity').value = 1; document.querySelector('#level').value = 1; diff --git a/demo/filters-004.html b/demo/filters-004.html index 4ea089088..e9d2c4c40 100644 --- a/demo/filters-004.html +++ b/demo/filters-004.html @@ -33,6 +33,9 @@

    Parameters for: threshold filter

    Level
    +
    Opacity
    +
    +
    diff --git a/demo/filters-004.js b/demo/filters-004.js index cea270e53..39b4aeaee 100644 --- a/demo/filters-004.js +++ b/demo/filters-004.js @@ -57,7 +57,8 @@ let report = function () { let lowCol = document.querySelector('#lowColor'), highCol = document.querySelector('#highColor'), - level = document.querySelector('#level'); + level = document.querySelector('#level'), + opacity = document.querySelector('#opacity'); return function () { @@ -66,9 +67,9 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - low color: ${lowCol.value} - high color: ${highCol.value} - level: ${level.value}`; + Low color: ${lowCol.value}, High color: ${highCol.value} + Level: ${level.value} + Opacity: ${opacity.value}`; }; }(); @@ -93,6 +94,11 @@ scrawl.addNativeListener( (e) => myFilter.set({ level: parseFloat(e.target.value) }), '#level'); +scrawl.addNativeListener( + ['input', 'change'], + (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), + '#opacity'); + scrawl.addNativeListener( ['input', 'change'], (e) => { @@ -125,6 +131,7 @@ scrawl.addNativeListener( document.querySelector('#lowColor').value = '#000000'; document.querySelector('#highColor').value = '#ffffff'; document.querySelector('#level').value = 127; +document.querySelector('#opacity').value = 1; // #### Development and testing diff --git a/demo/filters-005.html b/demo/filters-005.html index 8435f32f0..038914f50 100644 --- a/demo/filters-005.html +++ b/demo/filters-005.html @@ -10,7 +10,7 @@

    Scrawl-canvas v8 - Filters test 010

    -

    Parameters for: matrix, matrix5 filters

    +

    Parameters for: chroma filter

    -
    - -
    -
     
    -
     
    -
     
    -
     
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    Low color
    +
    + +
    High color
    +
    + +
    Opacity
    +
    +
    @@ -83,7 +46,7 @@

    Test purpose

    diff --git a/demo/filters-010.js b/demo/filters-010.js index 441386c6b..00f1ac156 100644 --- a/demo/filters-010.js +++ b/demo/filters-010.js @@ -1,5 +1,5 @@ // # Demo Filters 010 -// Filter parameters: matrix, matrix5 +// Filter parameters: chroma // [Run code](../../demo/filters-010.html) import scrawl from '../source/scrawl.js'; @@ -10,26 +10,19 @@ const canvas = scrawl.library.canvas.mycanvas; scrawl.importDomImage('.flowers'); -// Create the filters -const matrix3 = scrawl.makeFilter({ +// Create the filter +// + Chroma filters can have more than one range; each range array should be added to the `ranges` attribute +const myFilter = scrawl.makeFilter({ - name: 'matrix3', - method: 'matrix', + name: 'chroma', + method: 'chroma', - weights: [0, 0, 0, 0, 1, 0, 0, 0, 0], -}); - -const matrix5 = scrawl.makeFilter({ - - name: 'matrix5', - method: 'matrix5', - - weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ranges: [[0, 0, 0, 92, 127, 92]], }); // Create the target entity -const target = scrawl.makePicture({ +scrawl.makePicture({ name: 'base-piccy', @@ -43,7 +36,7 @@ const target = scrawl.makePicture({ method: 'fill', - filters: ['matrix3'], + filters: ['chroma'], }); @@ -56,7 +49,8 @@ let report = function () { testMessage = document.querySelector('#reportmessage'); let lowCol = document.querySelector('#lowColor'), - highCol = document.querySelector('#highColor'); + highCol = document.querySelector('#highColor'), + opacity = document.querySelector('#opacity'); return function () { @@ -65,8 +59,9 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - matrix3: ${matrix3.weights} - matrix5: ${matrix5.weights}`; + (Low color: ${lowCol.value}, High color: ${highCol.value}) + Range: [${myFilter.ranges}] + Opacity: ${opacity.value}`; }; }(); @@ -82,99 +77,54 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction // Setup form observer functionality -const changeMatrix = function () { +const interpretColors = function () { - const selector = document.querySelector('#selectMatrix'); + const converter = scrawl.makeColor({ + name: 'converter', + }); - return function () { + const lowColor = document.querySelector('#lowColor'); + const highColor = document.querySelector('#highColor'); - target.set({ - filters: [selector.value], - }); - } -}(); -scrawl.addNativeListener(['input', 'change'], changeMatrix, '#selectMatrix'); - -const updateWeights = function () { - - const m11 = document.querySelector('#m11'); - const m12 = document.querySelector('#m12'); - const m13 = document.querySelector('#m13'); - const m14 = document.querySelector('#m14'); - const m15 = document.querySelector('#m15'); - const m21 = document.querySelector('#m21'); - const m22 = document.querySelector('#m22'); - const m23 = document.querySelector('#m23'); - const m24 = document.querySelector('#m24'); - const m25 = document.querySelector('#m25'); - const m31 = document.querySelector('#m31'); - const m32 = document.querySelector('#m32'); - const m33 = document.querySelector('#m33'); - const m34 = document.querySelector('#m34'); - const m35 = document.querySelector('#m35'); - const m41 = document.querySelector('#m41'); - const m42 = document.querySelector('#m42'); - const m43 = document.querySelector('#m43'); - const m44 = document.querySelector('#m44'); - const m45 = document.querySelector('#m45'); - const m51 = document.querySelector('#m51'); - const m52 = document.querySelector('#m52'); - const m53 = document.querySelector('#m53'); - const m54 = document.querySelector('#m54'); - const m55 = document.querySelector('#m55'); - - let weights3, weights5; + let lowRed = 0, + lowGreen = 0, + lowBlue = 0, + highRed = 92, + highGreen = 127, + highBlue = 92; return function () { - weights3 = [parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), - parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), - parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value)]; + converter.convert(lowColor.value); + + lowRed = converter.r; + lowGreen = converter.g; + lowBlue = converter.b; + + converter.convert(highColor.value); - weights5 = [parseFloat(m11.value), parseFloat(m12.value), parseFloat(m13.value), parseFloat(m14.value), parseFloat(m15.value), - parseFloat(m21.value), parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), parseFloat(m25.value), - parseFloat(m31.value), parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), parseFloat(m35.value), - parseFloat(m41.value), parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value), parseFloat(m45.value), - parseFloat(m51.value), parseFloat(m52.value), parseFloat(m53.value), parseFloat(m54.value), parseFloat(m55.value)]; + highRed = converter.r; + highGreen = converter.g; + highBlue = converter.b; - matrix3.set({ - weights: weights3, - }); + myFilter.set({ - matrix5.set({ - weights: weights5, - }); + ranges: [[lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue]], + }) } }(); -scrawl.addNativeListener(['input', 'change'], updateWeights, '.weight'); +scrawl.addNativeListener(['input', 'change'], interpretColors, '.controlItem'); + +scrawl.addNativeListener( + ['input', 'change'], + (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), + '#opacity'); + // Setup form -document.querySelector('#selectMatrix').value = 'matrix3'; -document.querySelector('#m11').value = 0; -document.querySelector('#m12').value = 0; -document.querySelector('#m13').value = 0; -document.querySelector('#m14').value = 0; -document.querySelector('#m15').value = 0; -document.querySelector('#m21').value = 0; -document.querySelector('#m22').value = 0; -document.querySelector('#m23').value = 0; -document.querySelector('#m24').value = 0; -document.querySelector('#m25').value = 0; -document.querySelector('#m31').value = 0; -document.querySelector('#m32').value = 0; -document.querySelector('#m33').value = 1; -document.querySelector('#m34').value = 0; -document.querySelector('#m35').value = 0; -document.querySelector('#m41').value = 0; -document.querySelector('#m42').value = 0; -document.querySelector('#m43').value = 0; -document.querySelector('#m44').value = 0; -document.querySelector('#m45').value = 0; -document.querySelector('#m51').value = 0; -document.querySelector('#m52').value = 0; -document.querySelector('#m53').value = 0; -document.querySelector('#m54').value = 0; -document.querySelector('#m55').value = 0; +document.querySelector('#lowColor').value = '#000000'; +document.querySelector('#highColor').value = '#5c7f5c'; +document.querySelector('#opacity').value = 1; // #### Development and testing diff --git a/demo/filters-011.html b/demo/filters-011.html index 2329cff9b..41c043f9e 100644 --- a/demo/filters-011.html +++ b/demo/filters-011.html @@ -15,43 +15,27 @@ img { display: none; } - canvas { - margin: 0 auto; - }

    Scrawl-canvas v8 - Filters test 011

    -

    Canvas engine filter strings (based on CSS filters)

    +

    Parameters for: chromakey filter

    +

    Where red represents the background color -
    only shows through where pixels have been removed by the filter

    -
    Filter string
    -
    - -
    - -
    Apply to
    -
    - -
    +
    Transparent at
    +
    + +
    Opaque at
    +
    + +
    Key color
    +
    + +
    Opacity
    +
    @@ -65,9 +49,9 @@

    Canvas engine filter strings (based on CSS filters)

    Test purpose

    Annotated code

    diff --git a/demo/filters-011.js b/demo/filters-011.js index e0ce1da71..64fae409f 100644 --- a/demo/filters-011.js +++ b/demo/filters-011.js @@ -1,5 +1,5 @@ // # Demo Filters 011 -// Canvas engine filter strings (based on CSS filters) +// Filter parameters: chromakey // [Run code](../../demo/filters-011.html) import scrawl from '../source/scrawl.js'; @@ -9,9 +9,29 @@ const canvas = scrawl.library.canvas.mycanvas; scrawl.importDomImage('.flowers'); +canvas.setBase({ + backgroundColor: 'red', +}) -// Create the target entitys -const piccy = scrawl.makePicture({ + +// Create the filter +// + Chroma filters can have more than one range; each range array should be added to the `ranges` attribute +const myFilter = scrawl.makeFilter({ + + name: 'chromakey', + method: 'chromakey', + + red: 0, + green: 127, + blue: 0, + + opaqueAt: 1, + transparentAt: 0, +}); + + +// Create the target entity +scrawl.makePicture({ name: 'base-piccy', @@ -24,20 +44,8 @@ const piccy = scrawl.makePicture({ copyHeight: '100%', method: 'fill', -}); -const text = scrawl.makePhrase({ - - name: 'demo-text', - text: 'Hello world', - font: 'bold 70px sans-serif', - start: ['center', 'center'], - handle: ['center', 'center'], - lineHeight: 0.5, - fillStyle: 'aliceblue', - strokeStyle: 'red', - lineWidth: 3, - method: 'fillThenDraw', + filters: ['chromakey'], }); @@ -49,13 +57,21 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let col = document.querySelector('#color'), + trans = document.querySelector('#transparentAt'), + opaq = document.querySelector('#opaqueAt'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); testTime = testNow - testTicker; testTicker = testNow; - testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Key color: ${col.value} + Transparent at: ${trans.value}, Opaque at: ${opaq.value} + Opacity: ${opacity.value}`; }; }(); @@ -70,65 +86,48 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -// No additional work required in the Javascript file to create the CSS filters; these are defined as Strings in the HTML select <option> elements, and will be set on the target entitys as part of the form control user interaction below. -// -// ``` -// -// ``` - -let filterTarget = piccy, - filterString = 'none'; - -// Setup form functionality -let updateTarget = (e) => { - - e.preventDefault(); - e.returnValue = false; - - let val = e.target.value; - - if (val) { - - piccy.set({ filter: 'none'}); - text.set({ filter: 'none'}); - canvas.setBase({ filter: 'none'}); - - if (val === 'picture') filterTarget = piccy; - else if (val === 'phrase') filterTarget = text; - else if (val === 'cell') filterTarget = canvas.base; - - filterTarget.set({ filter: filterString }); - } -}; -scrawl.addNativeListener(['input', 'change'], updateTarget, '#target'); +// Setup form observer functionality +const interpretColors = function () { -let updateFilter = (e) => { + const converter = scrawl.makeColor({ + name: 'converter', + }); - e.preventDefault(); - e.returnValue = false; + const color = document.querySelector('#color'); - if (e.target && e.target.value) { + return function () { + + converter.convert(color.value); - filterString = e.target.value; - filterTarget.set({ filter: filterString }); + myFilter.set({ + red: converter.r, + green: converter.g, + blue: converter.b, + }); } -}; -scrawl.addNativeListener(['input', 'change'], updateFilter, '#filter'); +}(); +scrawl.addNativeListener(['input', 'change'], interpretColors, '.controlItem'); + +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: myFilter, + + useNativeListener: true, + preventDefault: true, + + updates: { + transparentAt: ['transparentAt', 'float'], + opaqueAt: ['opaqueAt', 'float'], + opacity: ['opacity', 'float'], + }, +}); + -document.querySelector('#filter').options.selectedIndex = 0; -document.querySelector('#target').options.selectedIndex = 0; +// Setup form +document.querySelector('#color').value = '#007700'; // #### Development and testing diff --git a/demo/filters-012.html b/demo/filters-012.html index 334ba926d..56aa2959a 100644 --- a/demo/filters-012.html +++ b/demo/filters-012.html @@ -12,6 +12,11 @@ .controls { grid-template-columns: 1fr 2fr 1fr 2fr; } + .matrixcontrols { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + width: 400px; + margin: 0 auto; + } img { display: none; } @@ -23,25 +28,93 @@

    Scrawl-canvas v8 - Filters test 012

    -

    SVG-based filter example: gaussian blur

    +

    Parameters for: matrix, matrix5 filters

    + +
    + +
    + +
    +
     
    +
     
    +
     
    +
     
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    -
    Standard deviation
    -
    +
    Include red
    +
    + +
    -
    Edge mode
    +
    Include green
    +
    + +
    + +
    Include blue
    - + +
    -
    +
    Include alpha
    +
    + +
    - +
    Opacity
    +
    + +
    @@ -52,20 +125,13 @@

    Test purpose

    Annotated code

    - - - - - - diff --git a/demo/filters-012.js b/demo/filters-012.js index ed20d9052..925ecac95 100644 --- a/demo/filters-012.js +++ b/demo/filters-012.js @@ -1,5 +1,5 @@ // # Demo Filters 012 -// SVG-based filter example: gaussian blur +// Filter parameters: matrix, matrix5 // [Run code](../../demo/filters-012.html) import scrawl from '../source/scrawl.js'; @@ -10,8 +10,26 @@ const canvas = scrawl.library.canvas.mycanvas; scrawl.importDomImage('.flowers'); +// Create the filters +const matrix3 = scrawl.makeFilter({ + + name: 'matrix3', + method: 'matrix', + + weights: [0, 0, 0, 0, 1, 0, 0, 0, 0], +}); + +const matrix5 = scrawl.makeFilter({ + + name: 'matrix5', + method: 'matrix5', + + weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +}); + + // Create the target entity -const piccy = scrawl.makePicture({ +const target = scrawl.makePicture({ name: 'base-piccy', @@ -25,22 +43,10 @@ const piccy = scrawl.makePicture({ method: 'fill', - filter: 'url(#svg-blur)', + filters: ['matrix3'], }); -// #### SVG filter -// We create the filter in the HTML script, not here: -// ``` -// -// -// -// -// -// ``` -let feGaussianBlur = document.querySelector('feGaussianBlur'); - - // #### Scene animation // Function to display frames-per-second data, and other information relevant to the demo let report = function () { @@ -49,6 +55,8 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -56,10 +64,9 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - - - -`; + matrix3 weights array: ${matrix3.weights} + matrix5 weights array: ${matrix5.weights} + Opacity: ${opacity.value}`; }; }(); @@ -74,27 +81,147 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -// Setup form functionality -let updateStdDeviation = (e) => { +// Setup form observer functionality +const changeMatrix = function () { + + const selector = document.querySelector('#selectMatrix'); + + return function () { + + target.set({ + filters: [selector.value], + }); + } +}(); +scrawl.addNativeListener(['input', 'change'], changeMatrix, '#selectMatrix'); + +const updateWeights = function () { + + const m11 = document.querySelector('#m11'); + const m12 = document.querySelector('#m12'); + const m13 = document.querySelector('#m13'); + const m14 = document.querySelector('#m14'); + const m15 = document.querySelector('#m15'); + const m21 = document.querySelector('#m21'); + const m22 = document.querySelector('#m22'); + const m23 = document.querySelector('#m23'); + const m24 = document.querySelector('#m24'); + const m25 = document.querySelector('#m25'); + const m31 = document.querySelector('#m31'); + const m32 = document.querySelector('#m32'); + const m33 = document.querySelector('#m33'); + const m34 = document.querySelector('#m34'); + const m35 = document.querySelector('#m35'); + const m41 = document.querySelector('#m41'); + const m42 = document.querySelector('#m42'); + const m43 = document.querySelector('#m43'); + const m44 = document.querySelector('#m44'); + const m45 = document.querySelector('#m45'); + const m51 = document.querySelector('#m51'); + const m52 = document.querySelector('#m52'); + const m53 = document.querySelector('#m53'); + const m54 = document.querySelector('#m54'); + const m55 = document.querySelector('#m55'); + + let weights3, weights5; - e.preventDefault(); - e.returnValue = false; + return function () { + + weights3 = [parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), + parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), + parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value)]; + + weights5 = [parseFloat(m11.value), parseFloat(m12.value), parseFloat(m13.value), parseFloat(m14.value), parseFloat(m15.value), + parseFloat(m21.value), parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), parseFloat(m25.value), + parseFloat(m31.value), parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), parseFloat(m35.value), + parseFloat(m41.value), parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value), parseFloat(m45.value), + parseFloat(m51.value), parseFloat(m52.value), parseFloat(m53.value), parseFloat(m54.value), parseFloat(m55.value)]; + + matrix3.set({ + weights: weights3, + }); + + matrix5.set({ + weights: weights5, + }); + } +}(); +scrawl.addNativeListener(['input', 'change'], updateWeights, '.weight'); + +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: matrix3, - feGaussianBlur.setAttribute('stdDeviation', parseFloat(e.target.value)); -}; -scrawl.addNativeListener(['input', 'change'], updateStdDeviation, '#stdDeviation'); + useNativeListener: true, + preventDefault: true, -let updateEdgeMode = (e) => { + updates: { - e.preventDefault(); - e.returnValue = false; + includeRed: ['includeRed', 'boolean'], + includeGreen: ['includeGreen', 'boolean'], + includeBlue: ['includeBlue', 'boolean'], + includeAlpha: ['includeAlpha', 'boolean'], - feGaussianBlur.setAttribute(`edgeMode`, e.target.value); -}; -scrawl.addNativeListener(['input', 'change'], updateEdgeMode, '#edgeMode'); + opacity: ['opacity', 'float'], + }, +}); + +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: matrix5, + + useNativeListener: true, + preventDefault: true, + + updates: { + + includeRed: ['includeRed', 'boolean'], + includeGreen: ['includeGreen', 'boolean'], + includeBlue: ['includeBlue', 'boolean'], + includeAlpha: ['includeAlpha', 'boolean'], + + opacity: ['opacity', 'float'], + }, +}); -document.querySelector('#stdDeviation').value = 5; -document.querySelector('#edgeMode').options.selectedIndex = 0; +// Setup form +document.querySelector('#selectMatrix').value = 'matrix3'; +document.querySelector('#m11').value = 0; +document.querySelector('#m12').value = 0; +document.querySelector('#m13').value = 0; +document.querySelector('#m14').value = 0; +document.querySelector('#m15').value = 0; +document.querySelector('#m21').value = 0; +document.querySelector('#m22').value = 0; +document.querySelector('#m23').value = 0; +document.querySelector('#m24').value = 0; +document.querySelector('#m25').value = 0; +document.querySelector('#m31').value = 0; +document.querySelector('#m32').value = 0; +document.querySelector('#m33').value = 1; +document.querySelector('#m34').value = 0; +document.querySelector('#m35').value = 0; +document.querySelector('#m41').value = 0; +document.querySelector('#m42').value = 0; +document.querySelector('#m43').value = 0; +document.querySelector('#m44').value = 0; +document.querySelector('#m45').value = 0; +document.querySelector('#m51').value = 0; +document.querySelector('#m52').value = 0; +document.querySelector('#m53').value = 0; +document.querySelector('#m54').value = 0; +document.querySelector('#m55').value = 0; +document.querySelector('#includeRed').options.selectedIndex = 1; +document.querySelector('#includeGreen').options.selectedIndex = 1; +document.querySelector('#includeBlue').options.selectedIndex = 1; +document.querySelector('#includeAlpha').options.selectedIndex = 0; +document.querySelector('#opacity').value = 1; // #### Development and testing diff --git a/demo/filters-013.html b/demo/filters-013.html index 7aa4d9f33..653c9d5a9 100644 --- a/demo/filters-013.html +++ b/demo/filters-013.html @@ -10,39 +10,25 @@

    Scrawl-canvas v8 - Filters test 013

    -

    SVG-based filter example: posterize

    +

    Parameters for: flood filter

    -
    -
    -
    -
    - -
    -
    -
    -
    +
    Flood color
    +
    -
    -
    -
    -
    +
    Opacity
    +
    @@ -57,24 +43,13 @@

    Test purpose

    Annotated code

    - - - - - - - - - - diff --git a/demo/filters-013.js b/demo/filters-013.js index e05ba099e..9f52b701c 100644 --- a/demo/filters-013.js +++ b/demo/filters-013.js @@ -1,5 +1,5 @@ // # Demo Filters 013 -// SVG-based filter example: posterize +// Filter parameters: flood // [Run code](../../demo/filters-013.html) import scrawl from '../source/scrawl.js'; @@ -10,8 +10,21 @@ const canvas = scrawl.library.canvas.mycanvas; scrawl.importDomImage('.flowers'); +// Create the filter +const myFilter = scrawl.makeFilter({ + + name: 'flood', + method: 'flood', + + red: 0, + green: 0, + blue: 0, + alpha: 255 +}); + + // Create the target entity -const piccy = scrawl.makePicture({ +scrawl.makePicture({ name: 'base-piccy', @@ -25,28 +38,10 @@ const piccy = scrawl.makePicture({ method: 'fill', - filter: 'url(#svg-posterize)', + filters: ['flood'], }); -// #### SVG filter -// We create the filter in the HTML script, not here: -// ``` -// -// -// -// -// -// -// -// -// -// ``` -let feFuncR = document.querySelector('feFuncR'), - feFuncG = document.querySelector('feFuncG'), - feFuncB = document.querySelector('feFuncB'); - - // #### Scene animation // Function to display frames-per-second data, and other information relevant to the demo let report = function () { @@ -55,6 +50,9 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let flood = document.querySelector('#flood'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -62,14 +60,8 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - - - - - - - -`; + Flood color: ${flood.value} + Opacity: ${opacity.value}`; }; }(); @@ -84,43 +76,33 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -let r1 = document.querySelector('#r1'), - r2 = document.querySelector('#r2'), - r3 = document.querySelector('#r3'), - r4 = document.querySelector('#r4'); - -let g1 = document.querySelector('#g1'), - g2 = document.querySelector('#g2'), - g3 = document.querySelector('#g3'), - g4 = document.querySelector('#g4'); - -let b1 = document.querySelector('#b1'), - b2 = document.querySelector('#b2'), - b3 = document.querySelector('#b3'), - b4 = document.querySelector('#b4'); - -r1.value = 0.1; -r2.value = 0.4; -r3.value = 0.7; -r4.value = 1; -g1.value = 0.1; -g2.value = 0.4; -g3.value = 0.7; -g4.value = 1; -b1.value = 0.1; -b2.value = 0.4; -b3.value = 0.7; -b4.value = 1; - -// Setup form functionality -let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value} ${r3.value} ${r4.value}`); -scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR'); - -let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value} ${g3.value} ${g4.value}`); -scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG'); - -let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value} ${b3.value} ${b4.value}`); -scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB'); +// Use a color object to convert between CSS hexadecimal and RGB decimal colors +const converter = scrawl.makeColor({ + name: 'converter', +}); + +scrawl.addNativeListener( + ['input', 'change'], + (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), + '#opacity'); + +scrawl.addNativeListener( + ['input', 'change'], + (e) => { + + converter.convert(e.target.value); + + myFilter.set({ + red: converter.r, + green: converter.g, + blue: converter.b, + }); + }, + '#flood'); + +// Setup form +document.querySelector('#flood').value = '#000000'; +document.querySelector('#opacity').value = 1; // #### Development and testing diff --git a/demo/filters-014.html b/demo/filters-014.html index 8306a240a..4519b5f04 100644 --- a/demo/filters-014.html +++ b/demo/filters-014.html @@ -10,33 +10,52 @@

    Scrawl-canvas v8 - Filters test 014

    -

    SVG-based filter example: duotone

    +

    Parameters for: areaAlpha filter

    -
    -
    +
    Tile Width
    +
    + +
    Tile Height
    +
    + +
    Gutter Width
    +
    + +
    Gutter Height
    +
    + +
    Offset X
    +
    -
    -
    +
    Offset Y
    +
    -
    -
    +
    Tile-Tile alpha
    +
    + +
    Tile-Gutter alpha
    +
    + +
    Gutter-Tile alpha
    +
    + +
    Gutter-Gutter alpha
    +
    + +
    Opacity
    +
    @@ -51,30 +70,13 @@

    Test purpose

    Annotated code

    - - - - - - - - - - - - - diff --git a/demo/filters-014.js b/demo/filters-014.js index 1b55d5f9f..d62511d37 100644 --- a/demo/filters-014.js +++ b/demo/filters-014.js @@ -1,5 +1,5 @@ // # Demo Filters 014 -// SVG-based filter example: duotone +// Filter parameters: areaAlpha // [Run code](../../demo/filters-014.html) import scrawl from '../source/scrawl.js'; @@ -10,8 +10,24 @@ const canvas = scrawl.library.canvas.mycanvas; scrawl.importDomImage('.flowers'); +// Create the filter +const myFilter = scrawl.makeFilter({ + + name: 'areaAlpha', + method: 'areaAlpha', + + tileWidth: 10, + tileHeight: 10, + gutterWidth: 10, + gutterHeight: 10, + offsetX: 0, + offsetY: 0, + areaAlphaLevels: [255, 255, 0, 0], +}); + + // Create the target entity -const piccy = scrawl.makePicture({ +scrawl.makePicture({ name: 'base-piccy', @@ -25,34 +41,10 @@ const piccy = scrawl.makePicture({ method: 'fill', - filter: 'url(#svg-duotone)', + filters: ['areaAlpha'], }); -// #### SVG filter -// We create the filter in the HTML script, not here: -// ``` -// -// -// -// - -// -// -// -// -// -// -// -// ``` -let feFuncR = document.querySelector('feFuncR'), - feFuncG = document.querySelector('feFuncG'), - feFuncB = document.querySelector('feFuncB'); - - // #### Scene animation // Function to display frames-per-second data, and other information relevant to the demo let report = function () { @@ -61,6 +53,18 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let tile_width = document.querySelector('#tile_width'), + tile_height = document.querySelector('#tile_height'), + gutter_width = document.querySelector('#gutter_width'), + gutter_height = document.querySelector('#gutter_height'), + alpha_0 = document.querySelector('#alpha_0'), + alpha_1 = document.querySelector('#alpha_1'), + alpha_2 = document.querySelector('#alpha_2'), + alpha_3 = document.querySelector('#alpha_3'), + offset_x = document.querySelector('#offset_x'), + offset_y = document.querySelector('#offset_y'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -68,16 +72,11 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - - - - - - - - - -`; + Tile dimensions - width: ${tile_width.value} height: ${tile_height.value} + Gutter dimensions - width: ${gutter_width.value} height: ${gutter_height.value} + Offset - x: ${offset_x.value} y: ${offset_y.value} + areaAlphaLevels array: [${alpha_0.value}, ${alpha_1.value}, ${alpha_2.value}, ${alpha_3.value}] + Opacity: ${opacity.value}`; }; }(); @@ -92,31 +91,54 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -let r1 = document.querySelector('#r1'), - r2 = document.querySelector('#r2'); +// Setup form observer functionality +scrawl.observeAndUpdate({ -let g1 = document.querySelector('#g1'), - g2 = document.querySelector('#g2'); + event: ['input', 'change'], + origin: '.controlItem', -let b1 = document.querySelector('#b1'), - b2 = document.querySelector('#b2'); + target: myFilter, -r1.value = 0.996; -r2.value = 0.984; -g1.value = 0.125; -g2.value = 0.941; -b1.value = 0.552; -b2.value = 0.478; + useNativeListener: true, + preventDefault: true, -// Setup form functionality -let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value}`); -scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR'); + updates: { -let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value}`); -scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG'); + tile_width: ['tileWidth', 'round'], + tile_height: ['tileHeight', 'round'], + gutter_width: ['gutterWidth', 'round'], + gutter_height: ['gutterHeight', 'round'], + offset_x: ['offsetX', 'round'], + offset_y: ['offsetY', 'round'], + opacity: ['opacity', 'float'], + }, +}); -let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value}`); -scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB'); +scrawl.addNativeListener(['input', 'change'], function (e) { + + let a0 = parseInt(document.querySelector('#alpha_0').value, 10), + a1 = parseInt(document.querySelector('#alpha_1').value, 10), + a2 = parseInt(document.querySelector('#alpha_2').value, 10), + a3 = parseInt(document.querySelector('#alpha_3').value, 10); + + myFilter.set({ + areaAlphaLevels: [a0, a2, a1, a3], + }); + +}, '.alphas'); + +// Setup form +document.querySelector('#tile_width').value = 10; +document.querySelector('#tile_height').value = 10; +document.querySelector('#gutter_width').value = 10; +document.querySelector('#gutter_height').value = 10; +document.querySelector('#offset_x').value = 0; +document.querySelector('#offset_y').value = 0; +document.querySelector('#opacity').value = 1; +document.querySelector('#alpha_0').value = 255; +document.querySelector('#alpha_1').value = 0; +document.querySelector('#alpha_2').value = 255; +document.querySelector('#alpha_3').value = 0; // #### Development and testing diff --git a/demo/filters-015.html b/demo/filters-015.html index 8f6da837d..3a239bd28 100644 --- a/demo/filters-015.html +++ b/demo/filters-015.html @@ -10,53 +10,69 @@

    Scrawl-canvas v8 - Filters test 015

    -

    SVG-based filter example: noise

    +

    Using assets in the filter stream; filter compositing

    -
    Base freq X
    -
    -
    Base freq Y
    -
    -
    Octaves
    -
    - -
    Scale
    -
    - -
    X channel
    -
    - + + + + + + +
    + +
    Line MIX
    +
    +
    -
    Y channel
    -
    - + + + + + + + + + + + +
    +
    Opacity
    +
    + +
    Offset - X
    +
    + +
    Offset - Y
    +
    +
    @@ -69,22 +85,19 @@

    SVG-based filter example: noise

    Test purpose

    Annotated code

    - - - - - - - diff --git a/demo/filters-015.js b/demo/filters-015.js index 3e13e7c3b..1fe28e33a 100644 --- a/demo/filters-015.js +++ b/demo/filters-015.js @@ -1,59 +1,163 @@ // # Demo Filters 015 -// SVG-based filter example: noise +// Using assets in the filter stream; filter compositing -// [Run code](../../demo/filters-0115.html) +// [Run code](../../demo/filters-015.html) import scrawl from '../source/scrawl.js'; // #### Scene setup const canvas = scrawl.library.canvas.mycanvas; +canvas.setBase({ + compileOrder: 1, +}); + +// Create the assets scrawl.importDomImage('.flowers'); +canvas.buildCell({ + + name: 'star-cell', + dimensions: [400, 400], + shown: false, +}); -// Create the target entity -const piccy = scrawl.makePicture({ +scrawl.makeStar({ - name: 'base-piccy', + name: 'my-star', + group: 'star-cell', + + radius1: 200, + radius2: 100, + + roll: 60, + + points: 4, + + start: ['center', 'center'], + handle: ['center', 'center'], + + fillStyle: 'blue', + strokeStyle: 'red', + lineWidth: 10, + method: 'fillThenDraw', +}); + +canvas.buildCell({ + + name: 'wheel-cell', + dimensions: [400, 400], + shown: false, +}); + +scrawl.makeWheel({ + + name: 'my-wheel', + group: 'wheel-cell', + + radius: 150, + + startAngle: 30, + endAngle: -30, + includeCenter: true, + + start: ['center', 'center'], + handle: ['center', 'center'], + + fillStyle: 'green', + strokeStyle: 'yellow', + lineWidth: 10, + method: 'fillThenDraw', + + delta: { + roll: -0.3, + }, +}); - asset: 'iris', - width: '100%', - height: '100%', +// Create the filters +scrawl.makeFilter({ - copyWidth: '100%', - copyHeight: '100%', + name: 'star-filter', + method: 'image', - method: 'fill', + asset: 'star-cell', - filter: 'url(#svg-noise)', + width: 400, + height: 400, + + copyWidth: 400, + copyHeight: 400, + + lineOut: 'star', + +}).clone({ + + name: 'wheel-filter', + asset: 'wheel-cell', + lineOut: 'wheel', +}); + +scrawl.makeFilter({ + + name: 'flower-filter', + method: 'image', + + asset: 'iris', + + width: 200, + height: 200, + + copyX: '25%', + copyY: 100, + copyWidth: '50%', + copyHeight: 200, + + lineOut: 'flower', }); +let composeFilter = scrawl.makeFilter({ -// #### SVG filter -// We create the filter in the HTML script, not here: -// ``` -// -// -// -// -// -// -// ``` -let bfx = document.querySelector('#bfx'), - bfy = document.querySelector('#bfy'), - octaves = document.querySelector('#octaves'), - scale = document.querySelector('#scale'), - xChannelSelector = document.querySelector('#xChannelSelector'), - yChannelSelector = document.querySelector('#yChannelSelector'), - feTurbulence = document.querySelector('feTurbulence'), - feDisplacementMap = document.querySelector('feDisplacementMap'); - -bfx.value = 0.01; -bfy.value = 0.04; -octaves.value = 2; -scale.value = 20; -xChannelSelector.options.selectedIndex = 0; -yChannelSelector.options.selectedIndex = 0; + name: 'block-filter', + method: 'compose', + + lineIn: 'star', + lineMix: 'source', + + offsetX: 30, + offsetY: 30, + + compose: 'source-over', +}); + +// Display the filter in a Block entity +scrawl.makeGradient({ + name: 'linear', + endX: '100%', +}) +.updateColor(0, 'blue') +.updateColor(495, 'red') +.updateColor(500, 'yellow') +.updateColor(505, 'red') +.updateColor(999, 'green'); + +scrawl.makeBlock({ + + name: 'display-block', + start: ['center', 'center'], + handle: ['center', 'center'], + dimensions: ['90%', '90%'], + roll: -20, + + lineWidth: 10, + fillStyle: 'linear', + lockFillStyleToEntity: true, + strokeStyle: 'coral', + method: 'fillThenDraw', + + // Load in the three image filters, then the compose filter to combine two of them + // + the results display in a Block entity! + filters: ['star-filter', 'wheel-filter', 'flower-filter', 'block-filter'], +}); // #### Scene animation @@ -64,6 +168,10 @@ let report = function () { testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let ox = document.querySelector('#offset-x'), + oy = document.querySelector('#offset-y'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -71,11 +179,8 @@ let report = function () { testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - - - - -`; + Offset - x: ${ox.value}, y: ${oy.value} + Opacity: ${opacity.value}`; }; }(); @@ -90,21 +195,35 @@ const demoAnimation = scrawl.makeRender({ // #### User interaction -// Setup form functionality -let baseFrequency = () => feTurbulence.setAttribute('baseFrequency', `${bfx.value} ${bfy.value}`); -scrawl.addNativeListener(['input', 'change'], baseFrequency, '.baseFreq'); +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', -let numOctaves = () => feTurbulence.setAttribute('numOctaves', octaves.value); -scrawl.addNativeListener(['input', 'change'], numOctaves, '#octaves'); + target: composeFilter, -let dmScale = () => feDisplacementMap.setAttribute('scale', scale.value); -scrawl.addNativeListener(['input', 'change'], dmScale, '#scale'); + useNativeListener: true, + preventDefault: true, -let dmX = () => feDisplacementMap.setAttribute('xChannelSelector', xChannelSelector.value); -scrawl.addNativeListener(['input', 'change'], dmX, '#xChannelSelector'); + updates: { + + source: ['lineIn', 'raw'], + destination: ['lineMix', 'raw'], + composite: ['compose', 'raw'], + opacity: ['opacity', 'float'], + 'offset-x': ['offsetX', 'round'], + 'offset-y': ['offsetY', 'round'], + }, +}); -let dmY = () => feDisplacementMap.setAttribute('yChannelSelector', yChannelSelector.value); -scrawl.addNativeListener(['input', 'change'], dmY, '#yChannelSelector'); +// Setup form +document.querySelector('#source').options.selectedIndex = 2; +document.querySelector('#destination').options.selectedIndex = 0; +document.querySelector('#composite').options.selectedIndex = 0; +document.querySelector('#opacity').value = 1; +document.querySelector('#offset-x').value = 30; +document.querySelector('#offset-y').value = 30; // #### Development and testing diff --git a/demo/filters-016.html b/demo/filters-016.html new file mode 100644 index 000000000..099b30d34 --- /dev/null +++ b/demo/filters-016.html @@ -0,0 +1,110 @@ + + + + + + + + Demo Filters 016 + + + + + + +

    Scrawl-canvas v8 - Filters test 016

    +

    Filter blend operation

    + +
    + +
    Line IN
    +
    + +
    + +
    Line MIX
    +
    + +
    + +
    Blend operation
    +
    + +
    + +
    Opacity
    +
    + +
    Offset - X
    +
    + +
    Offset - Y
    +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + + +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-016.js b/demo/filters-016.js new file mode 100644 index 000000000..32a505fb1 --- /dev/null +++ b/demo/filters-016.js @@ -0,0 +1,231 @@ +// # Demo Filters 016 +// Filter blend operation + +// [Run code](../../demo/filters-016.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +canvas.setBase({ + compileOrder: 1, +}); + +// Create the assets +scrawl.importDomImage('.flowers'); + +canvas.buildCell({ + + name: 'star-cell', + dimensions: [400, 400], + shown: false, +}); + +scrawl.makeStar({ + + name: 'my-star', + group: 'star-cell', + + radius1: 200, + radius2: 100, + + roll: 60, + + points: 4, + + start: ['center', 'center'], + handle: ['center', 'center'], + + fillStyle: 'blue', + strokeStyle: 'red', + lineWidth: 10, + method: 'fillThenDraw', +}); + +canvas.buildCell({ + + name: 'wheel-cell', + dimensions: [400, 400], + shown: false, +}); + +scrawl.makeWheel({ + + name: 'my-wheel', + group: 'wheel-cell', + + radius: 150, + + startAngle: 30, + endAngle: -30, + includeCenter: true, + + start: ['center', 'center'], + handle: ['center', 'center'], + + fillStyle: 'green', + strokeStyle: 'yellow', + lineWidth: 10, + method: 'fillThenDraw', + + delta: { + roll: -0.3, + }, +}); + + +// Create the filters +scrawl.makeFilter({ + + name: 'star-filter', + method: 'image', + + asset: 'star-cell', + + width: 400, + height: 400, + + copyWidth: 400, + copyHeight: 400, + + lineOut: 'star', + +}).clone({ + + name: 'wheel-filter', + asset: 'wheel-cell', + lineOut: 'wheel', +}); + +scrawl.makeFilter({ + + name: 'flower-filter', + method: 'image', + + asset: 'iris', + + width: 200, + height: 200, + + copyX: '25%', + copyY: 100, + copyWidth: '50%', + copyHeight: 200, + + lineOut: 'flower', +}); + +let composeFilter = scrawl.makeFilter({ + + name: 'block-filter', + method: 'blend', + + lineIn: 'source', + lineMix: 'star', + + offsetX: 30, + offsetY: 30, + + compose: 'normal', +}); + +// Display the filter in a Block entity + +scrawl.makeGradient({ + name: 'linear', + endX: '100%', +}) +.updateColor(0, 'blue') +.updateColor(495, 'red') +.updateColor(500, 'yellow') +.updateColor(505, 'red') +.updateColor(999, 'green'); + +scrawl.makeBlock({ + + name: 'display-block', + start: ['center', 'center'], + handle: ['center', 'center'], + dimensions: ['90%', '90%'], + roll: -20, + + lineWidth: 10, + fillStyle: 'linear', + lockFillStyleToEntity: true, + strokeStyle: 'coral', + method: 'fillThenDraw', + + // Load in the three image filters, then the compose filter to combine two of them + // + the results display in a Block entity! + filters: ['star-filter', 'wheel-filter', 'flower-filter', 'block-filter'], +}); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + let ox = document.querySelector('#offset-x'), + oy = document.querySelector('#offset-y'), + opacity = document.querySelector('#opacity'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Offset - x: ${ox.value}, y: ${oy.value} + Opacity: ${opacity.value}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: composeFilter, + + useNativeListener: true, + preventDefault: true, + + updates: { + + source: ['lineIn', 'raw'], + destination: ['lineMix', 'raw'], + blend: ['blend', 'raw'], + opacity: ['opacity', 'float'], + 'offset-x': ['offsetX', 'round'], + 'offset-y': ['offsetY', 'round'], + }, +}); + +// Setup form +document.querySelector('#source').options.selectedIndex = 0; +document.querySelector('#destination').options.selectedIndex = 2; +document.querySelector('#blend').options.selectedIndex = 0; +document.querySelector('#opacity').value = 1; +document.querySelector('#offset-x').value = 30; +document.querySelector('#offset-y').value = 30; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-017.html b/demo/filters-017.html new file mode 100644 index 000000000..851eb7b9a --- /dev/null +++ b/demo/filters-017.html @@ -0,0 +1,77 @@ + + + + + + + + Demo Filters 017 + + + + + + +

    Scrawl-canvas v8 - Filters test 017

    +

    Parameters for: displace filter

    + +
    + +
    Scale X
    +
    + +
    Scale Y
    +
    + +
    Offset X
    +
    + +
    Offset Y
    +
    + +
    Transparent edges
    +
    + +
    + +
    Opacity
    +
    + +
    + + + + + + +

    + +
    +

    Test purpose

    + + + +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-017.js b/demo/filters-017.js new file mode 100644 index 000000000..b0d1fd2a0 --- /dev/null +++ b/demo/filters-017.js @@ -0,0 +1,134 @@ +// # Demo Filters 017 +// Filter parameters: displace + +// [Run code](../../demo/filters-017.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the filter +scrawl.makeFilter({ + + name: 'noise', + method: 'image', + + asset: 'perlin', + + width: 500, + height: 500, + + copyWidth: '100%', + copyHeight: '100%', + + lineOut: 'map', +}); + +const myFilter = scrawl.makeFilter({ + + name: 'displace', + method: 'displace', + + lineMix: 'map', + + offsetX: 0, + offsetY: 0, + + scaleX: 1, + scaleY: 1, +}); + + +// Create the target entity +scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filters: ['noise', 'displace'], +}); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + let scale_x = document.querySelector('#scale_x'), + scale_y = document.querySelector('#scale_y'), + offset_x = document.querySelector('#offset_x'), + offset_y = document.querySelector('#offset_y'), + opacity = document.querySelector('#opacity'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Scales - x: ${scale_x.value} y: ${scale_y.value} + Offsets - x: ${offset_x.value} y: ${offset_y.value} + Opacity - ${opacity.value}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: myFilter, + + useNativeListener: true, + preventDefault: true, + + updates: { + + offset_x: ['offsetX', 'round'], + offset_y: ['offsetY', 'round'], + scale_x: ['scaleX', 'float'], + scale_y: ['scaleY', 'float'], + transparent_edges: ['transparentEdges', 'boolean'], + opacity: ['opacity', 'float'], + }, +}); + +// Setup form +document.querySelector('#offset_x').value = 0; +document.querySelector('#offset_y').value = 0; +document.querySelector('#scale_x').value = 1; +document.querySelector('#scale_y').value = 1; +document.querySelector('#opacity').value = 1; +document.querySelector('#transparent_edges').options.selectedIndex = 0; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-018.html b/demo/filters-018.html new file mode 100644 index 000000000..40dc741e0 --- /dev/null +++ b/demo/filters-018.html @@ -0,0 +1,94 @@ + + + + + + + + Demo Filters 018 + + + + + + +

    Scrawl-canvas v8 - Filters test 018

    +

    Parameters for: emboss filter

    + +
    + +
    Angle
    +
    + +
    Strength
    +
    + +
    Use natural grayscale
    +
    + +
    + +
    Smoothing
    +
    + +
    Clamp
    +
    + +
    Opacity
    +
    + +
    Post-process results
    +
    + +
    + +
    Tolerance
    +
    + +
    Keep changed areas
    +
    + +
    + +
    + + + + + + +

    + +
    +

    Test purpose

    + + + +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-018.js b/demo/filters-018.js new file mode 100644 index 000000000..9d1e5ddb4 --- /dev/null +++ b/demo/filters-018.js @@ -0,0 +1,128 @@ +// # Demo Filters 018 +// Filter parameters: emboss + +// [Run code](../../demo/filters-018.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + +canvas.setBase({ + backgroundColor: 'red', +}); + + +// Create the filter +const myFilter = scrawl.makeFilter({ + + name: 'emboss', + method: 'emboss', + angle: 225, + strength: 3, + smoothing: 0, + tolerance: 0, + clamp: 0, + postProcessResults: true, + useNaturalGrayscale: false, + keepOnlyChangedAreas: false, +}); + + +// Create the target entity +scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filters: ['emboss'], +}); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + let strength = document.querySelector('#strength'), + angle = document.querySelector('#angle'), + smoothing = document.querySelector('#smoothing'), + clamp = document.querySelector('#clamp'), + tolerance = document.querySelector('#tolerance'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Angle - ${angle.value}°, Strength - ${strength.value}, Smoothing - ${smoothing.value}, Clamp - ${clamp.value} + Tolerance - ${tolerance.value} + Opacity - ${opacity.value}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: myFilter, + + useNativeListener: true, + preventDefault: true, + + updates: { + + strength: ['strength', 'float'], + angle: ['angle', 'float'], + smoothing: ['smoothing', 'round'], + tolerance: ['tolerance', 'round'], + clamp: ['clamp', 'round'], + postProcessResults: ['postProcessResults', 'boolean'], + useNaturalGrayscale: ['useNaturalGrayscale', 'boolean'], + keepOnlyChangedAreas: ['keepOnlyChangedAreas', 'boolean'], + opacity: ['opacity', 'float'], + }, +}); + +// Setup form +document.querySelector('#strength').value = 3; +document.querySelector('#angle').value = 225; +document.querySelector('#smoothing').value = 0; +document.querySelector('#tolerance').value = 0; +document.querySelector('#clamp').value = 0; +document.querySelector('#postProcessResults').options.selectedIndex = 1; +document.querySelector('#useNaturalGrayscale').options.selectedIndex = 0; +document.querySelector('#keepOnlyChangedAreas').options.selectedIndex = 0; +document.querySelector('#opacity').value = 1; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-019.html b/demo/filters-019.html new file mode 100644 index 000000000..841701ec8 --- /dev/null +++ b/demo/filters-019.html @@ -0,0 +1,199 @@ + + + + + + + + Demo Filters 019 + + + + + + +

    Scrawl-canvas v8 - Filters test 019

    +

    Using a Noise asset with a displace filter

    + +
    + +
    Noise engine
    +
    + +
    + +
    Color output
    +
    + +
    + +
    Monochrome start
    +
    + +
    Monochrome range
    +
    + +
    Gradient start
    +
    + +
    Gradient end
    +
    + +
    Hue start
    +
    + +
    Hue range
    +
    + +
    Saturation
    +
    + +
    Luminosity
    +
    + +
    Octaves
    +
    + +
    Octave function
    +
    + +
    + +
    Persistence
    +
    + +
    Lacunarity
    +
    + +
    Sum function
    +
    + +
    + +
    Sine Frequency Coefficient
    +
    + +
    Smoothing
    +
    + +
    + +
    Modular Amplitude
    +
    + +
    Canvas width
    +
    + +
    Canvas height
    +
    + +
    Scale
    +
    + +
    Size
    +
    + +
    Random seed
    +
    + +
    + +
    + + +
    + + + +

    + +
    +

    Test purpose

    + + + +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-019.js b/demo/filters-019.js new file mode 100644 index 000000000..a5dd8c5ca --- /dev/null +++ b/demo/filters-019.js @@ -0,0 +1,288 @@ +// # Demo Filters 019 +// Using a Noise asset with a displace filter + +// [Run code](../../demo/filters-019.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas, + noiseCanvas = scrawl.library.canvas['noise-canvas']; + +scrawl.importDomImage('.flowers'); + + +let noiseAsset = scrawl.makeNoise({ + + name: 'my-noise-generator', + width: 400, + height: 400, + + noiseEngine: 'improved-perlin', +}); + +// Create the filter +scrawl.makeFilter({ + + name: 'noise', + method: 'image', + + asset: 'my-noise-generator', + + width: 400, + height: 400, + + copyWidth: '100%', + copyHeight: '100%', + + lineOut: 'map', +}); + +scrawl.makeFilter({ + + name: 'displace', + method: 'displace', + + lineMix: 'map', + + scaleX: 20, + scaleY: 20, + + transparentEdges: true, +}); + + +// Create the target entity +scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filters: ['noise', 'displace'], +}); + +scrawl.makePicture({ + + name: 'noisecanvas-display', + group: noiseCanvas.base.name, + + width: '100%', + height: '100%', + copyWidth: '100%', + copyHeight: '100%', + + asset: 'my-noise-generator', +}); + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testMessage = document.querySelector('#reportmessage'), + width = document.querySelector('#width'), + height = document.querySelector('#height'), + octaves = document.querySelector('#octaves'), + sineFrequencyCoeff = document.querySelector('#sineFrequencyCoeff'), + scale = document.querySelector('#scale'), + size = document.querySelector('#size'), + persistence = document.querySelector('#persistence'), + lacunarity = document.querySelector('#lacunarity'), + monochromeStart = document.querySelector('#monochromeStart'), + monochromeRange = document.querySelector('#monochromeRange'), + hueStart = document.querySelector('#hueStart'), + hueRange = document.querySelector('#hueRange'), + saturation = document.querySelector('#saturation'), + luminosity = document.querySelector('#luminosity'), + modularAmplitude = document.querySelector('#modularAmplitude'); + + return function () { + + testMessage.textContent = `Dimensions: width - ${width.value}, height - ${height.value} +Color (monochrome): start: ${monochromeStart.value}; range: ${monochromeRange.value} +Color (hue): start: ${hueStart.value}; range: ${hueRange.value}; saturation: ${saturation.value}; luminosity: ${luminosity.value} +Scale: ${scale.value}; Size: ${size.value} +Octaves: ${octaves.value}; Sine frequency coefficient: ${sineFrequencyCoeff.value} +Persistence: ${persistence.value}; Lacunarity: ${lacunarity.value}; Modular amplitude: ${modularAmplitude.value}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: [canvas, noiseCanvas], + afterShow: report, +}); + + +// #### User interaction +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: noiseAsset, + + useNativeListener: true, + preventDefault: true, + + updates: { + + width: ['width', 'round'], + height: ['height', 'round'], + noiseEngine: ['noiseEngine', 'raw'], + color: ['color', 'raw'], + gradientStart: ['gradientStart', 'raw'], + gradientEnd: ['gradientEnd', 'raw'], + octaveFunction: ['octaveFunction', 'raw'], + octaves: ['octaves', 'round'], + sumFunction: ['sumFunction', 'raw'], + sineFrequencyCoeff: ['sineFrequencyCoeff', 'float'], + smoothing: ['smoothing', 'raw'], + scale: ['scale', 'round'], + size: ['size', 'round'], + seed: ['seed', 'raw'], + persistence: ['persistence', 'float'], + lacunarity: ['lacunarity', 'round'], + modularAmplitude: ['modularAmplitude', 'float'], + monochromeStart: ['monochromeStart', 'round'], + monochromeRange: ['monochromeRange', 'round'], + hueStart: ['hueStart', 'float'], + hueRange: ['hueRange', 'float'], + saturation: ['saturation', 'float'], + luminosity: ['luminosity', 'float'], + }, +}); + +scrawl.addNativeListener(['input', 'change'], function (e) { + + let val = e.target.value, + sfc = document.querySelector('#sineFrequencyCoeff'), + ma = document.querySelector('#modularAmplitude'); + + if (val === 'sine' || val === 'sine-x' || val === 'sine-y') { + + sfc.removeAttribute('disabled'); + ma.setAttribute('disabled', 'true'); + } + else if (val === 'modular') { + + sfc.setAttribute('disabled', 'true'); + ma.removeAttribute('disabled'); + } + else { + + sfc.setAttribute('disabled', 'true'); + ma.setAttribute('disabled', 'true'); + } + +}, '#sumFunction'); + +scrawl.addNativeListener(['input', 'change'], function (e) { + + let val = e.target.value, + s = document.querySelector('#smoothing'); + + if (val === 'simplex') s.setAttribute('disabled', 'true'); + else s.removeAttribute('disabled'); + +}, '#noiseEngine'); + +scrawl.addNativeListener(['input', 'change'], function (e) { + + let val = parseFloat(e.target.value), + p = document.querySelector('#persistence'), + l = document.querySelector('#lacunarity'); + + if (val > 1) { + p.removeAttribute('disabled'); + l.removeAttribute('disabled'); + } + else { + p.setAttribute('disabled', 'true'); + l.setAttribute('disabled', 'true'); + } +}, '#octaves'); + +scrawl.addNativeListener(['input', 'change'], function (e) { + + let val = e.target.value, + ms = document.querySelector('#monochromeStart'), + mr = document.querySelector('#monochromeRange'), + gs = document.querySelector('#gradientStart'), + ge = document.querySelector('#gradientEnd'), + hs = document.querySelector('#hueStart'), + hr = document.querySelector('#hueRange'), + s = document.querySelector('#saturation'), + l = document.querySelector('#luminosity'); + + if (val === 'monochrome') { + ms.removeAttribute('disabled'); + mr.removeAttribute('disabled'); + gs.setAttribute('disabled', 'true'); + ge.setAttribute('disabled', 'true'); + hs.setAttribute('disabled', 'true'); + hr.setAttribute('disabled', 'true'); + s.setAttribute('disabled', 'true'); + l.setAttribute('disabled', 'true'); + } + else if (val === 'hue') { + hs.removeAttribute('disabled'); + hr.removeAttribute('disabled'); + s.removeAttribute('disabled'); + l.removeAttribute('disabled'); + ms.setAttribute('disabled', 'true'); + mr.setAttribute('disabled', 'true'); + gs.setAttribute('disabled', 'true'); + ge.setAttribute('disabled', 'true'); + } + else { + gs.removeAttribute('disabled'); + ge.removeAttribute('disabled'); + ms.setAttribute('disabled', 'true'); + mr.setAttribute('disabled', 'true'); + hs.setAttribute('disabled', 'true'); + hr.setAttribute('disabled', 'true'); + s.setAttribute('disabled', 'true'); + l.setAttribute('disabled', 'true'); + } +}, '#color'); + +// Setup form +document.querySelector('#width').value = 400; +document.querySelector('#height').value = 400; +document.querySelector('#noiseEngine').options.selectedIndex = 1; +document.querySelector('#color').options.selectedIndex = 0; +document.querySelector('#gradientStart').value = '#ff0000'; +document.querySelector('#gradientEnd').value = '#00ff00'; +document.querySelector('#octaveFunction').options.selectedIndex = 0; +document.querySelector('#octaves').value = 1; +document.querySelector('#sumFunction').options.selectedIndex = 0; +document.querySelector('#sineFrequencyCoeff').value = 1; +document.querySelector('#smoothing').options.selectedIndex = 3; +document.querySelector('#scale').value = 50; +document.querySelector('#size').value = 256; +document.querySelector('#seed').value = 'noize'; +document.querySelector('#persistence').value = 0.5; +document.querySelector('#lacunarity').value = 2; +document.querySelector('#modularAmplitude').value = 5; +document.querySelector('#monochromeStart').value = 0; +document.querySelector('#monochromeRange').value = 255; +document.querySelector('#hueStart').value = 0; +document.querySelector('#hueRange').value = 120; +document.querySelector('#saturation').value = 100; +document.querySelector('#luminosity').value = 50; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-020.html b/demo/filters-020.html new file mode 100644 index 000000000..3e36aa555 --- /dev/null +++ b/demo/filters-020.html @@ -0,0 +1,72 @@ + + + + + + + + Demo Filters 020 + + + + + + +

    Scrawl-canvas v8 - Filters test 020

    +

    Parameters for: clampChannels filter

    + +
    + +
    Low red
    +
    + +
    High red
    +
    + +
    Low green
    +
    + +
    High green
    +
    + +
    Low blue
    +
    + +
    High blue
    +
    + +
    Opacity
    +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + + +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-020.js b/demo/filters-020.js new file mode 100644 index 000000000..c43ae3df5 --- /dev/null +++ b/demo/filters-020.js @@ -0,0 +1,121 @@ +// # Demo Filters 020 +// Parameters for: clampChannels filter + +// [Run code](../../demo/filters-020.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +const myFilter = scrawl.makeFilter({ + + name: 'clamp', + method: 'clampChannels', + + lowRed: 0, + lowGreen: 0, + lowBlue: 0, + highRed: 255, + highGreen: 255, + highBlue: 255, + opacity: 1, +}); + + +// Create the target entity +scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filters: ['clamp'], +}); + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + let lowRed = document.querySelector('#low-red'), + lowGreen = document.querySelector('#low-green'), + lowBlue = document.querySelector('#low-blue'), + highRed = document.querySelector('#high-red'), + highGreen = document.querySelector('#high-green'), + highBlue = document.querySelector('#high-blue'), + opacity = document.querySelector('#opacity'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Red: ${lowRed.value} - ${highRed.value} + Green: ${lowGreen.value} - ${highGreen.value} + Blue: ${lowBlue.value} - ${highBlue.value} + Opacity - ${opacity.value}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form observer functionality +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: myFilter, + + useNativeListener: true, + preventDefault: true, + + updates: { + + 'low-red': ['lowRed', 'round'], + 'low-green': ['lowGreen', 'round'], + 'low-blue': ['lowBlue', 'round'], + 'high-red': ['highRed', 'round'], + 'high-green': ['highGreen', 'round'], + 'high-blue': ['highBlue', 'round'], + 'opacity': ['opacity', 'float'], + }, +}); + +// Setup form +document.querySelector('#low-red').value = 0; +document.querySelector('#low-green').value = 0; +document.querySelector('#low-blue').value = 0; +document.querySelector('#high-red').value = 255; +document.querySelector('#high-green').value = 255; +document.querySelector('#high-blue').value = 255; +document.querySelector('#opacity').value = 1; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-501.html b/demo/filters-501.html new file mode 100644 index 000000000..ad1661cbf --- /dev/null +++ b/demo/filters-501.html @@ -0,0 +1,80 @@ + + + + + + + + Demo Filters 501 + + + + + + +

    Scrawl-canvas v8 - Filters test 501

    +

    Canvas engine filter strings (based on CSS filters)

    + +
    + +
    Filter string
    +
    + +
    + +
    Apply to
    +
    + +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + +

    Known issue: Webkit-based browsers (for instance, Safari) have not yet implemented the Canvas context 'filter' attribute functionality.

    +

    Annotated code

    +
    + + + + + + diff --git a/demo/filters-501.js b/demo/filters-501.js new file mode 100644 index 000000000..6f24e8fec --- /dev/null +++ b/demo/filters-501.js @@ -0,0 +1,135 @@ +// # Demo Filters 501 +// Canvas engine filter strings (based on CSS filters) + +// [Run code](../../demo/filters-501.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the target entitys +const piccy = scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', +}); + +const text = scrawl.makePhrase({ + + name: 'demo-text', + text: 'Hello world', + font: 'bold 70px sans-serif', + start: ['center', 'center'], + handle: ['center', 'center'], + lineHeight: 0.5, + fillStyle: 'aliceblue', + strokeStyle: 'red', + lineWidth: 3, + method: 'fillThenDraw', +}); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// No additional work required in the Javascript file to create the CSS filters; these are defined as Strings in the HTML select <option> elements, and will be set on the target entitys as part of the form control user interaction below. +// +// ``` +// +// ``` + +let filterTarget = piccy, + filterString = 'none'; + +// Setup form functionality +let updateTarget = (e) => { + + e.preventDefault(); + e.returnValue = false; + + let val = e.target.value; + + if (val) { + + piccy.set({ filter: 'none'}); + text.set({ filter: 'none'}); + canvas.setBase({ filter: 'none'}); + + if (val === 'picture') filterTarget = piccy; + else if (val === 'phrase') filterTarget = text; + else if (val === 'cell') filterTarget = canvas.base; + + filterTarget.set({ filter: filterString }); + } +}; +scrawl.addNativeListener(['input', 'change'], updateTarget, '#target'); + +let updateFilter = (e) => { + + e.preventDefault(); + e.returnValue = false; + + if (e.target && e.target.value) { + + filterString = e.target.value; + filterTarget.set({ filter: filterString }); + } +}; +scrawl.addNativeListener(['input', 'change'], updateFilter, '#filter'); + +document.querySelector('#filter').options.selectedIndex = 0; +document.querySelector('#target').options.selectedIndex = 0; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-502.html b/demo/filters-502.html new file mode 100644 index 000000000..d8524732a --- /dev/null +++ b/demo/filters-502.html @@ -0,0 +1,73 @@ + + + + + + + + Demo Filters 502 + + + + + + +

    Scrawl-canvas v8 - Filters test 502

    +

    SVG-based filter example: gaussian blur

    + +
    + +
    Standard deviation
    +
    + +
    Edge mode
    +
    + +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + +

    Known issue: Webkit-based browsers (for instance, Safari) have not yet implemented the Canvas context 'filter' attribute functionality.

    +

    Annotated code

    +
    + + + + + + + + + + + + diff --git a/demo/filters-502.js b/demo/filters-502.js new file mode 100644 index 000000000..9dba69b86 --- /dev/null +++ b/demo/filters-502.js @@ -0,0 +1,101 @@ +// # Demo Filters 502 +// SVG-based filter example: gaussian blur + +// [Run code](../../demo/filters-502.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the target entity +const piccy = scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filter: 'url(#svg-blur)', +}); + + +// #### SVG filter +// We create the filter in the HTML script, not here: +// ``` +// +// +// +// +// +// ``` +let feGaussianBlur = document.querySelector('feGaussianBlur'); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + + + +`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form functionality +let updateStdDeviation = (e) => { + + e.preventDefault(); + e.returnValue = false; + + feGaussianBlur.setAttribute('stdDeviation', parseFloat(e.target.value)); +}; +scrawl.addNativeListener(['input', 'change'], updateStdDeviation, '#stdDeviation'); + +let updateEdgeMode = (e) => { + + e.preventDefault(); + e.returnValue = false; + + feGaussianBlur.setAttribute(`edgeMode`, e.target.value); +}; +scrawl.addNativeListener(['input', 'change'], updateEdgeMode, '#edgeMode'); + +document.querySelector('#stdDeviation').value = 5; +document.querySelector('#edgeMode').options.selectedIndex = 0; + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-503.html b/demo/filters-503.html new file mode 100644 index 000000000..4ab7e3069 --- /dev/null +++ b/demo/filters-503.html @@ -0,0 +1,82 @@ + + + + + + + + Demo Filters 503 + + + + + + +

    Scrawl-canvas v8 - Filters test 503

    +

    SVG-based filter example: posterize

    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + +

    Known issue: Webkit-based browsers (for instance, Safari) have not yet implemented the Canvas context 'filter' attribute functionality.

    +

    Annotated code

    +
    + + + + + + + + + + + + + + + + diff --git a/demo/filters-503.js b/demo/filters-503.js new file mode 100644 index 000000000..a856f32a4 --- /dev/null +++ b/demo/filters-503.js @@ -0,0 +1,127 @@ +// # Demo Filters 503 +// SVG-based filter example: posterize + +// [Run code](../../demo/filters-503.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the target entity +const piccy = scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filter: 'url(#svg-posterize)', +}); + + +// #### SVG filter +// We create the filter in the HTML script, not here: +// ``` +// +// +// +// +// +// +// +// +// +// ``` +let feFuncR = document.querySelector('feFuncR'), + feFuncG = document.querySelector('feFuncG'), + feFuncB = document.querySelector('feFuncB'); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + + + + + + + +`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +let r1 = document.querySelector('#r1'), + r2 = document.querySelector('#r2'), + r3 = document.querySelector('#r3'), + r4 = document.querySelector('#r4'); + +let g1 = document.querySelector('#g1'), + g2 = document.querySelector('#g2'), + g3 = document.querySelector('#g3'), + g4 = document.querySelector('#g4'); + +let b1 = document.querySelector('#b1'), + b2 = document.querySelector('#b2'), + b3 = document.querySelector('#b3'), + b4 = document.querySelector('#b4'); + +r1.value = 0.1; +r2.value = 0.4; +r3.value = 0.7; +r4.value = 1; +g1.value = 0.1; +g2.value = 0.4; +g3.value = 0.7; +g4.value = 1; +b1.value = 0.1; +b2.value = 0.4; +b3.value = 0.7; +b4.value = 1; + +// Setup form functionality +let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value} ${r3.value} ${r4.value}`); +scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR'); + +let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value} ${g3.value} ${g4.value}`); +scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG'); + +let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value} ${b3.value} ${b4.value}`); +scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB'); + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-504.html b/demo/filters-504.html new file mode 100644 index 000000000..7f488526e --- /dev/null +++ b/demo/filters-504.html @@ -0,0 +1,82 @@ + + + + + + + + Demo Filters 504 + + + + + + +

    Scrawl-canvas v8 - Filters test 504

    +

    SVG-based filter example: duotone

    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + +

    Known issue: Webkit-based browsers (for instance, Safari) have not yet implemented the Canvas context 'filter' attribute functionality.

    +

    Annotated code

    +
    + + + + + + + + + + + + + + + + + + + diff --git a/demo/filters-504.js b/demo/filters-504.js new file mode 100644 index 000000000..3a4abe653 --- /dev/null +++ b/demo/filters-504.js @@ -0,0 +1,123 @@ +// # Demo Filters 504 +// SVG-based filter example: duotone + +// [Run code](../../demo/filters-504.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the target entity +const piccy = scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filter: 'url(#svg-duotone)', +}); + + +// #### SVG filter +// We create the filter in the HTML script, not here: +// ``` +// +// +// +// + +// +// +// +// +// +// +// +// ``` +let feFuncR = document.querySelector('feFuncR'), + feFuncG = document.querySelector('feFuncG'), + feFuncB = document.querySelector('feFuncB'); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + + + + + + + + + +`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +let r1 = document.querySelector('#r1'), + r2 = document.querySelector('#r2'); + +let g1 = document.querySelector('#g1'), + g2 = document.querySelector('#g2'); + +let b1 = document.querySelector('#b1'), + b2 = document.querySelector('#b2'); + +r1.value = 0.996; +r2.value = 0.984; +g1.value = 0.125; +g2.value = 0.941; +b1.value = 0.552; +b2.value = 0.478; + +// Setup form functionality +let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value}`); +scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR'); + +let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value}`); +scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG'); + +let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value}`); +scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB'); + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/filters-505.html b/demo/filters-505.html new file mode 100644 index 000000000..b323b5004 --- /dev/null +++ b/demo/filters-505.html @@ -0,0 +1,92 @@ + + + + + + + + Demo Filters 505 + + + + + + +

    Scrawl-canvas v8 - Filters test 505

    +

    SVG-based filter example: noise

    + +
    + +
    Base freq X
    +
    +
    Base freq Y
    +
    +
    Octaves
    +
    + +
    Scale
    +
    + +
    X channel
    +
    + +
    + +
    Y channel
    +
    + +
    + +
    + + + + + +

    + +
    +

    Test purpose

    + + +

    Known issue: Webkit-based browsers (for instance, Safari) have not yet implemented the Canvas context 'filter' attribute functionality.

    +

    Annotated code

    +
    + + + + + + + + + + + + + diff --git a/demo/filters-505.js b/demo/filters-505.js new file mode 100644 index 000000000..dc94be1d2 --- /dev/null +++ b/demo/filters-505.js @@ -0,0 +1,111 @@ +// # Demo Filters 505 +// SVG-based filter example: noise + +// [Run code](../../demo/filters-505.html) +import scrawl from '../source/scrawl.js'; + +// #### Scene setup +const canvas = scrawl.library.canvas.mycanvas; + +scrawl.importDomImage('.flowers'); + + +// Create the target entity +const piccy = scrawl.makePicture({ + + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filter: 'url(#svg-noise)', +}); + + +// #### SVG filter +// We create the filter in the HTML script, not here: +// ``` +// +// +// +// +// +// +// ``` +let bfx = document.querySelector('#bfx'), + bfy = document.querySelector('#bfy'), + octaves = document.querySelector('#octaves'), + scale = document.querySelector('#scale'), + xChannelSelector = document.querySelector('#xChannelSelector'), + yChannelSelector = document.querySelector('#yChannelSelector'), + feTurbulence = document.querySelector('feTurbulence'), + feDisplacementMap = document.querySelector('feDisplacementMap'); + +bfx.value = 0.01; +bfy.value = 0.04; +octaves.value = 2; +scale.value = 20; +xChannelSelector.options.selectedIndex = 0; +yChannelSelector.options.selectedIndex = 0; + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + + + + +`; + }; +}(); + + +// Create the Display cycle animation +const demoAnimation = scrawl.makeRender({ + + name: "demo-animation", + target: canvas, + afterShow: report, +}); + + +// #### User interaction +// Setup form functionality +let baseFrequency = () => feTurbulence.setAttribute('baseFrequency', `${bfx.value} ${bfy.value}`); +scrawl.addNativeListener(['input', 'change'], baseFrequency, '.baseFreq'); + +let numOctaves = () => feTurbulence.setAttribute('numOctaves', octaves.value); +scrawl.addNativeListener(['input', 'change'], numOctaves, '#octaves'); + +let dmScale = () => feDisplacementMap.setAttribute('scale', scale.value); +scrawl.addNativeListener(['input', 'change'], dmScale, '#scale'); + +let dmX = () => feDisplacementMap.setAttribute('xChannelSelector', xChannelSelector.value); +scrawl.addNativeListener(['input', 'change'], dmX, '#xChannelSelector'); + +let dmY = () => feDisplacementMap.setAttribute('yChannelSelector', yChannelSelector.value); +scrawl.addNativeListener(['input', 'change'], dmY, '#yChannelSelector'); + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/img/iris-120.png b/demo/img/iris-120.png new file mode 100644 index 000000000..dd9f31432 Binary files /dev/null and b/demo/img/iris-120.png differ diff --git a/demo/img/perlin-noise-texture.png b/demo/img/perlin-noise-texture.png new file mode 100644 index 000000000..49bd82589 Binary files /dev/null and b/demo/img/perlin-noise-texture.png differ diff --git a/demo/index.html b/demo/index.html index 357b565b2..884446586 100644 --- a/demo/index.html +++ b/demo/index.html @@ -66,7 +66,7 @@

    Hello World

    -
    +
    Hello world test
    @@ -77,269 +77,311 @@

    Canvas tests

    -
    +
    Canvas 001 - Block and Wheel entitys (make, clone, method); drag and drop block and wheel entitys
    -
    +
    Canvas 002 - Block and wheel entity positioning (start, pivot, mimic, mouse)
    -
    +
    Canvas 003 - Linear gradients
    -
    +
    Canvas 004 - Radial gradients
    -
    +
    Canvas 005 - Cell-locked, and Entity-locked, gradients; animating gradients by delta, and by tween
    -
    +
    Canvas 006 - Canvas tween stress test
    -
    +
    Canvas 007 - Apply filters at the entity, group and cell level
    -
    +
    Canvas 008 - Picture entity position; manipulate copy attributes
    -
    +
    Canvas 009 - Pattern styles; Entity web link anchors; Dynamic accessibility
    -
    +
    Canvas 010 - Use video sources and media streams for Picture entitys
    -
    +
    Canvas 011 - Shape entity (make, clone, method); drag and drop shape entitys
    -
    +
    Canvas 012 - Shape entity position; shape entity as a path for other artefacts to follow
    -
    +
    Canvas 013 - Path-defined entitys: Oval, Rectangle, Line, Quadratic, Bezier, Tetragon, Polygon, Star, Spiral, Cog
    -
    +
    Canvas 014 - Line, Quadratic and Bezier entitys - control lock alternatives
    -
    +
    Canvas 015 - Phrase entity (make, clone, method, multiline)
    -
    +
    Canvas 016 - Phrase entity position and font attributes; Block mimic functionality
    -
    +
    Canvas 017 - Phrase entity - test lineHeight, letterSpacing and justify attributes; Section classes functionality
    -
    +
    Canvas 018 - Phrase entity - text along a path
    -
    +
    Canvas 019 - Gradient and Color factories - transparency
    -
    +
    Canvas 020 - Testing createImageFromXXX functionality
    -
    +
    Canvas 021 - Import and use spritesheets
    -
    +
    Canvas 022 - Grid entity - basic functionality (color, gradients)
    -
    +
    Canvas 023 - Grid entity - using picture-based assets (image, video, sprite)
    -
    +
    Canvas 024 - Loom entity functionality
    -
    +
    Canvas 025 - Responsive images
    -
    +
    Canvas 026 - Tower of Hanoi
    -
    +
    Canvas 027 - Video control and manipulation; chroma-based hit zone
    -
    +
    Canvas 028 - Image magnifier; test some composite operations
    -
    +
    Canvas 029 - Phrase entitys and gradients
    -
    +
    Canvas 030 - Polyline entity functionality
    -
    +
    Canvas 031 - Cell generation and processing order - kaleidoscope clock
    -
    +
    Canvas 032 - Freehand drawing
    -
    +
    Canvas 033 - User preferences: prefers-color-scheme; prefers-reduced-motion; Javascript disabled
    -
    +
    Canvas 034 - Determine the displayed shape of the visible canvas; react to changes in the displayed shape
    -
    +
    Canvas 035 - Pattern style functionality
    -
    +
    Canvas 036 - Cell artefact-like positional functionality
    -
    +
    Canvas 037 - Pan and zoom using a Picture entity
    -
    +
    Canvas 038 - Responsive Shape-based entitys
    -
    +
    Canvas 039 - Detecting mouse/pointer cursor movements across a non-base Cell
    -
    +
    Canvas 040 - Trace out a Shape path over time
    -
    +
    Canvas 041 - Access and use a canvas context engine using the makeAnimation factory
    -
    +
    Canvas 042 - Canvas entity clip regions
    -
    +
    Canvas 043 - Test various clipping strategies
    -
    +
    Canvas 044 - Building more complex patterns
    -
    +
    Canvas 045 - Use clipping entitys as pivots; clipping entitys and cascade events
    -
    +
    Canvas 046 - Kill cycles for Cell, Group, Tween/Ticker, Picture and Asset objects, and Picture source elements in the DOM
    + +
    +
    Canvas 047 - Easing functions for Color and Tween factories

    Filters functionality tests

    -

    To be aware: CSS and SVG based filters (see demos Filters 011 to 015, below) are not currently supported by all browsers (specifically: Safari); Scrawl-canvas native filters (demos Filters 001 to 010) should work in all environments that support Web Worker functionality.

    - +
    -
    +
    Filters 001 - Parameters for: blur filter
    -
    +
    Filters 002 - Parameters for: red, green, blue, cyan, magenta, yellow, notred, notgreen, notblue, grayscale, sepia, invert filters
    -
    +
    Filters 003 - Parameters for: brightness, saturation filters
    -
    +
    Filters 004 - Parameters for: threshold filter
    -
    +
    Filters 005 - Parameters for: channelstep filter
    -
    -
    Filters 006 - Parameters for: channels filter
    +
    +
    Filters 006 - Parameters for: channelLevels filter
    + +
    +
    Filters 007 - Parameters for: channels filter
    + +
    +
    Filters 008 - Parameters for: tint filter
    + +
    +
    Filters 009 - Parameters for: pixelate filter
    + +
    +
    Filters 010 - Parameters for: chroma filter
    + +
    +
    Filters 011 - Parameters for: chromakey filter
    + +
    +
    Filters 012 - Parameters for: matrix, matrix5 filters
    + +
    +
    Filters 013 - Parameters for: flood filter
    -
    -
    Filters 007 - Parameters for: tint filter
    +
    +
    Filters 014 - Parameters for: areaAlpha filter
    -
    -
    Filters 008 - Parameters for: pixelate filter
    +
    +
    Filters 015 - Using assets in the filter stream; filter compositing
    -
    -
    Filters 009 - Parameters for: chroma filter
    +
    +
    Filters 016 - Filter blend operation
    -
    -
    Filters 010 - Parameters for: matrix, matrix5 filters
    +
    +
    Filters 017 - Parameters for: displace filter
    -
    -
    Filters 011 - Canvas engine filter strings (based on CSS filters)
    +
    +
    Filters 018 - Parameters for: emboss filter
    -
    -
    Filters 012 - SVG-based filter example: gaussian blur
    +
    +
    Filters 019 - Using a Noise asset with a displace filter
    -
    -
    Filters 013 - SVG-based filter example: posterize
    +
    +
    Filters 020 - Parameters for: clampChannels filter
    -
    -
    Filters 014 - SVG-based filter example: duotone
    +
    + +

    CSS/SVG filter String functionality tests

    + +

    To be aware: CSS and SVG based filters are not currently supported by all browsers (specifically: Safari).

    +
    + +
    +
    Filters 501 - Canvas engine filter strings (based on CSS filters)
    -
    -
    Filters 015 - SVG-based filter example: noise
    +
    +
    Filters 502 - SVG-based filter example: gaussian blur
    + +
    +
    Filters 503 - SVG-based filter example: posterize
    + +
    +
    Filters 504 - SVG-based filter example: duotone
    + +
    +
    Filters 505 - SVG-based filter example: noise

    Particles and physics tests

    -
    +
    Particles 001 - Emitter entity, and Particle World, basic functionality
    -
    +
    Particles 002 - Emitter using artefacts
    -
    +
    Particles 003 - Position Emitter entity: start; pivot; mimic; path; mouse; drag-and-drop
    -
    +
    Particles 004 - Emit particles along the length of a path
    -
    +
    Particles 005 - Emit particles from inside an artefact's area
    -
    +
    Particles 006 - Fixed number of particles in a field; preAction and postAction functionality
    -
    +
    Particles 007 - Particle Force objects: generation and functionality
    -
    +
    Particles 008 - Net entity: generation and basic functionality, including Spring objects
    -
    +
    Particles 009 - Net particles: drag-and-drop functionality
    -
    +
    Particles 010 - Net entity: using a shape path as a net template
    -
    +
    Particles 011 - Tracer entity: generation and functionality
    -
    +
    Particles 012 - Use Net entity particles as reference coordinates for other artefacts
    -
    +
    Particles 013 - Seasons greetings
    -
    +
    Particles 014 - Emitter functionality: generate from existing particles
    -
    +
    Particles 015 - Emitter functionality: generate from existing particle histories
    + +
    +
    Particles 016 - Mesh entitys

    Component tests (experimental!)

    -
    +
    Component 001 - Scrawl-canvas DOM element components
    -
    +
    Component 002 - Scrawl-canvas stack element components
    -
    +
    Component 003 - Save and load Scrawl-canvas entity using text packets
    -
    +
    Component 004 - Scrawl-canvas packets - save and load a range of different entitys
    -
    +
    Component 005 - Scrawl-canvas modularized code - London crime charts
    -
    +
    Component 006 - Spiral charts
    -
    +
    Component 007 - Factory functions to create more complex, compound entitys
    @@ -347,49 +389,49 @@

    DOM and Stack tests

    -
    +
    DOM 001 - Loading the scrawl-canvas library using a script tag in the HTML code
    -
    +
    DOM 002 - Element mouse, pivot and mimic functionality
    -
    +
    DOM 003 - Dynamically create and clone Element artefacts; drag and drop elements (including SVG elements) around a Stack
    -
    +
    DOM 004 - Limitless rockets (clone and destroy elements, tweens, tickers)
    -
    +
    DOM 005 - DOM tween stress test
    -
    +
    DOM 006 - Tween actions on a DOM element; tracking tween and ticker activity (analytics)
    -
    +
    DOM 007 - Animate a DOM element using the delta attribute object; dynamically change classes on a DOM element
    -
    +
    DOM 008 - 3d animated cube
    -
    +
    DOM 009 - Stop and restart the main animation loop; add and remove event listener; retrieve all artefacts at a given coordinate
    -
    +
    DOM 010 - Add and remove (kill) Scrawl-canvas stack elements programmatically
    -
    +
    DOM 011 - Canvas controller 'fit' attribute; Cell positioning (mouse)
    -
    +
    DOM 012 - Add and remove (kill) Scrawl-canvas canvas elements programmatically
    -
    +
    DOM 013 - Track mouse movements over a 3d rotated and scaled canvas element
    -
    +
    DOM-015 - Use stacked DOM artefact corners as pivot points
    -
    +
    DOM-016 - Determine the displayed shape of the visible stack; react to changes in the displayed shape
    diff --git a/demo/particles-007.html b/demo/particles-007.html index af5e8d182..2ee3d3f6e 100644 --- a/demo/particles-007.html +++ b/demo/particles-007.html @@ -44,7 +44,6 @@

    Particle Force objects: generation and functionality

    - @@ -59,6 +58,9 @@

    Particle Force objects: generation and functionality

    Test purpose

    + +

    Be aware that applying a filter to a particle system can cause it to fail in interesting yet mysterious ways! In some browsers, the addition of a filter can also cause a significant degradation in canvas animation speeds

    +
    @@ -81,10 +89,13 @@

    Test purpose

  • Check the effect of changing the Net's 'generate' attribute
  • Navigate away from the web page (open another tab in the browser) and, a few seconds later, navigate back to the page - check that the animation resets and starts, and does not freeze the page
  • +

    Note: The Safari browser is particularly sensitive to attempts to display images on Net entitys (using the Mesh entity) - the Demo most often works for this scenario when the tick multiplier attribute is reduced.

    Annotated code

    + + diff --git a/demo/particles-008.js b/demo/particles-008.js index ad8b35702..9afb9482f 100644 --- a/demo/particles-008.js +++ b/demo/particles-008.js @@ -5,6 +5,10 @@ import scrawl from '../source/scrawl.js' +// Import image from DOM +scrawl.importDomImage('.flowers'); + + // #### Scene setup let canvas = scrawl.library.artefact.mycanvas; @@ -20,7 +24,7 @@ scrawl.makeLine({ endX: 'center', endY: 'bottom', - lineWidth: 16, + lineWidth: 8, lineCap: 'round', strokeStyle: 'brown', @@ -137,6 +141,7 @@ const myNet = scrawl.makeNet({ mass: 1, forces: ['gravity', 'wind'], engine: 'runge-kutta', + damperConstant: 5, // We can assign an artefact that we will be using for the particle animation, or we can define it here as part of the Net factory artefact: scrawl.makeWheel({ @@ -152,6 +157,8 @@ const myNet = scrawl.makeNet({ visibility: false, + globalAlpha: 1, + noUserInteraction: true, noPositionDependencies: true, noFilters: true, @@ -171,6 +178,35 @@ const myNet = scrawl.makeNet({ }, }); +scrawl.makePicture({ + + name: 'my-flower', + asset: 'iris', + + copyStartX: 0, + copyStartY: 0, + + copyWidth: '100%', + copyHeight: '100%', + + visibility: false, +}); + + +// ___The Loom entity definition___ +let myMesh = scrawl.makeMesh({ + + name: 'display-mesh', + + net: 'test-net', + source: 'my-flower', + + method: 'fillThenDraw', + + visibility: false, +}); + + // #### Scene animation // Function to display frames-per-second data, and other information relevant to the demo @@ -225,6 +261,30 @@ const updateSprings = function (e) { if (e.target.id === 'engine') myNet.set({ engine: e.target.value}); if (e.target.id === 'mass') myNet.set({ mass: parseFloat(e.target.value)}); if (e.target.id === 'tickMultiplier') myWorld.set({ tickMultiplier: parseFloat(e.target.value)}); + if (e.target.id === 'mesh') { + if (parseInt(e.target.value, 10)) { + myNet.set({ + showSprings: false, + }); + myNet.artefact.set({ + globalAlpha: 0, + }); + myMesh.set({ + visibility: true, + }); + } + else { + myNet.set({ + showSprings: true, + }); + myNet.artefact.set({ + globalAlpha: 1, + }); + myMesh.set({ + visibility: false, + }); + } + } myNet.restart(); } @@ -233,11 +293,12 @@ scrawl.addNativeListener(['input', 'change'], updateSprings, '.controlItem'); document.querySelector('#generate').value = 'weak-net'; document.querySelector('#springConstant').value = 50; -document.querySelector('#damperConstant').value = 10; +document.querySelector('#damperConstant').value = 5; document.querySelector('#restLength').value = 1; document.querySelector('#mass').value = 1; document.querySelector('#engine').value = 'runge-kutta'; document.querySelector('#tickMultiplier').value = 2; +document.querySelector('#mesh').value = '0'; // #### Development and testing diff --git a/demo/particles-009.html b/demo/particles-009.html index a6b5cc654..efe382ae3 100644 --- a/demo/particles-009.html +++ b/demo/particles-009.html @@ -33,7 +33,6 @@

    The yellow beads and the orange ball are draggable

    - @@ -48,6 +47,9 @@

    The yellow beads and the orange ball are draggable

    Test purpose

    + +

    Be aware that applying a filter to a particle system can cause it to fail in interesting yet mysterious ways! In some browsers, the addition of a filter can also cause a significant degradation in canvas animation speeds

    +
    @@ -47,6 +46,9 @@

    Tracer entity: generation and functionality

    Test purpose

    + +

    Be aware that applying a filter to a particle system can cause it to fail in interesting yet mysterious ways! In some browsers, the addition of a filter can also cause a significant degradation in canvas animation speeds

    + +

    Known issue: the Demo in Firefox differs from other browsers as that browser is less tolerant to retaining previous paints during the clear stage of the Display cycle (based on the Cell wrapper's 'clearAlpha' attribute's value).

    Annotated code

    diff --git a/demo/particles-015.html b/demo/particles-015.html index 14b7f6783..57b41dfd3 100644 --- a/demo/particles-015.html +++ b/demo/particles-015.html @@ -25,6 +25,7 @@

    Test purpose

  • Check that the generated particles move only at specific angles (multiples of 60 degrees, all rotated by 15 degrees)
  • Check that the tween correctly updates the color range for new particles
  • +

    Known issue: the Demo in Firefox differs from other browsers as that browser is less tolerant to retaining previous paints during the clear stage of the Display cycle (based on the Cell wrapper's 'clearAlpha' attribute's value).

    Annotated code

    diff --git a/demo/particles-016.html b/demo/particles-016.html new file mode 100644 index 000000000..f71d581c0 --- /dev/null +++ b/demo/particles-016.html @@ -0,0 +1,48 @@ + + + + + + + + Demo Particles 016 + + + + + + +

    Scrawl-canvas v8 - Particles test 016

    +

    Mesh entitys

    +

    The red beads are draggable

    + + + +

    + +
    +

    Test purpose

    + +

    Annotated code

    +
    + + + + + + + diff --git a/demo/particles-016.js b/demo/particles-016.js new file mode 100644 index 000000000..28fbd1de4 --- /dev/null +++ b/demo/particles-016.js @@ -0,0 +1,174 @@ +// # Demo Particles 016 +// Mesh entitys + +// [Run code](../../demo/particles-016.html) +import scrawl from '../source/scrawl.js' + + +// #### Scene setup +let canvas = scrawl.library.artefact.mycanvas; + +canvas.setBase({ + backgroundColor: 'azure', +}); + +// Import image from DOM +scrawl.importDomImage('.flowers'); + + +// Every Mesh uses a Net entity +const myNet = scrawl.makeNet({ + + name: 'test-net', + order: 1, + + world: scrawl.makeWorld({ + name: 'demo-world', + }), + + start: [50, 50], + + generate: 'weak-net', + + postGenerate: function () { + + this.springs.forEach(s => { + + s.particleFromIsStatic = true; + s.particleToIsStatic = true; + }); + }, + + rows: 4, + columns: 6, + rowDistance: 100, + columnDistance: 100, + + showSprings: true, + showSpringsColor: 'rgba(255, 255, 255, 0.3)', + + forces: [], + + engine: 'euler', + + artefact: scrawl.makeWheel({ + + name: 'particle-wheel', + radius: 7, + + handle: ['center', 'center'], + + method: 'fillThenDraw', + fillStyle: 'rgba(200, 0, 0, 0.7)', + strokeStyle: 'white', + lineWidth: 2, + + visibility: false, + + noUserInteraction: true, + noPositionDependencies: true, + noFilters: true, + noDeltaUpdates: true, + }), + + stampAction: function (artefact, particle, host) { + + let [r, z, ...start] = particle.history[0]; + + artefact.simpleStamp(host, { + start, + fillStyle: particle.fill, + strokeStyle: particle.stroke, + }); + }, + + // Make all of the Net entity's Particles draggable + particlesAreDraggable: true, +}); + +// Every Mesh also needs a source image +const myPicture = scrawl.makePicture({ + + name: 'my-flower', + asset: 'iris', + + copyStartX: 0, + copyStartY: 0, + + copyWidth: '100%', + copyHeight: '100%', + + visibility: false, +}); + + +// ___The Loom entity definition___ +let myMesh = scrawl.makeMesh({ + + name: 'display-mesh', + + net: 'test-net', + source: 'my-flower', + + lineWidth: 2, + lineJoin: 'round', + strokeStyle: 'orange', + + method: 'fillThenDraw', + + onEnter: function () { this.set({ lineWidth: 6 }) }, + onLeave: function () { this.set({ lineWidth: 2 }) }, +}); + + +// #### Scene animation +// Function to display frames-per-second data, and other information relevant to the demo +let report = function () { + + let testTicker = Date.now(), + testTime, testNow, dragging, + testMessage = document.querySelector('#reportmessage'); + + return function () { + + testNow = Date.now(); + testTime = testNow - testTicker; + testTicker = testNow; + + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + }; +}(); + +// Create the Display cycle animation +scrawl.makeRender({ + + name: 'demo-animation', + target: canvas, + afterShow: report, +}); + + +// #### User interaction + +// Mouse movement over and away from the Loom (emulates CSS element `hover` functionality) +let interactions = function () { canvas.cascadeEventAction('move') }; +scrawl.addListener('move', interactions, canvas.domElement); + + +// Make both the Net entity's Particles, and the big ball, draggable +scrawl.makeGroup({ + + name: 'my-draggable-group', + +}).addArtefacts('test-net'); + +scrawl.makeDragZone({ + + zone: canvas, + collisionGroup: 'my-draggable-group', + endOn: ['up', 'leave'], +}); + + +// #### Development and testing +console.log(scrawl.library); diff --git a/demo/thumbs/canvas-001.png b/demo/thumbs/canvas-001.png deleted file mode 100644 index 4d9ffcde8..000000000 Binary files a/demo/thumbs/canvas-001.png and /dev/null differ diff --git a/demo/thumbs/canvas-001.webp b/demo/thumbs/canvas-001.webp new file mode 100644 index 000000000..3091469fd Binary files /dev/null and b/demo/thumbs/canvas-001.webp differ diff --git a/demo/thumbs/canvas-002.png b/demo/thumbs/canvas-002.png deleted file mode 100644 index f417e86ac..000000000 Binary files a/demo/thumbs/canvas-002.png and /dev/null differ diff --git a/demo/thumbs/canvas-002.webp b/demo/thumbs/canvas-002.webp new file mode 100644 index 000000000..709d7bf5e Binary files /dev/null and b/demo/thumbs/canvas-002.webp differ diff --git a/demo/thumbs/canvas-003.png b/demo/thumbs/canvas-003.png deleted file mode 100644 index fd652eba3..000000000 Binary files a/demo/thumbs/canvas-003.png and /dev/null differ diff --git a/demo/thumbs/canvas-003.webp b/demo/thumbs/canvas-003.webp new file mode 100644 index 000000000..63151f4f8 Binary files /dev/null and b/demo/thumbs/canvas-003.webp differ diff --git a/demo/thumbs/canvas-004.png b/demo/thumbs/canvas-004.png deleted file mode 100644 index b656b9c19..000000000 Binary files a/demo/thumbs/canvas-004.png and /dev/null differ diff --git a/demo/thumbs/canvas-004.webp b/demo/thumbs/canvas-004.webp new file mode 100644 index 000000000..8990cd06b Binary files /dev/null and b/demo/thumbs/canvas-004.webp differ diff --git a/demo/thumbs/canvas-005.png b/demo/thumbs/canvas-005.png deleted file mode 100644 index a366a30db..000000000 Binary files a/demo/thumbs/canvas-005.png and /dev/null differ diff --git a/demo/thumbs/canvas-005.webp b/demo/thumbs/canvas-005.webp new file mode 100644 index 000000000..4a26760df Binary files /dev/null and b/demo/thumbs/canvas-005.webp differ diff --git a/demo/thumbs/canvas-006.png b/demo/thumbs/canvas-006.png deleted file mode 100644 index 898745f79..000000000 Binary files a/demo/thumbs/canvas-006.png and /dev/null differ diff --git a/demo/thumbs/canvas-006.webp b/demo/thumbs/canvas-006.webp new file mode 100644 index 000000000..c1a9a6493 Binary files /dev/null and b/demo/thumbs/canvas-006.webp differ diff --git a/demo/thumbs/canvas-007.png b/demo/thumbs/canvas-007.png deleted file mode 100644 index 3058d88c0..000000000 Binary files a/demo/thumbs/canvas-007.png and /dev/null differ diff --git a/demo/thumbs/canvas-007.webp b/demo/thumbs/canvas-007.webp new file mode 100644 index 000000000..0e8b5c9f4 Binary files /dev/null and b/demo/thumbs/canvas-007.webp differ diff --git a/demo/thumbs/canvas-008.png b/demo/thumbs/canvas-008.png deleted file mode 100644 index cbd199ac9..000000000 Binary files a/demo/thumbs/canvas-008.png and /dev/null differ diff --git a/demo/thumbs/canvas-008.webp b/demo/thumbs/canvas-008.webp new file mode 100644 index 000000000..26f02f47f Binary files /dev/null and b/demo/thumbs/canvas-008.webp differ diff --git a/demo/thumbs/canvas-009.png b/demo/thumbs/canvas-009.png deleted file mode 100644 index 9b22a8fd7..000000000 Binary files a/demo/thumbs/canvas-009.png and /dev/null differ diff --git a/demo/thumbs/canvas-009.webp b/demo/thumbs/canvas-009.webp new file mode 100644 index 000000000..6982522c2 Binary files /dev/null and b/demo/thumbs/canvas-009.webp differ diff --git a/demo/thumbs/canvas-010.png b/demo/thumbs/canvas-010.png deleted file mode 100644 index 1ce8d5b01..000000000 Binary files a/demo/thumbs/canvas-010.png and /dev/null differ diff --git a/demo/thumbs/canvas-010.webp b/demo/thumbs/canvas-010.webp new file mode 100644 index 000000000..738c46595 Binary files /dev/null and b/demo/thumbs/canvas-010.webp differ diff --git a/demo/thumbs/canvas-011.png b/demo/thumbs/canvas-011.png deleted file mode 100644 index 1dc2de9cc..000000000 Binary files a/demo/thumbs/canvas-011.png and /dev/null differ diff --git a/demo/thumbs/canvas-011.webp b/demo/thumbs/canvas-011.webp new file mode 100644 index 000000000..8e6ff193a Binary files /dev/null and b/demo/thumbs/canvas-011.webp differ diff --git a/demo/thumbs/canvas-012.png b/demo/thumbs/canvas-012.png deleted file mode 100644 index fe9c32ba6..000000000 Binary files a/demo/thumbs/canvas-012.png and /dev/null differ diff --git a/demo/thumbs/canvas-012.webp b/demo/thumbs/canvas-012.webp new file mode 100644 index 000000000..95dc3325f Binary files /dev/null and b/demo/thumbs/canvas-012.webp differ diff --git a/demo/thumbs/canvas-013.png b/demo/thumbs/canvas-013.png deleted file mode 100644 index e0eefdf0f..000000000 Binary files a/demo/thumbs/canvas-013.png and /dev/null differ diff --git a/demo/thumbs/canvas-013.webp b/demo/thumbs/canvas-013.webp new file mode 100644 index 000000000..e06dfab1a Binary files /dev/null and b/demo/thumbs/canvas-013.webp differ diff --git a/demo/thumbs/canvas-014.png b/demo/thumbs/canvas-014.png deleted file mode 100644 index c7230ae93..000000000 Binary files a/demo/thumbs/canvas-014.png and /dev/null differ diff --git a/demo/thumbs/canvas-014.webp b/demo/thumbs/canvas-014.webp new file mode 100644 index 000000000..5604ff902 Binary files /dev/null and b/demo/thumbs/canvas-014.webp differ diff --git a/demo/thumbs/canvas-015.png b/demo/thumbs/canvas-015.png deleted file mode 100644 index 8e9928d00..000000000 Binary files a/demo/thumbs/canvas-015.png and /dev/null differ diff --git a/demo/thumbs/canvas-015.webp b/demo/thumbs/canvas-015.webp new file mode 100644 index 000000000..19d7ed142 Binary files /dev/null and b/demo/thumbs/canvas-015.webp differ diff --git a/demo/thumbs/canvas-016.png b/demo/thumbs/canvas-016.png deleted file mode 100644 index aa225eddf..000000000 Binary files a/demo/thumbs/canvas-016.png and /dev/null differ diff --git a/demo/thumbs/canvas-016.webp b/demo/thumbs/canvas-016.webp new file mode 100644 index 000000000..51bf6d8d6 Binary files /dev/null and b/demo/thumbs/canvas-016.webp differ diff --git a/demo/thumbs/canvas-017.png b/demo/thumbs/canvas-017.png deleted file mode 100644 index 06fa741d7..000000000 Binary files a/demo/thumbs/canvas-017.png and /dev/null differ diff --git a/demo/thumbs/canvas-017.webp b/demo/thumbs/canvas-017.webp new file mode 100644 index 000000000..f1a1a5592 Binary files /dev/null and b/demo/thumbs/canvas-017.webp differ diff --git a/demo/thumbs/canvas-018.png b/demo/thumbs/canvas-018.png deleted file mode 100644 index 73e55e1d9..000000000 Binary files a/demo/thumbs/canvas-018.png and /dev/null differ diff --git a/demo/thumbs/canvas-018.webp b/demo/thumbs/canvas-018.webp new file mode 100644 index 000000000..758a78f4d Binary files /dev/null and b/demo/thumbs/canvas-018.webp differ diff --git a/demo/thumbs/canvas-019.png b/demo/thumbs/canvas-019.png deleted file mode 100644 index 3722719d5..000000000 Binary files a/demo/thumbs/canvas-019.png and /dev/null differ diff --git a/demo/thumbs/canvas-019.webp b/demo/thumbs/canvas-019.webp new file mode 100644 index 000000000..7ff112c3c Binary files /dev/null and b/demo/thumbs/canvas-019.webp differ diff --git a/demo/thumbs/canvas-020.png b/demo/thumbs/canvas-020.png deleted file mode 100644 index 75b6863bb..000000000 Binary files a/demo/thumbs/canvas-020.png and /dev/null differ diff --git a/demo/thumbs/canvas-020.webp b/demo/thumbs/canvas-020.webp new file mode 100644 index 000000000..9f32bbfd3 Binary files /dev/null and b/demo/thumbs/canvas-020.webp differ diff --git a/demo/thumbs/canvas-021.png b/demo/thumbs/canvas-021.png deleted file mode 100644 index bc479cd20..000000000 Binary files a/demo/thumbs/canvas-021.png and /dev/null differ diff --git a/demo/thumbs/canvas-021.webp b/demo/thumbs/canvas-021.webp new file mode 100644 index 000000000..dfaab9898 Binary files /dev/null and b/demo/thumbs/canvas-021.webp differ diff --git a/demo/thumbs/canvas-022.png b/demo/thumbs/canvas-022.png deleted file mode 100644 index 1f0ea03c6..000000000 Binary files a/demo/thumbs/canvas-022.png and /dev/null differ diff --git a/demo/thumbs/canvas-022.webp b/demo/thumbs/canvas-022.webp new file mode 100644 index 000000000..5e4695225 Binary files /dev/null and b/demo/thumbs/canvas-022.webp differ diff --git a/demo/thumbs/canvas-023.png b/demo/thumbs/canvas-023.png deleted file mode 100644 index af1f82e98..000000000 Binary files a/demo/thumbs/canvas-023.png and /dev/null differ diff --git a/demo/thumbs/canvas-023.webp b/demo/thumbs/canvas-023.webp new file mode 100644 index 000000000..2eb84e0c5 Binary files /dev/null and b/demo/thumbs/canvas-023.webp differ diff --git a/demo/thumbs/canvas-024.png b/demo/thumbs/canvas-024.png deleted file mode 100644 index ffaee9686..000000000 Binary files a/demo/thumbs/canvas-024.png and /dev/null differ diff --git a/demo/thumbs/canvas-024.webp b/demo/thumbs/canvas-024.webp new file mode 100644 index 000000000..850f662b7 Binary files /dev/null and b/demo/thumbs/canvas-024.webp differ diff --git a/demo/thumbs/canvas-025.png b/demo/thumbs/canvas-025.png deleted file mode 100644 index 8b2a772fb..000000000 Binary files a/demo/thumbs/canvas-025.png and /dev/null differ diff --git a/demo/thumbs/canvas-025.webp b/demo/thumbs/canvas-025.webp new file mode 100644 index 000000000..e506e67ec Binary files /dev/null and b/demo/thumbs/canvas-025.webp differ diff --git a/demo/thumbs/canvas-026.png b/demo/thumbs/canvas-026.png deleted file mode 100644 index 3da6c7a2c..000000000 Binary files a/demo/thumbs/canvas-026.png and /dev/null differ diff --git a/demo/thumbs/canvas-026.webp b/demo/thumbs/canvas-026.webp new file mode 100644 index 000000000..beb4a8ba9 Binary files /dev/null and b/demo/thumbs/canvas-026.webp differ diff --git a/demo/thumbs/canvas-027.png b/demo/thumbs/canvas-027.png deleted file mode 100644 index d3506551b..000000000 Binary files a/demo/thumbs/canvas-027.png and /dev/null differ diff --git a/demo/thumbs/canvas-027.webp b/demo/thumbs/canvas-027.webp new file mode 100644 index 000000000..0871563b1 Binary files /dev/null and b/demo/thumbs/canvas-027.webp differ diff --git a/demo/thumbs/canvas-028.png b/demo/thumbs/canvas-028.png deleted file mode 100644 index e9809efa9..000000000 Binary files a/demo/thumbs/canvas-028.png and /dev/null differ diff --git a/demo/thumbs/canvas-028.webp b/demo/thumbs/canvas-028.webp new file mode 100644 index 000000000..c031f36c1 Binary files /dev/null and b/demo/thumbs/canvas-028.webp differ diff --git a/demo/thumbs/canvas-029.png b/demo/thumbs/canvas-029.png deleted file mode 100644 index de414537a..000000000 Binary files a/demo/thumbs/canvas-029.png and /dev/null differ diff --git a/demo/thumbs/canvas-029.webp b/demo/thumbs/canvas-029.webp new file mode 100644 index 000000000..6d68e6d1d Binary files /dev/null and b/demo/thumbs/canvas-029.webp differ diff --git a/demo/thumbs/canvas-030.png b/demo/thumbs/canvas-030.png deleted file mode 100644 index 33b478934..000000000 Binary files a/demo/thumbs/canvas-030.png and /dev/null differ diff --git a/demo/thumbs/canvas-030.webp b/demo/thumbs/canvas-030.webp new file mode 100644 index 000000000..eb6998245 Binary files /dev/null and b/demo/thumbs/canvas-030.webp differ diff --git a/demo/thumbs/canvas-031.png b/demo/thumbs/canvas-031.png deleted file mode 100644 index 3e840de65..000000000 Binary files a/demo/thumbs/canvas-031.png and /dev/null differ diff --git a/demo/thumbs/canvas-031.webp b/demo/thumbs/canvas-031.webp new file mode 100644 index 000000000..bd915ad1b Binary files /dev/null and b/demo/thumbs/canvas-031.webp differ diff --git a/demo/thumbs/canvas-032.png b/demo/thumbs/canvas-032.png deleted file mode 100644 index b9a760dec..000000000 Binary files a/demo/thumbs/canvas-032.png and /dev/null differ diff --git a/demo/thumbs/canvas-032.webp b/demo/thumbs/canvas-032.webp new file mode 100644 index 000000000..cc4af7dee Binary files /dev/null and b/demo/thumbs/canvas-032.webp differ diff --git a/demo/thumbs/canvas-033.png b/demo/thumbs/canvas-033.png deleted file mode 100644 index 451fd7539..000000000 Binary files a/demo/thumbs/canvas-033.png and /dev/null differ diff --git a/demo/thumbs/canvas-033.webp b/demo/thumbs/canvas-033.webp new file mode 100644 index 000000000..a3c8c689e Binary files /dev/null and b/demo/thumbs/canvas-033.webp differ diff --git a/demo/thumbs/canvas-034.png b/demo/thumbs/canvas-034.png deleted file mode 100644 index 25b1e07e0..000000000 Binary files a/demo/thumbs/canvas-034.png and /dev/null differ diff --git a/demo/thumbs/canvas-034.webp b/demo/thumbs/canvas-034.webp new file mode 100644 index 000000000..032fe52be Binary files /dev/null and b/demo/thumbs/canvas-034.webp differ diff --git a/demo/thumbs/canvas-035.png b/demo/thumbs/canvas-035.png deleted file mode 100644 index 9198c835d..000000000 Binary files a/demo/thumbs/canvas-035.png and /dev/null differ diff --git a/demo/thumbs/canvas-035.webp b/demo/thumbs/canvas-035.webp new file mode 100644 index 000000000..8042d9659 Binary files /dev/null and b/demo/thumbs/canvas-035.webp differ diff --git a/demo/thumbs/canvas-036.png b/demo/thumbs/canvas-036.png deleted file mode 100644 index 63478fe4a..000000000 Binary files a/demo/thumbs/canvas-036.png and /dev/null differ diff --git a/demo/thumbs/canvas-036.webp b/demo/thumbs/canvas-036.webp new file mode 100644 index 000000000..7d7a6fda4 Binary files /dev/null and b/demo/thumbs/canvas-036.webp differ diff --git a/demo/thumbs/canvas-037.png b/demo/thumbs/canvas-037.png deleted file mode 100644 index a53c459cb..000000000 Binary files a/demo/thumbs/canvas-037.png and /dev/null differ diff --git a/demo/thumbs/canvas-037.webp b/demo/thumbs/canvas-037.webp new file mode 100644 index 000000000..ca5a67b81 Binary files /dev/null and b/demo/thumbs/canvas-037.webp differ diff --git a/demo/thumbs/canvas-038.png b/demo/thumbs/canvas-038.png deleted file mode 100644 index 4946e4628..000000000 Binary files a/demo/thumbs/canvas-038.png and /dev/null differ diff --git a/demo/thumbs/canvas-038.webp b/demo/thumbs/canvas-038.webp new file mode 100644 index 000000000..69599f9b9 Binary files /dev/null and b/demo/thumbs/canvas-038.webp differ diff --git a/demo/thumbs/canvas-039.png b/demo/thumbs/canvas-039.png deleted file mode 100644 index c7a91a3a4..000000000 Binary files a/demo/thumbs/canvas-039.png and /dev/null differ diff --git a/demo/thumbs/canvas-039.webp b/demo/thumbs/canvas-039.webp new file mode 100644 index 000000000..090413340 Binary files /dev/null and b/demo/thumbs/canvas-039.webp differ diff --git a/demo/thumbs/canvas-040.png b/demo/thumbs/canvas-040.png deleted file mode 100644 index 499efb87b..000000000 Binary files a/demo/thumbs/canvas-040.png and /dev/null differ diff --git a/demo/thumbs/canvas-040.webp b/demo/thumbs/canvas-040.webp new file mode 100644 index 000000000..a07c39c88 Binary files /dev/null and b/demo/thumbs/canvas-040.webp differ diff --git a/demo/thumbs/canvas-041.png b/demo/thumbs/canvas-041.png deleted file mode 100644 index 423d60dd7..000000000 Binary files a/demo/thumbs/canvas-041.png and /dev/null differ diff --git a/demo/thumbs/canvas-041.webp b/demo/thumbs/canvas-041.webp new file mode 100644 index 000000000..b56edcb77 Binary files /dev/null and b/demo/thumbs/canvas-041.webp differ diff --git a/demo/thumbs/canvas-042.png b/demo/thumbs/canvas-042.png deleted file mode 100644 index 8b8b7ffb1..000000000 Binary files a/demo/thumbs/canvas-042.png and /dev/null differ diff --git a/demo/thumbs/canvas-042.webp b/demo/thumbs/canvas-042.webp new file mode 100644 index 000000000..8d9af83fa Binary files /dev/null and b/demo/thumbs/canvas-042.webp differ diff --git a/demo/thumbs/canvas-043.png b/demo/thumbs/canvas-043.png deleted file mode 100644 index 30f6c7d28..000000000 Binary files a/demo/thumbs/canvas-043.png and /dev/null differ diff --git a/demo/thumbs/canvas-043.webp b/demo/thumbs/canvas-043.webp new file mode 100644 index 000000000..1d80f7e2a Binary files /dev/null and b/demo/thumbs/canvas-043.webp differ diff --git a/demo/thumbs/canvas-044.png b/demo/thumbs/canvas-044.png deleted file mode 100644 index 3b9b48aa8..000000000 Binary files a/demo/thumbs/canvas-044.png and /dev/null differ diff --git a/demo/thumbs/canvas-044.webp b/demo/thumbs/canvas-044.webp new file mode 100644 index 000000000..aac834953 Binary files /dev/null and b/demo/thumbs/canvas-044.webp differ diff --git a/demo/thumbs/canvas-045.png b/demo/thumbs/canvas-045.png deleted file mode 100644 index fdb795fe3..000000000 Binary files a/demo/thumbs/canvas-045.png and /dev/null differ diff --git a/demo/thumbs/canvas-045.webp b/demo/thumbs/canvas-045.webp new file mode 100644 index 000000000..ffb70a4c3 Binary files /dev/null and b/demo/thumbs/canvas-045.webp differ diff --git a/demo/thumbs/canvas-046.png b/demo/thumbs/canvas-046.png deleted file mode 100644 index 4010da078..000000000 Binary files a/demo/thumbs/canvas-046.png and /dev/null differ diff --git a/demo/thumbs/canvas-046.webp b/demo/thumbs/canvas-046.webp new file mode 100644 index 000000000..0d993f97d Binary files /dev/null and b/demo/thumbs/canvas-046.webp differ diff --git a/demo/thumbs/canvas-047.webp b/demo/thumbs/canvas-047.webp new file mode 100644 index 000000000..95b674dcc Binary files /dev/null and b/demo/thumbs/canvas-047.webp differ diff --git a/demo/thumbs/component-001.png b/demo/thumbs/component-001.png deleted file mode 100644 index 01e616213..000000000 Binary files a/demo/thumbs/component-001.png and /dev/null differ diff --git a/demo/thumbs/component-001.webp b/demo/thumbs/component-001.webp new file mode 100644 index 000000000..5ea18616d Binary files /dev/null and b/demo/thumbs/component-001.webp differ diff --git a/demo/thumbs/component-002.png b/demo/thumbs/component-002.png deleted file mode 100644 index c58bf2113..000000000 Binary files a/demo/thumbs/component-002.png and /dev/null differ diff --git a/demo/thumbs/component-002.webp b/demo/thumbs/component-002.webp new file mode 100644 index 000000000..24b5a2d8d Binary files /dev/null and b/demo/thumbs/component-002.webp differ diff --git a/demo/thumbs/component-003.png b/demo/thumbs/component-003.png deleted file mode 100644 index f1d120dcc..000000000 Binary files a/demo/thumbs/component-003.png and /dev/null differ diff --git a/demo/thumbs/component-003.webp b/demo/thumbs/component-003.webp new file mode 100644 index 000000000..2ef039cac Binary files /dev/null and b/demo/thumbs/component-003.webp differ diff --git a/demo/thumbs/component-004.png b/demo/thumbs/component-004.png deleted file mode 100644 index 137e163cc..000000000 Binary files a/demo/thumbs/component-004.png and /dev/null differ diff --git a/demo/thumbs/component-004.webp b/demo/thumbs/component-004.webp new file mode 100644 index 000000000..ecd796656 Binary files /dev/null and b/demo/thumbs/component-004.webp differ diff --git a/demo/thumbs/component-005.png b/demo/thumbs/component-005.png deleted file mode 100644 index 6b20f86cc..000000000 Binary files a/demo/thumbs/component-005.png and /dev/null differ diff --git a/demo/thumbs/component-005.webp b/demo/thumbs/component-005.webp new file mode 100644 index 000000000..7c88547cd Binary files /dev/null and b/demo/thumbs/component-005.webp differ diff --git a/demo/thumbs/component-006.png b/demo/thumbs/component-006.png deleted file mode 100644 index 7aed14bd0..000000000 Binary files a/demo/thumbs/component-006.png and /dev/null differ diff --git a/demo/thumbs/component-006.webp b/demo/thumbs/component-006.webp new file mode 100644 index 000000000..697c2cb8a Binary files /dev/null and b/demo/thumbs/component-006.webp differ diff --git a/demo/thumbs/component-007.png b/demo/thumbs/component-007.png deleted file mode 100644 index dc42ef047..000000000 Binary files a/demo/thumbs/component-007.png and /dev/null differ diff --git a/demo/thumbs/component-007.webp b/demo/thumbs/component-007.webp new file mode 100644 index 000000000..5b84bc15f Binary files /dev/null and b/demo/thumbs/component-007.webp differ diff --git a/demo/thumbs/core-001.png b/demo/thumbs/core-001.png deleted file mode 100644 index 9a9e18cb2..000000000 Binary files a/demo/thumbs/core-001.png and /dev/null differ diff --git a/demo/thumbs/core-001.webp b/demo/thumbs/core-001.webp new file mode 100644 index 000000000..d0bf3551a Binary files /dev/null and b/demo/thumbs/core-001.webp differ diff --git a/demo/thumbs/dom-001.png b/demo/thumbs/dom-001.png deleted file mode 100644 index ea3b3961a..000000000 Binary files a/demo/thumbs/dom-001.png and /dev/null differ diff --git a/demo/thumbs/dom-001.webp b/demo/thumbs/dom-001.webp new file mode 100644 index 000000000..bb57f69f0 Binary files /dev/null and b/demo/thumbs/dom-001.webp differ diff --git a/demo/thumbs/dom-002.png b/demo/thumbs/dom-002.png deleted file mode 100644 index 59ed4dd3c..000000000 Binary files a/demo/thumbs/dom-002.png and /dev/null differ diff --git a/demo/thumbs/dom-002.webp b/demo/thumbs/dom-002.webp new file mode 100644 index 000000000..96eb973d0 Binary files /dev/null and b/demo/thumbs/dom-002.webp differ diff --git a/demo/thumbs/dom-003.png b/demo/thumbs/dom-003.png deleted file mode 100644 index e94e99a5e..000000000 Binary files a/demo/thumbs/dom-003.png and /dev/null differ diff --git a/demo/thumbs/dom-003.webp b/demo/thumbs/dom-003.webp new file mode 100644 index 000000000..1e4b7201a Binary files /dev/null and b/demo/thumbs/dom-003.webp differ diff --git a/demo/thumbs/dom-004.png b/demo/thumbs/dom-004.png deleted file mode 100644 index 0cbcf85aa..000000000 Binary files a/demo/thumbs/dom-004.png and /dev/null differ diff --git a/demo/thumbs/dom-004.webp b/demo/thumbs/dom-004.webp new file mode 100644 index 000000000..e621223c9 Binary files /dev/null and b/demo/thumbs/dom-004.webp differ diff --git a/demo/thumbs/dom-005.png b/demo/thumbs/dom-005.png deleted file mode 100644 index d5d5a0198..000000000 Binary files a/demo/thumbs/dom-005.png and /dev/null differ diff --git a/demo/thumbs/dom-005.webp b/demo/thumbs/dom-005.webp new file mode 100644 index 000000000..0e2ca57c1 Binary files /dev/null and b/demo/thumbs/dom-005.webp differ diff --git a/demo/thumbs/dom-006.png b/demo/thumbs/dom-006.png deleted file mode 100644 index dc5dfac9c..000000000 Binary files a/demo/thumbs/dom-006.png and /dev/null differ diff --git a/demo/thumbs/dom-006.webp b/demo/thumbs/dom-006.webp new file mode 100644 index 000000000..3db1c8472 Binary files /dev/null and b/demo/thumbs/dom-006.webp differ diff --git a/demo/thumbs/dom-007.png b/demo/thumbs/dom-007.png deleted file mode 100644 index 17cc2c055..000000000 Binary files a/demo/thumbs/dom-007.png and /dev/null differ diff --git a/demo/thumbs/dom-007.webp b/demo/thumbs/dom-007.webp new file mode 100644 index 000000000..aa1e80c41 Binary files /dev/null and b/demo/thumbs/dom-007.webp differ diff --git a/demo/thumbs/dom-008.png b/demo/thumbs/dom-008.png deleted file mode 100644 index b8b456b1e..000000000 Binary files a/demo/thumbs/dom-008.png and /dev/null differ diff --git a/demo/thumbs/dom-008.webp b/demo/thumbs/dom-008.webp new file mode 100644 index 000000000..69d72aaab Binary files /dev/null and b/demo/thumbs/dom-008.webp differ diff --git a/demo/thumbs/dom-009.png b/demo/thumbs/dom-009.png deleted file mode 100644 index fbf2000e8..000000000 Binary files a/demo/thumbs/dom-009.png and /dev/null differ diff --git a/demo/thumbs/dom-009.webp b/demo/thumbs/dom-009.webp new file mode 100644 index 000000000..cb36e319f Binary files /dev/null and b/demo/thumbs/dom-009.webp differ diff --git a/demo/thumbs/dom-010.png b/demo/thumbs/dom-010.png deleted file mode 100644 index c80a594ba..000000000 Binary files a/demo/thumbs/dom-010.png and /dev/null differ diff --git a/demo/thumbs/dom-010.webp b/demo/thumbs/dom-010.webp new file mode 100644 index 000000000..a6f5c7567 Binary files /dev/null and b/demo/thumbs/dom-010.webp differ diff --git a/demo/thumbs/dom-011.png b/demo/thumbs/dom-011.png deleted file mode 100644 index 4e09bf099..000000000 Binary files a/demo/thumbs/dom-011.png and /dev/null differ diff --git a/demo/thumbs/dom-011.webp b/demo/thumbs/dom-011.webp new file mode 100644 index 000000000..2548f944b Binary files /dev/null and b/demo/thumbs/dom-011.webp differ diff --git a/demo/thumbs/dom-012.png b/demo/thumbs/dom-012.png deleted file mode 100644 index 3c0c94f7e..000000000 Binary files a/demo/thumbs/dom-012.png and /dev/null differ diff --git a/demo/thumbs/dom-012.webp b/demo/thumbs/dom-012.webp new file mode 100644 index 000000000..66f7ae8f8 Binary files /dev/null and b/demo/thumbs/dom-012.webp differ diff --git a/demo/thumbs/dom-013.png b/demo/thumbs/dom-013.png deleted file mode 100644 index e65e9458e..000000000 Binary files a/demo/thumbs/dom-013.png and /dev/null differ diff --git a/demo/thumbs/dom-013.webp b/demo/thumbs/dom-013.webp new file mode 100644 index 000000000..61951a1d9 Binary files /dev/null and b/demo/thumbs/dom-013.webp differ diff --git a/demo/thumbs/dom-014.png b/demo/thumbs/dom-014.png deleted file mode 100644 index a0dd7c02b..000000000 Binary files a/demo/thumbs/dom-014.png and /dev/null differ diff --git a/demo/thumbs/dom-014.webp b/demo/thumbs/dom-014.webp new file mode 100644 index 000000000..9031073fa Binary files /dev/null and b/demo/thumbs/dom-014.webp differ diff --git a/demo/thumbs/dom-015.png b/demo/thumbs/dom-015.png deleted file mode 100644 index 425ae6c2e..000000000 Binary files a/demo/thumbs/dom-015.png and /dev/null differ diff --git a/demo/thumbs/dom-015.webp b/demo/thumbs/dom-015.webp new file mode 100644 index 000000000..5e799409e Binary files /dev/null and b/demo/thumbs/dom-015.webp differ diff --git a/demo/thumbs/dom-016.png b/demo/thumbs/dom-016.png deleted file mode 100644 index 9e01fc8cb..000000000 Binary files a/demo/thumbs/dom-016.png and /dev/null differ diff --git a/demo/thumbs/dom-016.webp b/demo/thumbs/dom-016.webp new file mode 100644 index 000000000..11824c46a Binary files /dev/null and b/demo/thumbs/dom-016.webp differ diff --git a/demo/thumbs/filters-001.png b/demo/thumbs/filters-001.png deleted file mode 100644 index 09127ba53..000000000 Binary files a/demo/thumbs/filters-001.png and /dev/null differ diff --git a/demo/thumbs/filters-001.webp b/demo/thumbs/filters-001.webp new file mode 100644 index 000000000..c70acd019 Binary files /dev/null and b/demo/thumbs/filters-001.webp differ diff --git a/demo/thumbs/filters-002.png b/demo/thumbs/filters-002.png deleted file mode 100644 index 9129e7c31..000000000 Binary files a/demo/thumbs/filters-002.png and /dev/null differ diff --git a/demo/thumbs/filters-002.webp b/demo/thumbs/filters-002.webp new file mode 100644 index 000000000..5f1f3fbce Binary files /dev/null and b/demo/thumbs/filters-002.webp differ diff --git a/demo/thumbs/filters-003.png b/demo/thumbs/filters-003.png deleted file mode 100644 index 49d7f9c1e..000000000 Binary files a/demo/thumbs/filters-003.png and /dev/null differ diff --git a/demo/thumbs/filters-003.webp b/demo/thumbs/filters-003.webp new file mode 100644 index 000000000..224651b96 Binary files /dev/null and b/demo/thumbs/filters-003.webp differ diff --git a/demo/thumbs/filters-004.png b/demo/thumbs/filters-004.png deleted file mode 100644 index 029b9a8b5..000000000 Binary files a/demo/thumbs/filters-004.png and /dev/null differ diff --git a/demo/thumbs/filters-004.webp b/demo/thumbs/filters-004.webp new file mode 100644 index 000000000..b598e7c68 Binary files /dev/null and b/demo/thumbs/filters-004.webp differ diff --git a/demo/thumbs/filters-005.png b/demo/thumbs/filters-005.png deleted file mode 100644 index e6fcdd6d2..000000000 Binary files a/demo/thumbs/filters-005.png and /dev/null differ diff --git a/demo/thumbs/filters-005.webp b/demo/thumbs/filters-005.webp new file mode 100644 index 000000000..c63889c13 Binary files /dev/null and b/demo/thumbs/filters-005.webp differ diff --git a/demo/thumbs/filters-006.png b/demo/thumbs/filters-006.png deleted file mode 100644 index e1c8f4de4..000000000 Binary files a/demo/thumbs/filters-006.png and /dev/null differ diff --git a/demo/thumbs/filters-006.webp b/demo/thumbs/filters-006.webp new file mode 100644 index 000000000..051f64fb8 Binary files /dev/null and b/demo/thumbs/filters-006.webp differ diff --git a/demo/thumbs/filters-007.png b/demo/thumbs/filters-007.png deleted file mode 100644 index 772d3d91f..000000000 Binary files a/demo/thumbs/filters-007.png and /dev/null differ diff --git a/demo/thumbs/filters-007.webp b/demo/thumbs/filters-007.webp new file mode 100644 index 000000000..a709c59a2 Binary files /dev/null and b/demo/thumbs/filters-007.webp differ diff --git a/demo/thumbs/filters-008.png b/demo/thumbs/filters-008.png deleted file mode 100644 index ef7c438fe..000000000 Binary files a/demo/thumbs/filters-008.png and /dev/null differ diff --git a/demo/thumbs/filters-008.webp b/demo/thumbs/filters-008.webp new file mode 100644 index 000000000..50ce3bce5 Binary files /dev/null and b/demo/thumbs/filters-008.webp differ diff --git a/demo/thumbs/filters-009.png b/demo/thumbs/filters-009.png deleted file mode 100644 index 2586829d2..000000000 Binary files a/demo/thumbs/filters-009.png and /dev/null differ diff --git a/demo/thumbs/filters-009.webp b/demo/thumbs/filters-009.webp new file mode 100644 index 000000000..8a41b6bf7 Binary files /dev/null and b/demo/thumbs/filters-009.webp differ diff --git a/demo/thumbs/filters-010.png b/demo/thumbs/filters-010.png deleted file mode 100644 index f24510cca..000000000 Binary files a/demo/thumbs/filters-010.png and /dev/null differ diff --git a/demo/thumbs/filters-010.webp b/demo/thumbs/filters-010.webp new file mode 100644 index 000000000..e3a41fb2c Binary files /dev/null and b/demo/thumbs/filters-010.webp differ diff --git a/demo/thumbs/filters-011.png b/demo/thumbs/filters-011.png deleted file mode 100644 index 9529e4c14..000000000 Binary files a/demo/thumbs/filters-011.png and /dev/null differ diff --git a/demo/thumbs/filters-011.webp b/demo/thumbs/filters-011.webp new file mode 100644 index 000000000..61ba438c2 Binary files /dev/null and b/demo/thumbs/filters-011.webp differ diff --git a/demo/thumbs/filters-012.png b/demo/thumbs/filters-012.png deleted file mode 100644 index 4a4009e32..000000000 Binary files a/demo/thumbs/filters-012.png and /dev/null differ diff --git a/demo/thumbs/filters-012.webp b/demo/thumbs/filters-012.webp new file mode 100644 index 000000000..ae5261994 Binary files /dev/null and b/demo/thumbs/filters-012.webp differ diff --git a/demo/thumbs/filters-013.png b/demo/thumbs/filters-013.png deleted file mode 100644 index ce1130e78..000000000 Binary files a/demo/thumbs/filters-013.png and /dev/null differ diff --git a/demo/thumbs/filters-013.webp b/demo/thumbs/filters-013.webp new file mode 100644 index 000000000..02475adda Binary files /dev/null and b/demo/thumbs/filters-013.webp differ diff --git a/demo/thumbs/filters-014.png b/demo/thumbs/filters-014.png deleted file mode 100644 index d8cd20932..000000000 Binary files a/demo/thumbs/filters-014.png and /dev/null differ diff --git a/demo/thumbs/filters-014.webp b/demo/thumbs/filters-014.webp new file mode 100644 index 000000000..36b00e1ac Binary files /dev/null and b/demo/thumbs/filters-014.webp differ diff --git a/demo/thumbs/filters-015.png b/demo/thumbs/filters-015.png deleted file mode 100644 index 560a5ab59..000000000 Binary files a/demo/thumbs/filters-015.png and /dev/null differ diff --git a/demo/thumbs/filters-015.webp b/demo/thumbs/filters-015.webp new file mode 100644 index 000000000..4bce7cda7 Binary files /dev/null and b/demo/thumbs/filters-015.webp differ diff --git a/demo/thumbs/filters-016.webp b/demo/thumbs/filters-016.webp new file mode 100644 index 000000000..baf0ced3b Binary files /dev/null and b/demo/thumbs/filters-016.webp differ diff --git a/demo/thumbs/filters-017.webp b/demo/thumbs/filters-017.webp new file mode 100644 index 000000000..e2cce8418 Binary files /dev/null and b/demo/thumbs/filters-017.webp differ diff --git a/demo/thumbs/filters-018.webp b/demo/thumbs/filters-018.webp new file mode 100644 index 000000000..4c14e672e Binary files /dev/null and b/demo/thumbs/filters-018.webp differ diff --git a/demo/thumbs/filters-019.webp b/demo/thumbs/filters-019.webp new file mode 100644 index 000000000..2967b711c Binary files /dev/null and b/demo/thumbs/filters-019.webp differ diff --git a/demo/thumbs/filters-020.webp b/demo/thumbs/filters-020.webp new file mode 100644 index 000000000..bf23cc60a Binary files /dev/null and b/demo/thumbs/filters-020.webp differ diff --git a/demo/thumbs/filters-501.webp b/demo/thumbs/filters-501.webp new file mode 100644 index 000000000..e0bbb1e1d Binary files /dev/null and b/demo/thumbs/filters-501.webp differ diff --git a/demo/thumbs/filters-502.webp b/demo/thumbs/filters-502.webp new file mode 100644 index 000000000..506423f25 Binary files /dev/null and b/demo/thumbs/filters-502.webp differ diff --git a/demo/thumbs/filters-503.webp b/demo/thumbs/filters-503.webp new file mode 100644 index 000000000..bcc330358 Binary files /dev/null and b/demo/thumbs/filters-503.webp differ diff --git a/demo/thumbs/filters-504.webp b/demo/thumbs/filters-504.webp new file mode 100644 index 000000000..7201314e2 Binary files /dev/null and b/demo/thumbs/filters-504.webp differ diff --git a/demo/thumbs/filters-505.webp b/demo/thumbs/filters-505.webp new file mode 100644 index 000000000..58f65ecc7 Binary files /dev/null and b/demo/thumbs/filters-505.webp differ diff --git a/demo/thumbs/hello-world.png b/demo/thumbs/hello-world.png deleted file mode 100644 index e67e34ee7..000000000 Binary files a/demo/thumbs/hello-world.png and /dev/null differ diff --git a/demo/thumbs/hello-world.webp b/demo/thumbs/hello-world.webp new file mode 100644 index 000000000..e7377972f Binary files /dev/null and b/demo/thumbs/hello-world.webp differ diff --git a/demo/thumbs/particles-001.png b/demo/thumbs/particles-001.png deleted file mode 100644 index ca956f292..000000000 Binary files a/demo/thumbs/particles-001.png and /dev/null differ diff --git a/demo/thumbs/particles-001.webp b/demo/thumbs/particles-001.webp new file mode 100644 index 000000000..356d4fdc7 Binary files /dev/null and b/demo/thumbs/particles-001.webp differ diff --git a/demo/thumbs/particles-002.png b/demo/thumbs/particles-002.png deleted file mode 100644 index fa284309f..000000000 Binary files a/demo/thumbs/particles-002.png and /dev/null differ diff --git a/demo/thumbs/particles-002.webp b/demo/thumbs/particles-002.webp new file mode 100644 index 000000000..697b12514 Binary files /dev/null and b/demo/thumbs/particles-002.webp differ diff --git a/demo/thumbs/particles-003.png b/demo/thumbs/particles-003.png deleted file mode 100644 index 270a0c36d..000000000 Binary files a/demo/thumbs/particles-003.png and /dev/null differ diff --git a/demo/thumbs/particles-003.webp b/demo/thumbs/particles-003.webp new file mode 100644 index 000000000..aebef9a55 Binary files /dev/null and b/demo/thumbs/particles-003.webp differ diff --git a/demo/thumbs/particles-004.png b/demo/thumbs/particles-004.png deleted file mode 100644 index 1166bbf27..000000000 Binary files a/demo/thumbs/particles-004.png and /dev/null differ diff --git a/demo/thumbs/particles-004.webp b/demo/thumbs/particles-004.webp new file mode 100644 index 000000000..35ee9bb7a Binary files /dev/null and b/demo/thumbs/particles-004.webp differ diff --git a/demo/thumbs/particles-005.png b/demo/thumbs/particles-005.png deleted file mode 100644 index e1b30c6f1..000000000 Binary files a/demo/thumbs/particles-005.png and /dev/null differ diff --git a/demo/thumbs/particles-005.webp b/demo/thumbs/particles-005.webp new file mode 100644 index 000000000..ef2d3f317 Binary files /dev/null and b/demo/thumbs/particles-005.webp differ diff --git a/demo/thumbs/particles-006.png b/demo/thumbs/particles-006.png deleted file mode 100644 index 54f395dab..000000000 Binary files a/demo/thumbs/particles-006.png and /dev/null differ diff --git a/demo/thumbs/particles-006.webp b/demo/thumbs/particles-006.webp new file mode 100644 index 000000000..4df446fca Binary files /dev/null and b/demo/thumbs/particles-006.webp differ diff --git a/demo/thumbs/particles-007.png b/demo/thumbs/particles-007.png deleted file mode 100644 index d778b7185..000000000 Binary files a/demo/thumbs/particles-007.png and /dev/null differ diff --git a/demo/thumbs/particles-007.webp b/demo/thumbs/particles-007.webp new file mode 100644 index 000000000..ff229350f Binary files /dev/null and b/demo/thumbs/particles-007.webp differ diff --git a/demo/thumbs/particles-008.png b/demo/thumbs/particles-008.png deleted file mode 100644 index c115558eb..000000000 Binary files a/demo/thumbs/particles-008.png and /dev/null differ diff --git a/demo/thumbs/particles-008.webp b/demo/thumbs/particles-008.webp new file mode 100644 index 000000000..9b0f86301 Binary files /dev/null and b/demo/thumbs/particles-008.webp differ diff --git a/demo/thumbs/particles-009.png b/demo/thumbs/particles-009.png deleted file mode 100644 index ff0c774c5..000000000 Binary files a/demo/thumbs/particles-009.png and /dev/null differ diff --git a/demo/thumbs/particles-009.webp b/demo/thumbs/particles-009.webp new file mode 100644 index 000000000..76de46667 Binary files /dev/null and b/demo/thumbs/particles-009.webp differ diff --git a/demo/thumbs/particles-010.png b/demo/thumbs/particles-010.png deleted file mode 100644 index b448360c6..000000000 Binary files a/demo/thumbs/particles-010.png and /dev/null differ diff --git a/demo/thumbs/particles-010.webp b/demo/thumbs/particles-010.webp new file mode 100644 index 000000000..07c1361da Binary files /dev/null and b/demo/thumbs/particles-010.webp differ diff --git a/demo/thumbs/particles-011.png b/demo/thumbs/particles-011.png deleted file mode 100644 index 4de9ab846..000000000 Binary files a/demo/thumbs/particles-011.png and /dev/null differ diff --git a/demo/thumbs/particles-011.webp b/demo/thumbs/particles-011.webp new file mode 100644 index 000000000..1b7dd3cd3 Binary files /dev/null and b/demo/thumbs/particles-011.webp differ diff --git a/demo/thumbs/particles-012.png b/demo/thumbs/particles-012.png deleted file mode 100644 index c3732f435..000000000 Binary files a/demo/thumbs/particles-012.png and /dev/null differ diff --git a/demo/thumbs/particles-012.webp b/demo/thumbs/particles-012.webp new file mode 100644 index 000000000..e92f1156b Binary files /dev/null and b/demo/thumbs/particles-012.webp differ diff --git a/demo/thumbs/particles-013.png b/demo/thumbs/particles-013.png deleted file mode 100644 index a683aa4d6..000000000 Binary files a/demo/thumbs/particles-013.png and /dev/null differ diff --git a/demo/thumbs/particles-013.webp b/demo/thumbs/particles-013.webp new file mode 100644 index 000000000..8baef7524 Binary files /dev/null and b/demo/thumbs/particles-013.webp differ diff --git a/demo/thumbs/particles-014.png b/demo/thumbs/particles-014.png deleted file mode 100644 index 8d7a05634..000000000 Binary files a/demo/thumbs/particles-014.png and /dev/null differ diff --git a/demo/thumbs/particles-014.webp b/demo/thumbs/particles-014.webp new file mode 100644 index 000000000..36116cbc8 Binary files /dev/null and b/demo/thumbs/particles-014.webp differ diff --git a/demo/thumbs/particles-015.png b/demo/thumbs/particles-015.png deleted file mode 100644 index 7cc7fc17f..000000000 Binary files a/demo/thumbs/particles-015.png and /dev/null differ diff --git a/demo/thumbs/particles-015.webp b/demo/thumbs/particles-015.webp new file mode 100644 index 000000000..cf277744c Binary files /dev/null and b/demo/thumbs/particles-015.webp differ diff --git a/demo/thumbs/particles-016.webp b/demo/thumbs/particles-016.webp new file mode 100644 index 000000000..6c337f4e7 Binary files /dev/null and b/demo/thumbs/particles-016.webp differ diff --git a/docs/demo/canvas-001.html b/docs/demo/canvas-001.html index 599627654..405e02210 100644 --- a/docs/demo/canvas-001.html +++ b/docs/demo/canvas-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-002.html b/docs/demo/canvas-002.html index bd748938d..c202dbb73 100644 --- a/docs/demo/canvas-002.html +++ b/docs/demo/canvas-002.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-003.html b/docs/demo/canvas-003.html index 579dde609..3bfdb482e 100644 --- a/docs/demo/canvas-003.html +++ b/docs/demo/canvas-003.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-004.html b/docs/demo/canvas-004.html index 1253851d3..4c34a0dc6 100644 --- a/docs/demo/canvas-004.html +++ b/docs/demo/canvas-004.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-005.html b/docs/demo/canvas-005.html index 923a7c391..1d898c160 100644 --- a/docs/demo/canvas-005.html +++ b/docs/demo/canvas-005.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-006.html b/docs/demo/canvas-006.html index 06ffc048f..720d14d51 100644 --- a/docs/demo/canvas-006.html +++ b/docs/demo/canvas-006.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-007.html b/docs/demo/canvas-007.html index 6feed5864..6cd4baa6d 100644 --- a/docs/demo/canvas-007.html +++ b/docs/demo/canvas-007.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -589,9 +649,11 @@

    Scene setup

    let canvas = scrawl.library.artefact.mycanvas;
     
    +scrawl.importDomImage('.map');
    +
     canvas.set({
         fit: 'fill',
    -    backgroundColor: 'lightgray',
    +    backgroundColor: 'beige',
         css: {
             border: '1px solid black'
         }
    @@ -727,13 +789,13 @@ 

    Scene setup

    -

    Grayscale filter

    +

    Gray filter

    scrawl.makeFilter({
    -    name: 'grayscale',
    -    method: 'grayscale',
    + name: 'gray', + method: 'gray',
    @@ -744,6 +806,23 @@

    Scene setup

    +

    Grayscale filter

    + + + +
    }).clone({
    +    name: 'grayscale',
    +    method: 'grayscale',
    + + + + +
  • +
    + +
    + +

    Sepia filter

    @@ -755,11 +834,11 @@

    Scene setup

  • -
  • +
  • - +

    Invert filter

    @@ -772,11 +851,11 @@

    Scene setup

  • -
  • +
  • - +

    Red filter

    @@ -789,11 +868,11 @@

    Scene setup

  • -
  • +
  • - +

    Green filter

    @@ -806,11 +885,11 @@

    Scene setup

  • -
  • +
  • - +

    Blue filter

    @@ -823,11 +902,11 @@

    Scene setup

  • -
  • +
  • - +

    Notred filter

    @@ -840,11 +919,11 @@

    Scene setup

  • -
  • +
  • - +

    Notgreen filter

    @@ -857,11 +936,11 @@

    Scene setup

  • -
  • +
  • - +

    Notblue filter

    @@ -874,11 +953,11 @@

    Scene setup

  • -
  • +
  • - +

    Cyan filter

    @@ -891,11 +970,11 @@

    Scene setup

  • -
  • +
  • - +

    Magenta filter

    @@ -908,11 +987,11 @@

    Scene setup

  • -
  • +
  • - +

    Yellow filter

    @@ -920,17 +999,72 @@

    Scene setup

    }).clone({
         name: 'yellow',
    -    method: 'yellow',
    +    method: 'yellow',
    + +
  • + + +
  • +
    + +
    + +
    +

    Edge detect filter

    + +
    + +
    }).clone({
    +    name: 'edgeDetect',
    +    method: 'edgeDetect',
    + +
  • + + +
  • +
    + +
    + +
    +

    Sharpen filter

    + +
    + +
    }).clone({
    +    name: 'sharpen',
    +    method: 'sharpen',
     });
  • -
  • +
  • - + +
    +

    Emboss filter

    + +
    + +
    scrawl.makeFilter({
    +    name: 'emboss',
    +    method: 'emboss',
    +    angle: 225,
    +    strength: 10,
    +    tolerance: 50,
    +});
    + +
  • + + +
  • +
    + +
    +

    Chroma (green screen) filter

    @@ -945,11 +1079,11 @@

    Scene setup

  • -
  • +
  • - +

    Brightness filter

    @@ -963,11 +1097,11 @@

    Scene setup

  • -
  • +
  • - +

    Saturation filter

    @@ -981,11 +1115,11 @@

    Scene setup

  • -
  • +
  • - +

    Threshhold filter

    @@ -1006,11 +1140,11 @@

    Scene setup

  • -
  • +
  • - +

    Channels filter

    @@ -1026,11 +1160,11 @@

    Scene setup

  • -
  • +
  • - +

    Channelstep filter

    @@ -1047,11 +1181,11 @@

    Scene setup

  • -
  • +
  • - +

    Tint filter

    @@ -1074,11 +1208,54 @@

    Scene setup

  • -
  • +
  • - + +
    +

    Offset filter

    + +
    + +
    scrawl.makeFilter({
    +    name: 'offset',
    +    method: 'offset',
    +    offsetX: 12,
    +    offsetY: 12,
    +    opacity: 0.5,
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    Offset Channels filter

    + +
    + +
    scrawl.makeFilter({
    +    name: 'offsetChannels',
    +    method: 'offsetChannels',
    +    offsetRedX: -12,
    +    offsetGreenY: 12,
    +    offsetBlueX: 3,
    +    offsetBlueY: -3,
    +});
    + +
  • + + +
  • +
    + +
    +

    Pixellate filter

    @@ -1096,11 +1273,11 @@

    Scene setup

  • -
  • +
  • - +

    Blur filter

    @@ -1109,121 +1286,199 @@

    Scene setup

    scrawl.makeFilter({
         name: 'blur',
         method: 'blur',
    -    radius: 20,
    -    shrinkingRadius: true,
    -    includeAlpha: true,
    -    passes: 3,
    +    radius: 6,
    +    passes: 2,
     });
  • -
  • +
  • - +
    -

    Matrix filter

    +

    AreaAlpha filter

    scrawl.makeFilter({
    -    name: 'matrix',
    -    method: 'matrix',
    -    weights: [-1, -1, 0, -1, 1, 1, 0, 1, 1],
    + name: 'areaAlpha', + method: 'areaAlpha', + tileWidth: 20, + tileHeight: 20, + gutterWidth: 20, + gutterHeight: 20, + offsetX: 8, + offsetY: 8, + areaAlphaLevels: [255, 0, 0, 255], +});
  • -
  • +
  • - +
    -

    Matrix5 filter

    +

    Matrix filter

    -
    }).clone({
    -    name: 'matrix5',
    -    method: 'matrix5',
    -    weights: [-1, -1, -1, -1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 1],
    -});
    +
    scrawl.makeFilter({
    +    name: 'matrix',
    +    method: 'matrix',
    +    weights: [-1, -1, 0, -1, 1, 1, 0, 1, 1],
  • -
  • +
  • - +
    -

    First user-defined filter

    +

    Matrix5 filter

    -
    let myUDF = scrawl.makeFilter({
    -    name: 'totalRed',
    -    method: 'userDefined',
    -
    -    userDefined: `
    -        for (let i = 0, iz = cache.length; i < iz; i++) {
    -
    -            data[cache[i]] = 255;
    -        }`,
    +            
    }).clone({
    +    name: 'matrix5',
    +    method: 'matrix5',
    +    weights: [-1, -1, -1, -1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 1],
     });
  • -
  • +
  • - +
    -

    Second user-defined filter (cloned)

    +

    ChannelLevels filter

    -
    myUDF.clone({
    -    name: 'venetianBlinds',
    -    level: 9,
    -
    -    userDefined: `
    -        let i, iz, j, jz,
    -            level = filter.level || 6,
    -            halfLevel = level / 2,
    -            yw, transparent, pos;
    -
    -        for (i = localY, iz = localY + localHeight; i < iz; i++) {
    +            
    scrawl.makeFilter({
    +    name: 'channelLevels',
    +    method: 'channelLevels',
    +    red: [50, 200],
    +    green: [60, 220, 150],
    +    blue: [40, 180],
    +    alpha: [],
    +});
     
    -            transparent = (i % level > halfLevel) ? true : false;
    +scrawl.makeFilter({
    +    name: 'chromakey',
    +    method: 'chromakey',
    +    red: 0,
    +    green: 127,
    +    blue: 0,
    +    opaqueAt: 0.7,
    +    transparentAt: 0.5,
    +});
     
    -            if (transparent) {
    +scrawl.makeFilter({
    +    name: 'dropShadow',
    +    actions: [
    +        {
    +            action: 'blur',
    +            lineIn: 'source-alpha',
    +            lineOut: 'shadow',
    +            radius: 2, 
    +            passes: 2, 
    +            includeRed: false, 
    +            includeGreen: false, 
    +            includeBlue: false, 
    +            includeAlpha: true, 
    +        },
    +        {
    +            action: 'compose',
    +            lineIn: 'source',
    +            lineMix: 'shadow',
    +            offsetX: 6,
    +            offsetY: 6,
    +        }
    +    ],
    +});
     
    -                yw = (i * iWidth) + 3;
    -                
    -                for (j = localX, jz = localX + localWidth; j < jz; j ++) {
    +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',
    +        }
    +    ],
    +});
     
    -                    pos = yw + (j * 4);
    -                    data[pos] = 0;
    -                }
    -            }
    -        }`,
    +scrawl.makeFilter({
    +    name: 'noise',
    +    actions: [
    +        {
    +            action: 'process-image',
    +            asset: 'perlin',
    +            width: 500,
    +            height: 500,
    +            copyWidth: 500,
    +            copyHeight: 500,
    +            lineOut: 'map',
    +        },
    +        {
    +            action: 'displace',
    +            lineMix: 'map',
    +            scaleX: 20,
    +            scaleY: 30,
    +            transparentEdges: true,
    +        }
    +    ],
     });
  • -
  • +
  • - +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -1249,11 +1504,11 @@

    Scene animation

  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -1269,11 +1524,11 @@

    Scene animation

  • -
  • +
  • - +

    User interaction

    Event listeners function

    @@ -1359,11 +1614,11 @@

    User interaction

  • -
  • +
  • - +

    Event listeners

    @@ -1374,11 +1629,11 @@

    User interaction

  • -
  • +
  • - +

    Set DOM form initial input values

    @@ -1390,11 +1645,11 @@

    User interaction

  • -
  • +
  • - +

    Development and testing

    @@ -1405,11 +1660,11 @@

    Development and testing

  • -
  • +
  • - +

    Gradient packet test

    @@ -1421,11 +1676,11 @@

    Development and testing

  • -
  • +
  • - +

    RESULT:

    [
    diff --git a/docs/demo/canvas-008.html b/docs/demo/canvas-008.html
    index c69d6ba4f..f9338a4c4 100644
    --- a/docs/demo/canvas-008.html
    +++ b/docs/demo/canvas-008.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    diff --git a/docs/demo/canvas-009.html b/docs/demo/canvas-009.html
    index e7becc6c5..71d22b42e 100644
    --- a/docs/demo/canvas-009.html
    +++ b/docs/demo/canvas-009.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    diff --git a/docs/demo/canvas-010.html b/docs/demo/canvas-010.html
    index ed64fbc0f..cc338e8c4 100644
    --- a/docs/demo/canvas-010.html
    +++ b/docs/demo/canvas-010.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    diff --git a/docs/demo/canvas-011.html b/docs/demo/canvas-011.html
    index d792ec317..7eca63d62 100644
    --- a/docs/demo/canvas-011.html
    +++ b/docs/demo/canvas-011.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    diff --git a/docs/demo/canvas-012.html b/docs/demo/canvas-012.html
    index 110e82d49..d72911251 100644
    --- a/docs/demo/canvas-012.html
    +++ b/docs/demo/canvas-012.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    diff --git a/docs/demo/canvas-013.html b/docs/demo/canvas-013.html
    index 49cf09d8d..c8fb9c4fa 100644
    --- a/docs/demo/canvas-013.html
    +++ b/docs/demo/canvas-013.html
    @@ -250,6 +250,11 @@
                     
                   
                     
    +                
    +                  ./demo/canvas-047.js
    +                
    +              
    +                
                     
                       ./demo/component-001.js
                     
    @@ -455,6 +460,56 @@
                     
                   
                     
    +                
    +                  ./demo/filters-016.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-017.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-018.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-019.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-020.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-501.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-502.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-503.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-504.js
    +                
    +              
    +                
    +                
    +                  ./demo/filters-505.js
    +                
    +              
    +                
                     
                       ./demo/particles-001.js
                     
    @@ -530,6 +585,11 @@
                     
                   
                     
    +                
    +                  ./demo/particles-016.js
    +                
    +              
    +                
                     
                       ./demo/temp-001.js
                     
    @@ -724,15 +784,15 @@ 
    makeLine factory function
    }).clone({ name: 'secondLine', - startY: '22%', - endY: '19.2%', + startY: '16.5%', + endY: '13.7%', }).clone({ name: 'thirdLine', startX: '20%', - startY: '18.5%', + startY: '14%', endX: '85%', - endY: '18.5%', + endY: '14%', });
  • @@ -751,11 +811,11 @@
    makeQuadratic factory function
    scrawl.makeQuadratic({
         name: 'firstQuad',
         startX: '5%',
    -    startY: '30.5%',
    +    startY: '20%',
         controlX: '50%',
    -    controlY: '22%',
    +    controlY: '15%',
         endX: '95%',
    -    endY: '30.5%',
    +    endY: '20%',
         lineWidth: 3,
         lineCap: 'round',
         strokeStyle: 'darkseagreen',
    @@ -766,15 +826,17 @@ 
    makeQuadratic factory function
    }).clone({ name: 'secondQuad', - startX: '7%', - controlY: '18.5%', - endX: '93%', + startX: '12%', + endX: '88%', + startY: '21.5%', + endY: '21.5%', }).clone({ name: 'thirdQuad', - startX: '9%', - controlY: '15%', - endX: '91%', + startX: '19%', + endX: '81%', + startY: '23%', + endY: '23%', });
    @@ -793,13 +855,13 @@
    makeBezier factory function
    scrawl.makeBezier({
         name: 'firstBezier',
         startX: '5%',
    -    startY: '36%',
    +    startY: '27%',
         startControlX: '40%',
    -    startControlY: '31%',
    +    startControlY: '22%',
         endControlX: '60%',
    -    endControlY: '41%',
    +    endControlY: '32%',
         endX: '95%',
    -    endY: '36%',
    +    endY: '27%',
         lineWidth: 3,
         lineCap: 'round',
         strokeStyle: 'linen',
    @@ -811,15 +873,15 @@ 
    makeBezier factory function
    }).clone({ name: 'secondBezier', startX: '7%', - startControlY: '25%', - endControlY: '47%', + startControlY: '18%', + endControlY: '36%', endX: '93%', }).clone({ name: 'thirdBezier', startX: '9%', - startControlY: '19%', - endControlY: '53%', + startControlY: '14%', + endControlY: '40%', endX: '91%', });
    diff --git a/docs/demo/canvas-014.html b/docs/demo/canvas-014.html index 4f20eec91..550b7c563 100644 --- a/docs/demo/canvas-014.html +++ b/docs/demo/canvas-014.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-015.html b/docs/demo/canvas-015.html index 860cadb6b..00d2522e2 100644 --- a/docs/demo/canvas-015.html +++ b/docs/demo/canvas-015.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-016.html b/docs/demo/canvas-016.html index 5994ecfd8..8a8188c9e 100644 --- a/docs/demo/canvas-016.html +++ b/docs/demo/canvas-016.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -858,7 +918,7 @@

    User interaction

    document.querySelector('#variant').options.selectedIndex = 0; document.querySelector('#family').options.selectedIndex = 0; document.querySelector('#size_px').value = 16; -document.querySelector('#size_string').options.selectedIndex = 4; +document.querySelector('#size_string').options.selectedIndex = 0; diff --git a/docs/demo/canvas-017.html b/docs/demo/canvas-017.html index 0790f2e49..5bc27b6a4 100644 --- a/docs/demo/canvas-017.html +++ b/docs/demo/canvas-017.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -804,7 +864,7 @@

    User interaction

    document.querySelector('#justify').options.selectedIndex = 0; document.querySelector('#family').options.selectedIndex = 0; document.querySelector('#size_px').value = 16; -document.querySelector('#size_string').options.selectedIndex = 4; +document.querySelector('#size_string').options.selectedIndex = 0; diff --git a/docs/demo/canvas-018.html b/docs/demo/canvas-018.html index d432f5608..8ba06c22c 100644 --- a/docs/demo/canvas-018.html +++ b/docs/demo/canvas-018.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-019.html b/docs/demo/canvas-019.html index 0d4e38ddb..7a5d58933 100644 --- a/docs/demo/canvas-019.html +++ b/docs/demo/canvas-019.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-020.html b/docs/demo/canvas-020.html index 3f2a20651..3178f97e2 100644 --- a/docs/demo/canvas-020.html +++ b/docs/demo/canvas-020.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-021.html b/docs/demo/canvas-021.html index f9afc39fd..e190e5984 100644 --- a/docs/demo/canvas-021.html +++ b/docs/demo/canvas-021.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-022.html b/docs/demo/canvas-022.html index 4d6f3c9c5..e6bce32bf 100644 --- a/docs/demo/canvas-022.html +++ b/docs/demo/canvas-022.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -620,6 +680,7 @@

    Scene setup

    let gridGradient = scrawl.makeGradient({ name: 'red-blue', endX: '100%', + endY: '100%', }) .updateColor(0, 'red') .updateColor(500, 'gold') diff --git a/docs/demo/canvas-023.html b/docs/demo/canvas-023.html index 3c39fce7c..1e65aa5ad 100644 --- a/docs/demo/canvas-023.html +++ b/docs/demo/canvas-023.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-024.html b/docs/demo/canvas-024.html index fbd00478c..7e1cec92d 100644 --- a/docs/demo/canvas-024.html +++ b/docs/demo/canvas-024.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-025.html b/docs/demo/canvas-025.html index 31481e539..c31fd10f0 100644 --- a/docs/demo/canvas-025.html +++ b/docs/demo/canvas-025.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-026.html b/docs/demo/canvas-026.html index 2f2fae0aa..c779a5e44 100644 --- a/docs/demo/canvas-026.html +++ b/docs/demo/canvas-026.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-027.html b/docs/demo/canvas-027.html index 6b803472a..0f933f8ff 100644 --- a/docs/demo/canvas-027.html +++ b/docs/demo/canvas-027.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -859,13 +919,21 @@

    Scene setup

    scrawl.makeFilter({
     
    -    name: 'red',
    -    method: 'red',
    -}).clone({
    -
    -    name: 'chroma',
    -    method: 'chroma',
    -    ranges: [[0, 0, 0, 190, 190, 190]],
    +    name: 'swan-mask',
    +
    +    actions: [
    +        {
    +            action: 'threshold',
    +            level: 200,
    +            low: [0, 0, 0],
    +            high: [255, 0, 0],
    +        },
    +        {
    +            action: 'channels-to-alpha',
    +            includeGreen: false,
    +            includeBlue: false,
    +        },
    +    ],
     });
     
     scrawl.makePicture({
    @@ -884,7 +952,7 @@ 

    Scene setup

    copyStartY: '25%', - filters: ['chroma', 'red'], + filters: ['swan-mask'], globalAlpha: 0.01, @@ -1169,7 +1237,7 @@

    Scene setup

    name: 'test-video-time-phrase', family: 'monospace', - size: '2em', + size: '1em', weight: '700', startX: '1%', diff --git a/docs/demo/canvas-028.html b/docs/demo/canvas-028.html index e70e078c4..09ce710ca 100644 --- a/docs/demo/canvas-028.html +++ b/docs/demo/canvas-028.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-029.html b/docs/demo/canvas-029.html index b0337312b..7468e973d 100644 --- a/docs/demo/canvas-029.html +++ b/docs/demo/canvas-029.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -610,22 +670,8 @@

    Scene setup

    name: 'gradient-1', - endX: '100%',
    - - - - -
  • -
    - -
    - -
    -

    endY: ‘100%’,

    + endX: '100%', -
    - -
         paletteStart: 10,
         paletteEnd: 990,
     
    @@ -665,11 +711,11 @@ 

    Scene setup

  • -
  • +
  • - +

    Create Phrase entitys

    @@ -747,11 +793,11 @@

    Scene setup

  • -
  • +
  • - +

    Test to see if we can load a webfont from a remote server and see it show up in the canvas element

    + +
  • + + +
  • +
    + +
    + +
    +

    Add a new Cell to our canvas

    + +
    + +
    const patternCell = canvas.buildCell({
     
    -canvas.buildCell({
    -    name: 'gradient-sub-pattern',
    +    name: 'gradient-pattern-cell',
         dimensions: [50, 50],
         shown: false,
         compileOrder: 0,
    @@ -642,30 +717,37 @@ 

    Scene setup

  • -
  • +
  • - +
    -

    Populate our new Cell with blocks using our linear gradient

    +

    Populate our new Cell with Block entitys that use our linear gradient

    scrawl.makeBlock({
    +
         name: 'gradient-block-br',
    -    group: 'gradient-sub-pattern',
    +    group: 'gradient-pattern-cell',
         dimensions: [25, 25],
         start: ['center', 'center'],
         fillStyle: 'linear',
         lockFillStyleToEntity: true,
    +
     }).clone({
    +
         name: 'gradient-block-bl',
         roll: 90,
    +
     }).clone({
    +
         name: 'gradient-block-tl',
         roll: 180,
    +
     }).clone({
    +
         name: 'gradient-block-tr',
         roll: 270,
     });
    @@ -673,123 +755,163 @@

    Scene setup

  • -
  • +
  • - +
    -

    STEP 2: We have a pattern that we can use, but we can make it even more interesting using an SVG turbulence-and-displacement filter. We define the SVG in the HTML code, then apply it to a new Cell we create for the effect.

    +

    STEP 2. Apply a Noise-based displacement filter to our pattern Cell. We can then animate this filter to make it more interesting

    -
    canvas.buildCell({
    -    name: 'warped-pattern',
    -    dimensions: [400, 400],
    -    shown: false,
    -    compileOrder: 1,
    +        
  • + + +
  • +
    + +
    + +
    +

    Create the Noise asset

    + +
    + +
    scrawl.makeNoise({
    +
    +    name: 'my-noise-generator',
    +    width: 50,
    +    height: 50,
    +    octaves: 5,
    +    scale: 2,
    +    noiseFunction: 'simplex',
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    TEST: see if we can load the Noise asset directly into a Picture entity, and into a Pattern style - we’ll use these for background textures.

    + +
    + +
    scrawl.makePicture({
    +
    +    name: 'test-picture',
    +
    +    dimensions: [300, 400],
    +    copyDimensions: ['100%', '100%'],
    +
    +    asset: 'my-noise-generator',
    +
    +    globalAlpha: 0.2,
    +});
    +
    +scrawl.makePattern({
    +
    +    name: 'test-pattern',
    +    asset: 'my-noise-generator',
     });
     
     scrawl.makeBlock({
    -    name: 'warped-pattern-block',
    -    group: 'warped-pattern',
    -    dimensions: [900, 900],
    -    start: ['center', 'center'],
    -    handle: ['center', 'center'],
    -    roll: 45,
    -    fillStyle: 'gradient-sub-pattern',
    -    filter: 'url(#svg-noise)',
    +
    +    name: 'test-pattern-block',
    +    startX: 300,
    +    dimensions: [300, 400],
    +
    +    fillStyle: 'test-pattern',
    +
    +    globalAlpha: 0.2,
     });
  • -
  • +
  • - +
    -

    STEP 3: We can animate our SVG filter using a combination of a Scrawl-canvas World object and some SC tweens

    +

    Build filters that use the Noise asset

    -
    const turbulence = document.querySelector('feTurbulence');
    -const displacement = document.querySelector('feDisplacementMap');
    +            
    scrawl.makeFilter({
     
    -const myWorld = scrawl.makeWorld({
    -    name: 'svg-filter-accessor',
    -    userAttributes: [
    -        {
    -            key: 'baseFreqX', 
    -            defaultValue: 0,
    -            setter: function (item) {
    -                this.baseFreqX = item;
    -                turbulence.setAttribute('baseFrequency', `${this.baseFreqX} ${this.baseFreqY}`);
    -            },
    -        },
    -        {
    -            key: 'baseFreqY', 
    -            defaultValue: 0,
    -            setter: function (item) {
    -                this.baseFreqY = item;
    -                turbulence.setAttribute('baseFrequency', `${this.baseFreqX} ${this.baseFreqY}`);
    -            },
    -        },
    -        {
    -            key: 'scale', 
    -            defaultValue: 0,
    -            setter: function (item) {
    -                this.scale = item;
    -                displacement.setAttribute('scale', `${this.scale}`);
    -            },
    -        }
    -    ],
    +    name: 'noise',
    +    method: 'image',
    +    asset: 'my-noise-generator',
    +    width: 400,
    +    height: 400,
    +    copyWidth: '100%',
    +    copyHeight: '100%',
    +    lineOut: 'map',
     });
     
    -scrawl.makeTween({
    -    name: 'horizontal-turbulence',
    -    duration: 6000,
    -    targets: myWorld,
    -    cycles: 0,
    -    reverseOnCycleEnd: true,
    -    definitions: [
    -        {
    -            attribute: 'baseFreqX',
    -            start: 0.01,
    -            end: 0.025,
    -            engine: 'easeOutIn'
    -        },
    -    ]
    -}).run();
    +const displacer =  scrawl.makeFilter({
    +
    +    name: 'displace',
    +    method: 'displace',
    +    lineMix: 'map',
    +    scaleX: 20,
    +    scaleY: 20,
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    Update our Cell with the filters

    -scrawl.makeTween({ - name: 'vertical-turbulence', - duration: 7000, - targets: myWorld, +
    + +
    patternCell.set({
    +    filters: ['noise', 'displace']
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    Animate the displacer filter using a Tween

    + +
    + +
    scrawl.makeTween({
    +
    +    name: 'turbulence',
    +    duration: 6000,
    +    targets: displacer,
         cycles: 0,
         reverseOnCycleEnd: true,
         definitions: [
             {
    -            attribute: 'baseFreqY',
    -            start: 0.01,
    -            end: 0.025,
    +            attribute: 'scaleX',
    +            start: 1,
    +            end: 150,
                 engine: 'easeOutIn'
             },
    -    ]
    -}).run();
    -
    -scrawl.makeTween({
    -    name: 'scale-displacement',
    -    duration: 10000,
    -    targets: myWorld,
    -    cycles: 0,
    -    reverseOnCycleEnd: true,
    -    definitions: [
             {
    -            attribute: 'scale',
    -            start: 15,
    -            end: 25,
    +            attribute: 'scaleY',
    +            start: 150,
    +            end: 1,
                 engine: 'easeOutIn'
             },
         ]
    @@ -798,17 +920,18 @@ 

    Scene setup

  • -
  • +
  • - +
    -

    STEP 4. We are now in a position where we can use our Cells as pattern fills for some SC entitys.

    +

    STEP 3. We are now in a position where we can use our Cells as pattern fills for some SC entitys.

    scrawl.makePolygon({
    +
         name: 'hex',
         sides: 6,
         radius: 90,
    @@ -822,44 +945,51 @@ 

    Scene setup

  • -
  • +
  • - +
    -

    To use a Cell as a pattern we just assign its name to the entity’s fillStyle attribute

    +

    To use a Cell as a pattern we just assign its name to the entity’s fillStyle attribute +fillStyle: ‘warped-pattern’,

    -
        fillStyle: 'warped-pattern',
    +            
        fillStyle: 'gradient-pattern-cell',
     });
  • -
  • +
  • - +
    -

    STEP 5. If we want, we can add filters to our entitys, to give our pattern a different look.

    +

    STEP 4. If we want, we can add some color-based filters to our entitys, to give our pattern a different look.

    scrawl.makeFilter({
    +
         name: 'notred',
         method: 'notred',
    +
     }).clone({
    +
         name: 'sepia',
         method: 'sepia',
    +
     }).clone({
    +
         name: 'invert',
         method: 'invert',
     });
     
     scrawl.makeOval({
    +
         name: 'egg',
         radiusX: 60,
         radiusY: 80,
    @@ -870,14 +1000,15 @@ 

    Scene setup

    strokeStyle: 'green', lineJoin: 'round', method: 'fillThenDraw', - fillStyle: 'warped-pattern', + fillStyle: 'gradient-pattern-cell', filters: ['sepia'], }); scrawl.makeTetragon({ + name: 'arrow', start: [160, 290], - fillStyle: 'warped-pattern', + fillStyle: 'gradient-pattern-cell', radiusX: 60, radiusY: 80, intersectY: 1.2, @@ -893,24 +1024,26 @@

    Scene setup

  • -
  • +
  • - +
    -

    STEP 6. There’s one additional thing we can do with our pattern - pass it into a Pattern object where we can warp and resize it. Then we can apply it to entitys via the Pattern object.

    +

    STEP 5. There’s one additional thing we can do with our Cell-based pattern - pass it into a Pattern object where we can warp and resize it. Then we can apply it to entitys via the Pattern object.

    scrawl.makePattern({
    +
         name: 'wavy-pattern',
    -    asset: 'warped-pattern',
    +    asset: 'gradient-pattern-cell',
         matrixB: 0.7,
         matrixF: -150,
     });
     
     scrawl.makeBlock({
    +
         name: 'boring-block',
         start: [50, 50],
         dimensions: [140, 170],
    @@ -921,6 +1054,7 @@ 

    Scene setup

    method: 'fillThenDraw', }).clone({ + name: 'tipsy-block', start: [250, 130], dimensions: [210, 90], @@ -931,11 +1065,11 @@

    Scene setup

  • -
  • +
  • - +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -961,11 +1095,11 @@

    Scene animation

  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -976,21 +1110,39 @@

    Scene animation

    name: 'demo-animation', target: canvas, afterShow: report, -}); +});
    + +
  • + + +
  • +
    + +
    + +
    +

    User interaction

    + +
    + +
    scrawl.makeGroup({
    +    name: 'my-draggable-entitys',
    +}).addArtefacts('hex', 'egg', 'arrow', 'boring-block', 'tipsy-block');
     
     scrawl.makeDragZone({
         zone: canvas,
    +    collisionGroup: 'my-draggable-entitys',
         endOn: ['up', 'leave'],
     });
  • -
  • +
  • - +

    Development and testing

    diff --git a/docs/demo/canvas-045.html b/docs/demo/canvas-045.html index cecf18687..cba24b2d1 100644 --- a/docs/demo/canvas-045.html +++ b/docs/demo/canvas-045.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-046.html b/docs/demo/canvas-046.html index 7b5b4003d..27c2e94c6 100644 --- a/docs/demo/canvas-046.html +++ b/docs/demo/canvas-046.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/canvas-047.html b/docs/demo/canvas-047.html new file mode 100644 index 000000000..fc43fded9 --- /dev/null +++ b/docs/demo/canvas-047.html @@ -0,0 +1,798 @@ + + + + + Demo Canvas 047 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Canvas 047

      +

      Easing functions for Color and Tween factories

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +
      +canvas.set({
      +    backgroundColor: 'honeydew',
      +});
      +
      +const easingDisplayComponent = function (name, ypos) {
      +
      +    let color = scrawl.makeColor({
      +        name: `${name}-color`,
      +        easing: name,
      +        minimumColor: 'red',
      +        maximumColor: 'green',
      +    });
      +
      +    scrawl.makeLine({
      +        name: `${name}-line`,
      +        startX: 122,
      +        startY: ypos + 12,
      +        endX: 752,
      +        endY: ypos + 12,
      +        method: 'draw',
      +    });
      +
      +    let wheel = scrawl.makeWheel({
      +        name: `${name}-wheel`,
      +        radius: 12,
      +        startX: 110,
      +        startY: ypos,
      +        method: 'fill',
      +        fillStyle: color.getRangeColor(0),
      +    });
      +
      +    scrawl.makePhrase({
      +        name: `${name}-label`,
      +        text: name,
      +        startX: 10,
      +        startY: ypos + 6,
      +    });
      +
      +    scrawl.makeTween({
      +        name: `${name}-move-tween`,
      +        targets: wheel,
      +        duration: 8000,
      +        cycles: 0,
      +        reverseOnCycleEnd: true,
      +        definitions: [
      +            {
      +                attribute: 'startX',
      +                start: 110,
      +                end: 740,
      +                engine: name,
      +            },
      +        ],
      +    }).run();
      +
      +    scrawl.makeTween({
      +        name: `${name}-color-tween`,
      +        targets: wheel,
      +        duration: 8000,
      +        cycles: 0,
      +        reverseOnCycleEnd: true,
      +        definitions: [
      +            {
      +                attribute: 'fillStyle',
      +                start: 0,
      +                end: 1,
      +                engine: function (start, change, position) { return color.getRangeColor(position) },
      +            },
      +        ],
      +    }).run();
      +};
      +
      +const easings = ['easeOutSine', 'easeOutQuad', 'easeOutCubic', 'easeOutQuart', 'easeOutQuint', 'easeOutExpo', 'easeOutCirc', 'easeOutBack', 'easeOutElastic', 'easeOutBounce', 'easeInSine', 'easeInQuad', 'easeInCubic', 'easeInQuart', 'easeInQuint', 'easeInExpo', 'easeInCirc', 'easeInBack', 'easeInElastic', 'easeInBounce', 'easeOutInSine', 'easeOutInQuad', 'easeOutInCubic', 'easeOutInQuart', 'easeOutInQuint', 'easeOutInExpo', 'easeOutInCirc', 'easeOutInBack', 'easeOutInElastic', 'easeOutInBounce'];
      +
      +easings.forEach((easing, index) => easingDisplayComponent(easing, 50 + (index * 30)));
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/component-001.html b/docs/demo/component-001.html index 6975c8f6c..299517cb6 100644 --- a/docs/demo/component-001.html +++ b/docs/demo/component-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/component-002.html b/docs/demo/component-002.html index c3bdf5686..d7c540ba9 100644 --- a/docs/demo/component-002.html +++ b/docs/demo/component-002.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/component-003.html b/docs/demo/component-003.html index f8c73b3c2..6891f6f0a 100644 --- a/docs/demo/component-003.html +++ b/docs/demo/component-003.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/component-004.html b/docs/demo/component-004.html index d5cd62a6c..81a66f1eb 100644 --- a/docs/demo/component-004.html +++ b/docs/demo/component-004.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -664,31 +724,17 @@

    Scene setup

    let myfilter = scrawl.makeFilter({
    -    name: 'venetianBlinds',
    -    method: 'userDefined',
    -    level: 9,
    -
    -    userDefined: `
    -        let i, iz, j, jz,
    -            level = filter.level || 6,
    -            halfLevel = level / 2,
    -            yw, transparent, pos;
    -
    -        for (i = localY, iz = localY + localHeight; i < iz; i++) {
    -
    -            transparent = (i % level > halfLevel) ? true : false;
    -
    -            if (transparent) {
    -
    -                yw = (i * iWidth) + 3;
    -                
    -                for (j = localX, jz = localX + localWidth; j < jz; j ++) {
     
    -                    pos = yw + (j * 4);
    -                    data[pos] = 0;
    -                }
    -            }
    -        }`,
    +    name: 'emboss',
    +    method: 'emboss',
    +    angle: 225,
    +    strength: 3,
    +    smoothing: 0,
    +    tolerance: 0,
    +    clamp: 0,
    +    postProcessResults: true,
    +    useNaturalGrayscale: false,
    +    keepOnlyChangedAreas: false,
     });
  • @@ -962,7 +1008,7 @@

    Scene setup

    method: 'fillAndDraw', - filters: ['venetianBlinds'], + filters: ['emboss'], @@ -1468,7 +1514,7 @@

    Scene animation

    }); myshape.set({ - filters: 'venetianBlinds', + filters: 'emboss', }) }; diff --git a/docs/demo/component-005.html b/docs/demo/component-005.html index 39aeaa18b..f63886c8a 100644 --- a/docs/demo/component-005.html +++ b/docs/demo/component-005.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/component-006.html b/docs/demo/component-006.html index b11e84309..c9227a473 100644 --- a/docs/demo/component-006.html +++ b/docs/demo/component-006.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/component-007.html b/docs/demo/component-007.html index 0afd3903d..67733513c 100644 --- a/docs/demo/component-007.html +++ b/docs/demo/component-007.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/core-001.html b/docs/demo/core-001.html index 0d958c147..a0f567ed2 100644 --- a/docs/demo/core-001.html +++ b/docs/demo/core-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-001.html b/docs/demo/dom-001.html index 45a69b8e5..a9dd7a5b8 100644 --- a/docs/demo/dom-001.html +++ b/docs/demo/dom-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-002.html b/docs/demo/dom-002.html index 2ef7e21e1..3d978e718 100644 --- a/docs/demo/dom-002.html +++ b/docs/demo/dom-002.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-003.html b/docs/demo/dom-003.html index 54f64f032..3871e92da 100644 --- a/docs/demo/dom-003.html +++ b/docs/demo/dom-003.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-004.html b/docs/demo/dom-004.html index ca31ebdfb..751cf10e0 100644 --- a/docs/demo/dom-004.html +++ b/docs/demo/dom-004.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-005.html b/docs/demo/dom-005.html index db70f694a..271f75553 100644 --- a/docs/demo/dom-005.html +++ b/docs/demo/dom-005.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-006.html b/docs/demo/dom-006.html index 00e7fa90b..8b8cb9746 100644 --- a/docs/demo/dom-006.html +++ b/docs/demo/dom-006.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-007.html b/docs/demo/dom-007.html index 46976857d..3b9d7393f 100644 --- a/docs/demo/dom-007.html +++ b/docs/demo/dom-007.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-008.html b/docs/demo/dom-008.html index a8cf313e7..f569a2d9f 100644 --- a/docs/demo/dom-008.html +++ b/docs/demo/dom-008.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-009.html b/docs/demo/dom-009.html index 70c02433e..53988d9de 100644 --- a/docs/demo/dom-009.html +++ b/docs/demo/dom-009.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-010.html b/docs/demo/dom-010.html index 7917fc2aa..048cc0996 100644 --- a/docs/demo/dom-010.html +++ b/docs/demo/dom-010.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-011.html b/docs/demo/dom-011.html index d510ad009..21a45cfa7 100644 --- a/docs/demo/dom-011.html +++ b/docs/demo/dom-011.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-012.html b/docs/demo/dom-012.html index 1758f510d..639740a5d 100644 --- a/docs/demo/dom-012.html +++ b/docs/demo/dom-012.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-013.html b/docs/demo/dom-013.html index 70a42d425..b25347df0 100644 --- a/docs/demo/dom-013.html +++ b/docs/demo/dom-013.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-014a.html b/docs/demo/dom-014a.html index eea714fe9..4ca1e90e2 100644 --- a/docs/demo/dom-014a.html +++ b/docs/demo/dom-014a.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-014b.html b/docs/demo/dom-014b.html index ad9681742..29e50888f 100644 --- a/docs/demo/dom-014b.html +++ b/docs/demo/dom-014b.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-014c.html b/docs/demo/dom-014c.html index 424556fb2..91600b6c2 100644 --- a/docs/demo/dom-014c.html +++ b/docs/demo/dom-014c.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-015.html b/docs/demo/dom-015.html index a3db51ead..a16a72571 100644 --- a/docs/demo/dom-015.html +++ b/docs/demo/dom-015.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/dom-016.html b/docs/demo/dom-016.html index 1fc9fee3d..849b4f5d1 100644 --- a/docs/demo/dom-016.html +++ b/docs/demo/dom-016.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/filters-001.html b/docs/demo/filters-001.html index 2c0e5a303..e4108d815 100644 --- a/docs/demo/filters-001.html +++ b/docs/demo/filters-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -593,6 +653,8 @@

    Scene setup

    css: { display: 'inline-block', }, +}).setBase({ + compileOrder: 1, }); scrawl.importDomImage('.flowers'); @@ -614,10 +676,10 @@

    Scene setup

    name: 'blur', method: 'blur', - radius: 1, - shrinkingRadius: false, + radius: 10, includeAlpha: false, passes: 1, + step: 1, }); @@ -668,6 +730,11 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let radius = document.querySelector('#radius'), + passes = document.querySelector('#passes'), + step = document.querySelector('#step'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -675,8 +742,8 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - radius: ${blurFilter.radius} - passes: ${blurFilter.passes}`; + Radius: ${radius.value}, Step: ${step.value}, Passes: ${passes.value} + Opacity: ${opacity.value}`; }; }(); @@ -728,12 +795,17 @@

    User interaction

    radius: ['radius', 'round'], passes: ['passes', 'round'], + step: ['step', 'round'], - shrinkingRadius: ['shrinkingRadius', 'boolean'], + includeRed: ['includeRed', 'boolean'], + includeGreen: ['includeGreen', 'boolean'], + includeBlue: ['includeBlue', 'boolean'], includeAlpha: ['includeAlpha', 'boolean'], processHorizontal: ['processHorizontal', 'boolean'], processVertical: ['processVertical', 'boolean'], + + opacity: ['opacity', 'float'], }, }); @@ -750,12 +822,16 @@

    User interaction

    -
    document.querySelector('#radius').value = 1;
    +            
    document.querySelector('#radius').value = 10;
     document.querySelector('#passes').value = 1;
    -document.querySelector('#shrinkingRadius').options.selectedIndex = 0;
    +document.querySelector('#step').value = 1;
    +document.querySelector('#includeRed').options.selectedIndex = 1;
    +document.querySelector('#includeGreen').options.selectedIndex = 1;
    +document.querySelector('#includeBlue').options.selectedIndex = 1;
     document.querySelector('#includeAlpha').options.selectedIndex = 0;
     document.querySelector('#processHorizontal').options.selectedIndex = 1;
    -document.querySelector('#processVertical').options.selectedIndex = 1;
    +document.querySelector('#processVertical').options.selectedIndex = 1; +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-002.html b/docs/demo/filters-002.html index e2a5738bc..51b05b157 100644 --- a/docs/demo/filters-002.html +++ b/docs/demo/filters-002.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -587,7 +647,8 @@

    Scene setup

    -
    const canvas = scrawl.library.canvas.mycanvas;
    +            
    const canvas = scrawl.library.canvas.mycanvas,
    +    filter = scrawl.library.filter;
     
     scrawl.importDomImage('.flowers');
    @@ -840,13 +901,16 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); testTime = testNow - testTicker; testTicker = testNow; - testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Opacity: ${opacity.value}`; }; }();
    @@ -879,6 +943,55 @@

    Scene animation

    +

    User interaction

    + + + +
    const myFilters = [
    +    filter.red,
    +    filter.green,
    +    filter.blue,
    +    filter.cyan,
    +    filter.magenta,
    +    filter.yellow,
    +    filter.notred,
    +    filter.notgreen,
    +    filter.notblue,
    +    filter.grayscale,
    +    filter.sepia,
    +    filter.invert
    +];
    +
    +scrawl.addNativeListener(['input', 'change'], (e) => {
    +
    +    myFilters.forEach(f => f.set({ opacity: parseFloat(e.target.value) }));
    +
    +}, '#opacity');
    + + + + +
  • +
    + +
    + +
    +

    Setup form

    + +
    + +
    document.querySelector('#opacity').value = 1;
    + +
  • + + +
  • +
    + +
    + +

    Development and testing

    diff --git a/docs/demo/filters-003.html b/docs/demo/filters-003.html index 253381c2f..342fbf455 100644 --- a/docs/demo/filters-003.html +++ b/docs/demo/filters-003.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -694,6 +754,9 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let level = document.querySelector('#level'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -701,7 +764,8 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - level: ${filter.brightness.level}`; + Level: ${level.value} + Opacity: ${opacity.value}`; }; }(); @@ -738,11 +802,37 @@

    User interaction

    -
    scrawl.addNativeListener(['input', 'change'], (e) => {
    +            
    scrawl.observeAndUpdate({
    +
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
     
    -    levelFilters.forEach(f => f.set({ level: parseFloat(e.target.value) }));
    +    target: filter.brightness,
    +
    +    useNativeListener: true,
    +    preventDefault: true,
    +
    +    updates: {
    +        opacity: ['opacity', 'float'],
    +        level: ['level', 'float'],
    +    },
    +});
     
    -}, '#level')
    +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: filter.saturation, + + useNativeListener: true, + preventDefault: true, + + updates: { + opacity: ['opacity', 'float'], + level: ['level', 'float'], + }, +});
  • @@ -757,7 +847,8 @@

    User interaction

    -
    document.querySelector('#level').value = 1;
    +
    document.querySelector('#opacity').value = 1;
    +document.querySelector('#level').value = 1;
    diff --git a/docs/demo/filters-004.html b/docs/demo/filters-004.html index 2f58dcb87..8b0c02a76 100644 --- a/docs/demo/filters-004.html +++ b/docs/demo/filters-004.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -672,7 +732,8 @@

    Scene animation

    let lowCol = document.querySelector('#lowColor'), highCol = document.querySelector('#highColor'), - level = document.querySelector('#level'); + level = document.querySelector('#level'), + opacity = document.querySelector('#opacity'); return function () { @@ -681,9 +742,9 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - low color: ${lowCol.value} - high color: ${highCol.value} - level: ${level.value}`; + Low color: ${lowCol.value}, High color: ${highCol.value} + Level: ${level.value} + Opacity: ${opacity.value}`; }; }(); @@ -730,6 +791,11 @@

    User interaction

    (e) => myFilter.set({ level: parseFloat(e.target.value) }), '#level'); +scrawl.addNativeListener( + ['input', 'change'], + (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), + '#opacity'); + scrawl.addNativeListener( ['input', 'change'], (e) => { @@ -773,7 +839,8 @@

    User interaction

    document.querySelector('#lowColor').value = '#000000';
     document.querySelector('#highColor').value = '#ffffff';
    -document.querySelector('#level').value = 127;
    +document.querySelector('#level').value = 127; +document.querySelector('#opacity').value = 1; diff --git a/docs/demo/filters-005.html b/docs/demo/filters-005.html index afb0d8316..51151fc73 100644 --- a/docs/demo/filters-005.html +++ b/docs/demo/filters-005.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -666,7 +726,8 @@

    Scene animation

    let red = document.querySelector('#red'), green = document.querySelector('#green'), - blue = document.querySelector('#blue'); + blue = document.querySelector('#blue'), + opacity = document.querySelector('#opacity'); return function () { @@ -675,9 +736,8 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - red: ${red.value} - green: ${green.value} - blue: ${blue.value}`; + Red: ${red.value}, Green: ${green.value}, Blue: ${blue.value} + Opacity: ${opacity.value}`; }; }(); @@ -730,6 +790,7 @@

    User interaction

    red: ['red', 'float'], green: ['green', 'float'], blue: ['blue', 'float'], + opacity: ['opacity', 'float'], }, }); @@ -748,7 +809,8 @@

    User interaction

    document.querySelector('#red').value = 1;
     document.querySelector('#green').value = 1;
    -document.querySelector('#blue').value = 1;
    +document.querySelector('#blue').value = 1; +document.querySelector('#opacity').value = 1; diff --git a/docs/demo/filters-006.html b/docs/demo/filters-006.html index a8970b761..083c3de5f 100644 --- a/docs/demo/filters-006.html +++ b/docs/demo/filters-006.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 006

    -

    Filter parameters: channels

    +

    Filter parameters: channelLevels

    @@ -606,12 +666,13 @@

    Scene setup

    const myFilter = scrawl.makeFilter({
     
    -    name: 'channels',
    -    method: 'channels',
    +    name: 'channelLevels',
    +    method: 'channelLevels',
     
    -    red: 1,
    -    green: 1,
    -    blue: 1,
    +    red: [50, 200],
    +    green: [60, 220, 150],
    +    blue: [40, 180],
    +    alpha: [],
     });
    @@ -641,7 +702,7 @@

    Scene setup

    method: 'fill', - filters: ['channels'], + filters: ['channelLevels'], }); @@ -666,7 +727,9 @@

    Scene animation

    let red = document.querySelector('#red'), green = document.querySelector('#green'), - blue = document.querySelector('#blue'); + alpha = document.querySelector('#alpha'), + blue = document.querySelector('#blue'), + opacity = document.querySelector('#opacity'); return function () { @@ -675,9 +738,11 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - red: ${red.value} - green: ${green.value} - blue: ${blue.value}`; + Red: [${red.value}] + Green: [${green.value}] + Blue: [${blue.value}] + Alpha: [${alpha.value}] + Opacity: ${opacity.value}`; }; }(); @@ -711,27 +776,32 @@

    Scene animation

    User interaction

    -

    Setup form observer functionality

    -
    scrawl.observeAndUpdate({
    +            
    scrawl.addNativeListener(
    +    ['input', 'change'], 
    +    (e) => {
     
    -    event: ['input', 'change'],
    -    origin: '.controlItem',
    +        let a;
     
    -    target: myFilter,
    +        if (e.target.id === 'opacity') a = e.target.value;
    +        else {
     
    -    useNativeListener: true,
    -    preventDefault: true,
    +            let temp = e.target.value.split(',');
    +            a = [];
     
    -    updates: {
    +            temp.forEach(t => {
    +                let n = parseInt(t, 10);
    +                if (n.toFixed && !isNaN(n)) a.push(n)
    +            });
    +        }
     
    -        red: ['red', 'float'],
    -        green: ['green', 'float'],
    -        blue: ['blue', 'float'],
    -    },
    -});
    + myFilter.set({ + [e.target.id]: a, + }); + }, + '.controlItem');
    @@ -746,9 +816,11 @@

    User interaction

    -
    document.querySelector('#red').value = 1;
    -document.querySelector('#green').value = 1;
    -document.querySelector('#blue').value = 1;
    +
    document.querySelector('#red').value = '50, 200';
    +document.querySelector('#green').value = '60, 220, 150';
    +document.querySelector('#blue').value = '40, 180';
    +document.querySelector('#alpha').value = '';
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-007.html b/docs/demo/filters-007.html index 259ac7d1d..443520e62 100644 --- a/docs/demo/filters-007.html +++ b/docs/demo/filters-007.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 007

    -

    Filter parameters: tint

    +

    Filter parameters: channels

    @@ -606,18 +666,12 @@

    Scene setup

    const myFilter = scrawl.makeFilter({
     
    -    name: 'tint',
    -    method: 'tint',
    +    name: 'channels',
    +    method: 'channels',
     
    -    redInRed: 1,
    -    redInGreen: 0,
    -    redInBlue: 0,
    -    greenInRed: 0,
    -    greenInGreen: 1,
    -    greenInBlue: 0,
    -    blueInRed: 0,
    -    blueInGreen: 0,
    -    blueInBlue: 1,
    +    red: 1,
    +    green: 1,
    +    blue: 1,
     });
    @@ -647,7 +701,7 @@

    Scene setup

    method: 'fill', - filters: ['tint'], + filters: ['channels'], }); @@ -670,15 +724,11 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); - let redInRed = document.querySelector('#redInRed'), - greenInRed = document.querySelector('#greenInRed'), - blueInRed = document.querySelector('#blueInRed'), - redInGreen = document.querySelector('#redInGreen'), - greenInGreen = document.querySelector('#greenInGreen'), - blueInGreen = document.querySelector('#blueInGreen'), - redInBlue = document.querySelector('#redInBlue'), - greenInBlue = document.querySelector('#greenInBlue'), - blueInBlue = document.querySelector('#blueInBlue'); + let red = document.querySelector('#red'), + green = document.querySelector('#green'), + blue = document.querySelector('#blue'), + alpha = document.querySelector('#alpha'), + opacity = document.querySelector('#opacity'); return function () { @@ -687,9 +737,8 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - In Red - red: ${redInRed.value} green: ${greenInRed.value} blue: ${blueInRed.value} - In Green - red: ${redInGreen.value} green: ${greenInGreen.value} blue: ${blueInGreen.value} - In Blue - red: ${redInBlue.value} green: ${greenInBlue.value} blue: ${blueInBlue.value}`; + Red: ${red.value}, Green: ${green.value}, Blue: ${blue.value}, Alpha: ${alpha.value} + Opacity: ${opacity.value}`; }; }(); @@ -739,15 +788,11 @@

    User interaction

    updates: { - redInRed: ['redInRed', 'float'], - redInGreen: ['redInGreen', 'float'], - redInBlue: ['redInBlue', 'float'], - greenInRed: ['greenInRed', 'float'], - greenInGreen: ['greenInGreen', 'float'], - greenInBlue: ['greenInBlue', 'float'], - blueInRed: ['blueInRed', 'float'], - blueInGreen: ['blueInGreen', 'float'], - blueInBlue: ['blueInBlue', 'float'], + red: ['red', 'float'], + green: ['green', 'float'], + blue: ['blue', 'float'], + alpha: ['alpha', 'float'], + opacity: ['opacity', 'float'], }, }); @@ -764,15 +809,11 @@

    User interaction

    -
    document.querySelector('#redInRed').value = 1;
    -document.querySelector('#redInGreen').value = 0;
    -document.querySelector('#redInBlue').value = 0;
    -document.querySelector('#greenInRed').value = 0;
    -document.querySelector('#greenInGreen').value = 1;
    -document.querySelector('#greenInBlue').value = 0;
    -document.querySelector('#blueInRed').value = 0;
    -document.querySelector('#blueInGreen').value = 0;
    -document.querySelector('#blueInBlue').value = 1;
    +
    document.querySelector('#red').value = 1;
    +document.querySelector('#green').value = 1;
    +document.querySelector('#blue').value = 1;
    +document.querySelector('#alpha').value = 1;
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-008.html b/docs/demo/filters-008.html index 166eb9ae8..7b0467d5a 100644 --- a/docs/demo/filters-008.html +++ b/docs/demo/filters-008.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 008

    -

    Filter parameters: pixelate

    +

    Filter parameters: tint

    @@ -606,13 +666,18 @@

    Scene setup

    const myFilter = scrawl.makeFilter({
     
    -    name: 'pixelate',
    -    method: 'pixelate',
    +    name: 'tint',
    +    method: 'tint',
     
    -    tileWidth: 10,
    -    tileHeight: 10,
    -    offsetX: 0,
    -    offsetY: 0,
    +    redInRed: 1,
    +    redInGreen: 0,
    +    redInBlue: 0,
    +    greenInRed: 0,
    +    greenInGreen: 1,
    +    greenInBlue: 0,
    +    blueInRed: 0,
    +    blueInGreen: 0,
    +    blueInBlue: 1,
     });
    @@ -642,7 +707,7 @@

    Scene setup

    method: 'fill', - filters: ['pixelate'], + filters: ['tint'], }); @@ -665,10 +730,16 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); - let tile_width = document.querySelector('#tile_width'), - tile_height = document.querySelector('#tile_height'), - offset_x = document.querySelector('#offset_x'), - offset_y = document.querySelector('#offset_y'); + let redInRed = document.querySelector('#redInRed'), + greenInRed = document.querySelector('#greenInRed'), + blueInRed = document.querySelector('#blueInRed'), + redInGreen = document.querySelector('#redInGreen'), + greenInGreen = document.querySelector('#greenInGreen'), + blueInGreen = document.querySelector('#blueInGreen'), + redInBlue = document.querySelector('#redInBlue'), + greenInBlue = document.querySelector('#greenInBlue'), + blueInBlue = document.querySelector('#blueInBlue'), + opacity = document.querySelector('#opacity'); return function () { @@ -677,8 +748,10 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - Tile dimensions - width: ${tile_width.value} height: ${tile_height.value} - Offset - x: ${offset_x.value} y: ${offset_y.value}`; + In Red - red: ${redInRed.value} green: ${greenInRed.value} blue: ${blueInRed.value} + In Green - red: ${redInGreen.value} green: ${greenInGreen.value} blue: ${blueInGreen.value} + In Blue - red: ${redInBlue.value} green: ${greenInBlue.value} blue: ${blueInBlue.value} + Opacity: ${opacity.value}`; }; }(); @@ -728,10 +801,16 @@

    User interaction

    updates: { - tile_width: ['tileWidth', 'round'], - tile_height: ['tileHeight', 'round'], - offset_x: ['offsetX', 'round'], - offset_y: ['offsetY', 'round'], + redInRed: ['redInRed', 'float'], + redInGreen: ['redInGreen', 'float'], + redInBlue: ['redInBlue', 'float'], + greenInRed: ['greenInRed', 'float'], + greenInGreen: ['greenInGreen', 'float'], + greenInBlue: ['greenInBlue', 'float'], + blueInRed: ['blueInRed', 'float'], + blueInGreen: ['blueInGreen', 'float'], + blueInBlue: ['blueInBlue', 'float'], + opacity: ['opacity', 'float'], }, }); @@ -748,10 +827,16 @@

    User interaction

    -
    document.querySelector('#tile_width').value = 10;
    -document.querySelector('#tile_height').value = 10;
    -document.querySelector('#offset_x').value = 0;
    -document.querySelector('#offset_y').value = 0;
    +
    document.querySelector('#redInRed').value = 1;
    +document.querySelector('#redInGreen').value = 0;
    +document.querySelector('#redInBlue').value = 0;
    +document.querySelector('#greenInRed').value = 0;
    +document.querySelector('#greenInGreen').value = 1;
    +document.querySelector('#greenInBlue').value = 0;
    +document.querySelector('#blueInRed').value = 0;
    +document.querySelector('#blueInGreen').value = 0;
    +document.querySelector('#blueInBlue').value = 1;
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-009.html b/docs/demo/filters-009.html index 68b38927b..151410337 100644 --- a/docs/demo/filters-009.html +++ b/docs/demo/filters-009.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 009

    -

    Filter parameters: chroma

    +

    Filter parameters: pixelate

    @@ -601,18 +661,18 @@

    Scene setup

    Create the filter

    -
    const myFilter = scrawl.makeFilter({
     
    -    name: 'chroma',
    -    method: 'chroma',
    +    name: 'pixelate',
    +    method: 'pixelate',
     
    -    ranges: [[0, 0, 0, 92, 127, 92]],
    +    tileWidth: 10,
    +    tileHeight: 10,
    +    offsetX: 0,
    +    offsetY: 0,
     });
    @@ -642,7 +702,7 @@

    Scene setup

    method: 'fill', - filters: ['chroma'], + filters: ['pixelate'], }); @@ -665,8 +725,11 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); - let lowCol = document.querySelector('#lowColor'), - highCol = document.querySelector('#highColor'); + let tile_width = document.querySelector('#tile_width'), + tile_height = document.querySelector('#tile_height'), + offset_x = document.querySelector('#offset_x'), + offset_y = document.querySelector('#offset_y'), + opacity = document.querySelector('#opacity'); return function () { @@ -675,9 +738,9 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - low color: ${lowCol.value} - high color: ${highCol.value} - range: [${myFilter.ranges}]`; + Tile dimensions - width: ${tile_width.value} height: ${tile_height.value} + Offset - x: ${offset_x.value} y: ${offset_y.value} + Opacity: ${opacity.value}`; }; }(); @@ -715,44 +778,31 @@

    User interaction

    -
    const interpretColors = function () {
    -
    -    const converter = scrawl.makeColor({
    -        name: 'converter',
    -    });
    +            
    scrawl.observeAndUpdate({
     
    -    const lowColor = document.querySelector('#lowColor');
    -    const highColor = document.querySelector('#highColor');
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
     
    -    let lowRed = 0,
    -        lowGreen = 0,
    -        lowBlue = 0,
    -        highRed = 92,
    -        highGreen = 127,
    -        highBlue = 92;
    +    target: myFilter,
     
    -    return function () {
    -
    -        converter.convert(lowColor.value);
    -
    -        lowRed = converter.r;
    -        lowGreen = converter.g;
    -        lowBlue = converter.b;
    +    useNativeListener: true,
    +    preventDefault: true,
     
    -        converter.convert(highColor.value);
    +    updates: {
     
    -        highRed = converter.r;
    -        highGreen = converter.g;
    -        highBlue = converter.b;
    +        tile_width: ['tileWidth', 'round'],
    +        tile_height: ['tileHeight', 'round'],
    +        offset_x: ['offsetX', 'round'],
    +        offset_y: ['offsetY', 'round'],
     
    -        myFilter.set({
    +        includeRed: ['includeRed', 'boolean'],
    +        includeGreen: ['includeGreen', 'boolean'],
    +        includeBlue: ['includeBlue', 'boolean'],
    +        includeAlpha: ['includeAlpha', 'boolean'],
     
    -            ranges: [[lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue]],
    -        })
    -    }
    -}();
    -
    -scrawl.addNativeListener(['input', 'change'], interpretColors, '.controlItem');
    + opacity: ['opacity', 'float'], + }, +});
    @@ -767,8 +817,15 @@

    User interaction

    -
    document.querySelector('#lowColor').value = '#000000';
    -document.querySelector('#highColor').value = '#5c7f5c';
    +
    document.querySelector('#tile_width').value = 10;
    +document.querySelector('#tile_height').value = 10;
    +document.querySelector('#offset_x').value = 0;
    +document.querySelector('#offset_y').value = 0;
    +document.querySelector('#includeRed').options.selectedIndex = 1;
    +document.querySelector('#includeGreen').options.selectedIndex = 1;
    +document.querySelector('#includeBlue').options.selectedIndex = 1;
    +document.querySelector('#includeAlpha').options.selectedIndex = 0;
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-010.html b/docs/demo/filters-010.html index c82271626..f9933fae3 100644 --- a/docs/demo/filters-010.html +++ b/docs/demo/filters-010.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 010

    -

    Filter parameters: matrix, matrix5

    +

    Filter parameters: chroma

    @@ -600,24 +660,19 @@

    Scene setup

    -

    Create the filters

    +

    Create the filter

    + -
    const matrix3 = scrawl.makeFilter({
    -
    -    name: 'matrix3',
    -    method: 'matrix',
    +            
    const myFilter = scrawl.makeFilter({
     
    -    weights: [0, 0, 0, 0, 1, 0, 0, 0, 0],
    -});
    +    name: 'chroma',
    +    method: 'chroma',
     
    -const matrix5 = scrawl.makeFilter({
    -
    -    name: 'matrix5',
    -    method: 'matrix5',
    -
    -    weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    +    ranges: [[0, 0, 0, 92, 127, 92]],
     });
    @@ -633,7 +688,7 @@

    Scene setup

    -
    const target = scrawl.makePicture({
    +            
    scrawl.makePicture({
     
         name: 'base-piccy',
     
    @@ -647,7 +702,7 @@ 

    Scene setup

    method: 'fill', - filters: ['matrix3'], + filters: ['chroma'], });
    @@ -671,7 +726,8 @@

    Scene animation

    testMessage = document.querySelector('#reportmessage'); let lowCol = document.querySelector('#lowColor'), - highCol = document.querySelector('#highColor'); + highCol = document.querySelector('#highColor'), + opacity = document.querySelector('#opacity'); return function () { @@ -680,8 +736,9 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - matrix3: ${matrix3.weights} - matrix5: ${matrix5.weights}`; + (Low color: ${lowCol.value}, High color: ${highCol.value}) + Range: [${myFilter.ranges}] + Opacity: ${opacity.value}`; }; }();
    @@ -719,71 +776,48 @@

    User interaction

    -
    const changeMatrix = function () {
    +            
    const interpretColors = function () {
     
    -    const selector = document.querySelector('#selectMatrix');
    +    const converter = scrawl.makeColor({
    +        name: 'converter',
    +    });
     
    -    return function () {
    +    const lowColor = document.querySelector('#lowColor');
    +    const highColor = document.querySelector('#highColor');
     
    -        target.set({
    -            filters: [selector.value],
    -        });
    -    }
    -}();
    -scrawl.addNativeListener(['input', 'change'], changeMatrix, '#selectMatrix');
    -
    -const updateWeights = function () {
    -
    -    const m11 = document.querySelector('#m11');
    -    const m12 = document.querySelector('#m12');
    -    const m13 = document.querySelector('#m13');
    -    const m14 = document.querySelector('#m14');
    -    const m15 = document.querySelector('#m15');
    -    const m21 = document.querySelector('#m21');
    -    const m22 = document.querySelector('#m22');
    -    const m23 = document.querySelector('#m23');
    -    const m24 = document.querySelector('#m24');
    -    const m25 = document.querySelector('#m25');
    -    const m31 = document.querySelector('#m31');
    -    const m32 = document.querySelector('#m32');
    -    const m33 = document.querySelector('#m33');
    -    const m34 = document.querySelector('#m34');
    -    const m35 = document.querySelector('#m35');
    -    const m41 = document.querySelector('#m41');
    -    const m42 = document.querySelector('#m42');
    -    const m43 = document.querySelector('#m43');
    -    const m44 = document.querySelector('#m44');
    -    const m45 = document.querySelector('#m45');
    -    const m51 = document.querySelector('#m51');
    -    const m52 = document.querySelector('#m52');
    -    const m53 = document.querySelector('#m53');
    -    const m54 = document.querySelector('#m54');
    -    const m55 = document.querySelector('#m55');
    -
    -    let weights3, weights5;
    +    let lowRed = 0,
    +        lowGreen = 0,
    +        lowBlue = 0,
    +        highRed = 92,
    +        highGreen = 127,
    +        highBlue = 92;
     
         return function () {
     
    -        weights3 = [parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), 
    -                    parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), 
    -                    parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value)];
    +        converter.convert(lowColor.value);
    +
    +        lowRed = converter.r;
    +        lowGreen = converter.g;
    +        lowBlue = converter.b;
     
    -        weights5 = [parseFloat(m11.value), parseFloat(m12.value), parseFloat(m13.value), parseFloat(m14.value), parseFloat(m15.value), 
    -                    parseFloat(m21.value), parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), parseFloat(m25.value), 
    -                    parseFloat(m31.value), parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), parseFloat(m35.value), 
    -                    parseFloat(m41.value), parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value), parseFloat(m45.value), 
    -                    parseFloat(m51.value), parseFloat(m52.value), parseFloat(m53.value), parseFloat(m54.value), parseFloat(m55.value)];
    +        converter.convert(highColor.value);
     
    -        matrix3.set({
    -            weights: weights3,
    -        });
    +        highRed = converter.r;
    +        highGreen = converter.g;
    +        highBlue = converter.b;
     
    -        matrix5.set({
    -            weights: weights5,
    -        });
    +        myFilter.set({
    +
    +            ranges: [[lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue]],
    +        })
         }
     }();
    -scrawl.addNativeListener(['input', 'change'], updateWeights, '.weight');
    +scrawl.addNativeListener(['input', 'change'], interpretColors, '.controlItem'); + +scrawl.addNativeListener( + ['input', 'change'], + (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), + '#opacity');
    @@ -798,32 +832,9 @@

    User interaction

    -
    document.querySelector('#selectMatrix').value = 'matrix3';
    -document.querySelector('#m11').value = 0;
    -document.querySelector('#m12').value = 0;
    -document.querySelector('#m13').value = 0;
    -document.querySelector('#m14').value = 0;
    -document.querySelector('#m15').value = 0;
    -document.querySelector('#m21').value = 0;
    -document.querySelector('#m22').value = 0;
    -document.querySelector('#m23').value = 0;
    -document.querySelector('#m24').value = 0;
    -document.querySelector('#m25').value = 0;
    -document.querySelector('#m31').value = 0;
    -document.querySelector('#m32').value = 0;
    -document.querySelector('#m33').value = 1;
    -document.querySelector('#m34').value = 0;
    -document.querySelector('#m35').value = 0;
    -document.querySelector('#m41').value = 0;
    -document.querySelector('#m42').value = 0;
    -document.querySelector('#m43').value = 0;
    -document.querySelector('#m44').value = 0;
    -document.querySelector('#m45').value = 0;
    -document.querySelector('#m51').value = 0;
    -document.querySelector('#m52').value = 0;
    -document.querySelector('#m53').value = 0;
    -document.querySelector('#m54').value = 0;
    -document.querySelector('#m55').value = 0;
    +
    document.querySelector('#lowColor').value = '#000000';
    +document.querySelector('#highColor').value = '#5c7f5c';
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-011.html b/docs/demo/filters-011.html index 5b2349665..78662501e 100644 --- a/docs/demo/filters-011.html +++ b/docs/demo/filters-011.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 011

    -

    Canvas engine filter strings (based on CSS filters)

    +

    Filter parameters: chromakey

    @@ -589,7 +649,11 @@

    Scene setup

    const canvas = scrawl.library.canvas.mycanvas;
     
    -scrawl.importDomImage('.flowers');
    +scrawl.importDomImage('.flowers'); + +canvas.setBase({ + backgroundColor: 'red', +}) @@ -600,11 +664,40 @@

    Scene setup

    -

    Create the target entitys

    +

    Create the filter

    + + + + +
    const myFilter = scrawl.makeFilter({
    +
    +    name: 'chromakey',
    +    method: 'chromakey',
    +
    +    red: 0,
    +    green: 127,
    +    blue: 0,
    +
    +    opaqueAt: 1,
    +    transparentAt: 0,
    +});
    + + + + +
  • +
    + +
    + +
    +

    Create the target entity

    -
    const piccy = scrawl.makePicture({
    +            
    scrawl.makePicture({
     
         name: 'base-piccy',
     
    @@ -617,30 +710,18 @@ 

    Scene setup

    copyHeight: '100%', method: 'fill', -}); - -const text = scrawl.makePhrase({ - - name: 'demo-text', - text: 'Hello world', - font: 'bold 70px sans-serif', - start: ['center', 'center'], - handle: ['center', 'center'], - lineHeight: 0.5, - fillStyle: 'aliceblue', - strokeStyle: 'red', - lineWidth: 3, - method: 'fillThenDraw', + + filters: ['chromakey'], });
  • -
  • +
  • - +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -653,24 +734,32 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let col = document.querySelector('#color'), + trans = document.querySelector('#transparentAt'), + opaq = document.querySelector('#opaqueAt'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); testTime = testNow - testTicker; testTicker = testNow; - testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`; + testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} + Key color: ${col.value} + Transparent at: ${trans.value}, Opaque at: ${opaq.value} + Opacity: ${opacity.value}`; }; }();
  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -686,84 +775,54 @@

    Scene animation

  • -
  • -
    - -
    - -
    -

    User interaction

    -

    No additional work required in the Javascript file to create the CSS filters; these are defined as Strings in the HTML select <option> elements, and will be set on the target entitys as part of the form control user interaction below.

    -
    <select class="controlItem" id="filter">
    -    <option value="none">none</option>
    -    <option value="blur(6px)">blur(6px)</option>
    -    <option value="brightness(0.4)">brightness(0.4)</option>
    -    <option value="contrast(200%)">contrast(200%)</option>
    -    <option value="drop-shadow(4px 4px 4px blue)">drop-shadow(4px 4px 4px blue)</option>
    -    <option value="grayscale(100%)">grayscale(100%)</option>
    -    <option value="hue-rotate(90deg)">hue-rotate(90deg)</option>
    -    <option value="invert(75%)">invert(75%)</option>
    -    <option value="opacity(25%)">opacity(25%)</option>
    -    <option value="saturate(30%)">saturate(30%)</option>
    -    <option value="sepia(100%)">sepia(100%)</option>
    -</select>
    - -
    - -
    -let filterTarget = piccy,
    -    filterString = 'none';
    - -
  • - -
  • -

    Setup form functionality

    +

    User interaction

    +

    Setup form observer functionality

    -
    let updateTarget = (e) => {
    -
    -    e.preventDefault();
    -    e.returnValue = false;
    +            
    const interpretColors = function () {
     
    -    let val = e.target.value;
    +    const converter = scrawl.makeColor({
    +        name: 'converter',
    +    });
     
    -    if (val) {
    +    const color = document.querySelector('#color');
     
    -        piccy.set({ filter: 'none'});
    -        text.set({ filter: 'none'});
    -        canvas.setBase({ filter: 'none'});
    +    return function () {
     
    -        if (val === 'picture') filterTarget = piccy;
    -        else if (val === 'phrase') filterTarget = text;
    -        else if (val === 'cell') filterTarget = canvas.base;
    +        converter.convert(color.value);
     
    -        filterTarget.set({ filter: filterString });
    +        myFilter.set({
    +            red: converter.r,
    +            green: converter.g,
    +            blue: converter.b,
    +        });
         }
    -};
    -scrawl.addNativeListener(['input', 'change'], updateTarget, '#target');
    +}();
    +scrawl.addNativeListener(['input', 'change'], interpretColors, '.controlItem');
     
    -let updateFilter = (e) => {
    +scrawl.observeAndUpdate({
     
    -    e.preventDefault();
    -    e.returnValue = false;
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
     
    -    if (e.target && e.target.value) {
    +    target: myFilter,
     
    -        filterString = e.target.value;
    -        filterTarget.set({ filter: filterString });
    -    }
    -};
    -scrawl.addNativeListener(['input', 'change'], updateFilter, '#filter');
    +    useNativeListener: true,
    +    preventDefault: true,
     
    -document.querySelector('#filter').options.selectedIndex = 0;
    -document.querySelector('#target').options.selectedIndex = 0;
    + updates: { + transparentAt: ['transparentAt', 'float'], + opaqueAt: ['opaqueAt', 'float'], + opacity: ['opacity', 'float'], + }, +});
  • @@ -774,6 +833,21 @@

    User interaction

    +

    Setup form

    + + + +
    document.querySelector('#color').value = '#007700';
    + + + + +
  • +
    + +
    + +

    Development and testing

    diff --git a/docs/demo/filters-012.html b/docs/demo/filters-012.html index 7193816b4..0637a8548 100644 --- a/docs/demo/filters-012.html +++ b/docs/demo/filters-012.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 012

    -

    SVG-based filter example: gaussian blur

    +

    Filter parameters: matrix, matrix5

    @@ -600,25 +660,24 @@

    Scene setup

    -

    Create the target entity

    +

    Create the filters

    -
    const piccy = scrawl.makePicture({
    -
    -    name: 'base-piccy',
    +            
    const matrix3 = scrawl.makeFilter({
     
    -    asset: 'iris',
    +    name: 'matrix3',
    +    method: 'matrix',
     
    -    width: '100%',
    -    height: '100%',
    +    weights: [0, 0, 0, 0, 1, 0, 0, 0, 0],
    +});
     
    -    copyWidth: '100%',
    -    copyHeight: '100%',
    +const matrix5 = scrawl.makeFilter({
     
    -    method: 'fill',
    +    name: 'matrix5',
    +    method: 'matrix5',
     
    -    filter: 'url(#svg-blur)',
    +    weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     });
  • @@ -630,17 +689,26 @@

    Scene setup

    -

    SVG filter

    -

    We create the filter in the HTML script, not here:

    -
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    -  <filter id="svg-blur">
    -    <feGaussianBlur in="SourceGraphic" stdDeviation="5" edgeMode="duplicate" />
    -  </filter>
    -</svg>
    +

    Create the target entity

    -
    let feGaussianBlur = document.querySelector('feGaussianBlur');
    +
    const target = scrawl.makePicture({
    +
    +    name: 'base-piccy',
    +
    +    asset: 'iris',
    +
    +    width: '100%',
    +    height: '100%',
    +
    +    copyWidth: '100%',
    +    copyHeight: '100%',
    +
    +    method: 'fill',
    +
    +    filters: ['matrix3'],
    +});
    @@ -662,6 +730,8 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -669,10 +739,9 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - -<filter id="svg-blur"> - <feGaussianBlur in="SourceGraphic" stdDeviation="${feGaussianBlur.getAttribute('stdDeviation')}" edgeMode="${feGaussianBlur.getAttribute('edgeMode')}" /> -</filter>`; + matrix3 weights array: ${matrix3.weights} + matrix5 weights array: ${matrix5.weights} + Opacity: ${opacity.value}`; }; }(); @@ -706,30 +775,117 @@

    Scene animation

    User interaction

    -

    Setup form functionality

    +

    Setup form observer functionality

    -
    let updateStdDeviation = (e) => {
    +            
    const changeMatrix = function () {
    +
    +    const selector = document.querySelector('#selectMatrix');
    +
    +    return function () {
    +
    +        target.set({
    +            filters: [selector.value],
    +        });
    +    }
    +}();
    +scrawl.addNativeListener(['input', 'change'], changeMatrix, '#selectMatrix');
    +
    +const updateWeights = function () {
    +
    +    const m11 = document.querySelector('#m11');
    +    const m12 = document.querySelector('#m12');
    +    const m13 = document.querySelector('#m13');
    +    const m14 = document.querySelector('#m14');
    +    const m15 = document.querySelector('#m15');
    +    const m21 = document.querySelector('#m21');
    +    const m22 = document.querySelector('#m22');
    +    const m23 = document.querySelector('#m23');
    +    const m24 = document.querySelector('#m24');
    +    const m25 = document.querySelector('#m25');
    +    const m31 = document.querySelector('#m31');
    +    const m32 = document.querySelector('#m32');
    +    const m33 = document.querySelector('#m33');
    +    const m34 = document.querySelector('#m34');
    +    const m35 = document.querySelector('#m35');
    +    const m41 = document.querySelector('#m41');
    +    const m42 = document.querySelector('#m42');
    +    const m43 = document.querySelector('#m43');
    +    const m44 = document.querySelector('#m44');
    +    const m45 = document.querySelector('#m45');
    +    const m51 = document.querySelector('#m51');
    +    const m52 = document.querySelector('#m52');
    +    const m53 = document.querySelector('#m53');
    +    const m54 = document.querySelector('#m54');
    +    const m55 = document.querySelector('#m55');
    +
    +    let weights3, weights5;
    +
    +    return function () {
    +
    +        weights3 = [parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), 
    +                    parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), 
    +                    parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value)];
     
    -    e.preventDefault();
    -    e.returnValue = false;
    +        weights5 = [parseFloat(m11.value), parseFloat(m12.value), parseFloat(m13.value), parseFloat(m14.value), parseFloat(m15.value), 
    +                    parseFloat(m21.value), parseFloat(m22.value), parseFloat(m23.value), parseFloat(m24.value), parseFloat(m25.value), 
    +                    parseFloat(m31.value), parseFloat(m32.value), parseFloat(m33.value), parseFloat(m34.value), parseFloat(m35.value), 
    +                    parseFloat(m41.value), parseFloat(m42.value), parseFloat(m43.value), parseFloat(m44.value), parseFloat(m45.value), 
    +                    parseFloat(m51.value), parseFloat(m52.value), parseFloat(m53.value), parseFloat(m54.value), parseFloat(m55.value)];
     
    -    feGaussianBlur.setAttribute('stdDeviation', parseFloat(e.target.value));
    -};
    -scrawl.addNativeListener(['input', 'change'], updateStdDeviation, '#stdDeviation');
    +        matrix3.set({
    +            weights: weights3,
    +        });
     
    -let updateEdgeMode = (e) => {
    +        matrix5.set({
    +            weights: weights5,
    +        });
    +    }
    +}();
    +scrawl.addNativeListener(['input', 'change'], updateWeights, '.weight');
     
    -    e.preventDefault();
    -    e.returnValue = false;
    +scrawl.observeAndUpdate({
     
    -    feGaussianBlur.setAttribute(`edgeMode`, e.target.value);
    -};
    -scrawl.addNativeListener(['input', 'change'], updateEdgeMode, '#edgeMode');
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
     
    -document.querySelector('#stdDeviation').value = 5;
    -document.querySelector('#edgeMode').options.selectedIndex = 0;
    + target: matrix3, + + useNativeListener: true, + preventDefault: true, + + updates: { + + includeRed: ['includeRed', 'boolean'], + includeGreen: ['includeGreen', 'boolean'], + includeBlue: ['includeBlue', 'boolean'], + includeAlpha: ['includeAlpha', 'boolean'], + + opacity: ['opacity', 'float'], + }, +}); + +scrawl.observeAndUpdate({ + + event: ['input', 'change'], + origin: '.controlItem', + + target: matrix5, + + useNativeListener: true, + preventDefault: true, + + updates: { + + includeRed: ['includeRed', 'boolean'], + includeGreen: ['includeGreen', 'boolean'], + includeBlue: ['includeBlue', 'boolean'], + includeAlpha: ['includeAlpha', 'boolean'], + + opacity: ['opacity', 'float'], + }, +});
    @@ -740,6 +896,51 @@

    User interaction

    +

    Setup form

    + + + +
    document.querySelector('#selectMatrix').value = 'matrix3';
    +document.querySelector('#m11').value = 0;
    +document.querySelector('#m12').value = 0;
    +document.querySelector('#m13').value = 0;
    +document.querySelector('#m14').value = 0;
    +document.querySelector('#m15').value = 0;
    +document.querySelector('#m21').value = 0;
    +document.querySelector('#m22').value = 0;
    +document.querySelector('#m23').value = 0;
    +document.querySelector('#m24').value = 0;
    +document.querySelector('#m25').value = 0;
    +document.querySelector('#m31').value = 0;
    +document.querySelector('#m32').value = 0;
    +document.querySelector('#m33').value = 1;
    +document.querySelector('#m34').value = 0;
    +document.querySelector('#m35').value = 0;
    +document.querySelector('#m41').value = 0;
    +document.querySelector('#m42').value = 0;
    +document.querySelector('#m43').value = 0;
    +document.querySelector('#m44').value = 0;
    +document.querySelector('#m45').value = 0;
    +document.querySelector('#m51').value = 0;
    +document.querySelector('#m52').value = 0;
    +document.querySelector('#m53').value = 0;
    +document.querySelector('#m54').value = 0;
    +document.querySelector('#m55').value = 0;
    +document.querySelector('#includeRed').options.selectedIndex = 1;
    +document.querySelector('#includeGreen').options.selectedIndex = 1;
    +document.querySelector('#includeBlue').options.selectedIndex = 1;
    +document.querySelector('#includeAlpha').options.selectedIndex = 0;
    +document.querySelector('#opacity').value = 1;
    + + + + +
  • +
    + +
    + +

    Development and testing

    diff --git a/docs/demo/filters-013.html b/docs/demo/filters-013.html index 48948f754..ac6c750c7 100644 --- a/docs/demo/filters-013.html +++ b/docs/demo/filters-013.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 013

    -

    SVG-based filter example: posterize

    +

    Filter parameters: flood

    @@ -600,25 +660,19 @@

    Scene setup

    -

    Create the target entity

    +

    Create the filter

    -
    const piccy = scrawl.makePicture({
    -
    -    name: 'base-piccy',
    +            
    const myFilter = scrawl.makeFilter({
     
    -    asset: 'iris',
    -
    -    width: '100%',
    -    height: '100%',
    +    name: 'flood',
    +    method: 'flood',
     
    -    copyWidth: '100%',
    -    copyHeight: '100%',
    -
    -    method: 'fill',
    -
    -    filter: 'url(#svg-posterize)',
    +    red: 0,
    +    green: 0,
    +    blue: 0,
    +    alpha: 255
     });
  • @@ -630,23 +684,26 @@

    Scene setup

    -

    SVG filter

    -

    We create the filter in the HTML script, not here:

    -
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    -  <filter id="svg-posterize">
    -    <feComponentTransfer>
    -      <feFuncR type="discrete" tableValues=".1 .4 .7 1" />
    -      <feFuncG type="discrete" tableValues=".1 .4 .7 1" />
    -      <feFuncB type="discrete" tableValues=".1 .4 .7 1" />
    -    </feComponentTransfer>
    -  </filter>
    -</svg>
    +

    Create the target entity

    -
    let feFuncR = document.querySelector('feFuncR'),
    -    feFuncG = document.querySelector('feFuncG'),
    -    feFuncB = document.querySelector('feFuncB');
    +
    scrawl.makePicture({
    +
    +    name: 'base-piccy',
    +
    +    asset: 'iris',
    +
    +    width: '100%',
    +    height: '100%',
    +
    +    copyWidth: '100%',
    +    copyHeight: '100%',
    +
    +    method: 'fill',
    +
    +    filters: ['flood'],
    +});
    @@ -668,6 +725,9 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let flood = document.querySelector('#flood'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -675,14 +735,8 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - -<filter id="svg-posterize"> - <feComponentTransfer> - <feFuncR type="discrete" tableValues="${feFuncR.getAttribute('tableValues')}" /> - <feFuncG type="discrete" tableValues="${feFuncG.getAttribute('tableValues')}" /> - <feFuncB type="discrete" tableValues="${feFuncB.getAttribute('tableValues')}" /> - </feComponentTransfer> -</filter>`; + Flood color: ${flood.value} + Opacity: ${opacity.value}`; }; }(); @@ -716,36 +770,32 @@

    Scene animation

    User interaction

    +

    Use a color object to convert between CSS hexadecimal and RGB decimal colors

    -
    let r1 = document.querySelector('#r1'),
    -    r2 = document.querySelector('#r2'),
    -    r3 = document.querySelector('#r3'),
    -    r4 = document.querySelector('#r4');
    +            
    const converter = scrawl.makeColor({
    +    name: 'converter',
    +});
    +
    +scrawl.addNativeListener(
    +    ['input', 'change'], 
    +    (e) => myFilter.set({ opacity: parseFloat(e.target.value) }), 
    +    '#opacity');
     
    -let g1 = document.querySelector('#g1'),
    -    g2 = document.querySelector('#g2'),
    -    g3 = document.querySelector('#g3'),
    -    g4 = document.querySelector('#g4');
    +scrawl.addNativeListener(
    +    ['input', 'change'], 
    +    (e) => {
     
    -let b1 = document.querySelector('#b1'),
    -    b2 = document.querySelector('#b2'),
    -    b3 = document.querySelector('#b3'),
    -    b4 = document.querySelector('#b4');
    +        converter.convert(e.target.value);
     
    -r1.value = 0.1;
    -r2.value = 0.4;
    -r3.value = 0.7;
    -r4.value = 1;
    -g1.value = 0.1;
    -g2.value = 0.4;
    -g3.value = 0.7;
    -g4.value = 1;
    -b1.value = 0.1;
    -b2.value = 0.4;
    -b3.value = 0.7;
    -b4.value = 1;
    + myFilter.set({ + red: converter.r, + green: converter.g, + blue: converter.b, + }); + }, + '#flood');
    @@ -756,18 +806,12 @@

    User interaction

    -

    Setup form functionality

    +

    Setup form

    -
    let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value} ${r3.value} ${r4.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR');
    -
    -let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value} ${g3.value} ${g4.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG');
    -
    -let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value} ${b3.value} ${b4.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB');
    +
    document.querySelector('#flood').value = '#000000';
    +document.querySelector('#opacity').value = 1;
    diff --git a/docs/demo/filters-014.html b/docs/demo/filters-014.html index d124c8ed0..f3634310f 100644 --- a/docs/demo/filters-014.html +++ b/docs/demo/filters-014.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 014

    -

    SVG-based filter example: duotone

    +

    Filter parameters: areaAlpha

    @@ -600,25 +660,22 @@

    Scene setup

    -

    Create the target entity

    +

    Create the filter

    -
    const piccy = scrawl.makePicture({
    +            
    const myFilter = scrawl.makeFilter({
     
    -    name: 'base-piccy',
    +    name: 'areaAlpha',
    +    method: 'areaAlpha',
     
    -    asset: 'iris',
    -
    -    width: '100%',
    -    height: '100%',
    -
    -    copyWidth: '100%',
    -    copyHeight: '100%',
    -
    -    method: 'fill',
    -
    -    filter: 'url(#svg-duotone)',
    +    tileWidth: 10,
    +    tileHeight: 10,
    +    gutterWidth: 10,
    +    gutterHeight: 10,
    +    offsetX: 0,
    +    offsetY: 0,
    +    areaAlphaLevels: [255, 255, 0, 0],
     });
    @@ -630,50 +687,35 @@

    Scene setup

    -

    SVG filter

    -

    We create the filter in the HTML script, not here:

    -
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    -  <filter id="svg-duotone">
    -    <feColorMatrix type="matrix" values=".33 .33 .33 0 0
    -      .33 .33 .33 0 0
    -      .33 .33 .33 0 0
    -       0   0   0  1 0">
    -    </feColorMatrix>
    +

    Create the target entity

    - - - -
  • -
    - -
    - -
    -
    <feComponentTransfer color-interpolation-filters="sRGB">
    -  <feFuncR type="table" tableValues=".996 0.984"></feFuncR>
    -  <feFuncG type="table" tableValues=".125 0.941"></feFuncG>
    -  <feFuncB type="table" tableValues=".552 0.478"></feFuncB>
    -</feComponentTransfer>
    - - -``` +
    scrawl.makePicture({
     
    -            
    - -
    let feFuncR = document.querySelector('feFuncR'),
    -    feFuncG = document.querySelector('feFuncG'),
    -    feFuncB = document.querySelector('feFuncB');
    + name: 'base-piccy', + + asset: 'iris', + + width: '100%', + height: '100%', + + copyWidth: '100%', + copyHeight: '100%', + + method: 'fill', + + filters: ['areaAlpha'], +});
  • -
  • +
  • - +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -686,6 +728,18 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let tile_width = document.querySelector('#tile_width'), + tile_height = document.querySelector('#tile_height'), + gutter_width = document.querySelector('#gutter_width'), + gutter_height = document.querySelector('#gutter_height'), + alpha_0 = document.querySelector('#alpha_0'), + alpha_1 = document.querySelector('#alpha_1'), + alpha_2 = document.querySelector('#alpha_2'), + alpha_3 = document.querySelector('#alpha_3'), + offset_x = document.querySelector('#offset_x'), + offset_y = document.querySelector('#offset_y'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -693,27 +747,22 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - -<filter id="svg-duotone"> - <feColorMatrix type="matrix" values=".33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"></feColorMatrix> - - <feComponentTransfer color-interpolation-filters="sRGB"> - <feFuncR type="discrete" tableValues="${feFuncR.getAttribute('tableValues')}" /> - <feFuncG type="discrete" tableValues="${feFuncG.getAttribute('tableValues')}" /> - <feFuncB type="discrete" tableValues="${feFuncB.getAttribute('tableValues')}" /> - </feComponentTransfer> -</filter>`; + Tile dimensions - width: ${tile_width.value} height: ${tile_height.value} + Gutter dimensions - width: ${gutter_width.value} height: ${gutter_height.value} + Offset - x: ${offset_x.value} y: ${offset_y.value} + areaAlphaLevels array: [${alpha_0.value}, ${alpha_1.value}, ${alpha_2.value}, ${alpha_3.value}] + Opacity: ${opacity.value}`; }; }();
  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -729,62 +778,85 @@

    Scene animation

  • -
  • +
  • - +

    User interaction

    +

    Setup form observer functionality

    -
    let r1 = document.querySelector('#r1'),
    -    r2 = document.querySelector('#r2');
    +            
    scrawl.observeAndUpdate({
    +
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
    +
    +    target: myFilter,
     
    -let g1 = document.querySelector('#g1'),
    -    g2 = document.querySelector('#g2');
    +    useNativeListener: true,
    +    preventDefault: true,
     
    -let b1 = document.querySelector('#b1'),
    -    b2 = document.querySelector('#b2');
    +    updates: {
     
    -r1.value = 0.996;
    -r2.value = 0.984;
    -g1.value = 0.125;
    -g2.value = 0.941;
    -b1.value = 0.552;
    -b2.value = 0.478;
    + tile_width: ['tileWidth', 'round'], + tile_height: ['tileHeight', 'round'], + gutter_width: ['gutterWidth', 'round'], + gutter_height: ['gutterHeight', 'round'], + offset_x: ['offsetX', 'round'], + offset_y: ['offsetY', 'round'], + opacity: ['opacity', 'float'], + }, +}); + +scrawl.addNativeListener(['input', 'change'], function (e) { + + let a0 = parseInt(document.querySelector('#alpha_0').value, 10), + a1 = parseInt(document.querySelector('#alpha_1').value, 10), + a2 = parseInt(document.querySelector('#alpha_2').value, 10), + a3 = parseInt(document.querySelector('#alpha_3').value, 10); + + myFilter.set({ + areaAlphaLevels: [a0, a2, a1, a3], + }); + +}, '.alphas');
  • -
  • +
  • - +
    -

    Setup form functionality

    +

    Setup form

    -
    let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR');
    -
    -let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG');
    -
    -let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value}`);
    -scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB');
    +
    document.querySelector('#tile_width').value = 10;
    +document.querySelector('#tile_height').value = 10;
    +document.querySelector('#gutter_width').value = 10;
    +document.querySelector('#gutter_height').value = 10;
    +document.querySelector('#offset_x').value = 0;
    +document.querySelector('#offset_y').value = 0;
    +document.querySelector('#opacity').value = 1;
    +document.querySelector('#alpha_0').value = 255;
    +document.querySelector('#alpha_1').value = 0;
    +document.querySelector('#alpha_2').value = 255;
    +document.querySelector('#alpha_3').value = 0;
  • -
  • +
  • - +

    Development and testing

    diff --git a/docs/demo/filters-015.html b/docs/demo/filters-015.html index dca45a20b..57c299552 100644 --- a/docs/demo/filters-015.html +++ b/docs/demo/filters-015.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -555,7 +615,7 @@

    Demo Filters 015

    -

    SVG-based filter example: noise

    +

    Using assets in the filter stream; filter compositing

    @@ -568,7 +628,7 @@

    Demo Filters 015

    -

    Run code

    +

    Run code

    @@ -589,7 +649,9 @@

    Scene setup

    const canvas = scrawl.library.canvas.mycanvas;
     
    -scrawl.importDomImage('.flowers');
    +canvas.setBase({ + compileOrder: 1, +});
  • @@ -600,25 +662,69 @@

    Scene setup

    -

    Create the target entity

    +

    Create the assets

    -
    const piccy = scrawl.makePicture({
    +            
    scrawl.importDomImage('.flowers');
     
    -    name: 'base-piccy',
    +canvas.buildCell({
     
    -    asset: 'iris',
    +    name: 'star-cell',
    +    dimensions: [400, 400],
    +    shown: false,
    +});
    +
    +scrawl.makeStar({
    +
    +    name: 'my-star',
    +    group: 'star-cell',
    +
    +    radius1: 200,
    +    radius2: 100,
    +
    +    roll: 60,
    +
    +    points: 4,
    +
    +    start: ['center', 'center'],
    +    handle: ['center', 'center'],
    +
    +    fillStyle: 'blue',
    +    strokeStyle: 'red',
    +    lineWidth: 10,
    +    method: 'fillThenDraw',
    +});
    +
    +canvas.buildCell({
    +
    +    name: 'wheel-cell',
    +    dimensions: [400, 400],
    +    shown: false,
    +});
     
    -    width: '100%',
    -    height: '100%',
    +scrawl.makeWheel({
     
    -    copyWidth: '100%',
    -    copyHeight: '100%',
    +    name: 'my-wheel',
    +    group: 'wheel-cell',
     
    -    method: 'fill',
    +    radius: 150,
     
    -    filter: 'url(#svg-noise)',
    +    startAngle: 30,
    +    endAngle: -30,
    +    includeCenter: true,
    +
    +    start: ['center', 'center'],
    +    handle: ['center', 'center'],
    +
    +    fillStyle: 'green',
    +    strokeStyle: 'yellow',
    +    lineWidth: 10,
    +    method: 'fillThenDraw',
    +
    +    delta: {
    +        roll: -0.3,
    +    },
     });
    @@ -630,32 +736,63 @@

    Scene setup

    -

    SVG filter

    -

    We create the filter in the HTML script, not here:

    -
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    -  <filter id="svg-noise">
    -    <feTurbulence type="fractalNoise" baseFrequency="0.01 0.04" result="NOISE" numOctaves="2" />
    -    <feDisplacementMap in="SourceGraphic" in2="NOISE" scale="20" xChannelSelector="R" yChannelSelector="R"></feDisplacementMap>
    -  </filter>
    -</svg>
    +

    Create the filters

    -
    let bfx = document.querySelector('#bfx'),
    -    bfy = document.querySelector('#bfy'),
    -    octaves = document.querySelector('#octaves'),
    -    scale = document.querySelector('#scale'),
    -    xChannelSelector = document.querySelector('#xChannelSelector'),
    -    yChannelSelector = document.querySelector('#yChannelSelector'),
    -    feTurbulence = document.querySelector('feTurbulence'),
    -    feDisplacementMap = document.querySelector('feDisplacementMap');
    -
    -bfx.value = 0.01;
    -bfy.value = 0.04;
    -octaves.value = 2;
    -scale.value = 20;
    -xChannelSelector.options.selectedIndex = 0;
    -yChannelSelector.options.selectedIndex = 0;
    +
    scrawl.makeFilter({
    +
    +    name: 'star-filter',
    +    method: 'image',
    +
    +    asset: 'star-cell',
    +
    +    width: 400,
    +    height: 400,
    +
    +    copyWidth: 400,
    +    copyHeight: 400,
    +
    +    lineOut: 'star',
    +
    +}).clone({
    +
    +    name: 'wheel-filter',
    +    asset: 'wheel-cell',
    +    lineOut: 'wheel',
    +});
    +
    +scrawl.makeFilter({
    +
    +    name: 'flower-filter',
    +    method: 'image',
    +
    +    asset: 'iris',
    +
    +    width: 200,
    +    height: 200,
    +    
    +    copyX: '25%',
    +    copyY: 100,
    +    copyWidth: '50%',
    +    copyHeight: 200,
    +
    +    lineOut: 'flower',
    +});
    +
    +let composeFilter = scrawl.makeFilter({
    +
    +    name: 'block-filter',
    +    method: 'compose',
    +
    +    lineIn: 'star',
    +    lineMix: 'source',
    +
    +    offsetX: 30,
    +    offsetY: 30,
    +
    +    compose: 'source-over',
    +});
    @@ -666,6 +803,62 @@

    SVG filter

    +

    Display the filter in a Block entity

    + +
    + +
    scrawl.makeGradient({
    +    name: 'linear',
    +    endX: '100%',
    +})
    +.updateColor(0, 'blue')
    +.updateColor(495, 'red')
    +.updateColor(500, 'yellow')
    +.updateColor(505, 'red')
    +.updateColor(999, 'green');
    +
    +scrawl.makeBlock({
    +
    +    name: 'display-block',
    +    start: ['center', 'center'],
    +    handle: ['center', 'center'],
    +    dimensions: ['90%', '90%'],
    +    roll: -20,
    +
    +    lineWidth: 10,
    +    fillStyle: 'linear',
    +    lockFillStyleToEntity: true,
    +    strokeStyle: 'coral',
    +    method: 'fillThenDraw',
    + + + + +
  • +
    + +
    + +
    +

    Load in the three image filters, then the compose filter to combine two of them

    +
      +
    • the results display in a Block entity!
    • +
    + +
    + +
        filters: ['star-filter', 'wheel-filter', 'flower-filter', 'block-filter'],
    +});
    + +
  • + + +
  • +
    + +
    + +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -677,6 +870,10 @@

    Scene animation

    testTime, testNow, testMessage = document.querySelector('#reportmessage'); + let ox = document.querySelector('#offset-x'), + oy = document.querySelector('#offset-y'), + opacity = document.querySelector('#opacity'); + return function () { testNow = Date.now(); @@ -684,22 +881,19 @@

    Scene animation

    testTicker = testNow; testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)} - -<filter id="svg-noise"> - <feTurbulence type="fractalNoise" baseFrequency="${bfx.value} ${bfy.value}" result="NOISE" numOctaves="${octaves.value}" /> - <feDisplacementMap in="SourceGraphic" in2="NOISE" scale="${scale.value}" xChannelSelector="${xChannelSelector.value}" yChannelSelector="${yChannelSelector.value}"></feDisplacementMap> -</filter>`; + Offset - x: ${ox.value}, y: ${oy.value} + Opacity: ${opacity.value}`; }; }();
  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -715,40 +909,66 @@

    Scene animation

  • -
  • +
  • - +

    User interaction

    -

    Setup form functionality

    +

    Setup form observer functionality

    -
    let baseFrequency = () => feTurbulence.setAttribute('baseFrequency', `${bfx.value} ${bfy.value}`);
    -scrawl.addNativeListener(['input', 'change'], baseFrequency, '.baseFreq');
    +            
    scrawl.observeAndUpdate({
     
    -let numOctaves = () => feTurbulence.setAttribute('numOctaves', octaves.value);
    -scrawl.addNativeListener(['input', 'change'], numOctaves, '#octaves');
    +    event: ['input', 'change'],
    +    origin: '.controlItem',
     
    -let dmScale = () => feDisplacementMap.setAttribute('scale', scale.value);
    -scrawl.addNativeListener(['input', 'change'], dmScale, '#scale');
    +    target: composeFilter,
     
    -let dmX = () => feDisplacementMap.setAttribute('xChannelSelector', xChannelSelector.value);
    -scrawl.addNativeListener(['input', 'change'], dmX, '#xChannelSelector');
    +    useNativeListener: true,
    +    preventDefault: true,
     
    -let dmY = () => feDisplacementMap.setAttribute('yChannelSelector', yChannelSelector.value);
    -scrawl.addNativeListener(['input', 'change'], dmY, '#yChannelSelector');
    + updates: { + + source: ['lineIn', 'raw'], + destination: ['lineMix', 'raw'], + composite: ['compose', 'raw'], + opacity: ['opacity', 'float'], + 'offset-x': ['offsetX', 'round'], + 'offset-y': ['offsetY', 'round'], + }, +});
  • -
  • +
  • - + +
    +

    Setup form

    + +
    + +
    document.querySelector('#source').options.selectedIndex = 2;
    +document.querySelector('#destination').options.selectedIndex = 0;
    +document.querySelector('#composite').options.selectedIndex = 0;
    +document.querySelector('#opacity').value = 1;
    +document.querySelector('#offset-x').value = 30;
    +document.querySelector('#offset-y').value = 30;
    + +
  • + + +
  • +
    + +
    +

    Development and testing

    diff --git a/docs/demo/filters-016.html b/docs/demo/filters-016.html new file mode 100644 index 000000000..906ddb2e6 --- /dev/null +++ b/docs/demo/filters-016.html @@ -0,0 +1,985 @@ + + + + + Demo Filters 016 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 016

      +

      Filter blend operation

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +canvas.setBase({
      +    compileOrder: 1,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the assets

      + +
      + +
      scrawl.importDomImage('.flowers');
      +
      +canvas.buildCell({
      +
      +    name: 'star-cell',
      +    dimensions: [400, 400],
      +    shown: false,
      +});
      +
      +scrawl.makeStar({
      +
      +    name: 'my-star',
      +    group: 'star-cell',
      +
      +    radius1: 200,
      +    radius2: 100,
      +
      +    roll: 60,
      +
      +    points: 4,
      +
      +    start: ['center', 'center'],
      +    handle: ['center', 'center'],
      +
      +    fillStyle: 'blue',
      +    strokeStyle: 'red',
      +    lineWidth: 10,
      +    method: 'fillThenDraw',
      +});
      +
      +canvas.buildCell({
      +
      +    name: 'wheel-cell',
      +    dimensions: [400, 400],
      +    shown: false,
      +});
      +
      +scrawl.makeWheel({
      +
      +    name: 'my-wheel',
      +    group: 'wheel-cell',
      +
      +    radius: 150,
      +
      +    startAngle: 30,
      +    endAngle: -30,
      +    includeCenter: true,
      +
      +    start: ['center', 'center'],
      +    handle: ['center', 'center'],
      +
      +    fillStyle: 'green',
      +    strokeStyle: 'yellow',
      +    lineWidth: 10,
      +    method: 'fillThenDraw',
      +
      +    delta: {
      +        roll: -0.3,
      +    },
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the filters

      + +
      + +
      scrawl.makeFilter({
      +
      +    name: 'star-filter',
      +    method: 'image',
      +
      +    asset: 'star-cell',
      +
      +    width: 400,
      +    height: 400,
      +
      +    copyWidth: 400,
      +    copyHeight: 400,
      +
      +    lineOut: 'star',
      +
      +}).clone({
      +
      +    name: 'wheel-filter',
      +    asset: 'wheel-cell',
      +    lineOut: 'wheel',
      +});
      +
      +scrawl.makeFilter({
      +
      +    name: 'flower-filter',
      +    method: 'image',
      +
      +    asset: 'iris',
      +
      +    width: 200,
      +    height: 200,
      +    
      +    copyX: '25%',
      +    copyY: 100,
      +    copyWidth: '50%',
      +    copyHeight: 200,
      +
      +    lineOut: 'flower',
      +});
      +
      +let composeFilter = scrawl.makeFilter({
      +
      +    name: 'block-filter',
      +    method: 'blend',
      +
      +    lineIn: 'source',
      +    lineMix: 'star',
      +
      +    offsetX: 30,
      +    offsetY: 30,
      +
      +    compose: 'normal',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Display the filter in a Block entity

      + +
      + +
      +scrawl.makeGradient({
      +    name: 'linear',
      +    endX: '100%',
      +})
      +.updateColor(0, 'blue')
      +.updateColor(495, 'red')
      +.updateColor(500, 'yellow')
      +.updateColor(505, 'red')
      +.updateColor(999, 'green');
      +
      +scrawl.makeBlock({
      +
      +    name: 'display-block',
      +    start: ['center', 'center'],
      +    handle: ['center', 'center'],
      +    dimensions: ['90%', '90%'],
      +    roll: -20,
      +
      +    lineWidth: 10,
      +    fillStyle: 'linear',
      +    lockFillStyleToEntity: true,
      +    strokeStyle: 'coral',
      +    method: 'fillThenDraw',
      + +
    • + + +
    • +
      + +
      + +
      +

      Load in the three image filters, then the compose filter to combine two of them

      +
        +
      • the results display in a Block entity!
      • +
      + +
      + +
          filters: ['star-filter', 'wheel-filter', 'flower-filter', 'block-filter'],
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    let ox = document.querySelector('#offset-x'),
      +        oy = document.querySelector('#offset-y'),
      +        opacity = document.querySelector('#opacity');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +    Offset - x: ${ox.value}, y: ${oy.value}
      +    Opacity: ${opacity.value}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form observer functionality

      + +
      + +
      scrawl.observeAndUpdate({
      +
      +    event: ['input', 'change'],
      +    origin: '.controlItem',
      +
      +    target: composeFilter,
      +
      +    useNativeListener: true,
      +    preventDefault: true,
      +
      +    updates: {
      +
      +        source: ['lineIn', 'raw'],
      +        destination: ['lineMix', 'raw'],
      +        blend: ['blend', 'raw'],
      +        opacity: ['opacity', 'float'],
      +        'offset-x': ['offsetX', 'round'],
      +        'offset-y': ['offsetY', 'round'],
      +    },
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form

      + +
      + +
      document.querySelector('#source').options.selectedIndex = 0;
      +document.querySelector('#destination').options.selectedIndex = 2;
      +document.querySelector('#blend').options.selectedIndex = 0;
      +document.querySelector('#opacity').value = 1;
      +document.querySelector('#offset-x').value = 30;
      +document.querySelector('#offset-y').value = 30;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-017.html b/docs/demo/filters-017.html new file mode 100644 index 000000000..135ab0d3e --- /dev/null +++ b/docs/demo/filters-017.html @@ -0,0 +1,861 @@ + + + + + Demo Filters 017 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 017

      +

      Filter parameters: displace

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the filter

      + +
      + +
      scrawl.makeFilter({
      +
      +    name: 'noise',
      +    method: 'image',
      +
      +    asset: 'perlin',
      +
      +    width: 500,
      +    height: 500,
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    lineOut: 'map',
      +});
      +
      +const myFilter = scrawl.makeFilter({
      +
      +    name: 'displace',
      +    method: 'displace',
      +
      +    lineMix: 'map',
      +
      +    offsetX: 0,
      +    offsetY: 0,
      +
      +    scaleX: 1,
      +    scaleY: 1,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filters: ['noise', 'displace'],
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    let scale_x = document.querySelector('#scale_x'),
      +        scale_y = document.querySelector('#scale_y'),
      +        offset_x = document.querySelector('#offset_x'),
      +        offset_y = document.querySelector('#offset_y'),
      +        opacity = document.querySelector('#opacity');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +    Scales - x: ${scale_x.value} y: ${scale_y.value}
      +    Offsets - x: ${offset_x.value} y: ${offset_y.value}
      +    Opacity - ${opacity.value}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form observer functionality

      + +
      + +
      scrawl.observeAndUpdate({
      +
      +    event: ['input', 'change'],
      +    origin: '.controlItem',
      +
      +    target: myFilter,
      +
      +    useNativeListener: true,
      +    preventDefault: true,
      +
      +    updates: {
      +
      +        offset_x: ['offsetX', 'round'],
      +        offset_y: ['offsetY', 'round'],
      +        scale_x: ['scaleX', 'float'],
      +        scale_y: ['scaleY', 'float'],
      +        transparent_edges: ['transparentEdges', 'boolean'],
      +        opacity: ['opacity', 'float'],
      +    },
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form

      + +
      + +
      document.querySelector('#offset_x').value = 0;
      +document.querySelector('#offset_y').value = 0;
      +document.querySelector('#scale_x').value = 1;
      +document.querySelector('#scale_y').value = 1;
      +document.querySelector('#opacity').value = 1;
      +document.querySelector('#transparent_edges').options.selectedIndex = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-018.html b/docs/demo/filters-018.html new file mode 100644 index 000000000..9e59f05c6 --- /dev/null +++ b/docs/demo/filters-018.html @@ -0,0 +1,855 @@ + + + + + Demo Filters 018 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 018

      +

      Filter parameters: emboss

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      +
      +canvas.setBase({
      +    backgroundColor: 'red',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the filter

      + +
      + +
      const myFilter = scrawl.makeFilter({
      +
      +    name: 'emboss',
      +    method: 'emboss',
      +    angle: 225,
      +    strength: 3,
      +    smoothing: 0,
      +    tolerance: 0,
      +    clamp: 0,
      +    postProcessResults: true,
      +    useNaturalGrayscale: false,
      +    keepOnlyChangedAreas: false,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filters: ['emboss'],
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    let strength = document.querySelector('#strength'),
      +        angle = document.querySelector('#angle'),
      +        smoothing = document.querySelector('#smoothing'),
      +        clamp = document.querySelector('#clamp'),
      +        tolerance = document.querySelector('#tolerance');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +    Angle - ${angle.value}°, Strength - ${strength.value}, Smoothing - ${smoothing.value}, Clamp - ${clamp.value}
      +    Tolerance - ${tolerance.value}
      +    Opacity - ${opacity.value}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form observer functionality

      + +
      + +
      scrawl.observeAndUpdate({
      +
      +    event: ['input', 'change'],
      +    origin: '.controlItem',
      +
      +    target: myFilter,
      +
      +    useNativeListener: true,
      +    preventDefault: true,
      +
      +    updates: {
      +
      +        strength: ['strength', 'float'],
      +        angle: ['angle', 'float'],
      +        smoothing: ['smoothing', 'round'],
      +        tolerance: ['tolerance', 'round'],
      +        clamp: ['clamp', 'round'],
      +        postProcessResults: ['postProcessResults', 'boolean'],
      +        useNaturalGrayscale: ['useNaturalGrayscale', 'boolean'],
      +        keepOnlyChangedAreas: ['keepOnlyChangedAreas', 'boolean'],
      +        opacity: ['opacity', 'float'],
      +    },
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form

      + +
      + +
      document.querySelector('#strength').value = 3;
      +document.querySelector('#angle').value = 225;
      +document.querySelector('#smoothing').value = 0;
      +document.querySelector('#tolerance').value = 0;
      +document.querySelector('#clamp').value = 0;
      +document.querySelector('#postProcessResults').options.selectedIndex = 1;
      +document.querySelector('#useNaturalGrayscale').options.selectedIndex = 0;
      +document.querySelector('#keepOnlyChangedAreas').options.selectedIndex = 0;
      +document.querySelector('#opacity').value = 1;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-019.html b/docs/demo/filters-019.html new file mode 100644 index 000000000..70e39fa74 --- /dev/null +++ b/docs/demo/filters-019.html @@ -0,0 +1,1017 @@ + + + + + Demo Filters 019 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 019

      +

      Using a Noise asset with a displace filter

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas,
      +    noiseCanvas = scrawl.library.canvas['noise-canvas'];
      +
      +scrawl.importDomImage('.flowers');
      +
      +
      +let noiseAsset = scrawl.makeNoise({
      +
      +    name: 'my-noise-generator',
      +    width: 400,
      +    height: 400,
      +
      +    noiseEngine: 'improved-perlin',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the filter

      + +
      + +
      scrawl.makeFilter({
      +
      +    name: 'noise',
      +    method: 'image',
      +
      +    asset: 'my-noise-generator',
      +
      +    width: 400,
      +    height: 400,
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    lineOut: 'map',
      +});
      +
      +scrawl.makeFilter({
      +
      +    name: 'displace',
      +    method: 'displace',
      +
      +    lineMix: 'map',
      +
      +    scaleX: 20,
      +    scaleY: 20,
      +
      +    transparentEdges: true,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filters: ['noise', 'displace'],
      +});
      +
      +scrawl.makePicture({
      +
      +    name: 'noisecanvas-display',
      +    group: noiseCanvas.base.name,
      +
      +    width: '100%',
      +    height: '100%',
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    asset: 'my-noise-generator',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testMessage = document.querySelector('#reportmessage'),
      +        width = document.querySelector('#width'),
      +        height = document.querySelector('#height'),
      +        octaves = document.querySelector('#octaves'),
      +        sineFrequencyCoeff = document.querySelector('#sineFrequencyCoeff'),
      +        scale = document.querySelector('#scale'),
      +        size = document.querySelector('#size'),
      +        persistence = document.querySelector('#persistence'),
      +        lacunarity = document.querySelector('#lacunarity'),
      +        monochromeStart = document.querySelector('#monochromeStart'),
      +        monochromeRange = document.querySelector('#monochromeRange'),
      +        hueStart = document.querySelector('#hueStart'),
      +        hueRange = document.querySelector('#hueRange'),
      +        saturation = document.querySelector('#saturation'),
      +        luminosity = document.querySelector('#luminosity'),
      +        modularAmplitude = document.querySelector('#modularAmplitude');
      +
      +    return function () {
      +
      +        testMessage.textContent = `Dimensions: width - ${width.value}, height - ${height.value}
      +Color (monochrome): start: ${monochromeStart.value}; range: ${monochromeRange.value}
      +Color (hue): start: ${hueStart.value}; range: ${hueRange.value}; saturation: ${saturation.value}; luminosity: ${luminosity.value}
      +Scale: ${scale.value}; Size: ${size.value}
      +Octaves: ${octaves.value}; Sine frequency coefficient: ${sineFrequencyCoeff.value}
      +Persistence: ${persistence.value}; Lacunarity: ${lacunarity.value}; Modular amplitude: ${modularAmplitude.value}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: [canvas, noiseCanvas],
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form observer functionality

      + +
      + +
      scrawl.observeAndUpdate({
      +
      +    event: ['input', 'change'],
      +    origin: '.controlItem',
      +
      +    target: noiseAsset,
      +
      +    useNativeListener: true,
      +    preventDefault: true,
      +
      +    updates: {
      +
      +        width: ['width', 'round'],
      +        height: ['height', 'round'],
      +        noiseEngine: ['noiseEngine', 'raw'],
      +        color: ['color', 'raw'],
      +        gradientStart: ['gradientStart', 'raw'],
      +        gradientEnd: ['gradientEnd', 'raw'],
      +        octaveFunction: ['octaveFunction', 'raw'],
      +        octaves: ['octaves', 'round'],
      +        sumFunction: ['sumFunction', 'raw'],
      +        sineFrequencyCoeff: ['sineFrequencyCoeff', 'float'],
      +        smoothing: ['smoothing', 'raw'],
      +        scale: ['scale', 'round'],
      +        size: ['size', 'round'],
      +        seed: ['seed', 'raw'],
      +        persistence: ['persistence', 'float'],
      +        lacunarity: ['lacunarity', 'round'],
      +        modularAmplitude: ['modularAmplitude', 'float'],
      +        monochromeStart: ['monochromeStart', 'round'],
      +        monochromeRange: ['monochromeRange', 'round'],
      +        hueStart: ['hueStart', 'float'],
      +        hueRange: ['hueRange', 'float'],
      +        saturation: ['saturation', 'float'],
      +        luminosity: ['luminosity', 'float'],
      +    },
      +});
      +
      +scrawl.addNativeListener(['input', 'change'], function (e) {
      +
      +    let val = e.target.value,
      +        sfc = document.querySelector('#sineFrequencyCoeff'),
      +        ma = document.querySelector('#modularAmplitude');
      +
      +    if (val === 'sine' || val === 'sine-x' || val === 'sine-y') {
      +
      +        sfc.removeAttribute('disabled');
      +        ma.setAttribute('disabled', 'true');
      +    }
      +    else if (val === 'modular') {
      +
      +        sfc.setAttribute('disabled', 'true');
      +        ma.removeAttribute('disabled');
      +    }
      +    else {
      +
      +        sfc.setAttribute('disabled', 'true');
      +        ma.setAttribute('disabled', 'true');
      +    }
      +
      +}, '#sumFunction');
      +
      +scrawl.addNativeListener(['input', 'change'], function (e) {
      +
      +    let val = e.target.value,
      +        s = document.querySelector('#smoothing');
      +
      +    if (val === 'simplex') s.setAttribute('disabled', 'true');
      +    else s.removeAttribute('disabled');
      +
      +}, '#noiseEngine');
      +
      +scrawl.addNativeListener(['input', 'change'], function (e) {
      +
      +    let val = parseFloat(e.target.value),
      +        p = document.querySelector('#persistence'),
      +        l = document.querySelector('#lacunarity');
      +
      +    if (val > 1) {
      +        p.removeAttribute('disabled');
      +        l.removeAttribute('disabled');
      +    }
      +    else {
      +        p.setAttribute('disabled', 'true');
      +        l.setAttribute('disabled', 'true');
      +    }
      +}, '#octaves');
      +
      +scrawl.addNativeListener(['input', 'change'], function (e) {
      +
      +    let val = e.target.value,
      +        ms = document.querySelector('#monochromeStart'),
      +        mr = document.querySelector('#monochromeRange'),
      +        gs = document.querySelector('#gradientStart'),
      +        ge = document.querySelector('#gradientEnd'),
      +        hs = document.querySelector('#hueStart'),
      +        hr = document.querySelector('#hueRange'),
      +        s = document.querySelector('#saturation'),
      +        l = document.querySelector('#luminosity');
      +
      +    if (val === 'monochrome') {
      +        ms.removeAttribute('disabled');
      +        mr.removeAttribute('disabled');
      +        gs.setAttribute('disabled', 'true');
      +        ge.setAttribute('disabled', 'true');
      +        hs.setAttribute('disabled', 'true');
      +        hr.setAttribute('disabled', 'true');
      +        s.setAttribute('disabled', 'true');
      +        l.setAttribute('disabled', 'true');
      +    }
      +    else if (val === 'hue') {
      +        hs.removeAttribute('disabled');
      +        hr.removeAttribute('disabled');
      +        s.removeAttribute('disabled');
      +        l.removeAttribute('disabled');
      +        ms.setAttribute('disabled', 'true');
      +        mr.setAttribute('disabled', 'true');
      +        gs.setAttribute('disabled', 'true');
      +        ge.setAttribute('disabled', 'true');
      +    }
      +    else {
      +        gs.removeAttribute('disabled');
      +        ge.removeAttribute('disabled');
      +        ms.setAttribute('disabled', 'true');
      +        mr.setAttribute('disabled', 'true');
      +        hs.setAttribute('disabled', 'true');
      +        hr.setAttribute('disabled', 'true');
      +        s.setAttribute('disabled', 'true');
      +        l.setAttribute('disabled', 'true');
      +    }
      +}, '#color');
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form

      + +
      + +
      document.querySelector('#width').value = 400;
      +document.querySelector('#height').value = 400;
      +document.querySelector('#noiseEngine').options.selectedIndex = 1;
      +document.querySelector('#color').options.selectedIndex = 0;
      +document.querySelector('#gradientStart').value = '#ff0000';
      +document.querySelector('#gradientEnd').value = '#00ff00';
      +document.querySelector('#octaveFunction').options.selectedIndex = 0;
      +document.querySelector('#octaves').value = 1;
      +document.querySelector('#sumFunction').options.selectedIndex = 0;
      +document.querySelector('#sineFrequencyCoeff').value = 1;
      +document.querySelector('#smoothing').options.selectedIndex = 3;
      +document.querySelector('#scale').value = 50;
      +document.querySelector('#size').value = 256;
      +document.querySelector('#seed').value = 'noize';
      +document.querySelector('#persistence').value = 0.5;
      +document.querySelector('#lacunarity').value = 2;
      +document.querySelector('#modularAmplitude').value = 5;
      +document.querySelector('#monochromeStart').value = 0;
      +document.querySelector('#monochromeRange').value = 255;
      +document.querySelector('#hueStart').value = 0;
      +document.querySelector('#hueRange').value = 120;
      +document.querySelector('#saturation').value = 100;
      +document.querySelector('#luminosity').value = 50;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-020.html b/docs/demo/filters-020.html new file mode 100644 index 000000000..30bcf8ba7 --- /dev/null +++ b/docs/demo/filters-020.html @@ -0,0 +1,838 @@ + + + + + Demo Filters 020 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 020

      +

      Parameters for: clampChannels filter

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      +
      +
      +const myFilter = scrawl.makeFilter({
      +
      +    name: 'clamp',
      +    method: 'clampChannels',
      +
      +    lowRed: 0,
      +    lowGreen: 0,
      +    lowBlue: 0,
      +    highRed: 255,
      +    highGreen: 255,
      +    highBlue: 255,
      +    opacity: 1,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filters: ['clamp'],
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    let lowRed = document.querySelector('#low-red'),
      +        lowGreen = document.querySelector('#low-green'),
      +        lowBlue = document.querySelector('#low-blue'),
      +        highRed = document.querySelector('#high-red'),
      +        highGreen = document.querySelector('#high-green'),
      +        highBlue = document.querySelector('#high-blue'),
      +        opacity = document.querySelector('#opacity');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +    Red: ${lowRed.value} - ${highRed.value} 
      +    Green: ${lowGreen.value} - ${highGreen.value} 
      +    Blue: ${lowBlue.value} - ${highBlue.value} 
      +    Opacity - ${opacity.value}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form observer functionality

      + +
      + +
      scrawl.observeAndUpdate({
      +
      +    event: ['input', 'change'],
      +    origin: '.controlItem',
      +
      +    target: myFilter,
      +
      +    useNativeListener: true,
      +    preventDefault: true,
      +
      +    updates: {
      +
      +        'low-red': ['lowRed', 'round'],
      +        'low-green': ['lowGreen', 'round'],
      +        'low-blue': ['lowBlue', 'round'],
      +        'high-red': ['highRed', 'round'],
      +        'high-green': ['highGreen', 'round'],
      +        'high-blue': ['highBlue', 'round'],
      +        'opacity': ['opacity', 'float'],
      +    },
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form

      + +
      + +
      document.querySelector('#low-red').value = 0;
      +document.querySelector('#low-green').value = 0;
      +document.querySelector('#low-blue').value = 0;
      +document.querySelector('#high-red').value = 255;
      +document.querySelector('#high-green').value = 255;
      +document.querySelector('#high-blue').value = 255;
      +document.querySelector('#opacity').value = 1;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-501.html b/docs/demo/filters-501.html new file mode 100644 index 000000000..28b44a01c --- /dev/null +++ b/docs/demo/filters-501.html @@ -0,0 +1,848 @@ + + + + + Demo Filters 501 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 501

      +

      Canvas engine filter strings (based on CSS filters)

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entitys

      + +
      + +
      const piccy = scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +});
      +
      +const text = scrawl.makePhrase({
      +
      +    name: 'demo-text',
      +    text: 'Hello world',
      +    font: 'bold 70px sans-serif',
      +    start: ['center', 'center'],
      +    handle: ['center', 'center'],
      +    lineHeight: 0.5,
      +    fillStyle: 'aliceblue',
      +    strokeStyle: 'red',
      +    lineWidth: 3,
      +    method: 'fillThenDraw',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      No additional work required in the Javascript file to create the CSS filters; these are defined as Strings in the HTML select <option> elements, and will be set on the target entitys as part of the form control user interaction below.

      +
      <select class="controlItem" id="filter">
      +    <option value="none">none</option>
      +    <option value="blur(6px)">blur(6px)</option>
      +    <option value="brightness(0.4)">brightness(0.4)</option>
      +    <option value="contrast(200%)">contrast(200%)</option>
      +    <option value="drop-shadow(4px 4px 4px blue)">drop-shadow(4px 4px 4px blue)</option>
      +    <option value="grayscale(100%)">grayscale(100%)</option>
      +    <option value="hue-rotate(90deg)">hue-rotate(90deg)</option>
      +    <option value="invert(75%)">invert(75%)</option>
      +    <option value="opacity(25%)">opacity(25%)</option>
      +    <option value="saturate(30%)">saturate(30%)</option>
      +    <option value="sepia(100%)">sepia(100%)</option>
      +</select>
      + +
      + +
      +let filterTarget = piccy,
      +    filterString = 'none';
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form functionality

      + +
      + +
      let updateTarget = (e) => {
      +
      +    e.preventDefault();
      +    e.returnValue = false;
      +
      +    let val = e.target.value;
      +
      +    if (val) {
      +
      +        piccy.set({ filter: 'none'});
      +        text.set({ filter: 'none'});
      +        canvas.setBase({ filter: 'none'});
      +
      +        if (val === 'picture') filterTarget = piccy;
      +        else if (val === 'phrase') filterTarget = text;
      +        else if (val === 'cell') filterTarget = canvas.base;
      +
      +        filterTarget.set({ filter: filterString });
      +    }
      +};
      +scrawl.addNativeListener(['input', 'change'], updateTarget, '#target');
      +
      +let updateFilter = (e) => {
      +
      +    e.preventDefault();
      +    e.returnValue = false;
      +
      +    if (e.target && e.target.value) {
      +
      +        filterString = e.target.value;
      +        filterTarget.set({ filter: filterString });
      +    }
      +};
      +scrawl.addNativeListener(['input', 'change'], updateFilter, '#filter');
      +
      +document.querySelector('#filter').options.selectedIndex = 0;
      +document.querySelector('#target').options.selectedIndex = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-502.html b/docs/demo/filters-502.html new file mode 100644 index 000000000..e65a45bb4 --- /dev/null +++ b/docs/demo/filters-502.html @@ -0,0 +1,814 @@ + + + + + Demo Filters 502 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 502

      +

      SVG-based filter example: gaussian blur

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      const piccy = scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filter: 'url(#svg-blur)',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      SVG filter

      +

      We create the filter in the HTML script, not here:

      +
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      +  <filter id="svg-blur">
      +    <feGaussianBlur in="SourceGraphic" stdDeviation="5" edgeMode="duplicate" />
      +  </filter>
      +</svg>
      + +
      + +
      let feGaussianBlur = document.querySelector('feGaussianBlur');
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +
      +<filter id="svg-blur">
      +  <feGaussianBlur in="SourceGraphic" stdDeviation="${feGaussianBlur.getAttribute('stdDeviation')}" edgeMode="${feGaussianBlur.getAttribute('edgeMode')}" />
      +</filter>`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form functionality

      + +
      + +
      let updateStdDeviation = (e) => {
      +
      +    e.preventDefault();
      +    e.returnValue = false;
      +
      +    feGaussianBlur.setAttribute('stdDeviation', parseFloat(e.target.value));
      +};
      +scrawl.addNativeListener(['input', 'change'], updateStdDeviation, '#stdDeviation');
      +
      +let updateEdgeMode = (e) => {
      +
      +    e.preventDefault();
      +    e.returnValue = false;
      +
      +    feGaussianBlur.setAttribute(`edgeMode`, e.target.value);
      +};
      +scrawl.addNativeListener(['input', 'change'], updateEdgeMode, '#edgeMode');
      +
      +document.querySelector('#stdDeviation').value = 5;
      +document.querySelector('#edgeMode').options.selectedIndex = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-503.html b/docs/demo/filters-503.html new file mode 100644 index 000000000..20b349fcf --- /dev/null +++ b/docs/demo/filters-503.html @@ -0,0 +1,852 @@ + + + + + Demo Filters 503 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 503

      +

      SVG-based filter example: posterize

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      const piccy = scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filter: 'url(#svg-posterize)',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      SVG filter

      +

      We create the filter in the HTML script, not here:

      +
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      +  <filter id="svg-posterize">
      +    <feComponentTransfer>
      +      <feFuncR type="discrete" tableValues=".1 .4 .7 1" />
      +      <feFuncG type="discrete" tableValues=".1 .4 .7 1" />
      +      <feFuncB type="discrete" tableValues=".1 .4 .7 1" />
      +    </feComponentTransfer>
      +  </filter>
      +</svg>
      + +
      + +
      let feFuncR = document.querySelector('feFuncR'),
      +    feFuncG = document.querySelector('feFuncG'),
      +    feFuncB = document.querySelector('feFuncB');
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +
      +<filter id="svg-posterize">
      +  <feComponentTransfer>
      +    <feFuncR type="discrete" tableValues="${feFuncR.getAttribute('tableValues')}" />
      +    <feFuncG type="discrete" tableValues="${feFuncG.getAttribute('tableValues')}" />
      +    <feFuncB type="discrete" tableValues="${feFuncB.getAttribute('tableValues')}" />
      +  </feComponentTransfer>
      +</filter>`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      + +
      + +
      let r1 = document.querySelector('#r1'),
      +    r2 = document.querySelector('#r2'),
      +    r3 = document.querySelector('#r3'),
      +    r4 = document.querySelector('#r4');
      +
      +let g1 = document.querySelector('#g1'),
      +    g2 = document.querySelector('#g2'),
      +    g3 = document.querySelector('#g3'),
      +    g4 = document.querySelector('#g4');
      +
      +let b1 = document.querySelector('#b1'),
      +    b2 = document.querySelector('#b2'),
      +    b3 = document.querySelector('#b3'),
      +    b4 = document.querySelector('#b4');
      +
      +r1.value = 0.1;
      +r2.value = 0.4;
      +r3.value = 0.7;
      +r4.value = 1;
      +g1.value = 0.1;
      +g2.value = 0.4;
      +g3.value = 0.7;
      +g4.value = 1;
      +b1.value = 0.1;
      +b2.value = 0.4;
      +b3.value = 0.7;
      +b4.value = 1;
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form functionality

      + +
      + +
      let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value} ${r3.value} ${r4.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR');
      +
      +let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value} ${g3.value} ${g4.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG');
      +
      +let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value} ${b3.value} ${b4.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB');
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-504.html b/docs/demo/filters-504.html new file mode 100644 index 000000000..ea44ca100 --- /dev/null +++ b/docs/demo/filters-504.html @@ -0,0 +1,860 @@ + + + + + Demo Filters 504 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 504

      +

      SVG-based filter example: duotone

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      const piccy = scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filter: 'url(#svg-duotone)',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      SVG filter

      +

      We create the filter in the HTML script, not here:

      +
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      +  <filter id="svg-duotone">
      +    <feColorMatrix type="matrix" values=".33 .33 .33 0 0
      +      .33 .33 .33 0 0
      +      .33 .33 .33 0 0
      +       0   0   0  1 0">
      +    </feColorMatrix>
      + +
      + +
    • + + +
    • +
      + +
      + +
      +
      <feComponentTransfer color-interpolation-filters="sRGB">
      +  <feFuncR type="table" tableValues=".996 0.984"></feFuncR>
      +  <feFuncG type="table" tableValues=".125 0.941"></feFuncG>
      +  <feFuncB type="table" tableValues=".552 0.478"></feFuncB>
      +</feComponentTransfer>
      + + +``` + +
      + +
      let feFuncR = document.querySelector('feFuncR'),
      +    feFuncG = document.querySelector('feFuncG'),
      +    feFuncB = document.querySelector('feFuncB');
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +
      +<filter id="svg-duotone">
      +  <feColorMatrix type="matrix" values=".33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"></feColorMatrix>
      +
      +  <feComponentTransfer color-interpolation-filters="sRGB">
      +    <feFuncR type="discrete" tableValues="${feFuncR.getAttribute('tableValues')}" />
      +    <feFuncG type="discrete" tableValues="${feFuncG.getAttribute('tableValues')}" />
      +    <feFuncB type="discrete" tableValues="${feFuncB.getAttribute('tableValues')}" />
      +  </feComponentTransfer>
      +</filter>`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      + +
      + +
      let r1 = document.querySelector('#r1'),
      +    r2 = document.querySelector('#r2');
      +
      +let g1 = document.querySelector('#g1'),
      +    g2 = document.querySelector('#g2');
      +
      +let b1 = document.querySelector('#b1'),
      +    b2 = document.querySelector('#b2');
      +
      +r1.value = 0.996;
      +r2.value = 0.984;
      +g1.value = 0.125;
      +g2.value = 0.941;
      +b1.value = 0.552;
      +b2.value = 0.478;
      + +
    • + + +
    • +
      + +
      + +
      +

      Setup form functionality

      + +
      + +
      let updateR = () => feFuncR.setAttribute('tableValues', `${r1.value} ${r2.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateR, '.feFuncR');
      +
      +let updateG = () => feFuncG.setAttribute('tableValues', `${g1.value} ${g2.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateG, '.feFuncG');
      +
      +let updateB = () => feFuncB.setAttribute('tableValues', `${b1.value} ${b2.value}`);
      +scrawl.addNativeListener(['input', 'change'], updateB, '.feFuncB');
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/filters-505.html b/docs/demo/filters-505.html new file mode 100644 index 000000000..0a0eaa9a6 --- /dev/null +++ b/docs/demo/filters-505.html @@ -0,0 +1,824 @@ + + + + + Demo Filters 505 + + + + + +
    +
    + + + +
      + + + +
    • +
      + +
      + +
      +

      Demo Filters 505

      +

      SVG-based filter example: noise

      + +
      + +
    • + + +
    • +
      + +
      + +
      +

      Run code

      + +
      + +
      import scrawl from '../source/scrawl.js';
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene setup

      + +
      + +
      const canvas = scrawl.library.canvas.mycanvas;
      +
      +scrawl.importDomImage('.flowers');
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the target entity

      + +
      + +
      const piccy = scrawl.makePicture({
      +
      +    name: 'base-piccy',
      +
      +    asset: 'iris',
      +
      +    width: '100%',
      +    height: '100%',
      +
      +    copyWidth: '100%',
      +    copyHeight: '100%',
      +
      +    method: 'fill',
      +
      +    filter: 'url(#svg-noise)',
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      SVG filter

      +

      We create the filter in the HTML script, not here:

      +
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      +  <filter id="svg-noise">
      +    <feTurbulence type="fractalNoise" baseFrequency="0.01 0.04" result="NOISE" numOctaves="2" />
      +    <feDisplacementMap in="SourceGraphic" in2="NOISE" scale="20" xChannelSelector="R" yChannelSelector="R"></feDisplacementMap>
      +  </filter>
      +</svg>
      + +
      + +
      let bfx = document.querySelector('#bfx'),
      +    bfy = document.querySelector('#bfy'),
      +    octaves = document.querySelector('#octaves'),
      +    scale = document.querySelector('#scale'),
      +    xChannelSelector = document.querySelector('#xChannelSelector'),
      +    yChannelSelector = document.querySelector('#yChannelSelector'),
      +    feTurbulence = document.querySelector('feTurbulence'),
      +    feDisplacementMap = document.querySelector('feDisplacementMap');
      +
      +bfx.value = 0.01;
      +bfy.value = 0.04;
      +octaves.value = 2;
      +scale.value = 20;
      +xChannelSelector.options.selectedIndex = 0;
      +yChannelSelector.options.selectedIndex = 0;
      + +
    • + + +
    • +
      + +
      + +
      +

      Scene animation

      +

      Function to display frames-per-second data, and other information relevant to the demo

      + +
      + +
      let report = function () {
      +
      +    let testTicker = Date.now(),
      +        testTime, testNow,
      +        testMessage = document.querySelector('#reportmessage');
      +
      +    return function () {
      +
      +        testNow = Date.now();
      +        testTime = testNow - testTicker;
      +        testTicker = testNow;
      +
      +        testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms; fps: ${Math.floor(1000 / testTime)}
      +
      +<filter id="svg-noise">
      +  <feTurbulence type="fractalNoise" baseFrequency="${bfx.value} ${bfy.value}" result="NOISE" numOctaves="${octaves.value}" />
      +  <feDisplacementMap in="SourceGraphic" in2="NOISE" scale="${scale.value}" xChannelSelector="${xChannelSelector.value}" yChannelSelector="${yChannelSelector.value}"></feDisplacementMap>
      +</filter>`;
      +    };
      +}();
      + +
    • + + +
    • +
      + +
      + +
      +

      Create the Display cycle animation

      + +
      + +
      const demoAnimation = scrawl.makeRender({
      +
      +    name: "demo-animation",
      +    target: canvas,
      +    afterShow: report,
      +});
      + +
    • + + +
    • +
      + +
      + +
      +

      User interaction

      +

      Setup form functionality

      + +
      + +
      let baseFrequency = () => feTurbulence.setAttribute('baseFrequency', `${bfx.value} ${bfy.value}`);
      +scrawl.addNativeListener(['input', 'change'], baseFrequency, '.baseFreq');
      +
      +let numOctaves = () => feTurbulence.setAttribute('numOctaves', octaves.value);
      +scrawl.addNativeListener(['input', 'change'], numOctaves, '#octaves');
      +
      +let dmScale = () => feDisplacementMap.setAttribute('scale', scale.value);
      +scrawl.addNativeListener(['input', 'change'], dmScale, '#scale');
      +
      +let dmX = () => feDisplacementMap.setAttribute('xChannelSelector', xChannelSelector.value);
      +scrawl.addNativeListener(['input', 'change'], dmX, '#xChannelSelector');
      +
      +let dmY = () => feDisplacementMap.setAttribute('yChannelSelector', yChannelSelector.value);
      +scrawl.addNativeListener(['input', 'change'], dmY, '#yChannelSelector');
      + +
    • + + +
    • +
      + +
      + +
      +

      Development and testing

      + +
      + +
      console.log(scrawl.library);
      + +
    • + +
    +
    + + diff --git a/docs/demo/particles-001.html b/docs/demo/particles-001.html index 16f261d71..d3bbd65a8 100644 --- a/docs/demo/particles-001.html +++ b/docs/demo/particles-001.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-002.html b/docs/demo/particles-002.html index 2754bc450..aaf33e078 100644 --- a/docs/demo/particles-002.html +++ b/docs/demo/particles-002.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-003.html b/docs/demo/particles-003.html index a44587abf..4f9e18a48 100644 --- a/docs/demo/particles-003.html +++ b/docs/demo/particles-003.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-004.html b/docs/demo/particles-004.html index 5f21c6d2b..458cb59a6 100644 --- a/docs/demo/particles-004.html +++ b/docs/demo/particles-004.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-005.html b/docs/demo/particles-005.html index 90e19ae3a..2c8bb1b5a 100644 --- a/docs/demo/particles-005.html +++ b/docs/demo/particles-005.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-006.html b/docs/demo/particles-006.html index c58f34f26..61feb25b9 100644 --- a/docs/demo/particles-006.html +++ b/docs/demo/particles-006.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-007.html b/docs/demo/particles-007.html index 4bfd13a9b..96fe4b49c 100644 --- a/docs/demo/particles-007.html +++ b/docs/demo/particles-007.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -645,34 +705,6 @@

    Scene setup

    name: 'matrix', method: 'matrix', weights: [-1, -1, 0, -1, 1, 1, 0, 1, 1], -}); - -scrawl.makeFilter({ - name: 'venetianBlinds', - method: 'userDefined', - level: 9, - - userDefined: ` - let i, iz, j, jz, - level = filter.level || 6, - halfLevel = level / 2, - yw, transparent, pos; - - for (i = localY, iz = localY + localHeight; i < iz; i++) { - - transparent = (i % level > halfLevel) ? true : false; - - if (transparent) { - - yw = (i * iWidth) + 3; - - for (j = localX, jz = localX + localWidth; j < jz; j ++) { - - pos = yw + (j * 4); - data[pos] = 0; - } - } - }`, });
  • diff --git a/docs/demo/particles-008.html b/docs/demo/particles-008.html index 620287fe7..9a292e3de 100644 --- a/docs/demo/particles-008.html +++ b/docs/demo/particles-008.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -583,6 +643,21 @@

    Demo Particles 008

    +

    Import image from DOM

    + + + +
    scrawl.importDomImage('.flowers');
    + + + + +
  • +
    + +
    + +

    Scene setup

    @@ -596,11 +671,11 @@

    Scene setup

  • -
  • +
  • - +

    For this Demo, we are creating a flag and pinning it to a pole. This is the pole.

    @@ -613,7 +688,7 @@

    Scene setup

    endX: 'center', endY: 'bottom', - lineWidth: 16, + lineWidth: 8, lineCap: 'round', strokeStyle: 'brown', @@ -623,11 +698,11 @@

    Scene setup

  • -
  • +
  • - +

    Particle physics animation scene

    @@ -636,11 +711,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    Create a World object which we can then assign to the Net entity

    @@ -662,11 +737,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    Create a ‘wind’ force; we will update the wind direction/strength as part of the Display cycle

    @@ -699,11 +774,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    Create a Net entity

    @@ -716,11 +791,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    Every net must be associated with a World object. The attribute’s value can be the World object’s String name value, or the object itself

    @@ -731,11 +806,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    The entity’s start coordinates determine where the first pin will be placed on the canvas

    @@ -747,11 +822,11 @@

    Particle physics animation scene

  • -
  • +
  • - +

    The Net entity comes with four pre-defined generate functions - we will be testing ‘weak-net’ and ‘strong-net’ in this demo.

      @@ -765,11 +840,11 @@

      Particle physics animation scene

      -
    • +
    • - +

      The postGenerate function runs immediately after the generate function has created all of the Net entity’s Particle and Spring objects.

        @@ -784,11 +859,11 @@

        Particle physics animation scene

        -
      • +
      • - +

        Names for ‘weak-net’ and ‘strong-net’ Particles are consistent: ${Net-entity-name}-${row-number}-${column-number}

        @@ -803,11 +878,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        Change the appearance of the selected Particles, and remove the forces acting on them

        @@ -822,11 +897,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        Prevent Springs associated with the selected Particles from moving them

        @@ -850,11 +925,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        We tell the Net entity how many rows and columns of Particles we want it to create

        @@ -866,11 +941,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        The distance between rows and columns can be set using either absolute (px) Number values, or relative % String values

        @@ -882,11 +957,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        We can get the Net entity to display its springs

        @@ -898,11 +973,11 @@

        Particle physics animation scene

      • -
      • +
      • - +

        Particle physics attributes

        @@ -910,16 +985,17 @@

        Particle physics animation scene

            mass: 1,
             forces: ['gravity', 'wind'],
        -    engine: 'runge-kutta',
        + engine: 'runge-kutta', + damperConstant: 5,
    • -
    • +
    • - +

      We can assign an artefact that we will be using for the particle animation, or we can define it here as part of the Net factory

      @@ -938,6 +1014,8 @@

      Particle physics animation scene

      visibility: false, + globalAlpha: 1, + noUserInteraction: true, noPositionDependencies: true, noFilters: true, @@ -947,11 +1025,11 @@

      Particle physics animation scene

    • -
    • +
    • - +

      The stampAction function describes the steps that our Net will take to draw each of its particles (and springs, hit regions) onto the host canvas screen.

      @@ -967,16 +1045,55 @@

      Particle physics animation scene

      strokeStyle: particle.stroke, }); }, +}); + +scrawl.makePicture({ + + name: 'my-flower', + asset: 'iris', + + copyStartX: 0, + copyStartY: 0, + + copyWidth: '100%', + copyHeight: '100%', + + visibility: false, });
  • -
  • +
  • - + +
    +

    The Loom entity definition

    + +
    + +
    let myMesh = scrawl.makeMesh({
    +
    +    name: 'display-mesh',
    +
    +    net: 'test-net',
    +    source: 'my-flower',
    +
    +    method: 'fillThenDraw',
    +
    +    visibility: false,
    +});
    + +
  • + + +
  • +
    + +
    +

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    @@ -1014,11 +1131,11 @@

    Scene animation

  • -
  • +
  • - +

    Create the Display cycle animation

    @@ -1035,11 +1152,11 @@

    Scene animation

  • -
  • +
  • - +

    User interaction

    Setup form observer functionality

    @@ -1057,6 +1174,30 @@

    User interaction

    if (e.target.id === 'engine') myNet.set({ engine: e.target.value}); if (e.target.id === 'mass') myNet.set({ mass: parseFloat(e.target.value)}); if (e.target.id === 'tickMultiplier') myWorld.set({ tickMultiplier: parseFloat(e.target.value)}); + if (e.target.id === 'mesh') { + if (parseInt(e.target.value, 10)) { + myNet.set({ + showSprings: false, + }); + myNet.artefact.set({ + globalAlpha: 0, + }); + myMesh.set({ + visibility: true, + }); + } + else { + myNet.set({ + showSprings: true, + }); + myNet.artefact.set({ + globalAlpha: 1, + }); + myMesh.set({ + visibility: false, + }); + } + } myNet.restart(); } @@ -1065,20 +1206,21 @@

    User interaction

    document.querySelector('#generate').value = 'weak-net'; document.querySelector('#springConstant').value = 50; -document.querySelector('#damperConstant').value = 10; +document.querySelector('#damperConstant').value = 5; document.querySelector('#restLength').value = 1; document.querySelector('#mass').value = 1; document.querySelector('#engine').value = 'runge-kutta'; -document.querySelector('#tickMultiplier').value = 2;
    +document.querySelector('#tickMultiplier').value = 2; +document.querySelector('#mesh').value = '0';
  • -
  • +
  • - +

    Development and testing

    diff --git a/docs/demo/particles-009.html b/docs/demo/particles-009.html index c0e156f98..6e340ea64 100644 --- a/docs/demo/particles-009.html +++ b/docs/demo/particles-009.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -626,34 +686,6 @@

    Scene setup

    name: 'matrix', method: 'matrix', weights: [-1, -1, 0, -1, 1, 1, 0, 1, 1], -}); - -scrawl.makeFilter({ - name: 'venetianBlinds', - method: 'userDefined', - level: 9, - - userDefined: ` - let i, iz, j, jz, - level = filter.level || 6, - halfLevel = level / 2, - yw, transparent, pos; - - for (i = localY, iz = localY + localHeight; i < iz; i++) { - - transparent = (i % level > halfLevel) ? true : false; - - if (transparent) { - - yw = (i * iWidth) + 3; - - for (j = localX, jz = localX + localWidth; j < jz; j ++) { - - pos = yw + (j * 4); - data[pos] = 0; - } - } - }`, });
  • diff --git a/docs/demo/particles-010.html b/docs/demo/particles-010.html index 6e41423aa..6b3faa126 100644 --- a/docs/demo/particles-010.html +++ b/docs/demo/particles-010.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-011.html b/docs/demo/particles-011.html index 3d380529e..61b172eba 100644 --- a/docs/demo/particles-011.html +++ b/docs/demo/particles-011.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js @@ -626,34 +686,6 @@

    Scene setup

    name: 'matrix', method: 'matrix', weights: [-1, -1, 0, -1, 1, 1, 0, 1, 1], -}); - -scrawl.makeFilter({ - name: 'venetianBlinds', - method: 'userDefined', - level: 9, - - userDefined: ` - let i, iz, j, jz, - level = filter.level || 6, - halfLevel = level / 2, - yw, transparent, pos; - - for (i = localY, iz = localY + localHeight; i < iz; i++) { - - transparent = (i % level > halfLevel) ? true : false; - - if (transparent) { - - yw = (i * iWidth) + 3; - - for (j = localX, jz = localX + localWidth; j < jz; j ++) { - - pos = yw + (j * 4); - data[pos] = 0; - } - } - }`, }); diff --git a/docs/demo/particles-012.html b/docs/demo/particles-012.html index 14734bd77..9f068baf5 100644 --- a/docs/demo/particles-012.html +++ b/docs/demo/particles-012.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-013.html b/docs/demo/particles-013.html index b04a74152..2581069b6 100644 --- a/docs/demo/particles-013.html +++ b/docs/demo/particles-013.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-014.html b/docs/demo/particles-014.html index 1f33f1049..88fae9136 100644 --- a/docs/demo/particles-014.html +++ b/docs/demo/particles-014.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-015.html b/docs/demo/particles-015.html index a24b38330..a477272cb 100644 --- a/docs/demo/particles-015.html +++ b/docs/demo/particles-015.html @@ -250,6 +250,11 @@ + + ./demo/canvas-047.js + + + ./demo/component-001.js @@ -455,6 +460,56 @@ + + ./demo/filters-016.js + + + + + ./demo/filters-017.js + + + + + ./demo/filters-018.js + + + + + ./demo/filters-019.js + + + + + ./demo/filters-020.js + + + + + ./demo/filters-501.js + + + + + ./demo/filters-502.js + + + + + ./demo/filters-503.js + + + + + ./demo/filters-504.js + + + + + ./demo/filters-505.js + + + ./demo/particles-001.js @@ -530,6 +585,11 @@ + + ./demo/particles-016.js + + + ./demo/temp-001.js diff --git a/docs/demo/particles-016.html b/docs/demo/particles-016.html new file mode 100644 index 000000000..cc836d710 --- /dev/null +++ b/docs/demo/particles-016.html @@ -0,0 +1,947 @@ + + + + + Demo Particles 016 + + + + + +
    +
    + + + + +
    + + diff --git a/docs/index.html b/docs/index.html index e8bc89993..a63e2b81a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -91,8 +91,10 @@

    Assets

  • mixin/asset.js
  • mixin/assetConsumer.js
  • factory/imageAsset.js
  • +
  • factory/noise.js
  • factory/spriteAsset.js
  • factory/videoAsset.js
  • +
  • Note that Cells can also be used as assets
  • Styles

    @@ -218,6 +220,7 @@

    Canvas-based demos

  • Canvas-044 Building more complex patterns
  • Canvas-045 Use clipping entitys as pivots; clipping entitys and cascade events
  • Canvas-046 Kill cycles for Cell, Group, Tween/Ticker, Picture and Asset objects, and Picture source elements in the DOM
  • +
  • Canvas-047 Easing functions for Color and Tween factories
  • Filters demos

    @@ -227,16 +230,30 @@

    Filters demos

  • Filters-003 Parameters for: brightness, saturation filters
  • Filters-004 Parameters for: threshold filter
  • Filters-005 Parameters for: channelstep filter
  • -
  • Filters-006 Parameters for: channels filter
  • -
  • Filters-007 Parameters for: tint filter
  • -
  • Filters-008 Parameters for: pixelate filter
  • -
  • Filters-009 Parameters for: chroma filter
  • -
  • Filters-010 Parameters for: matrix, matrix5 filters
  • -
  • Filters-011 Canvas engine filter strings (based on CSS filters)
  • -
  • Filters-012 SVG-based filter example: gaussian blur
  • -
  • Filters-013 SVG-based filter example: posterize
  • -
  • Filters-014 SVG-based filter example: duotone
  • -
  • Filters-015 SVG-based filter example: noise
  • +
  • Filters-006 Parameters for: channelLevels filter
  • +
  • Filters-007 Parameters for: channels filter
  • +
  • Filters-008 Parameters for: tint filter
  • +
  • Filters-009 Parameters for: pixelate filter
  • +
  • Filters-010 Parameters for: chroma filter
  • +
  • Filters-011 Parameters for: chromakey filter
  • +
  • Filters-012 Parameters for: matrix, matrix5 filters
  • +
  • Filters-013 Parameters for: flood filter
  • +
  • Filters-014 Parameters for: areaAlpha filter
  • +
  • Filters-015 Using assets in the filter stream; filter compositing
  • +
  • Filters-016 Filter blend operation
  • +
  • Filters-017 Parameters for: displace filter
  • +
  • Filters-018 Parameters for: emboss filter
  • +
  • Filters-019 Using a Noise asset with a displace filter
  • +
  • Filters-020 Parameters for: clampChannels filter
  • + + +

    CSS/SVG filter String demos

    +

    Particles and physics demos

    @@ -256,6 +273,7 @@

    Particles and physics demos

  • Particles-013 Seasons greetings
  • Particles-014 Emitter functionality: generate from existing particles
  • Particles-015 Emitter functionality: generate from existing particle histories
  • +
  • Particles-016 Mesh entitys
  • Component demos

    diff --git a/docs/source/core/animationloop.html b/docs/source/core/animationloop.html index 4bd0f9949..836068905 100644 --- a/docs/source/core/animationloop.html +++ b/docs/source/core/animationloop.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/core/component.html b/docs/source/core/component.html index b2c380fa3..d778f4327 100644 --- a/docs/source/core/component.html +++ b/docs/source/core/component.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/core/document.html b/docs/source/core/document.html index d2dc9c8ac..d9734668b 100644 --- a/docs/source/core/document.html +++ b/docs/source/core/document.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/core/events.html b/docs/source/core/events.html index 093a0e849..a029bc360 100644 --- a/docs/source/core/events.html +++ b/docs/source/core/events.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/core/init.html b/docs/source/core/init.html index da73bb6ac..604273eb4 100644 --- a/docs/source/core/init.html +++ b/docs/source/core/init.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/core/library.html b/docs/source/core/library.html index 759becf93..2dab3bd82 100644 --- a/docs/source/core/library.html +++ b/docs/source/core/library.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -482,7 +487,7 @@

    Core library

    -
    const version = '8.3.4';
    +
    const version = '8.4.0';
    diff --git a/docs/source/core/userInteraction.html b/docs/source/core/userInteraction.html index 736e7e271..47db56b25 100644 --- a/docs/source/core/userInteraction.html +++ b/docs/source/core/userInteraction.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -504,7 +509,8 @@

    Imports

    let trackMouse = false,
    -    mouseChanged = false;
    + mouseChanged = false, + viewportChanged = false; @@ -551,6 +557,7 @@

    Imports

    currentCorePosition.w = w; currentCorePosition.h = h; mouseChanged = true; + viewportChanged = true; } }; @@ -845,6 +852,18 @@

    Imports

    } } } +}; + +const updatePhraseEntitys = function () { + + for (const [name, ent] of Object.entries(library.entity)) { + + if (ent.type === 'Phrase') { + + ent.dirtyDimensions = true; + ent.dirtyFont = true; + } + } }; @@ -922,6 +941,12 @@

    Imports

    updateUiSubscribedElements(); } + if (viewportChanged) { + + viewportChanged = false; + updatePhraseEntitys(); + } + resolve(true); }); }, diff --git a/docs/source/core/utilities.html b/docs/source/core/utilities.html index 417031c38..645e1dc6a 100644 --- a/docs/source/core/utilities.html +++ b/docs/source/core/utilities.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -619,6 +624,7 @@

    Functions

    const λnull = () => {};
    +const λfirstArg = function (a) { return a; };
     const λthis = function () { return this; };
     const λpromise = () => Promise.resolve(true);
    @@ -676,11 +682,13 @@

    Functions

    -

    __isa_boolean__ checks to make sure the argument is a boolean

    +

    interpolate clamp a value between a maximum and minimum value

    -
    const isa_boolean = item => (typeof item === 'boolean') ? true : false;
    +
    const interpolate = function (val, min, max) {
    +    return min + val * (max - min);
    +};
    @@ -691,11 +699,11 @@

    Functions

    -

    __isa_canvas__ checks to make sure the argument is a DOM <canvas> element

    +

    __isa_boolean__ checks to make sure the argument is a boolean

    -
    const isa_canvas = item => (Object.prototype.toString.call(item) === '[object HTMLCanvasElement]') ? true : false;
    +
    const isa_boolean = item => (typeof item === 'boolean') ? true : false;
    @@ -706,11 +714,11 @@

    Functions

    -

    __isa_dom__ checks to make sure the argument is a DOM element of some sort

    +

    __isa_canvas__ checks to make sure the argument is a DOM <canvas> element

    -
    const isa_dom = item => (item && item.querySelector && item.dispatchEvent) ? true : false;
    +
    const isa_canvas = item => (Object.prototype.toString.call(item) === '[object HTMLCanvasElement]') ? true : false;
    @@ -721,11 +729,11 @@

    Functions

    -

    __isa_fn__ checks to make sure the argument is a JavaScript function object

    +

    __isa_dom__ checks to make sure the argument is a DOM element of some sort

    -
    const isa_fn = item => (typeof item === 'function') ? true : false;
    +
    const isa_dom = item => (item && item.querySelector && item.dispatchEvent) ? true : false;
    @@ -736,11 +744,11 @@

    Functions

    -

    __isa_number__ checks to make sure the argument is true number (excluding NaN)

    +

    __isa_fn__ checks to make sure the argument is a JavaScript function object

    -
    const isa_number = item => (typeof item != 'undefined' && item.toFixed && !Number.isNaN(item)) ? true : false;
    +
    const isa_fn = item => (typeof item === 'function') ? true : false;
    @@ -751,11 +759,11 @@

    Functions

    -

    __isa_obj__ checks to make sure the argument is a JavaScript Object

    +

    __isa_number__ checks to make sure the argument is true number (excluding NaN)

    -
    const isa_obj = item => (Object.prototype.toString.call(item) === '[object Object]') ? true : false;
    +
    const isa_number = item => (typeof item != 'undefined' && item.toFixed && !Number.isNaN(item)) ? true : false;
    @@ -766,11 +774,11 @@

    Functions

    -

    __isa_quaternion__ checks to make sure the argument is a Scrawl-canvas Quaternion object

    +

    __isa_obj__ checks to make sure the argument is a JavaScript Object

    -
    const isa_quaternion = item => (item && item.type && item.type === 'Quaternion') ? true : false;
    +
    const isa_obj = item => (Object.prototype.toString.call(item) === '[object Object]') ? true : false;
    @@ -781,6 +789,21 @@

    Functions

    +

    __isa_quaternion__ checks to make sure the argument is a Scrawl-canvas Quaternion object

    + + + +
    const isa_quaternion = item => (item && item.type && item.type === 'Quaternion') ? true : false;
    + + + + +
  • +
    + +
    + +

    mergeInto takes two objects and merges the attributes of one into the other. This function mutates the ‘original’ object rather than generating a third, new onject

    Example:

    var original = { name: 'Peter', age: 42, job: 'lawyer' };
    @@ -805,11 +828,11 @@ 

    Functions

  • -
  • +
  • - +

    mergeOver takes two objects and writes the attributes of one over the other. This function mutates the ‘original’ object rather than generating a third, new onject

    Example:

    @@ -835,11 +858,11 @@

    Functions

  • -
  • +
  • - +

    mergeDiscard iterates over the additional object to perform a mergeOver operation, and also removing attributes from the original object where they have been set to null in the additional object

    Example:

    @@ -866,11 +889,11 @@

    Functions

  • -
  • +
  • - +

    pushUnique adds a value to the end of an array, if that value is not already present in the array. This function mutates the array

    Example:

    @@ -904,11 +927,11 @@

    Functions

  • -
  • +
  • - +

    removeItem removes a value from an array. This function mutates the array

    Example:

    @@ -937,11 +960,11 @@

    Functions

  • -
  • +
  • - +

    xt checks to see if argument exists (is not ‘undefined’)

    @@ -952,11 +975,11 @@

    Functions

  • -
  • +
  • - +

    xta checks to make sure that all the arguments supplied to the function exist (none are ‘undefined’)

    @@ -967,11 +990,11 @@

    Functions

  • -
  • +
  • - +

    xtGet returns the first existing (not ‘undefined’) argument supplied to the function

    @@ -982,11 +1005,11 @@

    Functions

  • -
  • +
  • - +

    xto checks to make sure that at least one of the arguments supplied to the function exists (is not ‘undefined’)

    @@ -997,11 +1020,660 @@

    Functions

  • -
  • +
  • - + +
    +

    Seedable random numbers +The following code has been lifted in its entirety from https://github.com/skratchdot/random-seed

    + +
    + +
    +/*
    + * random-seed
    + * https://github.com/skratchdot/random-seed
    + *
    + * This code was originally written by Steve Gibson and can be found here:
    + *
    + * https://www.grc.com/otg/uheprng.htm
    + *
    + * It was slightly modified for use in node, to pass jshint, and a few additional
    + * helper functions were added.
    + *
    + * Copyright (c) 2013 skratchdot
    + * Dual Licensed under the MIT license and the original GRC copyright/license
    + * included below.
    + */
    +/*  ============================================================================
    +                                    Gibson Research Corporation
    +                UHEPRNG - Ultra High Entropy Pseudo-Random Number Generator
    +    ============================================================================
    +    LICENSE AND COPYRIGHT:  THIS CODE IS HEREBY RELEASED INTO THE PUBLIC DOMAIN
    +    Gibson Research Corporation releases and disclaims ALL RIGHTS AND TITLE IN
    +    THIS CODE OR ANY DERIVATIVES. Anyone may be freely use it for any purpose.
    +    ============================================================================
    +    This is GRC's cryptographically strong PRNG (pseudo-random number generator)
    +    for JavaScript. It is driven by 1536 bits of entropy, stored in an array of
    +    48, 32-bit JavaScript variables.  Since many applications of this generator,
    +    including ours with the "Off The Grid" Latin Square generator, may require
    +    the deteriministic re-generation of a sequence of PRNs, this PRNG's initial
    +    entropic state can be read and written as a static whole, and incrementally
    +    evolved by pouring new source entropy into the generator's internal state.
    +    ----------------------------------------------------------------------------
    +    ENDLESS THANKS are due Johannes Baagoe for his careful development of highly
    +    robust JavaScript implementations of JS PRNGs.  This work was based upon his
    +    JavaScript "Alea" PRNG which is based upon the extremely robust Multiply-
    +    With-Carry (MWC) PRNG invented by George Marsaglia. MWC Algorithm References:
    +    http://www.GRC.com/otg/Marsaglia_PRNGs.pdf
    +    http://www.GRC.com/otg/Marsaglia_MWC_Generators.pdf
    +    ----------------------------------------------------------------------------
    +    The quality of this algorithm's pseudo-random numbers have been verified by
    +    multiple independent researchers. It handily passes the fermilab.ch tests as
    +    well as the "diehard" and "dieharder" test suites.  For individuals wishing
    +    to further verify the quality of this algorithm's pseudo-random numbers, a
    +    256-megabyte file of this algorithm's output may be downloaded from GRC.com,
    +    and a Microsoft Windows scripting host (WSH) version of this algorithm may be
    +    downloaded and run from the Windows command prompt to generate unique files
    +    of any size:
    +    The Fermilab "ENT" tests: http://fourmilab.ch/random/
    +    The 256-megabyte sample PRN file at GRC: https://www.GRC.com/otg/uheprng.bin
    +    The Windows scripting host version: https://www.GRC.com/otg/wsh-uheprng.js
    +    ----------------------------------------------------------------------------
    +    Qualifying MWC multipliers are: 187884, 686118, 898134, 1104375, 1250205,
    +    1460910 and 1768863. (We use the largest one that's < 2^21)
    +    ============================================================================ */
    +
    +
    +/*  ============================================================================
    +This is based upon Johannes Baagoe's carefully designed and efficient hash
    +function for use with JavaScript.  It has a proven "avalanche" effect such
    +that every bit of the input affects every bit of the output 50% of the time,
    +which is good.  See: http://baagoe.com/en/RandomMusings/hash/avalanche.xhtml
    +============================================================================
    +*/
    +var Mash = function () {
    +    var n = 0xefc8249d;
    +    var mash = function (data) {
    +        if (data) {
    +            data = data.toString();
    +            for (var i = 0; i < data.length; i++) {
    +                n += data.charCodeAt(i);
    +                var h = 0.02519603282416938 * n;
    +                n = h >>> 0;
    +                h -= n;
    +                h *= n;
    +                n = h >>> 0;
    +                h -= n;
    +                n += h * 0x100000000; // 2^32
    +            }
    +            return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
    +        } else {
    +            n = 0xefc8249d;
    +        }
    +    };
    +    return mash;
    +};
    +
    +var uheprng = function (seed) {
    +    return (function () {
    +        var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values
    +        var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm
    +        var p = o; // init the 'phase' (max-1) of the intermediate variable pointer
    +        var s = new Array(o); // declare our intermediate variables array
    +        var i; // general purpose local
    +        var j; // general purpose local
    +        var k = 0; // general purpose local
    + +
  • + + +
  • +
    + +
    + +
    +

    when our “uheprng” is initially invoked our PRNG state is initialized from the +browser’s own local PRNG. This is okay since although its generator might not +be wonderful, it’s useful for establishing large startup entropy for our usage.

    + +
    + +
            var mash = new Mash(); // get a pointer to our high-performance "Mash" hash
    + +
  • + + +
  • +
    + +
    + +
    +

    fill the array with initial mash hash values

    + +
    + +
            for (i = 0; i < o; i++) {
    +            s[i] = mash(Math.random());
    +        }
    + +
  • + + +
  • +
    + +
    + +
    +

    this PRIVATE (internal access only) function is the heart of the multiply-with-carry +(MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a +32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default +[0-1] return function, and by the random ‘string(n)’ function which returns ‘n’ +characters from 33 to 126.

    + +
    + +
            var rawprng = function () {
    +            if (++p >= o) {
    +                p = 0;
    +            }
    +            var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32
    +            return s[p] = t - (c = t | 0);
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this EXPORTED function is the default function returned by this library. +The values returned are integers in the range from 0 to range-1. We first +obtain two 32-bit fractions (from rawprng) to synthesize a single high +resolution 53-bit prng (0 to <1), then we multiply this by the caller’s +“range” param and take the “floor” to return a equally probable integer.

    + +
    + +
            var random = function (range) {
    +            return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16)); // 2^-53
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this EXPORTED function ‘string(n)’ returns a pseudo-random string of +‘n’ printable characters ranging from chr(33) to chr(126) inclusive.

    + +
    + +
            random.string = function (count) {
    +            var i;
    +            var s = '';
    +            for (i = 0; i < count; i++) {
    +                s += String.fromCharCode(33 + random(94));
    +            }
    +            return s;
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this PRIVATE “hash” function is used to evolve the generator’s internal +entropy state. It is also called by the EXPORTED addEntropy() function +which is used to pour entropy into the PRNG.

    + +
    + +
            var hash = function () {
    +            var args = Array.prototype.slice.call(arguments);
    +            for (i = 0; i < args.length; i++) {
    +                for (j = 0; j < o; j++) {
    +                    s[j] -= mash(args[i]);
    +                    if (s[j] < 0) {
    +                        s[j] += 1;
    +                    }
    +                }
    +            }
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this EXPORTED “clean string” function removes leading and trailing spaces and non-printing +control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, +from any string it is handed. this is also used by the ‘hashstring’ function (below) to help +users always obtain the same EFFECTIVE uheprng seeding key.

    + +
    + +
            random.cleanString = function (inStr) {
    +            inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ''); // remove any/all leading spaces
    +            inStr = inStr.replace(/[\x00-\x1F]/gi, ''); // remove any/all control characters
    +            inStr = inStr.replace(/\n /, '\n'); // remove any/all trailing spaces
    +            return inStr; // return the cleaned up result
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this EXPORTED “hash string” function hashes the provided character string after first removing +any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF)

    + +
    + +
            random.hashString = function (inStr) {
    +            inStr = random.cleanString(inStr);
    +            mash(inStr); // use the string to evolve the 'mash' state
    +            for (i = 0; i < inStr.length; i++) { // scan through the characters in our string
    +                k = inStr.charCodeAt(i); // get the character code at the location
    +                for (j = 0; j < o; j++) { //    "mash" it into the UHEPRNG state
    +                    s[j] -= mash(k);
    +                    if (s[j] < 0) {
    +                        s[j] += 1;
    +                    }
    +                }
    +            }
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this EXPORTED function allows you to seed the random generator.

    + +
    + +
            random.seed = function (seed) {
    +            if (typeof seed === 'undefined' || seed === null) {
    +                seed = Math.random();
    +            }
    +            if (typeof seed !== 'string') {
    +                seed = stringify(seed, function (key, value) {
    +                    if (typeof value === 'function') {
    +                        return (value).toString();
    +                    }
    +                    return value;
    +                });
    +            }
    +            random.initState();
    +            random.hashString(seed);
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    this handy exported function is used to add entropy to our uheprng at any time

    + +
    + +
            random.addEntropy = function ( /* accept zero or more arguments */ ) {
    +            var args = [];
    +            for (i = 0; i < arguments.length; i++) {
    +                args.push(arguments[i]);
    +            }
    +            hash((k++) + (new Date().getTime()) + args.join('') + Math.random());
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    if we want to provide a deterministic startup context for our PRNG, +but without directly setting the internal state variables, this allows +us to initialize the mash hash and PRNG’s internal state before providing +some hashing input

    + +
    + +
            random.initState = function () {
    +            mash(); // pass a null arg to force mash hash to init
    +            for (i = 0; i < o; i++) {
    +                s[i] = mash(' '); // fill the array with initial mash hash values
    +            }
    +            c = 1; // init our multiply-with-carry carry
    +            p = o; // init our phase
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    we use this (optional) exported function to signal the JavaScript interpreter +that we’re finished using the “Mash” hash function so that it can free up the +local “instance variables” is will have been maintaining. It’s not strictly +necessary, of course, but it’s good JavaScript citizenship.

    + +
    + +
            random.done = function () {
    +            mash = null;
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    if we called “uheprng” with a seed value, then execute random.seed() before returning

    + +
    + +
            if (typeof seed !== 'undefined') {
    +            random.seed(seed);
    +        }
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns a random integer between 0 (inclusive) and range (exclusive)

    + +
    + +
            random.range = function (range) {
    +            return random(range);
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns a random float between 0 (inclusive) and 1 (exclusive)

    + +
    + +
            random.random = function () {
    +            return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE;
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns a random float between min (inclusive) and max (exclusive)

    + +
    + +
            random.floatBetween = function (min, max) {
    +            return random.random() * (max - min) + min;
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns a random integer between min (inclusive) and max (inclusive)

    + +
    + +
            random.intBetween = function (min, max) {
    +            return Math.floor(random.random() * (max - min + 1)) + min;
    +        };
    + +
  • + + +
  • +
    + +
    + +
    +

    when our main outer “uheprng” function is called, after setting up our +initial variables and entropic state, we return an “instance pointer” +to the internal anonymous function which can then be used to access +the uheprng’s various exported functions. As with the “.done” function +above, we should set the returned value to ‘null’ once we’re finished +using any of these functions.

    + +
    + +
            return random;
    +    }());
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    … And this code comes from https://github.com/moll/json-stringify-safe

    + +
    + +
    function stringify(obj, replacer, spaces, cycleReplacer) {
    +    return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces)
    +}
    +
    +function serializer(replacer, cycleReplacer) {
    +    var stack = [], keys = []
    +
    +    if (cycleReplacer == null) cycleReplacer = function(key, value) {
    +        if (stack[0] === value) return "[Circular ~]"
    +        return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]"
    +    }
    +
    +    return function(key, value) {
    +        if (stack.length > 0) {
    +            var thisPos = stack.indexOf(this)
    +            ~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
    +            ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
    +            if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value)
    +        }
    +        else stack.push(value)
    +
    +        return replacer == null ? value : replacer.call(this, key, value)
    +    }
    +}
    +
    +const seededRandomNumberGenerator = function (seed) {
    +
    +    return new uheprng(seed);
    +};
    + +
  • + + +
  • +
    + +
    + +
    +
    Easing functions
    +

    The following easing variations come from the easings.net web page

    +
      +
    • Note: the naming convention for easing is different in Scrawl-canvas. Easing out implies a speeding up, while easing in implies a slowing down. Think of a train easing into a station, and then easing out of it again as it continues its journey.
    • +
    + +
    + +
    const easeOutSine = function (t) { return 1 - Math.cos((t * Math.PI) / 2) };
    +const easeInSine = function (t) { return Math.sin((t * Math.PI) / 2) };
    +const easeOutInSine = function (t) { return -(Math.cos(Math.PI * t) - 1) / 2 };
    +
    +const easeOutQuad = function (t) { return t * t };
    +const easeInQuad = function (t) { return 1 - ((1 - t) * (1 - t)) };
    +const easeOutInQuad = function(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 };
    +
    +const easeOutCubic = function(t) { return t * t * t };
    +const easeInCubic = function(t) { return 1 - Math.pow(1 - t, 3) };
    +const easeOutInCubic = function(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2 };
    +
    +const easeOutQuart = function(t) { return t * t * t * t };
    +const easeInQuart = function(t) { return 1 - Math.pow(1 - t, 4) };
    +const easeOutInQuart = function(t) { return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2 };
    +
    +const easeOutQuint = function(t) { return t * t * t * t * t };
    +const easeInQuint = function(t) { return 1 - Math.pow(1 - t, 5) };
    +const easeOutInQuint = function(t) { return t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2 };
    +
    +const easeOutExpo = function(t) { return t === 0 ? 0 : Math.pow(2, 10 * t - 10) };
    +const easeInExpo = function(t) { return t === 1 ? 1 : 1 - Math.pow(2, -10 * t) };
    +const easeOutInExpo = function(t) {
    +    if (t === 0 || t === 1) return t;
    +    return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;        
    +};
    +
    +const easeOutCirc = function(t) { return 1 - Math.sqrt(1 - Math.pow(t, 2)) };
    +const easeInCirc = function(t) { return Math.sqrt(1 - Math.pow(t - 1, 2)) };
    +const easeOutInCirc = function(t) { return t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2 };
    +
    +const easeOutBack = function(t) { return (2.70158 * t * t * t) - (1.70158 * t * t) };
    +const easeInBack = function(t) { return 1 + (2.70158 * Math.pow(t - 1, 3)) + (1.70158 * Math.pow(t - 1, 2)) };
    +const easeOutInBack = function(t) {
    +    let c1 = 1.70158, c2 = c1 * 1.525;
    +    return t < 0.5 ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
    +};
    +
    +const easeOutElastic = function(t) {
    +    const c4 = (2 * Math.PI) / 3;
    +    if (t === 0 || t === 1) return t;
    +    return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
    +};
    +
    +const easeInElastic = function(t) {
    +    const c4 = (2 * Math.PI) / 3;
    +    if (t === 0 || t === 1) return t;
    +    return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
    +};
    +
    +const easeOutInElastic = function(t) {
    +    const c5 = (2 * Math.PI) / 4.5;
    +    if (t === 0 || t === 1) return t;
    +    return t < 0.5 ? 
    +        -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 : 
    +        (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
    +};
    +
    +const easeOutBounce = function(t) {
    +    t = 1 - t;
    +    const n1 = 7.5625, d1 = 2.75;
    +    if (t < 1 / d1) return 1 - (n1 * t * t);
    +    if (t < 2 / d1) return 1 - (n1 * (t -= 1.5 / d1) * t + 0.75);
    +    if (t < 2.5 / d1) return 1 - (n1 * (t -= 2.25 / d1) * t + 0.9375);
    +    return 1 - (n1 * (t -= 2.625 / d1) * t + 0.984375);
    +};
    +
    +const easeInBounce = function(t) {
    +    const n1 = 7.5625, d1 = 2.75;
    +    if (t < 1 / d1) return n1 * t * t;
    +    if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
    +    if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
    +    return n1 * (t -= 2.625 / d1) * t + 0.984375;
    +};
    +
    +const easeOutInBounce = function(t) {
    +    const n1 = 7.5625, d1 = 2.75;
    +    let res;
    +    if (t < 0.5) {
    +        t = 1 - 2 * t;
    +        if (t < 1 / d1) res = n1 * t * t;
    +        else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75;
    +        else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375;
    +        else res = n1 * (t -= 2.625 / d1) * t + 0.984375;
    +        return (1 - res) / 2;
    +    }
    +    else {
    +        t = 2 * t - 1;
    +        if (t < 1 / d1) res = n1 * t * t;
    +        else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75;
    +        else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375;
    +        else res = n1 * (t -= 2.625 / d1) * t + 0.984375;
    +        return (1 + res) / 2;
    +    }
    +};
    + +
  • + + +
  • +
    + +
    +

    Exports

    @@ -1012,10 +1684,12 @@

    Exports

    convertTime, correctForZero, λnull, + λfirstArg, λthis, λpromise, generateUuid, generateUniqueString, + interpolate, isa_boolean, isa_canvas, isa_dom, @@ -1028,10 +1702,42 @@

    Exports

    mergeOver, pushUnique, removeItem, + seededRandomNumberGenerator, xt, xta, xtGet, xto, + + easeOutSine, + easeInSine, + easeOutInSine, + easeOutQuad, + easeInQuad, + easeOutInQuad, + easeOutCubic, + easeInCubic, + easeOutInCubic, + easeOutQuart, + easeInQuart, + easeOutInQuart, + easeOutQuint, + easeInQuint, + easeOutInQuint, + easeOutExpo, + easeInExpo, + easeOutInExpo, + easeOutCirc, + easeInCirc, + easeOutInCirc, + easeOutBack, + easeInBack, + easeOutInBack, + easeOutElastic, + easeInElastic, + easeOutInElastic, + easeOutBounce, + easeInBounce, + easeOutInBounce, };
  • diff --git a/docs/source/factory/action.html b/docs/source/factory/action.html index 9a141355f..e94218a60 100644 --- a/docs/source/factory/action.html +++ b/docs/source/factory/action.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/anchor.html b/docs/source/factory/anchor.html index 264064d9a..9a65b4431 100644 --- a/docs/source/factory/anchor.html +++ b/docs/source/factory/anchor.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/animation.html b/docs/source/factory/animation.html index 9e33fc3b6..a8ad42481 100644 --- a/docs/source/factory/animation.html +++ b/docs/source/factory/animation.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/bezier.html b/docs/source/factory/bezier.html index 008b7d266..63572e281 100644 --- a/docs/source/factory/bezier.html +++ b/docs/source/factory/bezier.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/block.html b/docs/source/factory/block.html index ea8563b02..6e1db8e82 100644 --- a/docs/source/factory/block.html +++ b/docs/source/factory/block.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/canvas.html b/docs/source/factory/canvas.html index 95f2a1d4f..95e203501 100644 --- a/docs/source/factory/canvas.html +++ b/docs/source/factory/canvas.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/cell.html b/docs/source/factory/cell.html index b91bb9c62..c309ff51f 100644 --- a/docs/source/factory/cell.html +++ b/docs/source/factory/cell.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -680,7 +685,7 @@

    Cell attributes

  • Attributes defined in the anchor mixin: anchor.
  • Attributes defined in the filter mixin: filters, isStencil.
  • Attributes defined in the cascade mixin: groups.
  • -
  • Attributes defined in the pattern mixin: repeat.
  • +
  • Attributes defined in the pattern mixin: repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF.
  • Attributes defined in the asset mixin: source, subscribers.
  • @@ -1802,6 +1807,11 @@

    Prototype functions

    The following functions are used as part of entity object stamp functionality - specifically for those with a method whose appearance is affected by shadows, and for the clear method

    + @@ -1916,11 +1926,24 @@

    Prototype functions

    -

    Display cycle functionality

    -

    This functionality is entirely Promise-based, and triggered by the Cell’s Canvas wrapper controller

    +

    getComputedFontSizes - internal function - the Cell wrapper gets passed by Phrase entitys to its fontAttributes object, which then invokes it when calculating font sizes

    +
    P.getComputedFontSizes = function () {
    +
    +    let host = this.getHost();
    +
    +    if (host && host.domElement) {
    +
    +        let em = window.getComputedStyle(host.domElement),
    +            rem = window.getComputedStyle(document.documentElement);
    +
    +        return [parseFloat(em.fontSize), parseFloat(rem.fontSize), window.innerWidth, window.innerHeight];
    +    }
    +    return false;
    +}
    + @@ -1930,6 +1953,20 @@

    Display cycle functionality

    +

    Display cycle functionality

    +

    This functionality is entirely Promise-based, and triggered by the Cell’s Canvas wrapper controller

    + + + + + + +
  • +
    + +
    + +

    clear

    @@ -1989,11 +2026,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    compile

    @@ -2013,11 +2050,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    Doing it this way to ensure that each group completes its stamp action before the next one starts

    @@ -2047,11 +2084,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    The else branch should only trigger once all the groups have been processed. At this point we should be okay to action any Cell-level filters on the output, and stash the output (if required)

    @@ -2081,11 +2118,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    show - Note that functionality here differs for base cells and other Cell wrappers

    @@ -2100,11 +2137,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    get the destination cell’s canvas context

    @@ -2123,11 +2160,11 @@

    Display cycle functionality

  • -
  • +
  • - +

    Cannot draw to the destination canvas if either of its dimensions === 0

      @@ -2161,11 +2198,11 @@

      Display cycle functionality

      -
    • +
    • - +

      copy the base canvas over to the display canvas. This copy operation ignores any scale, roll or position attributes set on the base cell, instead complying with the controller’s fit attribute requirements

      @@ -2190,11 +2227,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      base must copy into display resized, centered, letterboxing if necessary, maintaining aspect ratio

      @@ -2224,11 +2261,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      base must copy into display resized, centered, leaving no letterbox area, maintaining aspect ratio

      @@ -2258,11 +2295,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      base must copy into display resized, distorting the aspect ratio as necessary

      @@ -2280,11 +2317,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      base copies into display as-is, centred, maintaining aspect ratio

      @@ -2304,11 +2341,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      Cell canvases are treated like entitys on the base canvas: they can be positioned, scaled and rotated. Positioning will respect lockTo; flipReverse and flipUpend; and can be pivoted to other artefacts, or follow a path entity, etc. If pivoted to the mouse, they will use the base canvas’s .here attribute, which takes into account differences between the base and display canvas dimensions.

      @@ -2345,11 +2382,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      applyFilters - Internal function - add filters to the Cell’s current output.

        @@ -2368,7 +2405,22 @@

        Display cycle functionality

        image, worker; image = engine.getImageData(0, 0, self.currentDimensions[0], self.currentDimensions[1]); - worker = requestFilterWorker(); + worker = requestFilterWorker();
    + +
  • + + +
  • +
    + +
    + +
    +

    NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET’S IMAGEDATA AT THIS POINT

    + +
    + +
            self.preprocessFilters(self.currentFilters);
     
             actionFilterWorker(worker, {
                 image: image,
    @@ -2397,11 +2449,11 @@ 

    Display cycle functionality

  • -
  • +
  • - +

    stashOutputAction - Internal function - stash the Cell’s current output. While this function can be called at any time, the simplest way to invoke it is to set the Cell’s stashOutput flag to true, which will then invoke this function at the end of the compile step of the Display cycle (after any filters have been applied to the cell display).

      @@ -2434,11 +2486,11 @@

      Display cycle functionality

      -
    • +
    • - +

      Keep the stashed image within bounds of the Cell’s dimensions.

      @@ -2466,11 +2518,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      Get the imageData object, and stash it

      @@ -2484,11 +2536,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      Get the dataUrl String, updating the stashed <img> element with it

      @@ -2535,11 +2587,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      getHost - Internal function - get a reference to the Cell’s current host (where it will be stamping itself as part of the Display cycle).

        @@ -2565,11 +2617,11 @@

        Display cycle functionality

        -
      • +
      • - +

        updateBaseHere - Internal function - keeping the Canvas object’s ‘base’ Cell’s .here attribute up-to-date with accurate mouse/pointer/touch cursor data

        @@ -2664,11 +2716,11 @@

        Display cycle functionality

      • -
      • +
      • - +

        prepareStamp - Internal function - steps to be performed before the Cell stamps its visual contents onto a Canvas object’s base cell’s canvas. Will be invoked as part of the Display cycle ‘show’ functionality.

    • @@ -513,7 +518,7 @@

      Imports

    import { constructors, entity } from '../core/library.js';
    -import { mergeOver, xt, xtGet, isa_obj } from '../core/utilities.js';
    +import { mergeOver, xt, xtGet, isa_obj, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js';
     
     import baseMix from '../mixin/base.js';
    @@ -728,10 +733,12 @@

    Color attributes

    -
    Non-retained argument attributes (for factory, clone, set functions)
    +

    The easing attribute affects the getRangeColor function, applying an easing function to those requests.

    +
        easing: 'linear',
    +
  • @@ -741,7 +748,7 @@
    Non-re
    -

    random - the factory function, and the clone function, can ask the Color object to set its initial channel values randomly by including this attribute in the argument object; if the attribute resolves to true, random color functionality is invoked to set the r, g and b channel attributes to appropriately random values.

    +
    Non-retained argument attributes (for factory, clone, set functions)
    @@ -754,6 +761,19 @@
    Non-re
    +

    random - the factory function, and the clone function, can ask the Color object to set its initial channel values randomly by including this attribute in the argument object; if the attribute resolves to true, random color functionality is invoked to set the r, g and b channel attributes to appropriately random values.

    + + + + + + +
  • +
    + +
    + +

    color - a CSS color definition String which the Color object will attempt to convert into appropriate r, g, b and a channel attribute values.

    @@ -764,11 +784,11 @@
    Non-re
  • -
  • +
  • - +

    Packet management

    No additional packet functionality required

    @@ -778,11 +798,11 @@

    Packet management

  • -
  • +
  • - +

    Clone management

    No additional clone functionality required

    @@ -792,11 +812,11 @@

    Clone management

  • -
  • +
  • - +

    Kill management

    Overwrites ./mixin/base.js

    @@ -810,11 +830,11 @@

    Kill management

  • -
  • +
  • - +

    Remove style from all entity state objects

    @@ -837,11 +857,11 @@

    Kill management

  • -
  • +
  • - +

    Remove style from the Scrawl-canvas library

    @@ -855,11 +875,11 @@

    Kill management

  • -
  • +
  • - +

    Get, Set, deltaSet

    @@ -868,11 +888,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    get - overrides function in mixin/base.js

    @@ -919,11 +939,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    set - overrides function in mixin/base.js - see above for the additional attributes the set object argument can use.

    @@ -954,11 +974,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    else if (items.color) this.convert(items.color);

    @@ -972,11 +992,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    Get, Set, deltaSet

    @@ -1067,11 +1087,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    Prototype functions

    @@ -1080,11 +1100,11 @@

    Prototype functions

  • -
  • +
  • - +

    getData function called by Cell objects when calculating required updates to its CanvasRenderingContext2D engine, specifically for an entity’s fillStyle, strokeStyle and shadowColor attributes.

    @@ -1102,11 +1122,11 @@

    Prototype functions

  • -
  • +
  • - +

    generateRandomColor function asks the Color object to supply a random color, as restricted by its channel minimum and maximum attributes

    @@ -1144,13 +1164,13 @@

    Prototype functions

  • -
  • +
  • - +
    -

    getRangeColor - function which generates a color in the rtange between the minimum and maximum colors.

    +

    getRangeColor - function which generates a color in the range between the minimum and maximum colors.

    • when the argument is 0 the minimum color is returned; values below 0 are rounded up to 0
    • when the argument is 1 the maximum color is returned; values above 1 are rounded down to 1
    • @@ -1166,7 +1186,103 @@

      Prototype functions

      let floor = Math.floor; - let {rMin, gMin, bMin, aMin, rMax, gMax, bMax, aMax} = this; + let {rMin, gMin, bMin, aMin, rMax, gMax, bMax, aMax, easing} = this; + + if (easing !== 'linear') { + + switch (easing) { + case 'easeOutSine' : + item = easeOutSine(item); + break; + case 'easeInSine' : + item = easeInSine(item); + break; + case 'easeOutInSine' : + item = easeOutInSine(item); + break; + case 'easeOutQuad' : + item = easeOutQuad(item); + break; + case 'easeInQuad' : + item = easeInQuad(item); + break; + case 'easeOutInQuad' : + item = easeOutInQuad(item); + break; + case 'easeOutCubic' : + item = easeOutCubic(item); + break; + case 'easeInCubic' : + item = easeInCubic(item); + break; + case 'easeOutInCubic' : + item = easeOutInCubic(item); + break; + case 'easeOutQuart' : + item = easeOutQuart(item); + break; + case 'easeInQuart' : + item = easeInQuart(item); + break; + case 'easeOutInQuart' : + item = easeOutInQuart(item); + break; + case 'easeOutQuint' : + item = easeOutQuint(item); + break; + case 'easeInQuint' : + item = easeInQuint(item); + break; + case 'easeOutInQuint' : + item = easeOutInQuint(item); + break; + case 'easeOutExpo' : + item = easeOutExpo(item); + break; + case 'easeInExpo' : + item = easeInExpo(item); + break; + case 'easeOutInExpo' : + item = easeOutInExpo(item); + break; + case 'easeOutCirc' : + item = easeOutCirc(item); + break; + case 'easeInCirc' : + item = easeInCirc(item); + break; + case 'easeOutInCirc' : + item = easeOutInCirc(item); + break; + case 'easeOutBack' : + item = easeOutBack(item); + break; + case 'easeInBack' : + item = easeInBack(item); + break; + case 'easeOutInBack' : + item = easeOutInBack(item); + break; + case 'easeOutElastic' : + item = easeOutElastic(item); + break; + case 'easeInElastic' : + item = easeInElastic(item); + break; + case 'easeOutInElastic' : + item = easeOutInElastic(item); + break; + case 'easeOutBounce' : + item = easeOutBounce(item); + break; + case 'easeInBounce' : + item = easeInBounce(item); + break; + case 'easeOutInBounce' : + item = easeOutInBounce(item); + break; + } + } if (item > 1) item = 1; else if (item < 0) item = 0; @@ -1193,11 +1309,11 @@

      Prototype functions

      -
    • +
    • - +

      Internal function to make sure channel attribute values are in their correct format and ranges

      @@ -1222,11 +1338,11 @@

      Prototype functions

    • -
    • +
    • - +

      update function - adds the channel shift attributes to the r, g, b and a attributes. This functionality can be automated by setting the autoUpdate Boolean flag to true

      @@ -1281,11 +1397,11 @@

      Prototype functions

    • -
    • +
    • - +

      updateByDelta - alias for the update function

      @@ -1296,11 +1412,11 @@

      Prototype functions

    • -
    • +
    • - +

      We can also set a Color object to a new color value by invoking its convert function. Any CSS color string can be used as an argument (with exceptions - see above)

      @@ -1356,15 +1472,15 @@

      Prototype functions

      if (/%/.test(items)) { - r = round((temp[0] / 100) * 255); - g = round((temp[1] / 100) * 255); - b = round((temp[2] / 100) * 255); + r = round((parseFloat(temp[0]) / 100) * 255); + g = round((parseFloat(temp[1]) / 100) * 255); + b = round((parseFloat(temp[2]) / 100) * 255); } else { - r = round(temp[0]); - g = round(temp[1]); - b = round(temp[2]); + r = round(parseFloat(temp[0])); + g = round(parseFloat(temp[1])); + b = round(parseFloat(temp[2])); } } else if (/rgba\(/.test(items)) { @@ -1373,41 +1489,24 @@

      Prototype functions

      if (/%/.test(items)) { - r = round((temp[0] / 100) * 255); - g = round((temp[1] / 100) * 255); - b = round((temp[2] / 100) * 255); - a = temp[3] / 100; + r = round((parseFloat(temp[0]) / 100) * 255); + g = round((parseFloat(temp[1]) / 100) * 255); + b = round((parseFloat(temp[2]) / 100) * 255); + a = parseFloat(temp[3]) / 100; } else { - r = round(temp[0]); - g = round(temp[1]); - b = round(temp[2]); + r = round(parseFloat(temp[0])); + g = round(parseFloat(temp[1])); + b = round(parseFloat(temp[2])); a = temp[3]; } } - else if (/hsl\(/.test(items) || /hsla\(/.test(items)) {
    - -
  • - - -
  • -
    - -
    - -
    -

    the spec explanation can be found here https://developer.mozilla.org/en-US/docs/Web/CSS/color_value

    -

    see http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ for one way we can approach converting hsl values to rgb

    -

    currently, knock down to transparent black

    + else if (/hsl\(/.test(items) || /hsla\(/.test(items)) { -
    - -
                r = 0;
    -            g = 0;
    -            b = 0;
    -            a = 0;
    +            temp = items.match(/([0-9.]+\b)/g);
     
    +            this.setFromHSL(parseFloat(temp[0]), parseFloat(temp[1]), parseFloat(temp[2]), parseFloat(temp[3]));
             }
             else if (items === 'transparent') {
     
    @@ -1438,6 +1537,148 @@ 

    Prototype functions

    } return this; +}; + + +P.getHSLfromRGB = function (dr, dg, db) { + + let minColor = Math.min(dr, dg, db), + maxColor = Math.max(dr, dg, db); + + let lum = (minColor + maxColor) / 2; + + let sat = 0; + + if (minColor !== maxColor) { + + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } + + let hue = 0; + + if (maxColor === dr) hue = (dg - db) / (maxColor - minColor); + else if (maxColor === dg) hue = 2 + ((db - dr) / (maxColor - minColor)); + else hue = 4 + ((dr - dg) / (maxColor - minColor)); + + hue *= 60; + + if (hue < 0) hue += 360; + + return [hue, sat, lum]; +}; + +P.getHSL = function () { + + let {r, g, b} = this; + + let minColor = Math.min(r, g, b), + maxColor = Math.max(r, g, b); + + let lum = (minColor + maxColor) / 2; + + let sat = 0; + + if (minColor !== maxColor) { + + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } + + let hue = 0; + + if (maxColor === r) hue = (g - b) / (maxColor - minColor); + else if (maxColor === g) hue = 2 + ((b - r) / (maxColor - minColor)); + else hue = 4 + ((r - g) / (maxColor - minColor)); + + hue *= 60; + + if (hue < 0) hue += 360; + + return [hue, sat, lum]; +}; + +P.getRGBfromHSL = function (h, s, l, a) { + + if (!s) { + + let gray = Math.floor(l * 255); + return [gray, gray, gray]; + } + + let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s), + tempLum2 = (2 * l) - tempLum1; + + const calculator = function (t, l1, l2) { + + if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t); + if (t * 2 < 1) return l1; + if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666)); + return l2; + }; + + h /= 360; + + let tr = h + 0.333, + tg = h, + tb = h - 0.333; + + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; + + let r = calculator(tr, tempLum1, tempLum2) * 255, + g = calculator(tg, tempLum1, tempLum2) * 255, + b = calculator(tb, tempLum1, tempLum2) * 255; + + if (null == a) return [r, g, b]; + + return [r, g, b, a * 255]; +}; + +P.setFromHSL = function (h, s, l, a) { + + if (!s) { + + let gray = Math.floor(l * 255); + return [gray, gray, gray]; + } + + let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s), + tempLum2 = (2 * l) - tempLum1; + + const calculator = function (t, l1, l2) { + + if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t); + if (t * 2 < 1) return l1; + if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666)); + return l2; + }; + + h /= 360; + + let tr = h + 0.333, + tg = h, + tb = h - 0.333; + + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; + + this.r = calculator(tr, tempLum1, tempLum2) * 255, + this.g = calculator(tg, tempLum1, tempLum2) * 255, + this.b = calculator(tb, tempLum1, tempLum2) * 255; + + if (null != a) { + + this.a = a * 255; + } };
  • diff --git a/docs/source/factory/coordinate.html b/docs/source/factory/coordinate.html index af16939e6..5ed325403 100644 --- a/docs/source/factory/coordinate.html +++ b/docs/source/factory/coordinate.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/element.html b/docs/source/factory/element.html index 3c4ad0ae9..fb8baed65 100644 --- a/docs/source/factory/element.html +++ b/docs/source/factory/element.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/emitter.html b/docs/source/factory/emitter.html index cec167cea..e16152cc3 100644 --- a/docs/source/factory/emitter.html +++ b/docs/source/factory/emitter.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/filter.html b/docs/source/factory/filter.html index 454d2a4e9..ef3bd7b6b 100644 --- a/docs/source/factory/filter.html +++ b/docs/source/factory/filter.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -450,42 +455,80 @@

    Filter factory

    -

    Filters take in an image representation of an entity, Group of entitys or a Cell display and, by manipulating the image’s data, return an updated image which replaces those entitys or cell in the final output display.

    +

    Filters take in an image representation of an entity, Group of entitys or a Cell 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, which is used by the Cell, Group and all entity factories.

    -

    Scrawl-canvas uses a web worker to generate filter outputs - defined in the filter web worker. It supports a number of common filter algorithms:

    - -

    Scrawl-canvas can also use user-defined filters.

    -

    Filters use the base mixin, thus they come equipped with packet functionality, alongside clone and kill functions.

    -

    Note that CSS-mediated filters - url(), blur(), brightness(), contrast(), drop-shadow(), grayscale(), hue-rotate(), invert(), opacity(), saturate(), sepia() - can also be applied to DOM elements wrapped into Scrawl-canvas objects (Stack, Element, Canvas) in the normal way. Browsers will apply CSS filters as the final operation in their paint routines.

    -

    TODO: we’ve had to move all the code from the filter web worker into a new, comment-free module file because tools like CreateReactApp - which uses Webpack as its bundler of choice - breaks when we yarn add scrawl-canvas to a project.

    - +

    Scrawl-canvas uses a web worker to generate filter outputs - defined in the filter web worker. The web worker 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.

    +

    Web worker filter 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 zero. 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 - Set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the “red”, “green”, “blue” and “alpha” arguments. Setting these values to 0 disables the action for that channel. Object attributes: action, lineIn, lineOut, opacity, red, green, blue, alpha

    +

    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 - 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. 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 - 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

    +

    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

    +

    displace - Shift pixels around the image, based on the values supplied in a displacement process-image. 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

    +

    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

    +

    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

    +

    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, floored 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’. The output is a form of posterization. Object attributes: action, lineIn, lineOut, opacity, red, green, blue

    +

    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

    +

    tint-channels - Has similarities to the SVG 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

    +

    user-defined-legacy - Previous to Scrawl-canvas version 8.4.0, filters could be defined with an argument which passed a function string to the filter 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

    +
    // 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',
    +        }
    +    ],
    +});
    @@ -504,6 +547,7 @@

    Demos:

  • Canvas-020 - Testing createImageFromXXX functionality
  • Canvas-027 - Video control and manipulation; chroma-based hit zone
  • Component-004 - Scrawl-canvas packets; save and load a range of different entitys
  • +
  • Filters-019 - Using a Noise asset with a displace filter
  • @@ -521,8 +565,8 @@

    Imports

    -
    import { constructors, cell, group, entity } from '../core/library.js';
    -import { mergeOver, removeItem } from '../core/utilities.js';
    +            
    import { constructors, cell, group, entity, asset } from '../core/library.js';
    +import { mergeOver, removeItem, λnull } from '../core/utilities.js';
     
     import baseMix from '../mixin/base.js';
    @@ -543,7 +587,9 @@

    Filter constructor

    this.makeName(items.name); this.register(); - this.set(this.defs); + + this.actions = []; + this.set(items); return this; };
    @@ -594,6 +640,7 @@

    Mixins

    Filter attributes

    @@ -609,11 +656,12 @@

    Filter attributes

    -

    All filters need to set out their method. For preset methods, a method string (eg ‘grayscale’, ‘sepia’) is sufficient. Bespoke methods require a function

    +
    How the filter factory builds filters
    +

    Filter actions are defined in action objects - which are vanilla Javascript Objects which are collected together in the actions array. Each action object is processed sequentially by the web worker to produce the final output for that filter.

    -
        method: '',
    +
        actions: null,
    @@ -624,16 +672,17 @@

    Filter attributes

    -

    The following methods require no further attributes:

    +

    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.

    +
        method: '',
    + @@ -643,14 +692,25 @@

    Filter attributes

    -

    The following methods require the level attribute:

    +
    How filters process data
    +

    The Scrawl-canvas filters web worker 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.

    -
        level: 0,
    +
        lineIn: '',
    +    lineMix: '',
    +    lineOut: '',
    @@ -661,16 +721,16 @@

    Filter attributes

    -

    The threshhold filter will default to setting (desaturated) pixels below a given level (0 - 255) to black, and those above the level to white. These colours can be changed by using the low and high channel attributes

    +

    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)

    + -
        lowRed: 0,
    -    lowGreen: 0,
    -    lowBlue: 0,
    -    highRed: 255,
    -    highGreen: 255,
    -    highBlue: 255,
    +
        opacity: 1,
    @@ -681,13 +741,108 @@

    Filter attributes

    -

    The channels and channelstep methods make use of the red, green and blue attributes

    +
    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:

    + -
        red: 0,
    +            
        alpha: 255,
    +    angle: 0,
    +    areaAlphaLevels: null,
    +    asset: '',
    +    blend: 'normal',
    +    blue: 0,
    +    blueInBlue: 0,
    +    blueInGreen: 0,
    +    blueInRed: 0,
    +    channelX: 'red',
    +    channelY: 'green',
    +    clamp: 0,
    +    compose: 'source-over',
    +    copyHeight: 1,
    +    copyWidth: 1,
    +    copyX: 0,
    +    copyY: 0,
    +    excludeAlpha: true, 
    +    excludeBlue: false,
    +    excludeGreen: false,
    +    excludeRed: false,
         green: 0,
    -    blue: 0,
    + greenInBlue: 0, + greenInGreen: 0, + greenInRed: 0, + gutterHeight: 1, + gutterWidth: 1, + height: 1, + highBlue: 255, + highGreen: 255, + highRed: 255, + includeAlpha: false, + includeBlue: true, + includeGreen: true, + includeRed: true, + keepOnlyChangedAreas: false, + level: 0, + lowBlue: 0, + lowGreen: 0, + lowRed: 0, + offsetAlphaX: 0, + offsetAlphaY: 0, + offsetBlueX: 0, + offsetBlueY: 0, + offsetGreenX: 0, + offsetGreenY: 0, + offsetRedX: 0, + offsetRedY: 0, + offsetX: 0, + offsetY: 0, + opaqueAt: 1, + passes: 1, + postProcessResults: true, + processHorizontal: true, + processVertical: true, + radius: 1, + ranges: null, + red: 0, + redInBlue: 0, + redInGreen: 0, + redInRed: 0, + scaleX: 1, + scaleY: 1, + smoothing: 0, + step: 1, + strength: 1, + tileHeight: 1, + tileWidth: 1, + tolerance: 0, + transparentAt: 0, + transparentEdges: false, + useNaturalGrayscale: false, + weights: null, + width: 1, +}; +P.defs = mergeOver(P.defs, defaultAttributes);
    @@ -698,20 +853,11 @@

    Filter attributes

    -

    The tint method uses nine attributes

    +

    Packet management

    +

    No additional packet functionality required

    -
        redInRed: 0,
    -    redInGreen: 0,
    -    redInBlue: 0,
    -    greenInRed: 0,
    -    greenInGreen: 0,
    -    greenInBlue: 0,
    -    blueInRed: 0,
    -    blueInGreen: 0,
    -    blueInBlue: 0,
    - @@ -721,15 +867,11 @@

    Filter attributes

    -

    The pixelate method requires tile dimensions and, optionally, offset coordinates which should not exceed the tile dimensions

    +

    Clone management

    +

    No additional clone functionality required

    -
        offsetX: 0,
    -    offsetY: 0,
    -    tileWidth: 1,
    -    tileHeight: 1,
    - @@ -739,23 +881,14 @@

    Filter attributes

    -

    The blur method uses the following attributes:

    - +

    Kill management

    +

    Overwrites ./mixin/base.js

    -
        radius: 1,
    -    passes: 1,
    -    shrinkingRadius: false,
    -    includeAlpha: false,
    -    processVertical: true,
    -    processHorizontal: true,
    +
    P.kill = function () {
    +
    +    let myname = this.name;
    @@ -766,23 +899,16 @@

    Filter attributes

    -

    The matrix method requires a weights attribute - an array of 9 numbers (also known as a kernel) in the following format:

    -
    weights: [
    -  topLeftWeight,
    -  topCenterWeight,
    -  topRightWeight,
    -  middleLeftWeight,
    -  homePixelWeight,
    -  middleRightWeight,
    -  bottomLeftWeight,
    -  bottomCenterWeight,
    -  bottomRightWeight,
    -]
    -

    … where the top row is the row above the home pixel, etc

    -

    The method also makes use of the includeAlpha attribute.

    +

    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);
    +    });
    + @@ -792,25 +918,15 @@

    Filter attributes

    -

    The matrix5 method is the same as the matrix method except that its weights array should contain 25 elements, to cover all the positions (from top-left corner) in a 5x5 grid

    -

    Some common kernels include:

    - +

    Remove filter from all group filters attribute

    -
        weights: null,
    +
        Object.entries(group).forEach(([name, grp]) => {
    +
    +        let f = grp.filters;
    +        if (f && f.indexOf(myname) >= 0) removeItem(f, myname);
    +    });
    @@ -821,14 +937,15 @@

    Filter attributes

    -

    The ranges attribute - used by the chroma method - needs to be an array of arrays with the following format:

    -
    [[minRed, minGreen, minBlue, maxRed, maxGreen, maxBlue], etc]
    -

    … multiple ranges can be defined - for instance to key out the lightest and darkest hues:

    -
    ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]]
    +

    Remove filter from all cell filters attribute

    -
        ranges: null,
    +
        Object.entries(cell).forEach(([name, c]) => {
    +
    +        let f = c.filters;
    +        if (f && f.indexOf(myname) >= 0) removeItem(f, myname);
    +    });
    @@ -839,23 +956,14 @@

    Filter attributes

    -

    The user-defined filter should be set as a String value of the function’s contents (the bits between the { curly braces }) on the userDefined attribute. The function can take no arguments, and can only use variables defined above (or the udVariableN attributes below). The function can also use self variables supplied by the web worker - see the worker/filter.js for more information

    +

    Remove filter from the Scrawl-canvas library

    -
        userDefined: '',
    -    udVariable0: '',
    -    udVariable1: '',
    -    udVariable2: '',
    -    udVariable3: '',
    -    udVariable4: '',
    -    udVariable5: '',
    -    udVariable6: '',
    -    udVariable7: '',
    -    udVariable8: '',
    -    udVariable9: '',
    -};
    -P.defs = mergeOver(P.defs, defaultAttributes);
    +
        this.deregister();
    +    
    +    return this;
    +};
    @@ -866,11 +974,13 @@

    Filter attributes

    -

    Packet management

    -

    No additional packet functionality required

    +

    Get, Set, deltaSet

    +
    let S = P.setters, 
    +    D = P.deltaSetters;
    + @@ -880,11 +990,34 @@

    Packet management

    -

    Clone management

    -

    No additional clone functionality required

    +

    set - Overwrites mixin/base.js function

    +
    P.set = function (items = {}) {
    +
    +    if (Object.keys(items).length) {
    +
    +        let setters = this.setters,
    +            defs = this.defs,
    +            predefined;
    +
    +        Object.entries(items).forEach(([key, value]) => {
    +
    +            if (key && key !== 'name' && value != null) {
    +
    +                predefined = setters[key];
    +
    +                if (predefined) predefined.call(this, value);
    +                else if (typeof defs[key] !== 'undefined') this[key] = value;
    +            }
    +        }, this);
    +    }
    +    if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this);
    +
    +    return this;
    +};
    + @@ -894,14 +1027,33 @@

    Clone management

    -

    Kill management

    -

    Overwrites ./mixin/base.js

    +

    setDelta - Overwrites mixin/base.js function

    -
    P.kill = function () {
    +            
    P.setDelta = function (items = {}) {
     
    -    let myname = this.name;
    + if (Object.keys(items).length) { + + let setters = this.deltaSetters, + defs = this.defs, + predefined; + + Object.entries(items).forEach(([key, value]) => { + + 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); + } + }, this); + } + if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this); + + return this; +};
    @@ -912,16 +1064,18 @@

    Kill management

    -

    Remove filter from all entity filters attribute

    +

    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 coder a function string to pass on to the web worker, 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,
    -
        Object.entries(entity).forEach(([name, ent]) => {
    -
    -        let f = ent.filters;
    -        if (f && f.indexOf(myname) >= 0) removeItem(f, myname);
    -    });
    - @@ -931,16 +1085,22 @@

    Kill management

    -

    Remove filter from all group filters attribute

    +

    // Threshhold filter +}).clone({ + name: ‘my-duotone-filter’, + method: ‘threshold’, + level: 127, + lowRed: 100, + lowGreen: 0, + lowBlue: 0, + highRed: 220, + highGreen: 60, + highBlue: 60, +});

    +
    -
        Object.entries(group).forEach(([name, grp]) => {
    -
    -        let f = grp.filters;
    -        if (f && f.indexOf(myname) >= 0) removeItem(f, myname);
    -    });
    - @@ -950,15 +1110,11 @@

    Kill management

    -

    Remove filter from all cell filters attribute

    +

    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):

    -
        Object.entries(cell).forEach(([name, c]) => {
    -
    -        let f = c.filters;
    -        if (f && f.indexOf(myname) >= 0) removeItem(f, myname);
    -    });
    +
    const setActionsArray = {
    @@ -969,14 +1125,24 @@

    Kill management

    -

    Remove filter from the Scrawl-canvas library

    +

    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.

    -
        this.deregister();
    -    
    -    return this;
    -};
    +
        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,
    +        }];
    +    },
    @@ -987,11 +1153,26 @@

    Kill management

    -

    Get, Set, deltaSet

    -

    No additional functionality required

    +

    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],
    +        }];
    +    },
    + @@ -1001,11 +1182,23 @@

    Get, Set, deltaSet

    -

    Prototype functions

    -

    No additional prototype functions defined

    +

    binary (new in v8.4.0) - set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the “red”, “green”, “blue” and “alpha” arguments. Setting these values to 0 disables the action for that channel

    +
        binary: function (f) {
    +        f.actions = [{
    +            action: 'binary',
    +            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 : 0,
    +        }];
    +    },
    + @@ -1015,12 +1208,22 @@

    Prototype functions

    -

    Filter webworker pool

    -

    Because starting a web worker is an expensive operation, and a number of different filters may be required to render displays across a number of different <canvas> elements in a web page, Scrawl-canvas operates a pool of web workers to perform work as-and-when required.

    +

    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

    -
    const filterPool = [];
    +
        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,
    +        }];
    +    },
    @@ -1031,16 +1234,20 @@

    Filter webworker pool

    -

    Exported function requestFilterWorker

    +

    blue - removes red and green channel color from the image

    -
    const requestFilterWorker = function () {
    -
    -    if (!filterPool.length) filterPool.push(buildFilterWorker());
    -
    -    return filterPool.shift();
    -};
    +
        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,
    +        }];
    +    },
    @@ -1051,14 +1258,27 @@

    Filter webworker pool

    -

    Exported function releaseFilterWorker - to avoid memory leaks, all requested filter workers MUST be released back to the filter pool as soon as their work has completed.

    +

    blur - blurs the image

    -
    const releaseFilterWorker = function (f) {
    -
    -    filterPool.push(f);
    -};
    +
        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,
    +            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,
    +        }];
    +    },
    @@ -1069,21 +1289,24 @@

    Filter webworker pool

    -

    IMPORTANT!

    - -

    For use in a website that does not use webpack, rollup, parcel, etc in their toolchain

    - -

    By default, Scrawl-canvas is distributed in a bundler-safe form

    +

    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,
    +        }];
    +    },
    + @@ -1093,11 +1316,22 @@

    IMPORTANT!

    -

    BUNDLED SITE

    +

    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

    -
    import { filterUrl } from '../worker/filter-stringed.js';
    +
        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],
    +        }];
    +    },
    @@ -1108,11 +1342,22 @@

    IMPORTANT!

    -

    buildFilterWorker - create a new filter web worker

    +

    thiskey -

    -
    const buildFilterWorker = function () {
    +
        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,
    +        }];
    +    },
    @@ -1123,11 +1368,22 @@

    IMPORTANT!

    -

    MODERN SITE -let path = import.meta.url.slice(0, -(‘factory/filter.js’.length));

    +

    thiskey -

    +
        channelstep: function (f) {
    +        f.actions = [{
    +            action: 'step-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,
    +        }];
    +    },
    + @@ -1137,10 +1393,22 @@

    IMPORTANT!

    -

    MODERN SITE
    let filterUrl = (window.scrawlEnvironmentOffscreenCanvasSupported) ?

    +

    thiskey (new in v8.4.0) -

    +
        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,
    +        }];
    +    },
    + @@ -1150,11 +1418,20 @@

    IMPORTANT!

    -

    MODERN SITE - ${path}worker/filter_canvas.js :

    +

    thiskey -

    +
        chroma: function (f) {
    +        f.actions = [{
    +            action: 'chroma',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            ranges: (f.ranges != null) ? f.ranges : [],
    +        }];
    +    },
    + @@ -1164,10 +1441,24 @@

    IMPORTANT!

    -

    MODERN SITE
    ${path}worker/filter.js;

    +

    thiskey (new in v8.4.0) -

    +
        chromakey: function (f) {
    +        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: (f.red != null) ? f.red : 0,
    +            green: (f.green != null) ? f.green : 255,
    +            blue: (f.blue != null) ? f.blue : 0,
    +            transparentAt: (f.transparentAt != null) ? f.transparentAt : 0,
    +            opaqueAt: (f.opaqueAt != null) ? f.opaqueAt : 1,
    +        }];
    +    },
    + @@ -1177,12 +1468,24 @@

    IMPORTANT!

    -

    BUNDLED SITE

    +

    thiskey (new in v8.4.0) -

    -
        return new Worker(filterUrl);                                               
    -};
    +
        clampChannels: function (f) {
    +        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: (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,
    +        }];
    +    },
    @@ -1193,29 +1496,22 @@

    IMPORTANT!

    -

    Exported function actionFilterWorker - send a task to the filter web worker, and retrieve the resulting image. This function returns a Promise.

    +

    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

    -
    const actionFilterWorker = function (worker, items) {
    -
    -    return new Promise((resolve, reject) => {
    -
    -        worker.onmessage = (e) => {
    -
    -            if (e && e.data && e.data.image) resolve(e.data.image);
    -            else resolve(false);
    -        };
    -
    -        worker.onerror = (e) => {
    -
    -            console.log('error', e.lineno, e.message);
    -            resolve(false);
    -        };
    -
    -        worker.postMessage(items);
    -    });
    -};
    +
        compose: function (f) {
    +        f.actions = [{
    +            action: 'compose',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            lineMix: (f.lineMix != null) ? f.lineMix : '',
    +            compose: (f.compose != null) ? f.compose : 'source-over',
    +            offsetX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetY: (f.offsetY != null) ? f.offsetY : 0,
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +        }];
    +    },
    @@ -1226,72 +1522,948 @@

    IMPORTANT!

    -

    Factory

    -
    scrawl.makeFilter({
    -
    -    name: 'my-grayscale-filter',
    -    method: 'grayscale',
    -
    -}).clone({
    -
    -    name: 'my-sepia-filter',
    -    method: 'sepia',
    -});
    -
    -scrawl.makeFilter({
    -
    -    name: 'my-chroma-filter',
    -    method: 'chroma',
    -    ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]],
    -});
    -
    -scrawl.makeFilter({
    -
    -    name: 'venetian-blinds-filter',
    -    method: 'userDefined',
    +              

    cyan - removes red channel color from the image, and averages the remaining channel colors

    - level: 9, - - userDefined: ` - let i, iz, j, jz, - level = filter.level || 6, - halfLevel = level / 2, - yw, transparent, pos; - - for (i = localY, iz = localY + localHeight; i < iz; i++) { - - transparent = (i % level > halfLevel) ? true : false; - - if (transparent) { - - yw = (i * iWidth) + 3; - - for (j = localX, jz = localX + localWidth; j < jz; j ++) { - - pos = yw + (j * 4); - data[pos] = 0; - } - } - }`, -});
    + + +
        cyan: 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,
    +            includeGreen: true,
    +            includeBlue: true,
    +            excludeRed: true,
    +        }];
    +    },
    + + + + +
  • +
    + +
    + +
    +

    displace (new in v8.4.0) - moves pixels around the image, based on the color channel values supplied by a displacement map image

    -
    const makeFilter = function (items) {
    +            
        displace: function (f) {
    +        f.actions = [{
    +            action: 'displace',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            lineMix: (f.lineMix != null) ? f.lineMix : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            channelX: (f.channelX != null) ? f.channelX : 'red',
    +            channelY: (f.channelY != null) ? f.channelY : 'green',
    +            offsetX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetY: (f.offsetY != null) ? f.offsetY : 0,
    +            scaleX: (f.scaleX != null) ? f.scaleX : 1,
    +            scaleY: (f.scaleY != null) ? f.scaleY : 1,
    +            transparentEdges: (f.transparentEdges != null) ? f.transparentEdges : false,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    edgeDetect (new in v8.4.0) - applies a preset 3x3 edge-detect matrix to the image

    - return new Filter(items); -}; +
    + +
        edgeDetect: function (f) {
    +        f.actions = [{
    +            action: 'matrix',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            width: 3,
    +            height: 3,
    +            offsetX: 1,
    +            offsetY: 1,
    +            includeRed: true,
    +            includeGreen: true,
    +            includeBlue: true,
    +            includeAlpha: false,
    +            weights: [0,1,0,1,-4,1,0,1,0],
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    emboss (new in v8.4.0) - outputs a black-gray-red emboss effect

    -constructors.Filter = Filter;
    + + +
        emboss: function (f) {
    +        const actions = [];
    +        if (f.useNaturalGrayscale) {
    +            actions.push({
    +                action: 'grayscale',
    +                lineIn: (f.lineIn != null) ? f.lineIn : '',
    +                lineOut: 'emboss-work',
    +            });
    +        }
    +        else {
    +            actions.push({
    +                action: 'average-channels',
    +                lineIn: (f.lineIn != null) ? f.lineIn : '',
    +                lineOut: 'emboss-work',
    +                includeRed: true,
    +                includeGreen: true,
    +                includeBlue: true,
    +            });
    +        }
    +        if (f.clamp) {
    +            actions.push({
    +                action: 'clamp-channels',
    +                lineIn: 'emboss-work',
    +                lineOut: 'emboss-work',
    +                lowRed: 0 + f.clamp, 
    +                lowGreen: 0 + f.clamp, 
    +                lowBlue: 0 + f.clamp, 
    +                highRed: 255 - f.clamp, 
    +                highGreen: 255 - f.clamp, 
    +                highBlue: 255 - f.clamp,
    +            });
    +        }
    +        if (f.smoothing) {
    +            actions.push({
    +                action: 'blur',
    +                lineIn: 'emboss-work',
    +                lineOut: 'emboss-work',
    +                radius: f.smoothing,
    +                passes: 2,
    +            });
    +        }
    +        actions.push({
    +            action: 'emboss',
    +            lineIn: 'emboss-work',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            angle: (f.angle != null) ? f.angle : 0,
    +            strength: (f.strength != null) ? f.strength : 1,
    +            tolerance: (f.tolerance != null) ? f.tolerance : 0,
    +            keepOnlyChangedAreas: (f.keepOnlyChangedAreas != null) ? f.keepOnlyChangedAreas : false,
    +            postProcessResults: (f.postProcessResults != null) ? f.postProcessResults : true,
    +        });
    +        f.actions = actions;
    +    },
  • -
  • +
  • - + +
    +

    flood (new in v8.4.0) - creates a uniform sheet of the required color, which can then be used by other filter actions

    + +
    + +
        flood: function (f) {
    +        f.actions = [{
    +            action: 'flood',
    +            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,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    gray (new in v8.4.0) - averages the three color channel colors

    + +
    + +
        gray: 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,
    +            includeRed: true,
    +            includeGreen: true,
    +            includeBlue: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    grayscale - produces a more realistic black-and-white photograph effect

    + +
    + +
        grayscale: function (f) {
    +        f.actions = [{
    +            action: 'grayscale',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    green - removes red and blue channel color from the image

    + +
    + +
        green: 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,
    +            excludeBlue: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    image (new in v8.4.0) - load an image into the web worker, where it can then be used by other filter actions - useful for effects such as watermarking an image

    + +
    + +
        image: function (f) {
    +
    +        f.actions = [{
    +            action: 'process-image',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            asset: (f.asset != null) ? f.asset : '',
    +            width: (f.width != null) ? f.width : 1,
    +            height: (f.height != null) ? f.height : 1,
    +            copyWidth: (f.copyWidth != null) ? f.copyWidth : 1,
    +            copyHeight: (f.copyHeight != null) ? f.copyHeight : 1,
    +            copyX: (f.copyX != null) ? f.copyX : 0,
    +            copyY: (f.copyY != null) ? f.copyY : 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    invert - inverts the colors in the image, producing an effect similar to a photograph negative

    + +
    + +
        invert: function (f) {
    +        f.actions = [{
    +            action: 'invert-channels',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            includeRed: true,
    +            includeGreen: true,
    +            includeBlue: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    magenta - removes green channel color from the image, and averages the remaining channel colors

    + +
    + +
        magenta: 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,
    +            includeRed: true,
    +            includeBlue: true,
    +            excludeGreen: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    matrix - applies a 3x3 convolution matrix, kernel or mask operation to the image

    + +
    + +
        matrix: function (f) {
    +        f.actions = [{
    +            action: 'matrix',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            width: 3,
    +            height: 3,
    +            offsetX: 1,
    +            offsetY: 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,
    +            weights: (f.weights != null) ? f.weights : [0,0,0,0,1,0,0,0,0],
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    matrix5 - applies a 5x5 convolution matrix, kernel or mask operation to the image

    + +
    + +
        matrix5: function (f) {
    +        f.actions = [{
    +            action: 'matrix',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            width: 5,
    +            height: 5,
    +            offsetX: 2,
    +            offsetY: 2,
    +            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,
    +            weights: (f.weights != null) ? f.weights : [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    notblue - zeroes the blue channel values (not the same effect as the yellow filter)

    + +
    + +
        notblue: function (f) {
    +        f.actions = [{
    +            action: 'set-channel-to-level',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            includeBlue: true,
    +            level: 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    notgreen - zeroes the green channel values (not the same effect as the magenta filter)

    + +
    + +
        notgreen: function (f) {
    +        f.actions = [{
    +            action: 'set-channel-to-level',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            includeGreen: true,
    +            level: 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    notred - zeroes the red channel values (not the same effect as the cyan filter)

    + +
    + +
        notred: function (f) {
    +        f.actions = [{
    +            action: 'set-channel-to-level',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            includeRed: true,
    +            level: 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    offset (new in v8.4.0) - moves the image in its entirety by the given offset

    + +
    + +
        offset: function (f) {
    +        f.actions = [{
    +            action: 'offset',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            offsetRedX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetRedY: (f.offsetY != null) ? f.offsetY : 0,
    +            offsetGreenX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetGreenY: (f.offsetY != null) ? f.offsetY : 0,
    +            offsetBlueX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetBlueY: (f.offsetY != null) ? f.offsetY : 0,
    +            offsetAlphaX: (f.offsetX != null) ? f.offsetX : 0,
    +            offsetAlphaY: (f.offsetY != null) ? f.offsetY : 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    offsetChannels (new in v8.4.0) - moves each channel by an offset set for that channel. Can create a crude stereoscopic output

    + +
    + +
        offsetChannels: function (f) {
    +        f.actions = [{
    +            action: 'offset',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            offsetRedX: (f.offsetRedX != null) ? f.offsetRedX : 0,
    +            offsetRedY: (f.offsetRedY != null) ? f.offsetRedY : 0,
    +            offsetGreenX: (f.offsetGreenX != null) ? f.offsetGreenX : 0,
    +            offsetGreenY: (f.offsetGreenY != null) ? f.offsetGreenY : 0,
    +            offsetBlueX: (f.offsetBlueX != null) ? f.offsetBlueX : 0,
    +            offsetBlueY: (f.offsetBlueY != null) ? f.offsetBlueY : 0,
    +            offsetAlphaX: (f.offsetAlphaX != null) ? f.offsetAlphaX : 0,
    +            offsetAlphaY: (f.offsetAlphaY != null) ? f.offsetAlphaY : 0,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    pixelate - averages the colors in a block to produce a series of obscuring tiles

    + +
    + +
        pixelate: function (f) {
    +        f.actions = [{
    +            action: 'pixelate',
    +            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,
    +            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,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    red - removes blue and green channel color from the image

    + +
    + +
        red: 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,
    +            excludeGreen: true,
    +            excludeBlue: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    saturation - alters the saturation level of the image

    + +
    + +
        saturation: 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,
    +            saturation: true,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    sepia - recalculates the values of each color channel (a tint action) to create a more ‘antique’ version of the image

    + +
    + +
        sepia: function (f) {
    +        f.actions = [{
    +            action: 'tint-channels',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            redInRed: 0.393,
    +            redInGreen: 0.349,
    +            redInBlue: 0.272,
    +            greenInRed: 0.769,
    +            greenInGreen: 0.686,
    +            greenInBlue: 0.534,
    +            blueInRed: 0.189,
    +            blueInGreen: 0.168,
    +            blueInBlue: 0.131,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    sharpen (new in v8.4.0) - applies a preset 3x3 sharpen matrix to the image

    + +
    + +
        sharpen: function (f) {
    +        f.actions = [{
    +            action: 'matrix',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            width: 3,
    +            height: 3,
    +            offsetX: 1,
    +            offsetY: 1,
    +            includeRed: true,
    +            includeGreen: true,
    +            includeBlue: true,
    +            includeAlpha: false,
    +            weights: [0,-1,0,-1,5,-1,0,-1,0],
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    threshold - creates a duotone effect - 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.

    + +
    + +
        threshold: 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;
    +
    +        let low = (f.low != null) ? f.low : [lowRed, lowGreen, lowBlue],
    +            high = (f.high != null) ? f.high : [highRed, highGreen, highBlue];
    +
    +        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,
    +            low: low,
    +            high: high,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    tint - 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.

    + +
    + +
        tint: function (f) {
    +        f.actions = [{
    +            action: 'tint-channels',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +            redInRed: (f.redInRed != null) ? f.redInRed : 1,
    +            redInGreen: (f.redInGreen != null) ? f.redInGreen : 0,
    +            redInBlue: (f.redInBlue != null) ? f.redInBlue : 0,
    +            greenInRed: (f.greenInRed != null) ? f.greenInRed : 0,
    +            greenInGreen: (f.greenInGreen != null) ? f.greenInGreen : 1,
    +            greenInBlue: (f.greenInBlue != null) ? f.greenInBlue : 0,
    +            blueInRed: (f.blueInRed != null) ? f.blueInRed : 0,
    +            blueInGreen: (f.blueInGreen != null) ? f.blueInGreen : 0,
    +            blueInBlue: (f.blueInBlue != null) ? f.blueInBlue : 1,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    userDefined - DEPRECATED - this filter method no longer has any effect, returning an unchanged image

    + +
    + +
        userDefined: function (f) {
    +        f.actions = [{
    +            action: 'user-defined-legacy',
    +            lineIn: (f.lineIn != null) ? f.lineIn : '',
    +            lineOut: (f.lineOut != null) ? f.lineOut : '',
    +            opacity: (f.opacity != null) ? f.opacity : 1,
    +        }];
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    yellow - removes blue channel color from the image, and averages the remaining channel colors

    + +
    + +
        yellow: 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,
    +            includeRed: true,
    +            includeGreen: true,
    +            excludeBlue: true,
    +        }];
    +    },
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    Prototype functions

    +

    No additional prototype functions defined

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Filter webworker pool

    +

    Because starting a web worker is an expensive operation, and a number of different filters may be required to render displays across a number of different <canvas> elements in a web page, Scrawl-canvas operates a pool of web workers to perform work as-and-when required.

    +
      +
    • These workers (generally there’s only one) are not exposed to developers using the scrawl object, thus only get called internally
    • +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    START PRODUCTION CODE

    + +
    + +
    const filterPool = [];
    +import { filterUrl } from '../worker/filter-string.js';
    +
    +const requestFilterWorker = function () {
    +    if (!filterPool.length) filterPool.push(new Worker(filterUrl));
    +    return filterPool.shift();
    +};
    +
    +const releaseFilterWorker = function (f) {
    +    filterPool.push(f);
    +};
    +
    +const actionFilterWorker = function (worker, items) {
    +    return new Promise((resolve, reject) => {
    +        worker.onmessage = (e) => {
    +            if (e && e.data && e.data.image) resolve(e.data.image);
    +            else resolve(false);
    +        };
    +        worker.onerror = (e) => {
    +            console.log('error', e.lineno, e.message);
    +            resolve(false);
    +        };
    +        worker.postMessage(items);
    +    });
    +};
    + +
  • + + +
  • +
    + +
    + +
    +

    END PRODUCTION CODE

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    START DEV CODE

    + +
    + +
    /*
    +const filterPool = [];
    +
    +const requestFilterWorker = function () {
    +    if (!filterPool.length) filterPool.push(buildFilterWorker());
    +    return filterPool.shift();
    +};
    +
    +const buildFilterWorker = function () {
    +    let path = import.meta.url.slice(0, -('factory/filter.js'.length)); 
    +    let filterUrl = `${path}worker/filter.js`;
    +    return new Worker(filterUrl);
    +};
    +
    +const releaseFilterWorker = function (f) {
    +    filterPool.push(f);
    +};
    +
    +const actionFilterWorker = function (worker, items) {
    +    return new Promise((resolve, reject) => {
    +        worker.onmessage = (e) => {
    +            if (e && e.data && e.data.image) resolve(e.data.image);
    +            else resolve(false);
    +        };
    +        worker.onerror = (e) => {
    +            console.log('error', e.lineno, e.message);
    +            resolve(false);
    +        };
    +        worker.postMessage(items);
    +    });
    +};
    +*/
    + +
  • + + +
  • +
    + +
    + +
    +

    END DEV CODE

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Factory

    + +
    + +
    const makeFilter = function (items) {
    +
    +    return new Filter(items);
    +};
    +
    +constructors.Filter = Filter;
    + +
  • + + +
  • +
    + +
    +

    Exports

    diff --git a/docs/source/factory/fontAttributes.html b/docs/source/factory/fontAttributes.html index bd894aee1..ee09cbfdb 100644 --- a/docs/source/factory/fontAttributes.html +++ b/docs/source/factory/fontAttributes.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
  • @@ -579,17 +584,13 @@

    FontAttributes attributes

    -

    font-style

    -

    values:

    +

    font - pseudo-attribute String which gets broken down into its component parts.

    -

    CANVAS CONTEXT ENGINE - does not handle oblique slope values

    -
        style: 'normal',
    - @@ -599,19 +600,14 @@

    FontAttributes attributes

    -

    font-variant - the standard indicates that canvas context engine should only recognise normal and small-caps values

    -

    CANVAS CONTEXT ENGINE - complies with the standard, thus Scrawl-canvas ignores all other possible values. Do not use them in font strings:

    +

    font-style - saved in the style String attribute - acceptable values are: normal, italic and oblique. Note that browser handling of oblique (sloped rather than explicitly italic) fonts by their respective canvas context engines is, at best, idiosyncratic.

    -
        variant: 'normal',
    +
        style: 'normal',
    @@ -622,18 +618,11 @@

    FontAttributes attributes

    -

    font-weight

    -

    Values:

    - -

    (normal translates to 400; bold translates to 700)

    -

    CANVAS CONTEXT ENGINE - does seem to recognise both keyword and (x00) number values, but the interpretation of these values can be somewhat erratic. normal and bold keywords are generally respected and actioned as expected.

    +

    font-variant - saved in the variant String attribute - the standard indicates that canvas context engine should only recognise normal and small-caps values. Do not use other possibilities (font-variant-caps, font-variant-numeric, font-variant-ligatures, font-variant-east-asian, font-variant-alternates) in font strings; scrawl-canvas will remove and/or ignore them when it parses the font string.

    -
        weight: 'normal',
    +
        variant: 'normal',
    @@ -644,19 +633,14 @@

    FontAttributes attributes

    -

    font-stretch

    -

    Values:

    +

    font-weight - saved in the weight String attribute - acceptable values are: normal, bold, lighter, bolder; or a number (100. 200, 300, … 900). Bold is generally the equivalent of 700, and normal is 400; lighter/bolder are values relative to the <canvas> element’s computed font weight. Note that browser handling of font weight requirements by their respective canvas context engines is not entirely standards compliant - for instance Safari browsers will generally ignore weight assertions in font strings.

    -

    We ignore number% values (permitted values are 50% - 200%) because the context engine only accepts a single font string and the syntax requirements for that font string are that “font-stretch may only be a single keyword value”

    -

    CANVAS CONTEXT ENGINE - doesn’t seem to recognise font-stretch values (for Garamond), but doesn’t choke on their presence either

    -
        stretch: 'normal',
    +
        weight: 'normal',
    @@ -667,34 +651,72 @@

    FontAttributes attributes

    -

    font-size

    -

    Standard says: “with the ‘font-size’ component converted to CSS pixels”

    +

    font-stretch - saved in the stretch String attribute acceptable values are: normal, semi-condensed, condensed, extra-condensed, ultra-condensed, semi-expanded, expanded, extra-expanded, ultra-expanded. Browser support for these values by the <canvas> element’s context engine is, for the most part, non-existant.

    -

    Values can be:

    -

    Absolute or relative string values:

    + + + +
        stretch: 'normal',
    + + + + +
  • +
    + +
    + +
    +

    font-size - broken into two parts and saved in the sizeValue Number and sizeMetric String, each of which can be set separately. The W3C HTML Canvas 2D Context Recommendation states: “with the ‘font-size’ component converted to CSS pixels”. However, Scrawl-canvas makes every effort to respect and interpret non-px-based font-size requests.

      -
    • xx-small, x-small, small, medium, large, x-large, xx-large
    • -
    • smaller, larger
    • +
    • Note that these pixel values are representative of the <canvas> element’s intrinsic drawing area dimensions, not of any such measure modified by CSS dimension rules (width, height, scale, skew, etc) applied to the canvas.
    -

    Length values:

    +

    Length values relative to some intrinsic propertly of the font - Scrawl-canvas will not attempt to interpret the following, instead treating them as some fixed proportion of the <canvas> element’s computed font size:

      -
    • 1.2em, 1.2ch, 1.2ex, 1.2rem
    • -
    • (experimental!) 1.2cap, 1.2ic, 1.2lh, 1.2rlh
    • -
    • 1.2vh, 1.2vw, 1.2vmin, 1.2vmax
    • -
    • (experimental!) 1.2vi, 1.2vb
    • -
    • 1.2px, 1.2cm, 1.2mm, 1.2in, 1.2pc, 1.2pt
    • -
    • (experimental!) 1.2Q
    • +
    • 1em is the <canvas> element’s computed font size
    • +
    • 1rem is the document root (<html>) element’s computed font size
    • +
    • 1lh is the <canvas> element’s computed font size multiplied by the entity’s line height value
    • +
    • 1rlh is the document root (<html>) element’s computed font size multiplied by the entity’s line height value
    • +
    • 1ex is ≈ ‘0.5em’
    • +
    • 1cap, 1ch, 1ic are all ≈ ‘1em’
    -

    Note that only the following have wide support; these are the only metrics this code tests for: em, ch, ex, rem, vh, vw, vmin, vmax, px, cm, mm, in, pc, pt

    -

    Percent values:

    +

    Length percentage values, with calculation based on the <canvas> element’s computed font size:

      -
    • 1.2%
    • +
    • 120% = ‘1.2em’
    -

    (Percent values clash with font-stretch % values - assume any number followed by a % is a font-size value)

    -

    GOTCHA NOTE 1: font-size is never a number; it must always have a metric. Tweens should be able to handle this requirement with no issues.

    -

    GOTCHA NOTE 2: the canvas context engine refuses to handle line heights appended to the font size value (eg: 12px/1.2) and expects all line height values to = normal. Scrawl-canvas handles line height for multiline phrases using an alternative mechanism. Thus including a /lineheight value in a font string may cause Phrase.set functionality to fail in unexpected ways.

    +

    Non-numerical values (in the Fonts standards, ‘absolute-size’ and ‘relative-size’ keywords) will calculate a size based on the <canvas> element’s computed font size:

    +
      +
    • xx-small = 60% of the computed font size
    • +
    • x-small = 75% of the computed font size
    • +
    • small = 89% of the computed font size
    • +
    • medium = 100% of the computed font size
    • +
    • large = 120% of the computed font size
    • +
    • x-large = 150% of the computed font size
    • +
    • xx-large = 200% of the computed font size
    • +
    • xxx-large = 300% of the computed font size
    • +
    • smaller = 80% of the computed font size
    • +
    • larger = 130% of the computed font size
    • +
    +

    Length values relative to the browser’s viewport (window) dimensions:

    +
      +
    • 1vw and 1vh represent 1% of the viewport’s width and height, respectively
    • +
    • 1vmax - is 1% of the larger viewport dimension
    • +
    • 1vmin - is 1% of the smaller viewport dimension
    • +
    • Scrawl-canvas treats 1vi as ≈ 1vw, and 1vb as ≈ 1vh
    • +
    +

    Absolute length values convert as follow:

    +
      +
    • 1in (inch) = 96px
    • +
    • 1cm (centimeter) = 37.80px
    • +
    • 1mm (millimeter) = 3.78px
    • +
    • 1Q (quarter mm) = 0.95px
    • +
    • 1pc (pica) = 16px
    • +
    • 1pt (point) = 1.33px
    • +
    • 1px (pixel) = 1px
    • +
    +

    Note: we break down the size attribute into two components: sizeValue and sizeMetric. Line height values are ignored and, when present in a font string, may break the code!

    @@ -704,26 +726,17 @@

    FontAttributes attributes

  • -
  • +
  • - +
    -

    font-family

    -
      -
    • Always comes at the end of the string.
    • -
    • More than one can be included, with each separated by commas
    • -
    • Be aware that string may often include quotes around font families with spaces in their names.
    • -
    -

    Generic font names have been extended - values include:

    +

    font-family - any part of the font string that comes after the above declarations.

      -
    • serif, sans-serif, monospace, cursive, fantasy, system-ui, math, emoji, fangsong
    • +
    • It is generally a good idea to include one of the preset defaults - serif, sans-serif, monospace, cursive, fantasy, system-ui, math, emoji, fangsong - at the end of the font or family string, to act as a fallback default as other fonts load.
    • +
    • Scrawl-canvas will attempt to redraw the display when fonts complete their (asynchronous) upload, but this may fail and need to be triggered manually.
    -

    GOTCHA NOTE: current functionality tests the supplied string with the expectation that the font families will be preceded by a font size metric value. To set the fontFamily value direct, put a font size metric at the start of the string - % will do - followed by a space and then the font family string:

    -
    phraseInstance.set({
    -    font: '% "Gill Sans", sans-serif'
    -})
    @@ -735,11 +748,11 @@

    FontAttributes attributes

  • -
  • +
  • - +

    Packet management

    No additional packet functionality required

    @@ -749,11 +762,11 @@

    Packet management

  • -
  • +
  • - +

    Clone management

    No additional clone functionality required

    @@ -763,11 +776,11 @@

    Clone management

  • -
  • +
  • - +

    Kill management

    No additional kill functionality required

    @@ -777,11 +790,11 @@

    Kill management

  • -
  • +
  • - +

    Get, Set, deltaSet

    @@ -794,11 +807,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    size - pseudo-attribute

      @@ -815,11 +828,11 @@

      Get, Set, deltaSet

      -
    • +
    • - +
      • setter breaks the fontSize String into sizeValue and sizeMetric values
      • @@ -833,200 +846,92 @@

        Get, Set, deltaSet

        let res, size = 0, - metric = 'medium';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 9px Garamond

    + metric = 'medium'; + + if (item.indexOf('xx-small') >= 0) metric = 'xx-small'; + else if (item.indexOf('x-small') >= 0) metric = 'x-small'; + else if (item.indexOf('smaller') >= 0) metric = 'smaller'; + else if (item.indexOf('small') >= 0) metric = 'small'; + else if (item.indexOf('medium') >= 0) metric = 'medium'; + else if (item.indexOf('xxx-large') >= 0) metric = 'xxx-large'; + else if (item.indexOf('xx-large') >= 0) metric = 'xx-large'; + else if (item.indexOf('x-large') >= 0) metric = 'x-large'; + else if (item.indexOf('larger') >= 0) metric = 'larger'; + else if (item.indexOf('large') >= 0) metric = 'large'; -
    - -
            if (item.indexOf('xx-small') >= 0) metric = 'xx-small';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 10px Garamond

    + else { + size = 12; + metric = 'px' + } -
    - -
            else if (item.indexOf('x-small') >= 0) metric = 'x-small';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 8.33px Garamond

    + let full, val, suffix; -
    - -
            else if (item.indexOf('smaller') >= 0) metric = 'smaller';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 13px Garamond

    + let r = item.match(/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i); -
    - -
            else if (item.indexOf('small') >= 0) metric = 'small';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 32px Garamond

    + if (Array.isArray(r)) { -
    - -
            else if (item.indexOf('xx-large') >= 0) metric = 'xx-large';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 24px Garamond

    + [full, val, suffix] = r; -
    - -
            else if (item.indexOf('x-large') >= 0) metric = 'x-large';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 12px Garamond

    + if (val && suffix && val != '.') { -
    - -
            else if (item.indexOf('larger') >= 0) metric = 'larger';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 18px Garamond

    + size = val; + metric = suffix; + } + } + else { -
    - -
            else if (item.indexOf('large') >= 0) metric = 'large';
    - -
  • - - -
  • -
    - -
    - -
    -

    Canvas context engine (Chrome on MacBook Pro) interprets this as 16px Garamond

    + r = item.match(/\/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i); -
    - -
            else if (item.indexOf('medium') >= 0) metric = 'medium';
    -        else {
    -            size = 12;
    -            metric = 'px'
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    For when the size has stuff before it in the string (which can, sadly, include numbers)

    + if (Array.isArray(r)) { -
    - -
            if (/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(item)) {
    -            
    -            res = item.match(/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i);
    -            size = (res[1] !== '.') ? parseFloat(res[1]) : 12;
    -            metric = res[2];
    -        }
    - -
  • - - -
  • -
    - -
    - -
    -

    For when the size starts the string

    + [full, val, suffix] = r; -
    - -
            else if (/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(item)) {
    -            
    -            res = item.match(/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i);
    -            size = (res[1] !== '.') ? parseFloat(res[1]) : 12;
    -            metric = res[2];
    +                if (val && suffix && val != '.') {
    +
    +                    size = val;
    +                    metric = suffix;
    +                }
    +            }
    +        }
    +        if (size !== this.sizeValue) {
    +
    +            this.sizeValue = size;
    +            this.dirtyFont = true;
             }
    +        if (metric !== this.sizeMetric) {
     
    -        this.sizeValue = size;
    -        this.sizeMetric = metric;
    +            this.sizeMetric = metric;
    +            this.dirtyFont = true;
    +        }
         }
    -};
    +}; + +S.sizeValue = function (item) { + + if (xt(item) && item !== this.sizeValue) { + + this.sizeValue = item; + this.dirtyFont = true; + } +} + +S.sizeMetric = function (item) { + + if (xt(item) && item !== this.sizeMetric) { + + this.sizeMetric = item; + this.dirtyFont = true; + } +}
  • -
  • +
  • - +

    font - pseudo-attribute which calls various functions to break down the font String into its constituent parts

    @@ -1048,11 +953,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    style

    @@ -1060,25 +965,29 @@

    Get, Set, deltaSet

    S.style = function (item) {
     
    -    let v = 'normal';
    -
         if (xt(item)) {
     
    +        let v = 'normal';
    +
             v = (item.indexOf('italic') >= 0) ? 'italic' : v;
             v = (item.indexOf('oblique') >= 0) ? 'oblique' : v;
    -    }
     
    -    this.style = v;
    +        if (v !== this.style) {
    +
    +            this.style = v;
    +            this.dirtyFont = true;
    +        }
    +    }
     };
  • -
  • +
  • - +

    variant

    @@ -1086,21 +995,28 @@

    Get, Set, deltaSet

    S.variant = function (item) {
     
    -    let v = 'normal';
    +    if (xt(item)) {
    +
    +        let v = 'normal';
     
    -    v = (item.indexOf('small-caps') >= 0) ? 'small-caps' : v;
    +        v = (item.indexOf('small-caps') >= 0) ? 'small-caps' : v;
     
    -    this.variant = v;
    +        if (v !== this.variant) {
    +
    +            this.variant = v;
    +            this.dirtyFont = true;
    +        }
    +    }
     };
  • -
  • +
  • - +

    weight

    @@ -1108,18 +1024,18 @@

    Get, Set, deltaSet

    S.weight = function (item) {
     
    -    let v = 'normal';
    +    if (xt(item)) {
     
    -    if (xt(item)) {
    + let v = 'normal';
  • -
  • +
  • - +

    Handling direct entry of numbers

    @@ -1135,11 +1051,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    Putting spaces around the number should help identify it as a Weight value within the font string the string

    @@ -1158,11 +1074,11 @@

    Get, Set, deltaSet

  • -
  • +
  • - +

    Also need to capture instances where a number value has been directly set with no other font attributes around it

    @@ -1170,19 +1086,23 @@

    Get, Set, deltaSet

                v = (/^\d00$/.test(item)) ? item : v;
             }
    -    }
     
    -    this.weight = v;
    +        if (v !== this.weight) {
    +
    +            this.weight = v;
    +            this.dirtyFont = true;
    +        }
    +    }
     };
  • -
  • +
  • - +

    stretch

    @@ -1190,67 +1110,285 @@

    Get, Set, deltaSet

    S.stretch = function (item) {
     
    -    let v = 'normal';
    -
         if (xt(item)) {
     
    +        let v = 'normal';
    +
             v = (item.indexOf('semi-condensed') >= 0) ? 'semi-condensed' : v;
             v = (item.indexOf('condensed') >= 0) ? 'condensed' : v;
             v = (item.indexOf('extra-condensed') >= 0) ? 'extra-condensed' : v;
             v = (item.indexOf('ultra-condensed') >= 0) ? 'ultra-condensed' : v;
    -        v = (item.indexOf('semi-condensed') >= 0) ? 'semi-condensed' : v;
    -        v = (item.indexOf('condensed') >= 0) ? 'condensed' : v;
    -        v = (item.indexOf('extra-condensed') >= 0) ? 'extra-condensed' : v;
    -        v = (item.indexOf('ultra-condensed') >= 0) ? 'ultra-condensed' : v;
    -    }
    +        v = (item.indexOf('semi-expanded') >= 0) ? 'semi-expanded' : v;
    +        v = (item.indexOf('expanded') >= 0) ? 'expanded' : v;
    +        v = (item.indexOf('extra-expanded') >= 0) ? 'extra-expanded' : v;
    +        v = (item.indexOf('ultra-expanded') >= 0) ? 'ultra-expanded' : v;
     
    -    this.stretch = v;
    +        if (v !== this.stretch) {
    +
    +            this.stretch = v;
    +            this.dirtyFont = true;
    +        }
    +    }
     };
  • -
  • +
  • - +

    family

    -
    S.family = function (item) {
    +            
    P.rfsTestArray1 = ['italic','oblique','small-caps','normal','bold','lighter','bolder','ultra-condensed','extra-condensed','semi-condensed','condensed','ultra-expanded','extra-expanded','semi-expanded','expanded','xx-small','x-small','small','medium','xxx-large','xx-large','x-large','large'];
    +P.rfsTestArray2 = ['0','1','2','3','4','5','6','7','8','9'];
    +S.family = function (item) {
     
         if (xt(item)) {
     
    -        let guess = item.match(/( xx-small| x-small| small| medium| large| x-large| xx-large| smaller| larger|\d%|\dem|\dch|\dex|\drem|\dvh|\dvw|\dvmin|\dvmax|\dpx|\dcm|\dmm|\din|\dpc|\dpt) (.*)$/i);
    +        let v = 'sans-serif';
    +
    +        let itemArray = item.split(' '),
    +            len = itemArray.length;
    +
    +        if (len === 1) v = item;
    +
    +        let counter = 0,
    +            flag = true;
    +
    +        while (flag) {
    +
    +            if (counter === len) flag = false;
    +            else {
    +
    +                let el = itemArray[counter];
    +
    +                if (!el.length) counter++;
    +                else if (this.rfsTestArray1.indexOf(el) >= 0) counter++;
    +                else if (this.rfsTestArray2.indexOf(el[0]) >= 0) counter++;
    +                else flag = false;
    +            }
    +        }
    +        if (counter < len) v = itemArray.slice(counter).join(' ');
    +
    +        if (v !== this.family) {
     
    -        this.family = (guess && guess[2]) ? guess[2] : item;
    +            this.family = v;
    +            this.dirtyFont = true;
    +        }
         }
     };
  • -
  • +
  • - +

    Prototype functions

    +
    P.getFontString = function() {
    +
    +    if (!this.dirtyFont && this.temperedFontString) return this.temperedFontString;
    +    else return this.buildFont();
    +}
    +
    +P.updateMetadata = function (scale, lineHeight, host) {
    +
    +    if (scale && scale.toFixed && scale > 0 && scale !== this.scale) {
    +
    +        this.scale = scale;
    +        this.dirtyFont = true;
    +    }
    +
    +    if (lineHeight && lineHeight.toFixed && lineHeight > 0 && lineHeight !== this.lineHeight) {
    +
    +        this.lineHeight = lineHeight;
    +        this.dirtyFont = true;
    +    }
    +    
    +    let currentHost = (this.host && this.host.type && this.host.type === 'Cell') ? this.host.name : '';
    +    if (host && host.type && host.type === 'Cell' && host.name !== currentHost) {
    +
    +        this.host = host;
    +        this.dirtyFont = true;
    +    }
    +};
    +
    +P.calculateSize = function () {
    +
    +    if (this.host) {
    +
    +        let {scale, lineHeight, host, sizeValue, sizeMetric} = this;
    +
    +        let gcfs = host.getComputedFontSizes(),
    +            parentSize, rootSize, viewportWidth, viewportHeight;
    +
    +        if (!gcfs) {
    +
    +            if (['in', 'cm', 'mm', 'Q', 'pc', 'pt', 'px'].indexOf(sizeMetric) < 0) {
    +
    +                this.dirtyFont = true;
    +                return '12px';
    +            }
    +        }
    +        else {
    +
    +            [parentSize, rootSize, viewportWidth, viewportHeight] = host.getComputedFontSizes();
    +        }
    +
    +        if (isNaN(sizeValue)) sizeValue = 12;
    +
    +        let res = parentSize;
    +
    +        switch (sizeMetric) {
    +
    +            case 'rem' :
    +                res = rootSize * sizeValue;
    +                break;
    +
    +            case 'em' :
    +                res = parentSize * sizeValue;
    +                break;
    +
    +            case 'rlh' :
    +                res = (rootSize * lineHeight) * sizeValue;
    +                break;
    +
    +            case 'lh' :
    +                res = (parentSize * lineHeight) * sizeValue;
    +                break;
    +
    +            case 'ex' :
    +                res = (parentSize / 2) * sizeValue;
    +                break;
    +
    +            case 'cap' :
    +                res = parentSize * sizeValue;
    +                break;
    +
    +            case 'ch' :
    +                res = parentSize * sizeValue;
    +                break;
    +
    +            case 'ic' :
    +                res = parentSize * sizeValue;
    +                break;
    +
    +            case '%' :
    +                res = (parentSize / 100) * sizeValue;
    +                break;
    +
    +            case 'vw' :
    +                res = (viewportWidth / 100) * sizeValue;
    +                break;
    +
    +            case 'vh' :
    +                res = (viewportHeight / 100) * sizeValue;
    +                break;
    +
    +            case 'vmax' :
    +                res = (Math.max(viewportWidth, viewportHeight) / 100) * sizeValue;
    +                break;
    +
    +            case 'vmin' :
    +                res = (Math.min(viewportWidth, viewportHeight) / 100) * sizeValue;
    +                break;
    +
    +            case 'vi' :
    +                res = (viewportWidth / 100) * sizeValue;
    +                break;
    +
    +            case 'vb' :
    +                res = (viewportHeight / 100) * sizeValue;
    +                break;
    +
    +            case 'in' :
    +                res = 96 * sizeValue;
    +                break;
    +
    +            case 'cm' :
    +                res = 37.8 * sizeValue;
    +                break;
    +
    +            case 'mm' :
    +                res = 3.78 * sizeValue;
    +                break;
    +
    +            case 'Q' :
    +                res = 0.95 * sizeValue;
    +                break;
    +
    +            case 'pc' :
    +                res = 16 * sizeValue;
    +                break;
    +
    +            case 'pt' :
    +                res = 1.33 * sizeValue;
    +                break;
    +
    +            case 'px' :
    +                res = sizeValue;
    +                break;
    +
    +            case 'xx-small' :
    +                res = 0.6 * parentSize;
    +                break;
    +
    +            case 'x-small' :
    +                res = 0.75 * parentSize;
    +                break;
    +
    +            case 'smaller' :
    +                res = 0.8 * parentSize;
    +                break;
    +
    +            case 'small' :
    +                res = 0.89 * parentSize;
    +                break;
    +
    +            case 'xxx-large' :
    +                res = 3 * parentSize;
    +                break;
    +
    +            case 'xx-large' :
    +                res = 2 * parentSize;
    +                break;
    +
    +            case 'x-large' :
    +                res = 1.5 * parentSize;
    +                break;
    +
    +            case 'larger' :
    +                res = 1.3 * parentSize;
    +                break;
    +
    +            case 'large' :
    +                res = 1.2 * parentSize;
    +                break;
    +        }
    +        return `${res * scale}px`;
    +    }
    +    return '12px';
    +}
    +
  • -
  • +
  • - +

    buildFont - internal function

      @@ -1259,7 +1397,9 @@

      Prototype functions

    -
    P.buildFont = function (scale = 1) {
    +            
    P.buildFont = function () {
    +
    +    this.dirtyFont = false;
     
         let font = ''
     
    @@ -1267,61 +1407,61 @@ 

    Prototype functions

    if (this.variant !== 'normal') font += `${this.variant} `; if (this.weight !== 'normal') font += `${this.weight} `; if (this.stretch !== 'normal') font += `${this.stretch} `; - if (this.sizeValue) font += `${this.sizeValue * scale}${this.sizeMetric} `; - else font += `${this.sizeMetric} `; + + font += `${this.calculateSize()} `; font += `${this.family}`;
  • -
  • +
  • - +

    Temper the font string. Submit it to a canvas context engine to see what it makes of it

        let myCell = requestCell();
    -
         myCell.engine.font = font;
         font = myCell.engine.font;
    -
         releaseCell(myCell);
    +
    +    this.temperedFontString = font;
    +
         return font;
     };
  • -
  • +
  • - +

    update - sets items, then calls buildFont

    -
    P.update = function (scale = 1, items) {
    +            
    P.update = function (items) {
     
         if (items) this.set(items);
    -
    -    return this.buildFont(scale);
    +    return this.getFontString();
     };
  • -
  • +
  • - +

    Factory

    @@ -1336,11 +1476,11 @@

    Factory

  • -
  • +
  • - +

    Exports

    diff --git a/docs/source/factory/gradient.html b/docs/source/factory/gradient.html index bbac55995..cc36ec9c8 100644 --- a/docs/source/factory/gradient.html +++ b/docs/source/factory/gradient.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
  • diff --git a/docs/source/factory/grid.html b/docs/source/factory/grid.html index 0d8b0adf2..a844a79af 100644 --- a/docs/source/factory/grid.html +++ b/docs/source/factory/grid.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/group.html b/docs/source/factory/group.html index d07c1e02a..adbf724e9 100644 --- a/docs/source/factory/group.html +++ b/docs/source/factory/group.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -496,7 +501,7 @@

    Imports

    -
    import { constructors, cell, artefact, group, entity } from '../core/library.js';
    +            
    import { constructors, cell, artefact, group, entity, asset } from '../core/library.js';
     import { mergeOver, pushUnique, removeItem } from '../core/utilities.js';
     import { scrawlCanvasHold } from '../core/document.js';
     
    @@ -1571,7 +1576,22 @@ 

    Prototype functions

            filterEngine.setTransform(1, 0, 0, 1, 0, 0);
     
             let myimage = filterEngine.getImageData(0, 0, filterElement.width, filterElement.height),
    -            worker = requestFilterWorker();
    +            worker = requestFilterWorker();
    + + + + +
  • +
    + +
    + +
    +

    NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET’S IMAGEDATA AT THIS POINT

    + +
    + +
            self.preprocessFilters(self.currentFilters);
     
             actionFilterWorker(worker, {
                 image: myimage,
    @@ -1582,11 +1602,11 @@ 

    Prototype functions

  • -
  • +
  • - +

    Copy the filter worker’s output back onto the filterHost canvas

    @@ -1602,11 +1622,11 @@

    Prototype functions

  • -
  • +
  • - +

    On success, cleanup and resolve

    @@ -1628,11 +1648,11 @@

    Prototype functions

  • -
  • +
  • - +

    stashAction - internal function which creates an ImageAsset object (and, as determined by the setting of the Group’s stashOutputAsAsset flag, an <img> element which gets attached to the DOM document in the scrawlCanvasHold hidden <div> element) from the Group’s entity’s output.

    NOTE: the stashOutput and stashOutputAsAsset flags are not Group object attributes. They are set on the group as a result of invoking the scrawl.createImageFromGroup function, and will be set to false as soon as the Group.stashAction function runs (in other words, stashing a Group’s output is a one-off operation).

    @@ -1695,11 +1715,11 @@

    Prototype functions

  • -
  • +
  • - +

    getCellCoverage - internal function which calculates the cumulative coverage of the Group’s artefacts. Used as part of the stashAction functionality

    @@ -1739,11 +1759,11 @@

    Prototype functions

  • -
  • +
  • - +

    Artefact management

    Artefacts should be added to, and removed from, the Group object using the functions detailed below.

    @@ -1754,11 +1774,11 @@

    Artefact management

  • -
  • +
  • - +

    addArtefacts - an artefact can belong to more than one Group

    @@ -1784,11 +1804,11 @@

    Artefact management

  • -
  • +
  • - +

    removeArtefacts - remove an artefact from this Group

    @@ -1812,11 +1832,11 @@

    Artefact management

  • -
  • +
  • - +

    moveArtefactsIntoGroup - remove the artefact from their current Group (which is generally their Display cycle group, as set in their group and/or host attribute) and add them to this Group

    @@ -1855,11 +1875,11 @@

    Artefact management

  • -
  • +
  • - +

    clearArtefacts - remove all artefacts from this Group

    @@ -1876,11 +1896,11 @@

    Artefact management

  • -
  • +
  • - +

    updateArtefacts - passes the items argument object through to each of the Group’s artefact’s setDelta function

    @@ -1895,11 +1915,11 @@

    Artefact management

  • -
  • +
  • - +

    setArtefacts - passes the items argument object through to each of the Group’s artefact’s set function

    @@ -1914,11 +1934,11 @@

    Artefact management

  • -
  • +
  • - +

    updateByDelta - passes the items argument object through to each of the Group’s artefact’s updateByDelta function

    @@ -1933,11 +1953,11 @@

    Artefact management

  • -
  • +
  • - +

    reverseByDelta - passes the items argument object through to each of the Group’s artefact’s reverseByDelta function

    @@ -1952,11 +1972,11 @@

    Artefact management

  • -
  • +
  • - +

    addArtefactClasses - specifically for non-entity artefacts. Passes the items argument String through to each of the Group’s artefact’s addClasses function

    @@ -1971,11 +1991,11 @@

    Artefact management

  • -
  • +
  • - +

    removeArtefactClasses - specifically for non-entity artefacts. Passes the items argument String through to each of the Group’s artefact’s removeClasses function

    @@ -1990,11 +2010,11 @@

    Artefact management

  • -
  • +
  • - +

    cascadeAction - internal helper function for the above artefact manipulation functionality

    @@ -2014,11 +2034,11 @@

    Artefact management

  • -
  • +
  • - +

    setDeltaValues - passes the items argument object through to each of the Group’s artefact’s setDeltaValues function

    @@ -2034,11 +2054,11 @@

    Artefact management

  • -
  • +
  • - +

    We can add and remove filters to each of the Group’s entity artefacts using the following functions. The argument for each function can be one or more Filter name-Strings, or the Filter objects themselves, separated by commas.

    @@ -2047,11 +2067,11 @@

    Artefact management

  • -
  • +
  • - +

    addFiltersToEntitys

    @@ -2071,11 +2091,11 @@

    Artefact management

  • -
  • +
  • - +

    removeFiltersFromEntitys

    @@ -2095,11 +2115,11 @@

    Artefact management

  • -
  • +
  • - +

    clearFiltersFromEntitys - clears all filters from all the Group’s entity artefacts.

    @@ -2119,11 +2139,11 @@

    Artefact management

  • -
  • +
  • - +

    Collision functionality

    The getArtefactAt function checks to see if any of the Group object’s artefacts are located at the supplied coordinates in the argument object.

    @@ -2169,11 +2189,11 @@

    Collision functionality

  • -
  • +
  • - +

    The getAllArtefactsAt function returns an array of hit reports from all of the Group object’s artefacts located at the supplied coordinates in the argument object. The artefact with the highest order attribute value will be returned first in the response array.

    The function will always return an array of hit reports, or an empty array if no hits are reported.

    @@ -2217,11 +2237,11 @@

    Collision functionality

  • -
  • +
  • - +

    The getArtefactCollisions function returns an array of hit reports from all of the Group object’s artefacts which are currently in collision with the sensor coordinates supplied by the artefact argument. The function will always return an array of hit reports, or an empty array if no hits are reported.

    The argument must be either the artefact object itself, or its String name.

    @@ -2233,11 +2253,11 @@

    Collision functionality

  • -
  • +
  • - +

    return empty array if no argument, or the argument is not an artefact, or the Group is empty

    @@ -2250,11 +2270,11 @@

    Collision functionality

  • -
  • +
  • - +

    Return empty array if the artefact argument has not been setup to check for collisions

    @@ -2269,11 +2289,11 @@

    Collision functionality

  • -
  • +
  • - +

    Get entity collision data

    @@ -2284,11 +2304,11 @@

    Collision functionality

  • -
  • +
  • - +

    Winnow step 1: don’t check the entity itself, or any missing or malformed artefacts in the Group

    @@ -2305,11 +2325,11 @@

    Collision functionality

  • -
  • +
  • - +

    Winnow step 2: exclude all targets outside the artefact/target collision radius

    @@ -2341,11 +2361,11 @@

    Collision functionality

  • -
  • +
  • - +

    Winnow step 3: perform hit checks on remaining targets - this will not retrieve targets entirely inside the artefact

    @@ -2363,11 +2383,11 @@

    Collision functionality

  • -
  • +
  • - +

    return filtered results

    @@ -2381,11 +2401,11 @@

    Collision functionality

  • -
  • +
  • - +

    Factory

    let faces = scrawl.makeGroup({
    @@ -2409,11 +2429,11 @@ 

    Factory

  • -
  • +
  • - +

    Exports

    diff --git a/docs/source/factory/imageAsset.html b/docs/source/factory/imageAsset.html index db02be19b..53ec6ed08 100644 --- a/docs/source/factory/imageAsset.html +++ b/docs/source/factory/imageAsset.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
  • diff --git a/docs/source/factory/line.html b/docs/source/factory/line.html index e5ccc00dc..21d294d0f 100644 --- a/docs/source/factory/line.html +++ b/docs/source/factory/line.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
    diff --git a/docs/source/factory/loom.html b/docs/source/factory/loom.html index f4a0e1d2f..edf491239 100644 --- a/docs/source/factory/loom.html +++ b/docs/source/factory/loom.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
    @@ -451,7 +456,7 @@

    Loom factory

    A Loom offers functionality to render an image onto a <canvas> element, where the image is not a rectangle - it can have curved borders. It can also offer the illusion of flat 3D images in the canvas, giving them perspective.

    -

    Loom entitys are composite entitys - an wentity that relies on other entitys for its basic functionality.

    +

    Loom entitys are composite entitys - an entity that relies on other entitys for its basic functionality.

    • Every Loom object requires two (or one) path-enabled Shape entitys to act as its left and right tracks.
    • A Loom entity also requires a Picture entity to act as its image source.
    • diff --git a/docs/source/factory/mesh.html b/docs/source/factory/mesh.html new file mode 100644 index 000000000..e9203d2b1 --- /dev/null +++ b/docs/source/factory/mesh.html @@ -0,0 +1,2608 @@ + + + + + Mesh factory + + + + + +
      +
      + + + +
        + + + +
      • +
        + +
        + +
        +

        Mesh factory

        +

        The Scrawl-canvas Mesh entity applies a Net particle system to an image, allowing the image to be deformed by dragging particles around the canvas. This is a similar concept to Photoshop warp meshes and (more distantly) the Gimp’s cage tool.

        +

        Mesh entitys are composite entitys - an entity that relies on other entitys for its basic functionality.

        +
          +
        • Every Mesh object requires a Net entity create the grid that it uses for transforming its image.
        • +
        • A Mesh entity also requires a Picture entity to act as its image source.
        • +
        • Meshes can (in theory) use CSS color Strings for their strokeStyle values, alongside Gradient, RadialGradient, Color and Pattern objects.
        • +
        • They can (in theory) use Anchor objects for user navigation.
        • +
        • They can (in theory) be rendered to the canvas by including them in a Cell object’s Group.
        • +
        • They can (in theory) be animated directly, or using delta animation, or act as the target for Tween animations.
        • +
        • Meshes can (in theory) be cloned, and killed.
        • +
        +

        Note that this is experimental technology!

        +
          +
        • The Mesh entity code base shares many similarities to that of the Loom entity; some of the code has been copied over from that file directly.
        • +
        • Current code does not use position or entity mixins, meaning much of the code here has been copied over from those mixins (DRY issue).
        • +
        • TODO: packet management, clone and kill functionality not yet tested. Much of the other functionality also lacks tests.
        • +
        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Demos:

        + + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Imports

        + +
        + +
        import { constructors, artefact } from '../core/library.js';
        +import { currentGroup } from '../core/document.js';
        +import { mergeOver, mergeDiscard, pushUnique, λnull, λthis, xta } from '../core/utilities.js';
        +
        +import { makeState } from '../factory/state.js';
        +import { requestCell, releaseCell } from '../factory/cell.js';
        +
        +import baseMix from '../mixin/base.js';
        +import anchorMix from '../mixin/anchor.js';
        + +
      • + + +
      • +
        + +
        + +
        +

        Mesh constructor

        + +
        + +
        const Mesh = function (items = {}) {
        +
        +    this.makeName(items.name);
        +    this.register();
        +
        +    this.set(this.defs);
        +
        +    this.state = makeState();
        +
        +    if (!items.group) items.group = currentGroup;
        +
        +    this.onEnter = λnull;
        +    this.onLeave = λnull;
        +    this.onDown = λnull;
        +    this.onUp = λnull;
        +
        +    this.delta = {};
        +
        +    this.set(items);
        +
        +    this.fromPathData = [];
        +    this.toPathData = [];
        +
        +    this.watchFromPath = null;
        +    this.watchIndex = -1;
        +    this.engineInstructions = [];
        +    this.engineDeltaLengths = [];
        +
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Mesh prototype

        + +
        + +
        let P = Mesh.prototype = Object.create(Object.prototype);
        +P.type = 'Mesh';
        +P.lib = 'entity';
        +P.isArtefact = true;
        +P.isAsset = false;
        + +
      • + + +
      • +
        + +
        + +
        +

        Mixins

        + +
        + +
        P = baseMix(P);
        +P = anchorMix(P);
        + +
      • + + +
      • +
        + +
        + +
        +

        Mesh attributes

        + + +
        + +
        let defaultAttributes = {
        + +
      • + + +
      • +
        + +
        + +
        +

        net - A Mesh entity requires a Net entity, set to generate a weak or strong net, to supply Particle objects to act as its mapping coordinates.

        + +
        + +
            net: null,
        + +
      • + + +
      • +
        + +
        + +
        +

        isHorizontalCopy - Boolean flag - Copying the source image to the output happens, by default, by rows - which effectively means the struts are on the left-hand and right-hand edges of the image.

        +
          +
        • To change this to columns (which sets the struts to the top and bottom edges of the image) set the attribute to false
        • +
        + +
        + +
            isHorizontalCopy: true,
        + +
      • + + +
      • +
        + +
        + +
        +

        source - The Picture entity source for this Mesh. For initialization and/or set, we can supply either the Picture entity itself, or its name-String value.

        +
          +
        • The content image displayed by the Mesh entity are set in the Picture entity, not the Mesh, and can be any artefact supported by the Picture (image, video, sprite, or a Cell artefact).
        • +
        • Note that any filters should be applied to the Picture entity; Mesh entitys do not support filter functionality but will apply a Picture’s filters to the source image as-and-where appropriate.
        • +
        + +
        + +
            source: null,
        + +
      • + + +
      • +
        + +
        + +
        +

        sourceIsVideoOrSprite - Boolean flag - If the Picture entity is hosting a video or sprite asset, we need to update the input on every frame.

        +
          +
        • It’s easier to tell the Mesh entity to do this using a flag, rather than get the Picture entity to update all its Mesh subscribers on every display cycle.
        • +
        • For Pictures using image assets the flag must be set to false (the default); setting the flag to true will significantly degrade display and animation performance.
        • +
        + +
        + +
            sourceIsVideoOrSprite: false,
        + +
      • + + +
      • +
        + +
        + +
        +

        The current Frame drawing process often leads to moiré interference patterns appearing in the resulting image. Scrawl-canvas uses a resize trick to blur out these patterns.

        +

        interferenceLoops (positive integer Number), interferenceFactor (positive float Number) - The interferenceFactor attribute sets the resizing ratio; while he interferenceLoops attribute sets the number of times the image gets resized.

        +
          +
        • If inteference patterns still appear in the final image, tweak these values to see if a better output can be achieved.
        • +
        + +
        + +
            interferenceLoops: 2,
        +    interferenceFactor: 1.03,
        + +
      • + + +
      • +
        + +
        + +
        +

        The Mesh entity does not use the position or entity mixins (used by most other entitys) as its positioning is entirely dependent on the position, rotation, scale etc of its constituent Shape path entity struts.

        +

        It does, however, use these attributes (alongside their setters and getters): visibility, order, delta, host, group, anchor, collides.

        + +
        + +
            visibility: true,
        +    order: 0,
        +    delta: null,
        +    host: null,
        +    group: null,
        +    anchor: null,
        + +
      • + + +
      • +
        + +
        + +
        +

        noCanvasEngineUpdates - Boolean flag - Canvas engine updates are required for the Mesh’s border - strokeStyle and line styling; if a Mesh is to be drawn without a border, then setting this flag to true may help improve rendering efficiency.

        + +
        + +
            noCanvasEngineUpdates: false,
        + +
      • + + +
      • +
        + +
        + +
        +

        noDeltaUpdates - Boolean flag - Mesh entitys support delta animation - achieved by updating the ...path attributes by appropriate (and small!) values. If the Mesh is not going to be animated by delta values, setting the flag to true may help improve rendering efficiency.

        + +
        + +
            noDeltaUpdates: false,
        + +
      • + + +
      • +
        + +
        + +
        +

        onEnter, onLeave, onDown, onUp - Mesh entitys support collision detection, reporting a hit when a test coordinate falls within the Mesh’s output image. As a result, Looms can also accept and act on the four on functions - see entity event listener functions for more details.

        + +
        + +
            onEnter: null,
        +    onLeave: null,
        +    onDown: null,
        +    onUp: null,
        + +
      • + + +
      • +
        + +
        + +
        +

        noUserInteraction - Boolean flag - To switch off collision detection for a Mesh entity - which might help improve rendering efficiency - set the flag to true.

        + +
        + +
            noUserInteraction: false,
        + +
      • + + +
      • +
        + +
        + +
        +

        Anchor objects can be assigned to Mesh entitys, meaning the following attributes are supported:

        +
          +
        • anchorDescription
        • +
        • anchorType
        • +
        • anchorTarget
        • +
        • anchorRel
        • +
        • anchorReferrerPolicy
        • +
        • anchorPing
        • +
        • anchorHreflang
        • +
        • anchorHref
        • +
        • anchorDownload
        • +
        +

        And the anchor attributes can also be supplied as a key:value object assigned to the anchor attribute:

        +
            anchor: {
        +        description
        +        download
        +        href
        +        hreflang
        +        ping
        +        referrerpolicy
        +        rel:
        +        target:
        +        anchorType
        +        clickAction: 
        +    }
        +

        method - All normal Scrawl-canvas entity stamping methods are supported.

        + +
        + +
            method: 'fill',
        + +
      • + + +
      • +
        + +
        + +
        +

        Mesh entitys support appropriate styling attributes, mainly for their stroke styles (used with the draw, drawAndFill, fillAndDraw, drawThenFill and fillThenDraw stamping methods).

        +
          +
        • These state attributes are stored directly on the object, rather than in a separate State object.
        • +
        +

        The following attributes are thus supported:

        +

        Alpha and Composite operations will be applied to both the Mesh entity’s border (the Shape entitys, with connecting lines between their paths’ start and end points) and fill (the image displayed between the Mesh’s struts)

        +
          +
        • globalAlpha
        • +
        • globalCompositeOperation
        • +
        +

        All line attributes are supported

        +
          +
        • lineWidth
        • +
        • lineCap
        • +
        • lineJoin
        • +
        • lineDash
        • +
        • lineDashOffset
        • +
        • miterLimit
        • +
        +

        The Mesh entity’s strokeStyle can be any style supported by Scrawl-canvas - color strings, gradient objects, and pattern objects

        +
          +
        • strokeStyle
        • +
        +

        The shadow attributes will only be applied to the stroke (border), not to the Mesh’s fill (image)

        +
          +
        • shadowOffsetX
        • +
        • shadowOffsetY
        • +
        • shadowBlur
        • +
        • shadowColor
        • +
        + +
        + +
        };
        +P.defs = mergeOver(P.defs, defaultAttributes);
        + +
      • + + +
      • +
        + +
        + +
        +

        Packet management

        + +
        + +
        P.packetExclusions = pushUnique(P.packetExclusions, ['pathObject', 'state']);
        +P.packetExclusionsByRegex = pushUnique(P.packetExclusionsByRegex, ['^(local|dirty|current)', 'Subscriber$']);
        +P.packetCoordinates = pushUnique(P.packetCoordinates, []);
        +P.packetObjects = pushUnique(P.packetObjects, ['group', 'net', 'source']);
        +P.packetFunctions = pushUnique(P.packetFunctions, ['onEnter', 'onLeave', 'onDown', 'onUp']);
        +
        +P.processPacketOut = function (key, value, includes) {
        +
        +    let result = true;
        +
        +    if(includes.indexOf(key) < 0 && value === this.defs[key]) result = false;
        +
        +    return result;
        +};
        +
        +P.finalizePacketOut = function (copy, items) {
        +
        +    let stateCopy = JSON.parse(this.state.saveAsPacket(items))[3];
        +    copy = mergeOver(copy, stateCopy);
        +
        +    copy = this.handlePacketAnchor(copy, items);
        +
        +    return copy;
        +};
        +
        +P.handlePacketAnchor = function (copy, items) {
        +
        +    if (this.anchor) {
        +
        +        let a = JSON.parse(this.anchor.saveAsPacket(items))[3];
        +        copy.anchor = a;
        +    }
        +    return copy;
        +}
        + +
      • + + +
      • +
        + +
        + +
        +

        Clone management

        +

        TODO - this functionality is currently disabled, need to enable it and make it work properly

        + +
        + +
        P.clone = λthis;
        + +
      • + + +
      • +
        + +
        + +
        +

        Kill management

        +

        No additional kill functionality required

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Get, Set, deltaSet

        + +
        + +
        let G = P.getters,
        +    S = P.setters,
        +    D = P.deltaSetters;
        + +
      • + + +
      • +
        + +
        + +
        +

        get - copied over from the entity mixin

        + +
        + +
        P.get = function (item) {
        +
        +    let getter = this.getters[item];
        +
        +    if (getter) return getter.call(this);
        +
        +    else {
        +
        +        let def = this.defs[item],
        +            state = this.state,
        +            val;
        +
        +        if (typeof def != 'undefined') {
        +
        +            val = this[item];
        +            return (typeof val != 'undefined') ? val : def;
        +        }
        +
        +        def = state.defs[item];
        +
        +        if (typeof def != 'undefined') {
        +
        +            val = state[item];
        +            return (typeof val != 'undefined') ? val : def;
        +        }
        +        return undef;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        set - copied over from the entity mixin.

        + +
        + +
        P.set = function (items = {}) {
        +
        +    if (Object.keys(items).length) {
        +
        +        let setters = this.setters,
        +            defs = this.defs,
        +            state = this.state,
        +            stateSetters = (state) ? state.setters : {},
        +            stateDefs = (state) ? state.defs : {},
        +            predefined, stateFlag;
        +
        +        Object.entries(items).forEach(([key, value]) => {
        +
        +            if (key && key !== 'name' && value != null) {
        +
        +                predefined = setters[key];
        +                stateFlag = false;
        +
        +                if (!predefined) {
        +
        +                    predefined = stateSetters[key];
        +                    stateFlag = true;
        +                }
        +
        +                if (predefined) predefined.call(stateFlag ? this.state : this, value);
        +                else if (typeof defs[key] !== 'undefined') this[key] = value;
        +                else if (typeof stateDefs[key] !== 'undefined') state[key] = value;
        +            }
        +        }, this);
        +    }
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        setDelta - copied over from the entity mixin.

        + +
        + +
        P.setDelta = function (items = {}) {
        +
        +    if (Object.keys(items).length) {
        +
        +        let setters = this.deltaSetters,
        +            defs = this.defs,
        +            state = this.state,
        +            stateSetters = (state) ? state.deltaSetters : {},
        +            stateDefs = (state) ? state.defs : {},
        +            predefined, stateFlag;
        +
        +        Object.entries(items).forEach(([key, value]) => {
        +
        +            if (key && key !== 'name' && value != null) {
        +
        +                predefined = setters[key];
        +                stateFlag = false;
        +
        +                if (!predefined) {
        +
        +                    predefined = stateSetters[key];
        +                    stateFlag = true;
        +                }
        +
        +                if (predefined) predefined.call(stateFlag ? this.state : this, value);
        +                else if (typeof defs[key] !== 'undefined') this[key] = addStrings(this[key], value);
        +                else if (typeof stateDefs[key] !== 'undefined') state[key] = addStrings(state[key], value);
        +            }
        +        }, this);
        +    }
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        host, getHost - copied over from the position mixin.

        + +
        + +
        S.host = function (item) {
        +
        +    if (item) {
        +
        +        let host = artefact[item];
        +
        +        if (host && host.here) this.host = host.name;
        +        else this.host = item;
        +    }
        +    else this.host = '';
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        group - copied over from the position mixin.

        + +
        + +
        G.group = function () {
        +
        +    return (this.group) ? this.group.name : '';
        +};
        +S.group = function (item) {
        +
        +    let g;
        +
        +    if (item) {
        +
        +        if (this.group && this.group.type === 'Group') this.group.removeArtefacts(this.name);
        +
        +        if (item.substring) {
        +
        +            g = group[item];
        +
        +            if (g) this.group = g;
        +            else this.group = item;
        +        }
        +        else this.group = item;
        +    }
        +
        +    if (this.group && this.group.type === 'Group') this.group.addArtefacts(this.name);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        getHere - returns current core position.

        + +
        + +
        P.getHere = function () {
        +
        +    return currentCorePosition;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        delta - copied over from the position mixin.

        + +
        + +
        S.delta = function (items = {}) {
        +
        +    if (items) this.delta = mergeDiscard(this.delta, items);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        net

        + +
        + +
        S.net = function (item) {
        +
        +    if (item) {
        +
        +        item = (item.substring) ? artefact[item] : item;
        +
        +        if (item && item.type === 'Net') {
        +
        +            this.net = item;
        +            this.dirtyStart = true;
        +        }
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        source

        + +
        + +
        S.source = function (item) {
        +
        +    item = (item.substring) ? artefact[item] : item;
        +
        +    if (item && item.type === 'Picture') {
        +
        +        let src = this.source;
        +
        +        if (src && src.type === 'Picture') src.imageUnsubscribe(this.name);
        +
        +        this.source = item;
        +        item.imageSubscribe(this.name);
        +        this.dirtyInput = true;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        isHorizontalCopy

        + +
        + +
        S.isHorizontalCopy = function (item) {
        +
        +    this.isHorizontalCopy = (item) ? true : false;
        +    this.dirtyPathData = true;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Prototype functions

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        getHost - copied over from the position mixin.

        + +
        + +
        P.getHost = function () {
        +
        +    if (this.currentHost) return this.currentHost;
        +    else if (this.host) {
        +
        +        let host = artefact[this.host];
        +
        +        if (host) {
        +
        +            this.currentHost = host;
        +            this.dirtyHost = true;
        +            return this.currentHost;
        +        }
        +    }
        +    return currentCorePosition;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        updateByDelta, reverseByDelta - copied over from the position mixin.

        + +
        + +
        P.updateByDelta = function () {
        +
        +    this.setDelta(this.delta);
        +
        +    return this;
        +};
        +
        +P.reverseByDelta = function () {
        +
        +    let temp = {};
        +    
        +    Object.entries(this.delta).forEach(([key, val]) => {
        +
        +        if (val.substring) val = -(parseFloat(val)) + '%';
        +        else val = -val;
        +
        +        temp[key] = val;
        +    });
        +
        +    this.setDelta(temp);
        +
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        setDeltaValues - copied over from the position mixin.

        + +
        + +
        P.setDeltaValues = function (items = {}) {
        +
        +    let delta = this.delta, 
        +        oldVal, action;
        +
        +    Object.entries(items).forEach(([key, requirement]) => {
        +
        +        if (xt(delta[key])) {
        +
        +            action = requirement;
        +
        +            oldVal = delta[key];
        +
        +            switch (action) {
        +
        +                case 'reverse' :
        +                    if (oldVal.toFixed) delta[key] = -oldVal;
        + +
      • + + +
      • +
        + +
        + +
        +

        TODO: reverse String% (and em, etc) values

        + +
        + +
                            break;
        +
        +                case 'zero' :
        +                    if (oldVal.toFixed) delta[key] = 0;
        + +
      • + + +
      • +
        + +
        + +
        +

        TODO: zero String% (and em, etc) values

        + +
        + +
                            break;
        +
        +                case 'add' :
        +                    break;
        +
        +                case 'subtract' :
        +                    break;
        +
        +                case 'multiply' :
        +                    break;
        +
        +                case 'divide' :
        +                    break;
        +            }
        +        }
        +    })
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Invalidate mid-init functionality

        + +
        + +
        P.midInitActions = λnull;
        + +
      • + + +
      • +
        + +
        + +
        +

        Invalidating sensor functionality

        + +
        + +
        P.cleanCollisionData = function () {
        +
        +    return [0, []];
        +};
        +P.getSensors = function () {
        +
        +    return [];
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Display cycle functionality

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        prepareStamp - function called as part of the Display cycle compile step.

        +
          +
        • This function is called before we get into the entity stamp promise cascade (thus it’s a synchronous function). This is where we need to check whether we need to recalculate the path data which we’ll use later to build the Mesh entity’s output image.
        • +
        • We only need to recalculate the path data on the initial render, and afterwards when the dirtyPathData flag has been set.
        • +
        • If we perform the recalculation, then we need to make sure to set the dirtyOutput flag, which will trigger the output image build.
        • +
        + +
        + +
        P.prepareStamp = function() {
        +
        +    this.badNet = true;
        +    this.dirtyParticles = false;
        +
        +    let {net, particlePositions} = this;
        +
        +    if (net && net.particleStore && net.particleStore.length > 3) {
        +
        +        let {rows, columns, particleStore} = net;
        +
        +        if (rows && columns) {
        +
        +            this.badNet = false;
        +            this.rows = rows;
        +            this.columns = columns;
        +
        +            if (!particlePositions) particlePositions = [];
        + +
      • + + +
      • +
        + +
        + +
        +

        Sanity check

        +
          +
        • We will recalculate stuff if any of the net particles have moved since the last check. This is most simply done by constructing a string of all current particle position values and comparing it to the previous string. If they are the same, then we can use the stashed image construct, otherwise we build and stash a new image construct
        • +
        + +
        + +
        +            let checkPositions = [];
        +
        +            particleStore.forEach(p => {
        +
        +                let pos = p.position;
        +                let {x, y} = pos;
        +
        +                checkPositions.push([x, y]);
        +            });
        +            let checkPositionsString = checkPositions.join(','),
        +                particlePositionsString = particlePositions.join(',');
        +
        +            if (particlePositionsString !== checkPositionsString) {
        +
        +                this.particlePositions = checkPositions;
        +                this.dirtyInput = true;
        +            }
        +
        +            if (this.sourceIsVideoOrSprite) this.dirtyInput = true;
        +        }
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        setSourceDimension - internal function called by prepareStamp.

        +
          +
        • We make the source dimensions a square of the longest row ‘path’
        • +
        • This way, we can do a horizontal scan, or a vertical scan with no further calculation
        • +
        +

        This function also:

        +
          +
        • Calculates the bounding box
        • +
        • Creates the perimeter path object
        • +
        • Stores the (relative) lengths of individual struts in each row
        • +
        +

        TODO: consider drawing order of squares - is there a way we can predict which squares are going to be behind other squares …

        +
          +
        • For instance by adding together the particle z values for each square, then filling in the lowest square first?
        • +
        • May also be a way of calculating a cull of squares so that we don’t need to fill in squares entirely covered by other squares?
        • +
        + +
        + +
        P.setSourceDimension = function () {
        +
        +    if (!this.badNet) {
        +
        +        const {columns, rows, particlePositions} = this;
        +
        +        const results = [],
        +            lengths = [],
        +            xPos = [],
        +            yPos = [],
        +            top = [],
        +            left = [],
        +            right = [],
        +            coords = [],
        +            bottom = [];
        +
        +        let x, xz, y, res, pos, coord, x0, x1, y0, y1, dx, dy, len, l, i, iz, j, jz;
        +
        +        for (y = 0; y < rows; y++) {
        +
        +            res = 0;
        +            len = lengths[y] = [];
        +            coord = coords[y] = [];
        +
        +            for (x = 0, xz = columns - 1; x < xz; x++) {
        +
        +                pos = (y * columns) + x; 
        +
        +                [x0, y0] = particlePositions[pos];
        +                [x1, y1] = particlePositions[pos + 1];
        +
        +                coord.push([x0, y0, x1, y1]);
        +
        +                if (x === 0) {
        +
        +                    left.push([x0, y0]);
        +                }
        +                else if (x === xz - 1) {
        +
        +                    right.push([x1, y1]);
        +                }
        +                else if (y === 0) {
        +
        +                    top.push([x0, y0]);
        +
        +                    if (x === xz - 2) top.push([x1, y1]);
        +                }
        +                else if (y === rows - 1) {
        +
        +                    bottom.push([x0, y0]);
        +
        +                    if (x === xz - 2) bottom.push([x1, y1]);
        +                }
        +
        +                xPos.push(x0, x1);
        +                yPos.push(y0, y1);
        +
        +                dx = x1 - x0;
        +                dy = y1 - y0;
        +
        +                l = Math.sqrt((dx * dx) + (dy * dy));
        +                res += l;
        +                len.push(l);
        +            }
        +            results.push(res);
        +        }
        +        this.sourceDimension = Math.max(...results);
        + +
      • + + +
      • +
        + +
        + +
        +

        Sanity check - the particle system, when it breaks down, can create some massive dimension values!

        + +
        + +
                let host = this.currentHost || this.getHost();
        +        if (host) {
        +
        +            let max = Math.max(...host.currentDimensions);
        +            if (this.sourceDimension > max) this.sourceDimension = max;
        +        }
        +
        +        for (i = 0, iz = lengths.length; i < iz; i++) {
        +
        +            l = results[i];
        +            len = lengths[i];
        +            coord = coords[i];
        +
        +            for (j = 0, jz = len.length; j < jz; j++) {
        +
        +                if (l) len[j] = len[j] / l;
        +
        +                coord[j].push(len[j]);
        +            }
        +        }
        +
        +        this.struts = coords;
        +
        +        let xMin = Math.min(...xPos),
        +            yMin = Math.min(...yPos),
        +            xMax = Math.max(...xPos),
        +            yMax = Math.max(...yPos);
        +
        +        this.boundingBox = [xMin, yMin, xMax - xMin, yMax - yMin];
        +
        +        left.reverse();
        +        bottom.reverse();
        +
        +        let p = `M${top[0][0]},${top[0][1]}L`;
        +
        +        for (i = 1, iz = top.length; i < iz; i++) {
        +
        +            [x, y] = top[i];
        +            p += `${x},${y} `;
        +        }
        +        for (i = 0, iz = right.length; i < iz; i++) {
        +
        +            [x, y] = right[i];
        +            p += `${x},${y} `;
        +        }
        +        for (i = 0, iz = bottom.length; i < iz; i++) {
        +
        +            [x, y] = bottom[i];
        +            p += `${x},${y} `;
        +        }
        +        for (i = 0, iz = left.length; i < iz; i++) {
        +
        +            [x, y] = left[i];
        +            p += `${x},${y} `;
        +        }
        +        p += 'z';
        +
        +        this.pathObject = new Path2D(p);
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        simpleStamp - Simple stamping is entirely synchronous

        +
          +
        • TODO: we may have to disable this functionality for the Mesh entity, if we use a web worker for either the prepareStamp calculations, or to build the output image itself
        • +
        + +
        + +
        P.simpleStamp = function (host, changes = {}) {
        +
        +    if (host && host.type === 'Cell') {
        +
        +        this.currentHost = host;
        +        
        +        if (changes) {
        +
        +            this.set(changes);
        +            this.prepareStamp();
        +        }
        +
        +        this.regularStampSynchronousActions();
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        stamp - All entity stamping, except for simple stamps, goes through this function, which needs to return a Promise which will resolve in due course.

        +
          +
        • While other entitys have to worry about applying filters as part of the stamping process, this is not an issue for Mesh entitys because filters are defined on, and applied to, the source Picture entity, not the Mesh itself
        • +
        +

        Here we check which dirty flags need actioning, and call a range of different functions to process the work. These flags are:

        +
          +
        • dirtyInput - the Picture entity has reported a change in its source, or copy attributes)
        • +
        + +
        + +
        P.stamp = function (force = false, host, changes) {
        +
        +    if (force) {
        +
        +        if (host && host.type === 'Cell') this.currentHost = host;
        +
        +        if (changes) {
        +
        +            this.set(changes);
        +            this.prepareStamp();
        +        }
        +        return this.regularStamp();
        +    }
        +
        +    if (this.visibility) {
        +
        +        let self = this,
        +            dirtyInput = this.dirtyInput;
        +
        +        if (dirtyInput) {
        +
        +            return new Promise((resolve, reject) => {
        +
        +                self.cleanInput()
        +                .catch(err => {
        + +
      • + + +
      • +
        + +
        + +
        +

        We don’t need to completely reject if output is not clean

        +
          +
        • It should be enough to bale out of the stamp functionality and hope it resolves during the next RAF iteration
        • +
        + +
        + +
                            console.log(`${self.name} - cleanInput Error: source has a zero dimension`);
        +                    resolve(false);
        +                })
        +                .then(res => {
        +
        +                    self.sourceImageData = res;
        +                    return self.cleanOutput();
        +                })
        +                .then(res => {
        +
        +                    self.output = res;
        +                    return self.regularStamp();
        +                })
        +                .then(res => {
        +
        +                    resolve(true);
        +                })
        +                .catch(err => {
        +                    
        +                    reject(err);
        +                });
        +            })
        +        }
        +        else return this.regularStamp();
        +    }
        +    else return Promise.resolve(false);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Clean functions

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        cleanInput - internal function called by stamp

        + +
        + +
        P.cleanInput = function () {
        +
        +    let self = this;
        +
        +    return new Promise((resolve, reject) => {
        +
        +        self.dirtyInput = false;
        +
        +        self.setSourceDimension();
        +
        +        let sourceDimension = self.sourceDimension;
        +
        +        if (!sourceDimension) {
        +
        +            self.dirtyInput = true;
        +            reject();
        +        }
        +
        +        let cell = requestCell(),
        +            engine = cell.engine,
        +            canvas = cell.element;
        +
        +        canvas.width = sourceDimension;
        +        canvas.height = sourceDimension;
        +        engine.setTransform(1, 0, 0, 1, 0, 0);
        +
        +        self.source.stamp(true, cell, { 
        +            startX: 0,
        +            startY: 0,
        +            handleX: 0,
        +            handleY: 0,
        +            offsetX: 0,
        +            offsetY: 0,
        +            roll: 0,
        +            scale: 1,
        +
        +            width: sourceDimension,
        +            height: sourceDimension,
        +
        +            method: 'fill',
        +        })
        +        .then(res => {
        +
        +            let sourceImageData = engine.getImageData(0, 0, sourceDimension, sourceDimension);
        +
        +            releaseCell(cell);
        +            resolve(sourceImageData);
        +        })
        +        .catch(err => {
        +
        +            releaseCell(cell);
        +            reject(err);
        +        });
        +    });
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        cleanOutput - internal function called by stamp

        +
          +
        • If you’re not a fan of big functions, please look away now.
        • +
        + +
        + +
        P.cleanOutput = function () {
        +    
        +    let self = this;
        +
        +    return new Promise((resolve, reject) => {
        +
        +        const halfPi = Math.PI / 2;
        +
        +        self.dirtyOutput = false;
        +
        +        let {sourceDimension, sourceImageData, columns, rows, struts, boundingBox} = self;
        +
        +        sourceDimension = Math.ceil(sourceDimension);
        +
        +        if (sourceImageData && rows - 1 > 0) {
        +
        +            let [startX, startY, outputWidth, outputHeight] = boundingBox;
        +
        +            outputWidth += startX;
        +            outputHeight += startY;
        +
        +            const inputCell = requestCell(),
        +                inputEngine = inputCell.engine,
        +                inputCanvas = inputCell.element;
        +
        +            inputCanvas.width = sourceDimension;
        +            inputCanvas.height = sourceDimension;
        +            inputEngine.setTransform(1, 0, 0, 1, 0, 0);
        +            inputEngine.putImageData(sourceImageData, 0, 0);
        +
        +            const outputCell = requestCell(),
        +                outputEngine = outputCell.engine,
        +                outputCanvas = outputCell.element;
        +
        +            outputCanvas.width = outputWidth;
        +            outputCanvas.height = outputHeight;
        +            outputEngine.globalAlpha = self.state.globalAlpha;
        +            outputEngine.setTransform(1, 0, 0, 1, 0, 0);
        +
        +            const inputStrutHeight = parseFloat((sourceDimension / (rows - 1)).toFixed(4)),
        +                inputStrutWidth = parseFloat((sourceDimension / (columns - 1)).toFixed(4));
        +
        +            let topStruts, baseStruts,
        +                maxLen, tStep, bStep, iStep, xtStep, ytStep, xbStep, ybStep, tx, ty, bx, by, sx, sy,
        +                xLen, yLen, stripLength, stripAngle,
        +                c, cz, r, rz, i, iz;
        +
        +            for (r = 0, rz = rows - 1; r < rz; r++) {
        +
        +                topStruts = struts[r];
        +                baseStruts = struts[r + 1];
        +
        +                for (c = 0, cz = columns - 1; c < cz; c++) {
        +
        +                    let [ltx, lty, rtx, rty, tLen] = topStruts[c];
        +                    let [lbx, lby, rbx, rby, bLen] = baseStruts[c];
        +
        +                    tLen *= sourceDimension;
        +                    bLen *= sourceDimension;
        +
        +                    maxLen = Math.max(tLen, bLen, inputStrutWidth);
        +
        +                    tStep = tLen / maxLen;
        +                    bStep = bLen / maxLen;
        +                    iStep = inputStrutWidth / maxLen;
        +
        +                    xtStep = (rtx - ltx) / maxLen;
        +                    ytStep = (rty - lty) / maxLen;
        +                    xbStep = (rbx - lbx) / maxLen;
        +                    ybStep = (rby - lby) / maxLen;
        +
        +                    for (i = 0; i < maxLen; i++) {
        +
        +                        tx = ltx + (xtStep * i);
        +                        ty = lty + (ytStep * i);
        +                        bx = lbx + (xbStep * i);
        +                        by = lby + (ybStep * i);
        +                        sy = r * inputStrutHeight;
        +                        sx = (c * inputStrutWidth) + (iStep * i);
        +
        +                        xLen = tx - bx;
        +                        yLen = ty - by;
        +                        stripLength = Math.sqrt((xLen * xLen) + (yLen * yLen));
        +                        stripAngle = Math.atan2(yLen, xLen) + halfPi;
        +
        +                        outputEngine.setTransform(1, 0, 0, 1, tx, ty);
        +                        outputEngine.rotate(stripAngle);
        + +
      • + + +
      • +
        + +
        + +
        +

        Safari bugfix because we fall foul of of the Safari source-out-of-bounds bug

        + + +
        + +
                                let testHeight = (sy + inputStrutHeight > sourceDimension) ? sourceDimension - sy : inputStrutHeight;
        +
        +                        outputEngine.drawImage(inputCanvas, sx, sy, 1, testHeight, 0, 0, 1, stripLength);
        +                    }
        +                }
        +            }
        +
        +            let iFactor = self.interferenceFactor,
        +                iLoops = self.interferenceLoops,
        +
        +                iWidth = Math.ceil(outputWidth * iFactor),
        +                iHeight = Math.ceil(outputHeight * iFactor);
        +
        +            inputCanvas.width = iWidth;
        +            inputCanvas.height = iHeight;
        +
        +            outputEngine.setTransform(1, 0, 0, 1, 0, 0);
        +            inputEngine.setTransform(1, 0, 0, 1, 0, 0);
        +
        +            for (let j = 0; j < iLoops; j++) {
        +
        +                inputEngine.drawImage(outputCanvas, 0, 0, outputWidth, outputHeight, 0, 0, iWidth, iHeight);
        +                outputEngine.drawImage(inputCanvas, 0, 0, iWidth, iHeight, 0, 0, outputWidth, outputHeight);
        +            }
        +
        +            let outputData = outputEngine.getImageData(0, 0, outputWidth, outputHeight);
        +
        +            releaseCell(inputCell);
        +            releaseCell(outputCell);
        +
        +            self.dirtyTargetImage = true;
        +
        +            resolve(outputData);
        +        }
        +        else reject(new Error(`${this.name} - cleanOutput Error: source has a zero dimension, or no data`));
        +    });
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        regularStamp - internal function called by stamp

        + +
        + +
        P.regularStamp = function () {
        +
        +    let self = this;
        +
        +    return new Promise((resolve, reject) => {
        +
        +        if (self.currentHost) {
        +
        +            self.regularStampSynchronousActions();
        +            resolve(true);
        +        }
        +        reject(new Error(`${self.name} has no current host`));
        +    });
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        regularStampSynchronousActions - internal function called by regularStamp

        + +
        + +
        P.regularStampSynchronousActions = function () {
        +
        +    let dest = this.currentHost;
        +
        +    if (dest) {
        +
        +        let engine = dest.engine;
        +
        +        if (!this.noCanvasEngineUpdates) dest.setEngine(this);
        +
        +        this[this.method](engine);
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +
        Stamp methods
        +

        These ‘method’ functions stamp the Mesh entity onto the canvas context supplied to them in the engine argument.

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        fill

        + +
        + +
        P.fill = function (engine) {
        +
        +    this.doFill(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        draw

        + +
        + +
        P.draw = function (engine) {
        +
        +    this.doStroke(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        drawAndFill

        + +
        + +
        P.drawAndFill = function (engine) {
        +
        +    this.doStroke(engine);
        +    this.currentHost.clearShadow();
        +    this.doFill(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        fillAndDraw

        + +
        + +
        P.fillAndDraw = function (engine) {
        +
        +    this.doFill(engine);
        +    this.currentHost.clearShadow();
        +    this.doStroke(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        drawThenFill

        + +
        + +
        P.drawThenFill = function (engine) {
        +
        +    this.doStroke(engine);
        +    this.doFill(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        fillThenDraw

        + +
        + +
        P.fillThenDraw = function (engine) {
        +
        +    this.doFill(engine);
        +    this.doStroke(engine);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        clear

        + +
        + +
        P.clear = function (engine) {
        +
        +    let output = this.output,
        +        canvas = (this.currentHost) ? this.currentHost.element : false,
        +        gco = engine.globalCompositeOperation;
        +
        +    if (output && canvas) {
        +
        +        let tempCell = requestCell(),
        +            tempEngine = tempCell.engine,
        +            tempCanvas = tempCell.element;
        +
        +        let w = canvas.width,
        +            h = canvas.height;
        +
        +        tempCanvas.width = w;
        +        tempCanvas.height = h;
        +
        +        tempEngine.putImageData(output, 0, 0);
        +        engine.setTransform(1, 0, 0, 1, 0, 0);
        +        engine.globalCompositeOperation = 'destination-out';
        +        engine.drawImage(tempCanvas, 0, 0);
        +        engine.globalCompositeOperation = gco;
        +
        +        releaseCell(tempCell);
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        none

        + +
        + +
        P.none = λnull;
        + +
      • + + +
      • +
        + +
        + +
        +

        These stroke and fill functions handle most of the stuff that the method functions require to stamp the Mesh entity onto a canvas cell.

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        doStroke

        + +
        + +
        P.doStroke = function (engine) {
        +
        +    engine.setTransform(1, 0, 0, 1, 0, 0);
        +    engine.stroke(this.pathObject);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        doFill

        +
          +
        • Canvas API’s putImageData function turns transparent pixels in the output into transparent in the host canvas - which is not what we want
        • +
        • Problem solved by putting output into a pool cell, then drawing it from there to the host cell
        • +
        + +
        + +
        P.doFill = function (engine) {
        +
        +    let output = this.output,
        +        canvas = (this.currentHost) ? this.currentHost.element : false;
        +
        +    if (output && canvas) {
        +
        +        let tempCell = requestCell(),
        +            tempEngine = tempCell.engine,
        +            tempCanvas = tempCell.element;
        +
        +        let w = canvas.width,
        +            h = canvas.height;
        +
        +        tempCanvas.width = w;
        +        tempCanvas.height = h;
        +
        +        tempEngine.putImageData(output, 0, 0);
        +        engine.setTransform(1, 0, 0, 1, 0, 0);
        +        engine.drawImage(tempCanvas, 0, 0);
        +
        +        releaseCell(tempCell);
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Collision functionality

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        checkHit

        +
          +
        • Overwrites mixin/position.js function
        • +
        + +
        + +
        P.checkHit = function (items = [], mycell) {
        +
        +    if (this.noUserInteraction) return false;
        +
        +    if (!this.pathObject) return false;
        +
        +    let tests = (!Array.isArray(items)) ?  [items] : items,
        +        poolCellFlag = false;
        +
        +    if (!mycell) {
        +
        +        mycell = requestCell();
        +        poolCellFlag = true;
        +    }
        +
        +    let engine = mycell.engine,
        +        tx, ty;
        +
        +    if (tests.some(test => {
        +
        +        if (Array.isArray(test)) {
        +
        +            tx = test[0];
        +            ty = test[1];
        +        }
        +        else if (xta(test, test.x, test.y)) {
        +
        +            tx = test.x;
        +            ty = test.y;
        +        }
        +        else return false;
        +
        +        if (!tx.toFixed || !ty.toFixed || isNaN(tx) || isNaN(ty)) return false;
        +
        +        return engine.isPointInPath(this.pathObject, tx, ty, this.winding);
        +
        +    }, this)) {
        +
        +        let r = {
        +            x: tx,
        +            y: ty,
        +            artefact: this
        +        };
        +
        +        if (poolCellFlag) releaseCell(mycell);
        +
        +        return r;
        +    }
        +    
        +    if (poolCellFlag) releaseCell(mycell);
        +    
        +    return false;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Factory

        +
        let myMesh = scrawl.makeMesh({
        + +
        + +
      • + + +
      • +
        + +
        + +
        +
        name: 'display-mesh',
        + +
        + +
      • + + +
      • +
        + +
        + +
        +
        net: 'test-net',
        +source: 'my-flower',
        + +
        + +
      • + + +
      • +
        + +
        + +
        +
        lineWidth: 2,
        +lineJoin: 'round',
        +strokeStyle: 'orange',
        + +
        + +
      • + + +
      • +
        + +
        + +
        +
        method: 'fillThenDraw',
        + +
        + +
      • + + +
      • +
        + +
        + +
        +
        onEnter: function () { this.set({ lineWidth: 6 }) },
        +onLeave: function () { this.set({ lineWidth: 2 }) },
        +

        });

        +
        + +
        + +
        const makeMesh = function (items) {
        +    return new Mesh(items);
        +};
        +
        +constructors.Mesh = Mesh;
        + +
      • + + +
      • +
        + +
        + +
        +

        Exports

        + +
        + +
        export {
        +    makeMesh,
        +};
        + +
      • + +
      +
      + + diff --git a/docs/source/factory/net.html b/docs/source/factory/net.html index 130ef07f5..6801fa894 100644 --- a/docs/source/factory/net.html +++ b/docs/source/factory/net.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/noise.html b/docs/source/factory/noise.html new file mode 100644 index 000000000..11a483c27 --- /dev/null +++ b/docs/source/factory/noise.html @@ -0,0 +1,2275 @@ + + + + + Noise factory + + + + + +
      +
      + + + +
        + + + +
      • +
        + +
        + +
        +

        Noise factory

        +

        The purpose of the Noise asset is to give us a resource for generating noisy (semi-regular) maps. These can then be used directly as Picture or Pattern images, or uploaded to the filter worker object as part of a filter that uses displacement map functionality.

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Current functionality

        +

        At the moment the Noise asset can generate Perlin-type noise, with engines supplied for:

        +
          +
        • Perlin (classic)
        • +
        • Perlin (improved)
        • +
        • Simplex - the default engine
        • +
        • Value
        • +
        +

        These engines are supported by a number of settable (and thus animatable) attributes, including special functions for smoothing the engine output. Demo Filters-019 has been set up to allow for experimenting with these attributes

        +

        The noise generated will be output to a dedicated offscreen <canvas> element, which is the asset used by Picture entitys, Pattern styles and filters. The output image can be set to three color schemas:

        +
          +
        • Monochrome (black - gray - white)
        • +
        • Gradient - mediated by a Scrawl-canvas Color object
        • +
        • Hue - where the engine output for each pixel is interpreted as the hue component of an HSL color
        • +
        +

        (NOTE: Perlin, Simplex and Value noise generator code based on code found in the canvas-noise GitHub repository written by lencinhaus.

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Possible future functionality

        +

        There’s no reason why the Noise asset cannot be extended to output other types of (semi-regular) noise data. For instance:

        + + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Demos:

        +
          +
        • Canvas-044 - Building more complex patterns
        • +
        • Filters-019 - Using a Noise asset with a displace filter
        • +
        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Imports

        + +
        + +
        import { constructors } from '../core/library.js';
        +import { mergeOver, λnull, λthis, λfirstArg, removeItem, seededRandomNumberGenerator, interpolate, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js';
        +
        +import { makeColor } from './color.js';
        +
        +import baseMix from '../mixin/base.js';
        +import assetMix from '../mixin/asset.js';
        +import patternMix from '../mixin/pattern.js';
        + +
      • + + +
      • +
        + +
        + +
        +

        Noise constructor

        + +
        + +
        const Noise = function (items = {}) {
        +
        +    this.makeName(items.name);
        +    this.register();
        +
        +    let mycanvas = document.createElement('canvas');
        +    mycanvas.id = this.name;
        +    this.installElement(mycanvas);
        +
        +    this.perm = [];
        +    this.permMod8 = [];
        +    this.values = [];
        +    this.grad = [];
        +
        +    this.subscribers = [];
        +
        +    this.colorFactory = makeColor({
        +        name: `${this.name}-color-factory`,
        +        minimumColor: items.gradientStart || 'red',
        +        maximumColor: items.gradientEnd || 'green',
        +    });
        +
        +    this.set(this.defs);
        +    this.set(items);
        +
        +    if (items.subscribe) this.subscribers.push(items.subscribe);
        +
        +    this.dirtyOutput = true;
        +
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Noise prototype

        + +
        + +
        let P = Noise.prototype = Object.create(Object.prototype);
        +P.type = 'Noise';
        +P.lib = 'asset';
        +P.isArtefact = false;
        +P.isAsset = true;
        + +
      • + + +
      • +
        + +
        + +
        +

        Mixins

        + + +
        + +
        P = baseMix(P);
        +P = assetMix(P);
        +P = patternMix(P);
        + +
      • + + +
      • +
        + +
        + +
        +

        Noise attributes

        +
          +
        • Attributes defined in the base mixin: name.
        • +
        • Attributes defined in the asset mixin: source, subscribers.
        • +
        • Attributes defined in the pattern mixin: repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF.
        • +
        + +
        + +
        let defaultAttributes = {
        + +
      • + + +
      • +
        + +
        + +
        +

        The offscreen canvas dimensions, within which the noise will be generated, is set using the width and height attributes. These take Number values.

        + +
        + +
            width: 300,
        +    height: 150,
        + +
      • + + +
      • +
        + +
        + +
        +

        color - String value determining how the generated noise will be output on the canvas. Currently recognised values are: monochrome (default), gradient and hue

        + +
        + +
            color: 'monochrome',
        + +
      • + + +
      • +
        + +
        + +
        +

        When the color choice has been set to monochrome we can clamp the pixel values using the monochromeStart and monochromeRange attributes, both of which take integer Numbers.

        +
          +
        • Accepted monochromeStart values are 0 to 255
        • +
        • Accepted monochromeRange values are -255 to 255
        • +
        • Be aware that the monochromeRange value will be recalculated to make sure calculated pixel values remain in the 0-255 color channel range
        • +
        + +
        + +
            monochromeStart: 0,
        +    monochromeRange: 255,
        + +
      • + + +
      • +
        + +
        + +
        +

        When the color choice has been set to gradient we can control the start and end colors of the gradient using the gradientStart and gradientEnd attributes

        + +
        + +
            gradientStart: '#ff0000',
        +    gradientEnd: '#00ff00',
        + +
      • + + +
      • +
        + +
        + +
        +

        When the color choice has been set to hue we can control the pixel colors (in terms of their HSL components) using the hueStart, hueRange, saturation and luminosity attributes:

        +
          +
        • hueStart - float Number value in degrees, will be clamped to between 0 and 360
        • +
        • hueRange - float Number value in degrees, can be negative as well as positive
        • +
        • saturation - float Number value, between 0 and 100
        • +
        • luminosity - float Number value, between 0 and 100
        • +
        + +
        + +
            hueStart: 0,
        +    hueRange: 120,
        +    saturation: 100,
        +    luminosity: 50,
        + +
      • + + +
      • +
        + +
        + +
        +

        noiseEngine - String - the currently supported noise engines String values are: perlin, improved-perlin, simplex, value

        + +
        + +
            noiseEngine: 'simplex',
        + +
      • + + +
      • +
        + +
        + +
        +

        When a noise engine initializes it will create several Arrays of pseudo-random values. The seed attribute is a String used to initialize the pseudo-random number generator, while the size attribute is a Number (often a power of 2 value) which determines the lengths of the Arrays

        + +
        + +
            seed: 'any_random_string_will_do',
        +    size: 256,
        + +
      • + + +
      • +
        + +
        + +
        +

        The scale attribute determines the relative scale of the noise calculation, which affects the noise output. Think of it as a rather idiosyncratic zoom factor

        + +
        + +
            scale: 50,
        + +
      • + + +
      • +
        + +
        + +
        +

        Attributes used when calculating the noise map include:

        +
          +
        • octaves - a positive integer Number - the more octives, the more naturalistic the output - values over 6 are rarely productive
        • +
        • __octaveFunction - a String identifying the function to be run at the end of each octave loop. Currently only none and absolute functions are supported
        • +
        • persistance and lacunarity values change at the conclusion of each octave loop; these attributes set their initial values
        • +
        + +
        + +
            octaves: 1,
        +    octaveFunction: 'none',
        +    persistence: 0.5,
        +    lacunarity: 2,
        + +
      • + + +
      • +
        + +
        + +
        +

        The smoothing attribute - a String value - identifies the smoothing function that will be applied pixel noise values as they are calculated. There are a wide number of functions available, including: easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce, cosine, hermite, quintic (default)

        + +
        + +
            smoothing: 'quintic',
        + +
      • + + +
      • +
        + +
        + +
        +

        Post-processing the noise map: The sumFunction attribute - a String value - identifies the smoothing function that will be applied to the noise map once the noise calculations complete.

        +
          +
        • Permitted values include: none, sine-x, sine-y, sine, modular
        • +
        • sineFrequencyCoeff - a Number - is used by sine-based sum functions
        • +
        • modularAmplitude - a Number - is used by the modular sum function
        • +
        + +
        + +
            sumFunction: 'none',
        +    sineFrequencyCoeff: 1,
        +    modularAmplitude: 5,
        +
        +
        +};
        +P.defs = mergeOver(P.defs, defaultAttributes);
        +
        +delete P.defs.source;
        +delete P.defs.sourceLoaded;
        + +
      • + + +
      • +
        + +
        + +
        +

        Packet management

        +

        This functionality is disabled for Cell objects

        + +
        + +
        P.stringifyFunction = λnull;
        +P.processPacketOut = λnull;
        +P.finalizePacketOut = λnull;
        +P.saveAsPacket = function () {
        +
        +    return `[${this.name}, ${this.type}, ${this.lib}, {}]`
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Clone management

        + +
        + +
        P.clone = λthis;
        + +
      • + + +
      • +
        + +
        + +
        +

        Kill management

        +

        No additional kill functionality required

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Get, Set, deltaSet

        + +
        + +
        let G = P.getters,
        +    S = P.setters,
        +    D = P.deltaSetters;
        + +
      • + + +
      • +
        + +
        + +
        +

        source

        + +
        + +
        S.source = λnull;
        + +
      • + + +
      • +
        + +
        + +
        +

        subscribers - we disable the ability to set the subscribers Array directly. Picture entitys and Pattern styles will manage their subscription to the asset using their subscribe() and unsubscribe() functions. Filters will check for updates every time they run

        + +
        + +
        S.subscribers = λnull;
        +
        +S.octaveFunction = function (item) {
        +
        +    this.octaveFunction = this.octaveFunctions[item] || λfirstArg;
        +    this.dirtyNoise = true;
        +    this.dirtyOutput = true;
        +};
        +
        +S.sumFunction = function (item) {
        +
        +    this.sumFunction = this.sumFunctions[item] || λfirstArg;
        +    this.dirtyNoise = true;
        +    this.dirtyOutput = true;
        +};
        +
        +S.smoothing = function (item) {
        +
        +    this.smoothing = this.smoothingFunctions[item] || λfirstArg;
        +    this.dirtyNoise = true;
        +    this.dirtyOutput = true;
        +};
        +
        +S.noiseEngine = function (item) {
        +
        +    this.noiseEngine = this.noiseEngines[item] || this.noiseEngines['simplex'];
        +    this.dirtyNoise = true;
        +    this.dirtyOutput = true;
        +};
        +
        +S.octaves = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.octaves = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.seed = function (item) {
        +
        +    if (item.substring) {
        +
        +        this.seed = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +P.supportedColorSchemes = ['monochrome', 'gradient', 'hue'];
        +S.color = function (item) {
        +
        +    if (this.supportedColorSchemes.indexOf(item) >= 0) {
        +
        +        this.color = item;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.scale = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.scale = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.size = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.size = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.persistence = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.persistence = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.lacunarity = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.lacunarity = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.gradientStart = function (item) {
        +
        +    if (item.substring) {
        +
        +        this.colorFactory.setMinimumColor(item);
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.gradientEnd = function (item) {
        +
        +    if (item.substring) {
        +
        +        this.colorFactory.setMaximumColor(item);
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.monochromeStart = function (item) {
        +
        +    if (item.toFixed && item >= 0) {
        +
        +        this.monochromeStart = item % 360;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.monochromeRange = function (item) {
        +
        +    if (item.toFixed && item >= -255 && item < 256) {
        +
        +        this.monochromeRange = Math.floor(item);
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.hueStart = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.hueStart = item;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.hueRange = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.hueRange = item;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.saturation = function (item) {
        +
        +    if (item.toFixed && item >= 0 && item <= 100) {
        +
        +        this.saturation = Math.floor(item);
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.luminosity = function (item) {
        +
        +    if (item.toFixed && item >= 0 && item <= 100) {
        +
        +        this.luminosity = Math.floor(item);
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.sineFrequencyCoeff = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.sineFrequencyCoeff = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.modularAmplitude = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.modularAmplitude = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.width = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.width = item;
        +        this.sourceNaturalWidth = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        +
        +S.height = function (item) {
        +
        +    if (item.toFixed) {
        +
        +        this.height = item;
        +        this.sourceNaturalHeight = item;
        +        this.dirtyNoise = true;
        +        this.dirtyOutput = true;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Prototype functions

        +

        installElement - internal function, used by the constructor

        + +
        + +
        P.installElement = function (element) {
        +
        +    this.element = element;
        +    this.engine = this.element.getContext('2d');
        +
        +    return this;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        checkSource

        +
          +
        • Gets invoked by subscribers (who have a handle to the asset instance object) as part of the display cycle.
        • +
        • Noise assets will automatically pass this call onto notifySubscribers, where dirty flags get checked and rectified
        • +
        + +
        + +
        P.checkSource = function (width, height) {
        +
        +    this.notifySubscribers();
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        getData function called by Cell objects when calculating required updates to its CanvasRenderingContext2D engine, specifically for an entity’s fillStyle, strokeStyle and shadowColor attributes.

        +
          +
        • This is the point when we clean Scrawl-canvas assets which have told their subscribers that asset data/attributes have updated
        • +
        + +
        + +
        P.getData = function (entity, cell) {
        + +
      • + + +
      • +
        + +
        + +
        +

        this.checkSource(this.width, this.height);

        + +
        + +
            this.notifySubscribers();
        +
        +    return this.buildStyle(cell);
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        notifySubscribers, notifySubscriber - overwrites the functions defined in mixin/asset.js

        + +
        + +
        P.notifySubscribers = function () {
        +
        +    if (this.dirtyOutput || this.dirtyNoise) this.cleanOutput();
        +
        +    this.subscribers.forEach(sub => this.notifySubscriber(sub), this);
        +};
        +
        +P.notifySubscriber = function (sub) {
        +
        +    sub.sourceNaturalWidth = this.width;
        +    sub.sourceNaturalHeight = this.height;
        +    sub.sourceLoaded = true;
        +    sub.source = this.element;
        +    sub.dirtyImage = true;
        +    sub.dirtyCopyStart = true;
        +    sub.dirtyCopyDimensions = true;
        +    sub.dirtyImageSubscribers = true;
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        cleanOutput - internal function called by the notifySubscribers function

        + +
        + +
        P.cleanOutput = function () {
        +
        +    if (this.dirtyNoise) this.cleanNoise();
        +    if (this.dirtyOutput) this.paintCanvas();
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        cleanNoise - internal function called by the cleanOutput function

        + +
        + +
        P.cleanNoise = function () {
        +
        +    if (this.dirtyNoise) {
        +
        +        this.dirtyNoise = false;
        +
        +        let {noiseEngine, seed, width, height, element, engine, octaves, lacunarity, persistence, scale, octaveFunction, sumFunction} = this;
        +
        +        if (noiseEngine && noiseEngine.init) {
        + +
      • + + +
      • +
        + +
        + +
        +

        Seed our pseudo-random number generator

        + +
        + +
                    this.rndEngine = seededRandomNumberGenerator(seed);
        + +
      • + + +
      • +
        + +
        + +
        +

        Generate the permutations table(s)

        + +
        + +
                    this.generatePermutationTable();
        + +
      • + + +
      • +
        + +
        + +
        +

        Initialize the appropriate noise function

        + +
        + +
                    noiseEngine.init.call(this);
        +
        +            let x, y, o, i, iz,
        +                noiseValues = [],
        +                scaledX, scaledY,
        +                totalNoise, amplitude, frequency;
        + +
      • + + +
      • +
        + +
        + +
        +

        Prepare the noiseValues 2d array

        + +
        + +
                    for (y = 0; y < height; y++) {
        +
        +                noiseValues[y] = [];
        +
        +                for (x = 0; x < width; x++) {
        +                    noiseValues[y][x] = [];
        +                }
        +            }
        + +
      • + + +
      • +
        + +
        + +
        +

        Calculate a relative scale, and setup min/max variables

        + +
        + +
                    let relativeScale = Math.pow(width, -scale / 100);
        +
        +            let max = -1000, 
        +                min = 1000;
        + +
      • + + +
      • +
        + +
        + +
        +

        This is the core of the calculation, performed for each cell in the noiseValues 2d array

        + +
        + +
                    for (y = 0; y < height; y++) {
        +                for (x = 0; x < width; x++) {
        + +
      • + + +
      • +
        + +
        + +
        +

        We can modify the output by scaling it

        +
          +
        • Note that modifying the canvas dimensions (width, height) can also have a scaling effect
        • +
        + +
        + +
                            scaledX = x * relativeScale;
        +                    scaledY = y * relativeScale;
        + +
      • + + +
      • +
        + +
        + +
        +

        Amplitude and frequency will update once per octave calculation; totalNoise is the sum of all octave results

        + +
        + +
                            totalNoise = 0;
        +                    amplitude = 1; 
        +                    frequency = 1;
        + +
      • + + +
      • +
        + +
        + +
        +

        The calculation will be performed at least once

        +
          +
        • For some reason the literature insists on calling these loops “octaves”
        • +
        + +
        + +
                            for (o = 0; o < octaves; o++) {
        + +
      • + + +
      • +
        + +
        + +
        +

        Call the appropriate getNoiseValue function

        +
          +
        • The result needs to be stored in a variable scoped locally to this loop iteration
        • +
        + +
        + +
                                let octaveNoise = noiseEngine.getNoiseValue.call(this, scaledX * frequency, scaledY * frequency);
        + +
      • + + +
      • +
        + +
        + +
        +

        Update octave with a post-calculation octaveFunction, if required

        + +
        + +
                                octaveNoise = octaveFunction(octaveNoise, scaledX, scaledY, o + 1);
        + +
      • + + +
      • +
        + +
        + +
        +

        Modify result by the current amplitude, and add to the running total

        + +
        + +
                                octaveNoise *= amplitude;
        +                        totalNoise += octaveNoise;
        + +
      • + + +
      • +
        + +
        + +
        +

        Update the variables that change over multiple octave loops

        + +
        + +
                                frequency *= lacunarity;
        +                        amplitude *= persistence;
        +                    }
        + +
      • + + +
      • +
        + +
        + +
        +

        Update the noise value in its array

        + +
        + +
                            noiseValues[y][x] = totalNoise;
        + +
      • + + +
      • +
        + +
        + +
        +

        … and check for max/min spread of the generated values

        + +
        + +
                            min = Math.min(min, totalNoise);
        +                    max = Math.max(max, totalNoise);
        +                }
        +            }
        + +
      • + + +
      • +
        + +
        + +
        +

        Calculate the span of numbers generated - we need to get all the results in the range 0 to 1

        + +
        + +
                    let noiseSpan = max - min;
        +
        +            for (y = 0; y < height; y++) {
        +                for (x = 0; x < width; x++) {
        +
        +                    scaledX = x * relativeScale;
        +                    scaledY = y * relativeScale;
        + +
      • + + +
      • +
        + +
        + +
        +

        Clamp the cell’s noise value to between 0 and 1, then update it with the post-calculation sumFunction, if required

        + +
        + +
                            let clampedVal = (noiseValues[y][x] - min) / noiseSpan;
        +                    noiseValues[y][x] = sumFunction.call(this, clampedVal, x * relativeScale, y * relativeScale);
        +                }
        +            }
        + +
      • + + +
      • +
        + +
        + +
        +

        Update the cached noise values arrays

        + +
        + +
                    this.noiseValues = noiseValues;
        +        }
        +        else this.dirtyNoise = true;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        paintCanvas - internal function called by the cleanOutput function

        + +
        + +
        P.paintCanvas = function () {
        +
        +    if (this.dirtyOutput) {
        +
        +        this.dirtyOutput = false;
        +
        +        let {noiseValues, element, engine, width, height, color, colorFactory, monochromeStart, monochromeRange, hueStart, hueRange, saturation, luminosity} = this;
        + +
      • + + +
      • +
        + +
        + +
        +

        Noise values will be calculated in the cleanNoise function, but just in case this function gets invoked directly before the 2d array has been created …

        + +
        + +
                if (null != noiseValues) {
        + +
      • + + +
      • +
        + +
        + +
        +

        Update the Canvas element’s dimensions - this will also clear the canvas display

        + +
        + +
                    element.width = width;
        +            element.height = height;
        + +
      • + + +
      • +
        + +
        + +
        +

        Rebuild the display, pixel-by-pixel

        + +
        + +
                    switch (color) {
        +
        +                case 'hue' :
        +
        +                    for (let y = 0; y < height; y++) {
        +                        for (let x = 0; x < width; x++) {
        +
        +                            engine.fillStyle = `hsl(${(hueStart + (noiseValues[y][x] * hueRange)) % 360}, ${saturation}%, ${luminosity}%)`;
        +                            engine.fillRect(x, y, 1, 1);
        +                        }
        +                    }
        +                    break;
        +
        +                case 'gradient' :
        +
        +                    for (let y = 0; y < height; y++) {
        +                        for (let x = 0; x < width; x++) {
        +
        +                            engine.fillStyle = colorFactory.getRangeColor(noiseValues[y][x]);
        +                            engine.fillRect(x, y, 1, 1);
        +                        }
        +                    }
        +                    break;
        + +
      • + + +
      • +
        + +
        + +
        +

        The default color preference is monochrome

        + +
        + +
                        default :
        +
        +                    if (monochromeRange > 0) {
        +
        +                        if (monochromeStart + monochromeRange > 255) monochromeRange = 255 - monochromeStart;
        +                    }
        +                    else if (monochromeRange < 0) {
        +
        +                        if (monochromeStart - monochromeRange < 0) monochromeRange = monochromeStart;
        +                    }
        +
        +                    for (let y = 0; y < height; y++) {
        +                        for (let x = 0; x < width; x++) {
        +
        +                            let gray = Math.floor(monochromeStart + (noiseValues[y][x] * monochromeRange));
        +
        +                            engine.fillStyle = `rgb(${gray}, ${gray}, ${gray})`;
        +
        +                            engine.fillRect(x, y, 1, 1);
        +                        }
        +                    }
        +            }
        +        }
        +        else this.dirtyOutput = true;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Noise generator functionality

        + +
        + +
      • + + +
      • +
        + +
        + +
        +

        Convenience constants +P.F = 0.5 * (Math.sqrt(3) - 1); +P.G = (3 - Math.sqrt(3)) / 6;

        + +
        + +
        P.simplexConstantF = 0.5 * (Math.sqrt(3) - 1);
        +P.simplexConstantG = (3 - Math.sqrt(3)) / 6;
        +P.simplexConstantDoubleG = ((3 - Math.sqrt(3)) / 6) * 2;
        +P.perlinGrad = [[1, 1], [-1, 1], [1, -1], [-1, -1], [1, 0], [-1, 0], [0, 1], [0, -1]];
        + +
      • + + +
      • +
        + +
        + +
        +

        noiseEngines - a {key:object} object. Each named object contains two functions:

        +
          +
        • init - invoked to prepare the engine for a bout of calculations - called by the cleanNoise function
        • +
        • getNoiseValue - a function called on a per-pixel basis, which calculates the noise value for that pixel
        • +
        + +
        + +
        P.noiseEngines = {
        +
        +    'perlin': {
        +
        +        init: function () {
        +
        +            const {grad, size, rndEngine} = this;
        +
        +            let dist;
        +            
        +            grad.length = 0;
        +
        +            for(let i = 0; i < size; i++) {
        +
        +                grad[i] = [(rndEngine.random() * 2) - 1, (rndEngine.random() * 2) - 1];
        +                dist = Math.sqrt(grad[i][0] *  grad[i][0] + grad[i][1] * grad[i][1]);
        +                grad[i][0] /= dist;
        +                grad[i][1] /= dist;
        +            }
        +        },
        +
        +        getNoiseValue: function (x, y) {
        +
        +            const {size, perm, grad, smoothing} = this;
        +
        +            let a, b, u, v;
        +
        +            let bx0 = Math.floor(x) % size,
        +                bx1 = (bx0 + 1) % size;
        +
        +            let rx0 = x - Math.floor(x),
        +                rx1 = rx0 - 1;
        +
        +            let by0 = Math.floor(y) % size,
        +                by1 = (by0 + 1) % size;
        +
        +            let ry0 = y - Math.floor(y),
        +                ry1 = ry0 - 1;
        +
        +            let i = perm[bx0],
        +                j = perm[bx1];
        +
        +            let b00 = perm[i + by0],
        +                b10 = perm[j + by0],
        +                b01 = perm[i + by1],
        +                b11 = perm[j + by1];
        +
        +            let sx = smoothing(rx0),
        +                sy = smoothing(ry0);
        +            
        +            u = rx0 * grad[b00][0] + ry0 * grad[b00][1];
        +            v = rx1 * grad[b10][0] + ry0 * grad[b10][1];
        +            a = interpolate(sx, u, v);
        +            
        +            u = rx0 * grad[b01][0] + ry1 * grad[b01][1];
        +            v = rx1 * grad[b11][0] + ry1 * grad[b11][1];
        +            b = interpolate(sx, u, v);
        +            
        +            return 0.5 * (1 + interpolate(sy, a, b));
        +        },
        +    },
        +
        +    'improved-perlin': {
        +
        +        init: λnull,
        +
        +        getNoiseValue: function (x, y) {
        +
        +            const {size, perm, permMod8, perlinGrad, smoothing} = this;
        +
        +            let a, b, u, v;
        +
        +            let bx0 = Math.floor(x) % size, 
        +                bx1 = (bx0 + 1) % size;
        +
        +            let rx0 = x - Math.floor(x), 
        +                rx1 = rx0 - 1;
        +
        +            let by0 = Math.floor(y) % size, 
        +                by1 = (by0 + 1) % size;
        +
        +            let ry0 = y - Math.floor(y), 
        +                ry1 = ry0 - 1;
        +
        +            let i = perm[bx0], 
        +                j = perm[bx1]; 
        +
        +            let b00 = permMod8[i + by0], 
        +                b10 = permMod8[j + by0], 
        +                b01 = permMod8[i + by1], 
        +                b11 = permMod8[j + by1];
        +            
        +            let sx = smoothing(rx0),
        +                sy = smoothing(ry0);
        +            
        +            u = rx0 * perlinGrad[b00][0] + ry0 * perlinGrad[b00][1];
        +            v = rx1 * perlinGrad[b10][0] + ry0 * perlinGrad[b10][1];
        +            a = interpolate(sx, u, v);
        +            
        +            u = rx0 * perlinGrad[b01][0] + ry1 * perlinGrad[b01][1];
        +            v = rx1 * perlinGrad[b11][0] + ry1 * perlinGrad[b11][1];
        +            b = interpolate(sx, u, v);
        +
        +            return 0.5 * (1 + interpolate(sy, a, b));
        +        }
        +    },
        +
        +    'simplex': {
        +
        +        init: λnull,
        +
        +        getNoiseValue: function (x, y) {
        +
        +            const getCornerNoise = function (cx, cy, gridPos) {
        +
        +                let calc = 0.5 - (cx * cx) - (cy * cy);
        +                if (calc < 0) return 0;
        +
        +                let [gx, gy] = perlinGrad[gridPos];
        +                return calc * calc * ((gx * cx) + (gy * cy));
        +            };
        +            
        +            const {simplexConstantF, simplexConstantG, simplexConstantDoubleG, size, perlinGrad, perm, permMod8} = this;
        +            
        +            let summedCoordinates = (x + y) * simplexConstantF,
        +                summedX = Math.floor(x + summedCoordinates),
        +                summedY = Math.floor(y + summedCoordinates),
        +                modifiedSummedCoordinates = (summedX + summedY) * simplexConstantG;
        +
        +            let cornerX = x - (summedX - modifiedSummedCoordinates),
        +                cornerY = y - (summedY - modifiedSummedCoordinates);
        +            
        +            let remainderX = summedX % size,
        +                remainderY = summedY % size;
        +
        +            let pos = permMod8[remainderX + perm[remainderY]],
        +                noise = getCornerNoise(cornerX, cornerY, pos);
        +
        +            pos = permMod8[remainderX + 1 + perm[remainderY + 1]]
        +            noise += getCornerNoise((cornerX - 1 + simplexConstantDoubleG), (cornerY - 1 + simplexConstantDoubleG), pos);
        +
        +            let unitA = 0,
        +                unitB = 1;
        +
        +            if (cornerX > cornerY) {
        +                unitA = 1;
        +                unitB = 0;
        +            }
        +
        +            pos = permMod8[remainderX + unitA + perm[remainderY + unitB]];
        +            noise += getCornerNoise((cornerX - unitA + simplexConstantG), (cornerY - unitB + simplexConstantG), pos);
        +
        +            return 0.5 + (35 * noise);
        +        },
        +    },
        +
        +    'value': {
        +
        +        init: function () {
        +
        +            const {values, size, rndEngine} = this;
        +
        +            values.length = 0;
        +
        +            for(let i = 0; i < size; i++) {
        +
        +                values[i] = values[i + size] = rndEngine.random();
        +            }
        +        },
        +
        +        getNoiseValue: function (x, y) {
        +
        +            const {values, size, perm, smoothing} = this;
        +
        +            let x0 = Math.floor(x) % size,
        +                y0 = Math.floor(y) % size,
        +                x1 = (x0 + 1) % size,
        +                y1 = (y0 + 1) % size,
        +                vx = x - Math.floor(x),
        +                vy = y - Math.floor(y),
        +                sx = smoothing(vx),
        +                sy = smoothing(vy),
        +                i = perm[x0],
        +                j = perm[x1],
        +                p00 = perm[i + y0],
        +                p10 = perm[j + y0],
        +                p01 = perm[i + y1],
        +                p11 = perm[j + y1],
        +                i1 = interpolate(sx, values[p00], values[p10]),
        +                i2 = interpolate(sx, values[p01], values[p11]);
        +
        +            return interpolate(sy, i1, i2);
        +        },
        +    },
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        generatePermutationTable - internal function called by the cleanNoise function

        +
          +
        • The permutation tables get recalculated each time the noise data gets cleaned
        • +
        • rndEngine is a seedable pseudo-random number generator
        • +
        + +
        + +
        P.generatePermutationTable = function () {
        +
        +    const {perm, permMod8, rndEngine, size} = this;
        +
        +    perm.length = 0;
        +    permMod8.length = 0;
        +
        +    let i, j, k;
        +
        +    for(i = 0; i < size; i++) {
        +
        +        perm[i] = i;
        +    }
        +    
        +    while (--i) {
        +
        +        j = Math.floor(rndEngine.random() * size);
        +        k = perm[i];
        +        perm[i] = perm[j];
        +        perm[j] = k;
        +    }
        +    
        +    for(i = 0; i < size; i++) {
        +
        +        perm[i + size] = perm[i];
        +        permMod8[i] = permMod8[i + size] = perm[i] % 8;
        +    }
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        octaveFunctions - a {key:functions} object holding functions used to modify octave loop results

        +
          +
        • calling signature: octaveFunction(octave, scaledX, scaledY, o + 1)
        • +
        + +
        + +
        P.octaveFunctions = {
        +
        +    none: λfirstArg,
        +    absolute: function (octave) { return Math.abs((octave * 2) - 1) },
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        sumFunctions - a {key:functions} object holding functions used to modify noise values after their calculation has completed (post-processing)

        +
          +
        • calling signature: sumFunction.call(this, clampedVal, x * relativeScale, y * relativeScale)
        • +
        + +
        + +
        P.sumFunctions = {
        +
        +    none: λfirstArg,
        +
        +    'sine-x': function (v, sx, sy) { return 0.5 + (Math.sin((sx * this.sineFrequencyCoeff) + v) / 2) },
        +    'sine-y': function (v, sx, sy) { return 0.5 + (Math.sin((sy * this.sineFrequencyCoeff) + v) / 2) },
        +    sine: function (v, sx, sy) { return 0.5 + (Math.sin((sx * this.sineFrequencyCoeff) + v) / 4) + (Math.sin((sy * this.sineFrequencyCoeff) + v) / 4) },
        +
        +    modular: function(v) {
        +        let g = v * this.modularAmplitude;
        +        return g - Math.floor(g);
        +    },
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        smoothingFunctions - a {key:function} object containing various fade functions which can be used to smooth calculated coordinate values so that they will ease towards integral values.

        +
          +
        • Used by the “perlin_classic”, “perlin_improved” and “value” getNoiseValue functions; the “simplex” getNoiseValue function does away with the need for a smoothing operation.
        • +
        • calling signature: smoothing(value)
        • +
        + +
        + +
        P.smoothingFunctions = {
        + +
      • + + +
      • +
        + +
        + +
        +

        none - effectively linear - no smoothing gets performed

        + +
        + +
            none: λfirstArg,
        +
        +    easeOutSine,
        +    easeInSine,
        +    easeOutInSine,
        +    easeOutQuad,
        +    easeInQuad,
        +    easeOutInQuad,
        +    easeOutCubic,
        +    easeInCubic,
        +    easeOutInCubic,
        +    easeOutQuart,
        +    easeInQuart,
        +    easeOutInQuart,
        +    easeOutQuint,
        +    easeInQuint,
        +    easeOutInQuint,
        +    easeOutExpo,
        +    easeInExpo,
        +    easeOutInExpo,
        +    easeOutCirc,
        +    easeInCirc,
        +    easeOutInCirc,
        +    easeOutBack,
        +    easeInBack,
        +    easeOutInBack,
        +    easeOutElastic,
        +    easeInElastic,
        +    easeOutInElastic,
        +    easeOutBounce,
        +    easeInBounce,
        +    easeOutInBounce,
        + +
      • + + +
      • +
        + +
        + +
        +

        cosine - a cosine-based interpolator

        + +
        + +
            cosine: function(t) { return .5 * (1 + Math.cos((1 - t) * Math.PI)) },
        + +
      • + + +
      • +
        + +
        + +
        +

        hermite - a cubic Hermite interpolator

        + +
        + +
            hermite: function(t) { return t * t * (-t * 2 + 3) },
        + +
      • + + +
      • +
        + +
        + +
        +

        quintic - the original ease function used by Perlin

        + +
        + +
            quintic: function(t) { return t * t * t * (t * (t * 6 - 15) + 10) },
        +};
        + +
      • + + +
      • +
        + +
        + +
        +

        Factory

        +
        scrawl.makeNoise({
        +    name: 'my-noise-generator',
        +    width: 50,
        +    height: 50,
        +    octaves: 5,
        +    scale: 2,
        +    noiseFunction: 'simplex',
        +});
        + +
        + +
        const makeNoise = function (items) {
        +    return new Noise(items);
        +};
        +
        +constructors.Noise = Noise;
        + +
      • + + +
      • +
        + +
        + +
        +

        Exports

        + +
        + +
        export {
        +    makeNoise,
        +};
        + +
      • + +
      +
      + + diff --git a/docs/source/factory/oval.html b/docs/source/factory/oval.html index f2381f6b7..53cf6ef44 100644 --- a/docs/source/factory/oval.html +++ b/docs/source/factory/oval.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/palette.html b/docs/source/factory/palette.html index 7ec9b3ccb..1587a0217 100644 --- a/docs/source/factory/palette.html +++ b/docs/source/factory/palette.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/particle.html b/docs/source/factory/particle.html index ba024afed..ba72662f2 100644 --- a/docs/source/factory/particle.html +++ b/docs/source/factory/particle.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/particleForce.html b/docs/source/factory/particleForce.html index 886405d46..d6a67be65 100644 --- a/docs/source/factory/particleForce.html +++ b/docs/source/factory/particleForce.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/particleHistory.html b/docs/source/factory/particleHistory.html index 894f2b0b7..7ecb0b9ec 100644 --- a/docs/source/factory/particleHistory.html +++ b/docs/source/factory/particleHistory.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/particleSpring.html b/docs/source/factory/particleSpring.html index b28173fd6..1cc0e5505 100644 --- a/docs/source/factory/particleSpring.html +++ b/docs/source/factory/particleSpring.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/particleWorld.html b/docs/source/factory/particleWorld.html index ebe28b297..906f80b6c 100644 --- a/docs/source/factory/particleWorld.html +++ b/docs/source/factory/particleWorld.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/pattern.html b/docs/source/factory/pattern.html index 03d64c929..db66fd7eb 100644 --- a/docs/source/factory/pattern.html +++ b/docs/source/factory/pattern.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -573,7 +578,7 @@

      Mixins

      Pattern attributes

      • Attributes defined in the base mixin: name.
      • -
      • Attributes defined in the pattern mixin: repeat.
      • +
      • Attributes defined in the pattern mixin: repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF.
      • Attributes defined in the assetConsumer mixin: asset, spriteTrack, imageSource, spriteSource, videoSource, source.
      diff --git a/docs/source/factory/phrase.html b/docs/source/factory/phrase.html index ed13f62a8..0c4612c02 100644 --- a/docs/source/factory/phrase.html +++ b/docs/source/factory/phrase.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -1647,7 +1652,7 @@
      FontAttribute attributes
      -

      style - CSS font-style String - normal, italic

      +

      style - CSS font-style String

      @@ -1672,7 +1677,7 @@
      FontAttribute attributes
      -

      variant - CSS font-variant String - normal, small-caps

      +

      variant - CSS font-variant String

      @@ -1697,7 +1702,7 @@
      FontAttribute attributes
      -

      weight - CSS font-weight String - normal, bold, lighter, bolder, or an integer Number (between 1 and 1000)

      +

      weight - CSS font-weight String

      @@ -1722,7 +1727,7 @@
      FontAttribute attributes
      -

      stretch - CSS font-stretch String - normal

      +

      stretch - CSS font-stretch String

      @@ -1747,13 +1752,7 @@
      FontAttribute attributes
      -

      size - CSS font-size String:

      -
        -
      • xx-small, x-small, small, medium, large, x-large, xx-large
      • -
      • smaller, larger
      • -
      • 120%
      • -
      • 1.2rem, 12px, etc - Scrawl-canvas will work with the following metrics: em, ch, ex, rem, vh, vw, vmin, vmax, px, cm, mm, in, pc, pt
      • -
      +

      size - CSS font-size String

      @@ -1864,8 +1863,23 @@

      Prototype functions

      -
      -P.cleanDimensionsAdditionalActions = function () {
      +        
      +        
      +        
      +        
    • +
      + +
      + +
      +

      cleanDimensionsAdditionalActions - local overwrite

      + +
      + +
      P.cleanDimensionsAdditionalActions = function () {
      +
      +    this.fontAttributes.dirtyFont = true;
      +    this.fontAttributes.updateMetadata(this.scale, this.lineHeight, this.getHost());
       
           if (this.dimensions[0] === 'auto') {
       
      @@ -1874,9 +1888,9 @@ 

      Prototype functions

      let myCell = requestCell(), engine = myCell.engine; - engine.font = this.fontAttributes.buildFont(); + engine.font = this.fontAttributes.getFontString(); - this.currentDimensions[0] = Math.ceil(engine.measureText(this.currentText).width); + this.currentDimensions[0] = Math.ceil(engine.measureText(this.currentText).width / this.scale); releaseCell(myCell); } @@ -1888,11 +1902,11 @@

      Prototype functions

    • -
    • +
    • - +

      setSectionStyles - internal function

      @@ -1929,11 +1943,11 @@

      Prototype functions

    • -
    • +
    • - +

      addSectionClass, removeSectionClass - add and remove section class definitions to the entity’s sectionClasses object.

      @@ -1964,11 +1978,11 @@

      Prototype functions

    • -
    • +
    • - +

      getTextPath - internal function

      @@ -1995,11 +2009,11 @@

      Prototype functions

    • -
    • +
    • - +

      Display cycle functionality

      Phrase entitys, because they handle graphical text which has its own special requirements and methods in the Canvas API, has to overwrite a substantial portion of the Display cycle functionality defined in the entity mixin.

      @@ -2009,11 +2023,11 @@

      Display cycle functionality

    • -
    • +
    • - +

      cleanPathObject - overwrites mixin/entity.js functionality so that it can deal with the dirtyFont, dirtyText and dirtyHandle flags

        @@ -2031,7 +2045,6 @@

        Display cycle functionality

        if (this.dirtyFont && this.fontAttributes) { this.dirtyFont = false; - this.fontAttributes.buildFont(this.scale); this.dirtyText = true; this.dirtyMimicDimensions = true; this.dirtyPositionSubscribers = true; @@ -2060,11 +2073,11 @@

        Display cycle functionality

        -
      • +
      • - +

        buildText - internal function called by cleanPathObject

        @@ -2113,11 +2126,11 @@

        Display cycle functionality

      • -
      • +
      • - +

        convertTextEntityCharacters - internal function called by buildText

          @@ -2140,11 +2153,11 @@

          Display cycle functionality

          -
        • +
        • - +

          calculateTextPositions - internal function called by buildText

            @@ -2158,11 +2171,11 @@

            Display cycle functionality

            -
          • +
          • - +
            1. strokeStyle and fillStyle helper function
            2. @@ -2194,11 +2207,11 @@

              Display cycle functionality

              -
            3. +
            4. - +
              1. Setup - get values for text? arrays, current?, highlight?, ?Attributes, etc
              2. @@ -2246,7 +2259,10 @@

                Display cycle functionality

                justify = this.justify, handle, handleX, handleY; - let defaultFont = fontAttributes.update(scale), + fontAttributes.updateMetadata(scale, lineHeight, host); + glyphAttributes.updateMetadata(scale, lineHeight, host); + + let defaultFont = fontAttributes.getFontString(), defaultFillStyle = makeStyle(state.fillStyle), defaultStrokeStyle = makeStyle(state.strokeStyle), defaultSpace = this.letterSpacing * scale, @@ -2272,11 +2288,11 @@

                Display cycle functionality

                -
              3. +
              4. - +
                1. Create textGlyphs array
                2. @@ -2293,11 +2309,11 @@

                  Display cycle functionality

                  -
                3. +
                4. - +
                  1. textPositions array will include an array of data for each glyph
                  2. @@ -2321,11 +2337,11 @@

                    Display cycle functionality

                    -
                  3. +
                  4. - +
                    1. Process the sectionStyles array to start populating the textPositions arrays
                    2. @@ -2360,7 +2376,7 @@

                      Display cycle functionality

                      } if (gStyle.defaults) { - currentFont = glyphAttributes.update(scale, fontAttributes); + currentFont = glyphAttributes.update(fontAttributes); currentStrokeStyle = defaultStrokeStyle; currentFillStyle = defaultFillStyle; currentSpace = defaultSpace; @@ -2412,7 +2428,7 @@

                      Display cycle functionality

                      if (i !== 0 && (gStyle.variant || gStyle.weight || gStyle.style || gStyle.stretch || gStyle.size || gStyle.sizeValue || gStyle.sizeMetric || gStyle.family || gStyle.font)) { - item = glyphAttributes.update(scale, gStyle); + item = glyphAttributes.update(gStyle); if (item !== currentFont) { currentFont = item; @@ -2427,11 +2443,11 @@

                      Display cycle functionality

                      -
                    3. +
                    4. - +

                      Setup textGlyphWidths array, populating it with current letterSpacing values

                      @@ -2443,11 +2459,11 @@

                      Display cycle functionality

                    5. -
                    6. +
                    7. - +

                      Finish populating textGlyphWidths

                      @@ -2463,11 +2479,11 @@

                      Display cycle functionality

                    8. -
                    9. +
                    10. - +
                      1. Calculate the text height value
                      2. @@ -2490,11 +2506,11 @@

                        Display cycle functionality

                        -
                      3. +
                      4. - +
                        1. Calculate glyph width values
                        2. @@ -2542,11 +2558,11 @@

                          Display cycle functionality

                          -
                        3. +
                        4. - +

                          Calculate text line arrays

                          @@ -2568,11 +2584,11 @@

                          Display cycle functionality

                        5. -
                        6. +
                        7. - +

                          Need starts to be less than ends

                            @@ -2598,11 +2614,11 @@

                            Display cycle functionality

                            -
                          • +
                          • - +

                            Need to pick up the last (or only) line

                            @@ -2613,11 +2629,11 @@

                            Display cycle functionality

                          • -
                          • +
                          • - +

                            Pick up single line

                            @@ -2635,11 +2651,11 @@

                            Display cycle functionality

                          • -
                          • +
                          • - +

                            Final line of multiline text

                            @@ -2660,11 +2676,11 @@

                            Display cycle functionality

                          • -
                          • +
                          • - +

                            … And complete the population of data for highlight, overline, underline

                            @@ -2682,11 +2698,11 @@

                            Display cycle functionality

                          • -
                          • +
                          • - +
                            1. Calculate localHeight
                            2. @@ -2708,11 +2724,11 @@

                              Display cycle functionality

                              -
                            3. +
                            4. - +

                              Handle path positioning (which we’ll assume will need to be done for every display cycle) separately during stamping

                              @@ -2723,11 +2739,11 @@

                              Display cycle functionality

                            5. -
                            6. +
                            7. - +
                              1. We should now be in a position where we can calculate each glyph’s startXY values
                              2. @@ -2741,11 +2757,11 @@

                                Display cycle functionality

                                -
                              3. +
                              4. - +

                                Scenario 1: justify === 'full'

                                @@ -2786,11 +2802,11 @@

                                Display cycle functionality

                              5. -
                              6. +
                              7. - +

                                Scenario 2: regular text - justify === 'left', or 'centre', or 'right'

                                @@ -2814,11 +2830,11 @@

                                Display cycle functionality

                              8. -
                              9. +
                              10. - +

                                BUG: There’s an issue here which causes the function to fail when treatWordAsGlyph flag is set to true. Affects non-path-referencing Phrase entitys. This test to see if item exists is a temporary fix.

                                  @@ -2846,11 +2862,11 @@

                                  Display cycle functionality

                                  -
                                • +
                                • - +
                                  1. Clean up and exit
                                  2. @@ -2874,11 +2890,11 @@

                                    Display cycle functionality

                                    -
                                  3. +
                                  4. - +
                                    Stamping the entity onto a Cell wrapper <canvas> element
                                    @@ -2887,11 +2903,11 @@
                                    Stamping the ent
                                  5. -
                                  6. +
                                  7. - +

                                    regularStampSynchronousActions - overwrites the mixin/entity.js function

                                    @@ -2914,11 +2930,11 @@
                                    Stamping the ent
                                  8. -
                                  9. +
                                  10. - +

                                    Scrawl-canvas clips canvases to the Phrase’s hit area

                                      @@ -2978,7 +2994,7 @@
                                      Stamping the ent if (!this.noCanvasEngineUpdates) dest.setEngine(this); - pos = this.textPositions; + pos = this.textPositions || []; for (i = 0, iz = pos.length; i < iz; i++) { @@ -2993,11 +3009,11 @@
                                      Stamping the ent -
                                    • +
                                    • - +

                                      calculateGlyphPathPositions - internal helper function called by regularStampSynchronousActions

                                      @@ -3022,11 +3038,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      textPathPosition Array indexes [ 0-font - font definition, or null @@ -3086,11 +3102,11 @@

                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      preStamper - internal helper function called by regularStampSynchronousActions

                                      @@ -3123,11 +3139,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      data[0] - glyph data[1] - xpos @@ -3165,11 +3181,11 @@

                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper - object holding stamp method functions - functions called by regularStampSynchronousActions

                                      @@ -3180,11 +3196,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.draw

                                      @@ -3198,11 +3214,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.fill

                                      @@ -3216,11 +3232,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.drawAndFill

                                      @@ -3237,11 +3253,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.fillAndDraw

                                      @@ -3259,11 +3275,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.drawThenFill

                                      @@ -3278,11 +3294,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.fillThenDraw

                                      @@ -3297,11 +3313,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      stamper.clear

                                      @@ -3319,11 +3335,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      drawBoundingBox - internal helper function called by regularStampSynchronousActions

                                      @@ -3346,11 +3362,11 @@
                                      Stamping the ent
                                    • -
                                    • +
                                    • - +

                                      performRotation - internal helper function called by regularStampSynchronousActions

                                    • @@ -1526,7 +1531,8 @@
                                      Stamp methods
                                      P.fill = function (engine) {
                                       
                                      -    if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
                                      +    let [x, y, w, h] = this.copyArray;
                                      +    if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
                                       };
                                      @@ -1546,7 +1552,8 @@
                                      Stamp methods
                                      engine.stroke(this.pathObject); - if (this.source) { + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) { this.currentHost.clearShadow(); engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); @@ -1570,11 +1577,13 @@
                                      Stamp methods
                                      engine.stroke(this.pathObject); - if (this.source) { + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) { this.currentHost.clearShadow(); engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); } + engine.stroke(this.pathObject); };
    • @@ -1594,7 +1603,9 @@
      Stamp methods
      P.drawThenFill = function (engine) {
       
           engine.stroke(this.pathObject);
      -    if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
      +
      +    let [x, y, w, h] = this.copyArray;
      +    if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
       };
      @@ -1612,7 +1623,9 @@
      Stamp methods
      P.fillThenDraw = function (engine) {
       
      -    if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
      +    let [x, y, w, h] = this.copyArray;
      +    if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray);
      +
           engine.stroke(this.pathObject);
       };
      diff --git a/docs/source/factory/polygon.html b/docs/source/factory/polygon.html index d6989cf50..70f61327f 100644 --- a/docs/source/factory/polygon.html +++ b/docs/source/factory/polygon.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/polyline.html b/docs/source/factory/polyline.html index 7db15fdaa..fc7f5eea3 100644 --- a/docs/source/factory/polyline.html +++ b/docs/source/factory/polyline.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/quadratic.html b/docs/source/factory/quadratic.html index 346255c19..bf66cb9b4 100644 --- a/docs/source/factory/quadratic.html +++ b/docs/source/factory/quadratic.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/quaternion.html b/docs/source/factory/quaternion.html index 2ed0fc800..63d4a02a5 100644 --- a/docs/source/factory/quaternion.html +++ b/docs/source/factory/quaternion.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/radialGradient.html b/docs/source/factory/radialGradient.html index b488d35e4..1653b011b 100644 --- a/docs/source/factory/radialGradient.html +++ b/docs/source/factory/radialGradient.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/rectangle.html b/docs/source/factory/rectangle.html index 50aeb383e..0757eb023 100644 --- a/docs/source/factory/rectangle.html +++ b/docs/source/factory/rectangle.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/renderAnimation.html b/docs/source/factory/renderAnimation.html index e04734464..db7721b4d 100644 --- a/docs/source/factory/renderAnimation.html +++ b/docs/source/factory/renderAnimation.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/shape.html b/docs/source/factory/shape.html index 8b34c6444..628abc533 100644 --- a/docs/source/factory/shape.html +++ b/docs/source/factory/shape.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/spiral.html b/docs/source/factory/spiral.html index d057a5038..49c001d83 100644 --- a/docs/source/factory/spiral.html +++ b/docs/source/factory/spiral.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/spriteAsset.html b/docs/source/factory/spriteAsset.html index 0456c005c..63204ec62 100644 --- a/docs/source/factory/spriteAsset.html +++ b/docs/source/factory/spriteAsset.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/stack.html b/docs/source/factory/stack.html index 7c576eee2..90b7e4d32 100644 --- a/docs/source/factory/stack.html +++ b/docs/source/factory/stack.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/star.html b/docs/source/factory/star.html index 39e812b1d..5b7bb1b12 100644 --- a/docs/source/factory/star.html +++ b/docs/source/factory/star.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/state.html b/docs/source/factory/state.html index 2ace3daf0..0f7c6d38c 100644 --- a/docs/source/factory/state.html +++ b/docs/source/factory/state.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/tetragon.html b/docs/source/factory/tetragon.html index 1a88661c3..ee2364806 100644 --- a/docs/source/factory/tetragon.html +++ b/docs/source/factory/tetragon.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/ticker.html b/docs/source/factory/ticker.html index c7501438d..c45442e0d 100644 --- a/docs/source/factory/ticker.html +++ b/docs/source/factory/ticker.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/tracer.html b/docs/source/factory/tracer.html index 87c5da2a6..d542e46e7 100644 --- a/docs/source/factory/tracer.html +++ b/docs/source/factory/tracer.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/factory/tween.html b/docs/source/factory/tween.html index 6615e6d46..9aca6ac9d 100644 --- a/docs/source/factory/tween.html +++ b/docs/source/factory/tween.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -539,7 +544,7 @@

      Imports

      import { constructors, animationtickers, radian } from '../core/library.js';
      -import { mergeOver, pushUnique, xt, xtGet, xto, convertTime, λnull } from '../core/utilities.js';
      +import { mergeOver, pushUnique, xt, xtGet, xto, convertTime, λnull, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js';
       
       import { makeTicker } from './ticker.js';
       
      @@ -1683,8 +1688,7 @@ 

      Animation

          linear: function (start, change, position) {
       
               return start + (position * change);
      -    }
      -};
      + },
      @@ -1695,6 +1699,171 @@

      Animation

      +

      The following easing functions have been taken from the easings.net web page: easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce

      + + + +
      +    easeOutSine: function (start, change, position) {
      +
      +        return start + (change * easeOutSine(position));
      +    }, 
      +
      +    easeInSine: function (start, change, position) {
      +
      +        return start + (change * easeInSine(position));
      +    }, 
      +
      +    easeOutInSine: function (start, change, position) {
      +
      +        return start + (change * easeOutInSine(position));
      +    }, 
      +
      +    easeOutQuad: function (start, change, position) {
      +
      +        return start + (change * easeOutQuad(position));
      +    }, 
      +
      +    easeInQuad: function (start, change, position) {
      +
      +        return start + (change * easeInQuad(position));
      +    }, 
      +
      +    easeOutInQuad: function (start, change, position) {
      +
      +        return start + (change * easeOutInQuad(position));
      +    }, 
      +
      +    easeOutCubic: function (start, change, position) {
      +
      +        return start + (change * easeOutCubic(position));
      +    }, 
      +
      +    easeInCubic: function (start, change, position) {
      +
      +        return start + (change * easeInCubic(position));
      +    }, 
      +
      +    easeOutInCubic: function (start, change, position) {
      +
      +        return start + (change * easeOutInCubic(position));
      +    }, 
      +
      +    easeOutQuart: function (start, change, position) {
      +
      +        return start + (change * easeOutQuart(position));
      +    }, 
      +
      +    easeInQuart: function (start, change, position) {
      +
      +        return start + (change * easeInQuart(position));
      +    }, 
      +
      +    easeOutInQuart: function (start, change, position) {
      +
      +        return start + (change * easeOutInQuart(position));
      +    }, 
      +
      +    easeOutQuint: function (start, change, position) {
      +
      +        return start + (change * easeOutQuint(position));
      +    }, 
      +
      +    easeInQuint: function (start, change, position) {
      +
      +        return start + (change * easeInQuint(position));
      +    }, 
      +
      +    easeOutInQuint: function (start, change, position) {
      +
      +        return start + (change * easeOutInQuint(position));
      +    }, 
      +
      +    easeOutExpo: function (start, change, position) {
      +
      +        return start + (change * easeOutExpo(position));
      +    }, 
      +
      +    easeInExpo: function (start, change, position) {
      +
      +        return start + (change * easeInExpo(position));
      +    }, 
      +
      +    easeOutInExpo: function (start, change, position) {
      +
      +        return start + (change * easeOutInExpo(position));
      +    }, 
      +
      +    easeOutCirc: function (start, change, position) {
      +
      +        return start + (change * easeOutCirc(position));
      +    }, 
      +
      +    easeInCirc: function (start, change, position) {
      +
      +        return start + (change * easeInCirc(position));
      +    }, 
      +
      +    easeOutInCirc: function (start, change, position) {
      +
      +        return start + (change * easeOutInCirc(position));
      +    }, 
      +
      +    easeOutBack: function (start, change, position) {
      +
      +        return start + (change * easeOutBack(position));
      +    }, 
      +
      +    easeInBack: function (start, change, position) {
      +
      +        return start + (change * easeInBack(position));
      +    }, 
      +
      +    easeOutInBack: function (start, change, position) {
      +
      +        return start + (change * easeOutInBack(position));
      +    }, 
      +
      +    easeOutElastic: function (start, change, position) {
      +
      +        return start + (change * easeOutElastic(position));
      +    }, 
      +
      +    easeInElastic: function (start, change, position) {
      +
      +        return start + (change * easeInElastic(position));
      +    }, 
      +
      +    easeOutInElastic: function (start, change, position) {
      +
      +        return start + (change * easeOutInElastic(position));
      +    }, 
      +
      +    easeOutBounce: function (start, change, position) {
      +
      +        return start + (change * easeOutBounce(position));
      +    }, 
      +
      +    easeInBounce: function (start, change, position) {
      +
      +        return start + (change * easeInBounce(position));
      +    }, 
      +
      +    easeOutInBounce: function (start, change, position) {
      +
      +        return start + (change * easeOutInBounce(position));
      +    }, 
      +};
      + + + + +
    • +
      + +
      + +

      setDefinitionsValues - convert start and end values into float Numbers.

    • diff --git a/docs/source/mixin/anchor.html b/docs/source/mixin/anchor.html index e78c6ab9b..a29bfc846 100644 --- a/docs/source/mixin/anchor.html +++ b/docs/source/mixin/anchor.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/asset.html b/docs/source/mixin/asset.html index 9f5edf5c4..9b53f2ab8 100644 --- a/docs/source/mixin/asset.html +++ b/docs/source/mixin/asset.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -847,11 +852,12 @@
      Subscribe and unsubscribe to an a
      -

      notifySubscribers - Subscriber notification in the asset factories will happen when something changes with the image. Changes vary across the different types of asset:

      +

      notifySubscribers, notifySubscriber - Subscriber notification in the asset factories will happen when something changes with the image. Changes vary across the different types of asset:

      • imageAsset - needs to update its subscribers when an image completes loading - or, for <img> sources with srcset (and sizes) attributes, when the image completes a reload of its source data.
      • spriteAsset - will also update its subscribers each time it moves to a new sprite image frame, if the sprite is being animated
      • videoAsset - will update its subscribers for every RAF tick while the video is playing, or if the video is halted and seeks to a different time in the video play stream.
      • +
      • Noise - will update its subscribers when any of its attributes changes (Note: factory/noise.js overwrites these functions).

      All notifications are push; the notification is achieved by setting various attributes and flags in each subscriber.

      diff --git a/docs/source/mixin/assetConsumer.html b/docs/source/mixin/assetConsumer.html index 6904fdace..54837b22d 100644 --- a/docs/source/mixin/assetConsumer.html +++ b/docs/source/mixin/assetConsumer.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/base.html b/docs/source/mixin/base.html index c98e7456f..21cd6ff9a 100644 --- a/docs/source/mixin/base.html +++ b/docs/source/mixin/base.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/cascade.html b/docs/source/mixin/cascade.html index 1d9f8be92..89ad54e4e 100644 --- a/docs/source/mixin/cascade.html +++ b/docs/source/mixin/cascade.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/delta.html b/docs/source/mixin/delta.html index 5d0b4b79e..68cbb8f6b 100644 --- a/docs/source/mixin/delta.html +++ b/docs/source/mixin/delta.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/displayShape.html b/docs/source/mixin/displayShape.html index 5d8c48f56..b8ca5a9c5 100644 --- a/docs/source/mixin/displayShape.html +++ b/docs/source/mixin/displayShape.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/dom.html b/docs/source/mixin/dom.html index afcd49748..84b593933 100644 --- a/docs/source/mixin/dom.html +++ b/docs/source/mixin/dom.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/entity.html b/docs/source/mixin/entity.html index 1bcbb9a7f..02f639a62 100644 --- a/docs/source/mixin/entity.html +++ b/docs/source/mixin/entity.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -476,6 +481,7 @@

      Imports

      import { λnull, mergeOver, pushUnique, xt, addStrings, isa_obj } from '../core/utilities.js';
       import { currentGroup, scrawlCanvasHold } from '../core/document.js';
      +import { asset } from '../core/library.js';
       
       import { makeState } from '../factory/state.js';
       import { requestCell, releaseCell } from '../factory/cell.js';
      @@ -1585,6 +1591,9 @@ 
      Step 2: invoke the entity’s st if (worker) releaseFilterWorker(worker); currentEngine.save(); + + currentEngine.globalAlpha = (self.state && self.state.globalAlpha) ? self.state.globalAlpha : 1; + currentEngine.globalCompositeOperation = (self.state && self.state.globalCompositeOperation) ? self.state.globalCompositeOperation : 'source-over'; currentEngine.setTransform(1, 0, 0, 1, 0, 0); @@ -1770,6 +1779,21 @@
      Step 2: invoke the entity’s st
      +

      NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET’S IMAGEDATA AT THIS POINT

      + +
      + +
                      self.preprocessFilters(self.currentFilters);
      + + + + +
    • +
      + +
      + +

      Pass control over to the web worker

      @@ -1783,11 +1807,11 @@
      Step 2: invoke the entity’s st
    • -
    • +
    • - +

      Handle the web worker response

      @@ -1815,11 +1839,11 @@
      Step 2: invoke the entity’s st
    • -
    • +
    • - +

      Where no filter is required, but we still want to stash the results

      @@ -1836,11 +1860,11 @@
      Step 2: invoke the entity’s st
    • -
    • +
    • - +

      getCellCoverage - internal helper function - calculates the box start and dimensions values for the entity on its current Cell host, to help minimize work required when applying filters to the entity output. Also used when building an image when the scrawl.createImageFromEntity function is invoked.

      @@ -1880,11 +1904,11 @@
      Step 2: invoke the entity’s st
    • -
    • +
    • - +

      simpleStamp - an alternative to the stamp function, to get an entity to stamp its output onto a Cell.

        @@ -1910,11 +1934,11 @@
        Step 2: invoke the entity’s st -
      • +
      • - +
        Stamping the entity onto a Cell wrapper <canvas> element

        regularStampSynchronousActions - this function coordinates the actions required for an entity to display its output on a Cell wrapper’s <canvas> element.

        @@ -1934,11 +1958,11 @@
        Stamping the ent
      • -
      • +
      • - +

        Get a handle to the Cell wrapper - which needs to be assigned and checked prior to calling this function

        @@ -1956,11 +1980,11 @@
        Stamping the ent
      • -
      • +
      • - +

        Get the Cell wrapper to perform required transformations on its <canvas> element’s 2D engine

        @@ -1971,11 +1995,11 @@
        Stamping the ent
      • -
      • +
      • - +

        Get the Cell wrapper to update its 2D engine’s attributes to match the entity’s requirements

        @@ -1986,11 +2010,11 @@
        Stamping the ent
      • -
      • +
      • - +

        Invoke the appropriate stamping method (below)

        @@ -2003,11 +2027,11 @@
        Stamping the ent
      • -
      • +
      • - +
        Stamp methods

        All actual drawing is achieved using the entity’s pre-calculated Path2D object.

        @@ -2017,11 +2041,11 @@
        Stamp methods
      • -
      • +
      • - +

        draw - stroke the entity outline with the entity’s strokeStyle color, gradient or pattern - including shadow

        @@ -2035,11 +2059,11 @@
        Stamp methods
      • -
      • +
      • - +

        fill - fill the entity with the entity’s fillStyle color, gradient or pattern - including shadow

        @@ -2053,11 +2077,11 @@
        Stamp methods
      • -
      • +
      • - +

        drawAndFill - stroke the entity’s outline, remove shadow, then fill it

        @@ -2075,11 +2099,11 @@
        Stamp methods
      • -
      • +
      • - +

        fillAndDraw - fill the entity’s outline, remove shadow, then stroke it

        @@ -2098,11 +2122,11 @@
        Stamp methods
      • -
      • +
      • - +

        drawThenFill - stroke the entity’s outline, then fill it (shadow applied twice)

        @@ -2119,11 +2143,11 @@
        Stamp methods
      • -
      • +
      • - +

        fillThenDraw - fill the entity’s outline, then stroke it (shadow applied twice)

        @@ -2140,11 +2164,11 @@
        Stamp methods
      • -
      • +
      • - +

        clip - restrict drawing activities to the entity’s enclosed area

        @@ -2158,11 +2182,11 @@
        Stamp methods
      • -
      • +
      • - +

        clear - remove everything that would have been covered if the entity had performed fill (including shadow)

        @@ -2181,11 +2205,11 @@
        Stamp methods
      • -
      • +
      • - +

        none - perform all the calculations required, but don’t perform the final stamping

        @@ -2196,11 +2220,11 @@
        Stamp methods
      • -
      • +
      • - +

        Return the prototype

        diff --git a/docs/source/mixin/filter.html b/docs/source/mixin/filter.html index 5be377a61..5ad637748 100644 --- a/docs/source/mixin/filter.html +++ b/docs/source/mixin/filter.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
    • @@ -484,8 +489,9 @@

      Imports

      -
      import { filter } from '../core/library.js';
      -import { mergeOver, pushUnique, removeItem } from '../core/utilities.js';
      +
      import { filter, asset } from '../core/library.js';
      +import { mergeOver, pushUnique, removeItem } from '../core/utilities.js';
      +import { requestCell, releaseCell } from '../factory/cell.js';
      @@ -530,7 +536,8 @@

      filters - An array of filter object String names. If only one filter is to be applied, then it is enough to use the String name of that filter object - Scrawl-canvas will make sure it gets added to the Array.

      • To add/remove new filters to the filters array, use the addFilters and removeFilters functions. Note that the set function will replace all the existing filters in the array with the new filters. To remove all existing filters from the array, use the clearFilters function
      • -
      • Multiple filters can be batch-applied to an entity, group of entitys, or an entire cell in one operation. Filters are applied in the order that they appear in in the filters array.
      • +
      • Multiple filters will be batch-applied to an entity, group of entitys, or an entire cell in one operation. Filters are applied in the order that they appear in in the filters array.
      • +
      • Be aware that the “filters” (plural) attribute is different to the CSS/SVG “filter” (singular) attribute - details about how Scrawl-canvas uses CSS/SVG filter Strings to produce filtering effects (at the entity and Cell levels only) are investigated in the Filter Demos 051 to 055. CSS/SVG filter Strings can be applied in addition to Scrawl-canvas filters Array objects, and will be applied after them.
      @@ -546,7 +553,7 @@
      -

      isStencil - Use the entity as a stencil.

      +

      isStencil - Use the entity as a stencil. When this flag is set filter effects will be applied to the background imagery covered by the entity (or Group of entitys, or Cell), the results of which will replace the entity/Group/Cell in the final display.

      @@ -578,7 +585,7 @@

      Get, Set, deltaSet

      -

      filters - Replaces the existing filters array with a new filters array. If a string name is supplied, will add that name to the existing filters array

      +

      filters - ___Dangerous action!__ - replaces the existing filters Array with a new filters Array. If a string name is supplied, will add that name to the existing filters array

      @@ -591,13 +598,15 @@

      Get, Set, deltaSet

      if (Array.isArray(item)) { this.filters = item; + this.dirtyFilters = true; this.dirtyImageSubscribers = true; } else if (item.substring) { - pushUnique(this.filters, item); + pushUnique(this.filters, item); + this.dirtyFilters = true; this.dirtyImageSubscribers = true; } @@ -670,19 +679,7 @@

      Get, Set, deltaSet

      Prototype functions

      - - - - - - -
    • -
      - -
      - -
      -

      cleanFilters - Internal housekeeping

      +

      cleanFilters - Internal housekeeping

      @@ -700,11 +697,15 @@

      Prototype functions

      myfilters.forEach(name => { myobj = filter[name]; - order = floor(myobj.order) || 0; - if (!buckets[order]) buckets[order] = []; + if (myobj) { + + order = floor(myobj.order) || 0; + + if (!buckets[order]) buckets[order] = []; - buckets[order].push(myobj); + buckets[order].push(myobj); + } }); this.currentFilters = buckets.reduce((a, v) => a.concat(v), []); @@ -713,13 +714,13 @@

      Prototype functions

    • -
    • +
    • - +
      -

      addFilters - Add one or more filter name strings to the filters array. Filter name strings can be supplied as comma-separated arguments to the function

      +

      addFilters, removeFilters - Add or remove one or more filter name strings to/from the filters array. Filter name strings can be supplied as comma-separated arguments to the function

      @@ -729,43 +730,26 @@

      Prototype functions

      args.forEach(item => { - if (this.name, 'addFilters', item) { + if (item && item.type === 'Filter') item = item.name; + pushUnique(this.filters, item); - if (item.substring) pushUnique(this.filters, item); - else if (item.type === 'Filter') pushUnique(this.filters, item.name); - } }, this); this.dirtyFilters = true; this.dirtyImageSubscribers = true; return this; - }; - -
    • - - -
    • -
      - -
      - -
      -

      removeFilters - Remove one or more filter name strings from the filters array. Filter name strings can be supplied as comma-separated arguments to the function

      + }; -
      - -
          P.removeFilters = function (...args) {
      +    P.removeFilters = function (...args) {
       
               if (!Array.isArray(this.filters)) this.filters = [];
       
               args.forEach(item => {
       
      -            if (item) {
      +            if (item && item.type === 'Filter') item = item.name;
      +            removeItem(this.filters, item);
       
      -                if (item.substring) removeItem(this.filters, item);
      -                else if (item.type === 'Filter') removeItem(this.filters, item.name);
      -            }
               }, this);
       
               this.dirtyFilters = true;
      @@ -777,11 +761,11 @@ 

      Prototype functions

    • -
    • +
    • - +

      clearFilters - Clears the filters array

      @@ -802,11 +786,130 @@

      Prototype functions

    • -
    • +
    • +
      + +
      + +
      +

      preprocessFilters - internal function called as part of the Display cycle. The process-image filter action loads a Scrawl-canvas asset into the filters web worker, where it can be used as a lineIn or lineMix argument for other filter actions.

      + +
      + +
          P.preprocessFilters = function (filters) {
      +
      +        filters.forEach(filter => {
      +
      +            filter.actions.forEach(obj => {
      +
      +                if (obj.action == 'process-image') {
      +
      +                    let flag = true;
      +
      +                    let img = asset[obj.asset];
      +
      +                    if (img) {
      +
      +                        if (img.type === 'Noise') {
      +
      +                            img.checkSource();
      +                        }
      +
      +                        let width = img.sourceNaturalWidth || img.sourceNaturalDimensions[0] || img.currentDimensions[0],
      +                            height = img.sourceNaturalHeight || img.sourceNaturalDimensions[1] || img.currentDimensions[1];
      +
      +                        if (width && height) {
      +
      +                            flag = false;
      +
      +                            let copyX = obj.copyX || 0,
      +                                copyY = obj.copyY || 0,
      +                                copyWidth = obj.copyWidth || 1,
      +                                copyHeight = obj.copyHeight || 1,
      +                                destWidth = obj.width || 1,
      +                                destHeight = obj.height || 1;
      +
      +                            if (copyX.substring) copyX = (parseFloat(copyX) / 100) * width;
      +                            if (copyY.substring) copyY = (parseFloat(copyY) / 100) * height;
      +                            if (copyWidth.substring) copyWidth = (parseFloat(copyWidth) / 100) * width;
      +                            if (copyHeight.substring) copyHeight = (parseFloat(copyHeight) / 100) * height;
      +
      +                            copyX = Math.abs(copyX);
      +                            copyY = Math.abs(copyY);
      +                            copyWidth = Math.abs(copyWidth);
      +                            copyHeight = Math.abs(copyHeight);
      +
      +                            if (copyX > width) {
      +                                copyX = width - 2;
      +                                copyWidth = 1;
      +                            }
      +
      +                            if (copyY > height) {
      +                                copyY = height - 2;
      +                                copyHeight = 1;
      +                            }
      +
      +                            if (copyWidth > width) {
      +                                copyWidth = width - 1;
      +                                copyX = 0;
      +                            }
      +
      +                            if (copyHeight > height) {
      +                                copyHeight = height - 1;
      +                                copyY = 0;
      +                            }
      +
      +
      +                            if (copyX + copyWidth > width) {
      +                                copyX = width - copyWidth - 1;
      +                            }
      +
      +                            if (copyY + copyHeight > height) {
      +                                copyY = height - copyHeight - 1;
      +                            }
      +
      +                            let cell = requestCell(),
      +                                engine = cell.engine,
      +                                canvas = cell.element;
      +
      +                            canvas.width = destWidth;
      +                            canvas.height = destHeight;
      +
      +                            engine.setTransform(1, 0, 0, 1, 0, 0);
      +                            engine.globalCompositeOperation = 'source-over';
      +                            engine.globalAlpha = 1;
      +
      +                            let src = img.source || img.element;
      +
      +                            engine.drawImage(src, copyX, copyY, copyWidth, copyHeight, 0, 0, destWidth, destHeight);
      +
      +                            obj.assetData = engine.getImageData(0, 0, destWidth, destHeight);
      +
      +                            releaseCell(cell);
      +                        }
      +                    }
      +
      +                    if (flag) {
      +
      +                        obj.assetData = {
      +                            width: 1,
      +                            height: 1,
      +                            data: [0, 0, 0, 0],
      +                        }
      +                    }
      +                }
      +            });
      +        });
      +    };
      + +
    • + + +
    • - +

      Return the prototype

      diff --git a/docs/source/mixin/mimic.html b/docs/source/mixin/mimic.html index c7700289d..35273d053 100644 --- a/docs/source/mixin/mimic.html +++ b/docs/source/mixin/mimic.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
    • diff --git a/docs/source/mixin/path.html b/docs/source/mixin/path.html index 9c6197fe4..cc1aed650 100644 --- a/docs/source/mixin/path.html +++ b/docs/source/mixin/path.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/pattern.html b/docs/source/mixin/pattern.html index 33b677252..8e2d670ca 100644 --- a/docs/source/mixin/pattern.html +++ b/docs/source/mixin/pattern.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - @@ -734,7 +739,7 @@

      Prototype functions

      repeat = this.repeat, engine = mycell.engine; - if (this.type === 'Cell') { + if (this.type === 'Cell' || this.type === 'Noise') { source = this.element; loaded = true; diff --git a/docs/source/mixin/pivot.html b/docs/source/mixin/pivot.html index 27064ad9b..77bdba4d0 100644 --- a/docs/source/mixin/pivot.html +++ b/docs/source/mixin/pivot.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/position.html b/docs/source/mixin/position.html index 591f2a912..459ca8c59 100644 --- a/docs/source/mixin/position.html +++ b/docs/source/mixin/position.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/shapeBasic.html b/docs/source/mixin/shapeBasic.html index 7e8e24385..73bc1bfb9 100644 --- a/docs/source/mixin/shapeBasic.html +++ b/docs/source/mixin/shapeBasic.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/shapeCurve.html b/docs/source/mixin/shapeCurve.html index e4ec5a299..6f615bc44 100644 --- a/docs/source/mixin/shapeCurve.html +++ b/docs/source/mixin/shapeCurve.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/shapePathCalculation.html b/docs/source/mixin/shapePathCalculation.html index b47902f38..16e20970e 100644 --- a/docs/source/mixin/shapePathCalculation.html +++ b/docs/source/mixin/shapePathCalculation.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/styles.html b/docs/source/mixin/styles.html index 36a1f0e3d..9f6313f92 100644 --- a/docs/source/mixin/styles.html +++ b/docs/source/mixin/styles.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/mixin/tween.html b/docs/source/mixin/tween.html index bc11dd64e..fbd8bbd68 100644 --- a/docs/source/mixin/tween.html +++ b/docs/source/mixin/tween.html @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - - diff --git a/docs/source/scrawl.html b/docs/source/scrawl.html index 959838f4d..fed18dfa8 100644 --- a/docs/source/scrawl.html +++ b/docs/source/scrawl.html @@ -21,20 +21,8 @@
      - - - - - - -
    • -
      - -
      - -

      Scrawl-canvas

      -

      Version 8.3.4 - 6 Jan 2021

      +

      Version 8.4.0 - 2 Feb 2021


      @@ -42,11 +30,11 @@

      Version 8.3.4 - 6 Jan 2021

    • -
    • +
    • - +

      The MIT License (MIT)

      Permission is hereby granted, free of charge, to any person obtaining a copy @@ -71,11 +59,11 @@

      Version 8.3.4 - 6 Jan 2021

    • -
    • +
    • - +
      @@ -83,11 +71,11 @@

      Version 8.3.4 - 6 Jan 2021

    • -
    • +
    • - +

      Import Scrawl-canvas modules

      @@ -226,11 +214,21 @@

      Import Scrawl-canvas modules

      } from './factory/loom.js'; +import { + makeMesh, +} from './factory/mesh.js'; + + import { makeNet, } from './factory/net.js'; +import { + makeNoise, +} from './factory/noise.js'; + + import { makeOval, } from './factory/oval.js'; @@ -357,11 +355,11 @@

      Import Scrawl-canvas modules

    • -
    • +
    • - +

      Initialize Scrawl-canvas

      @@ -373,11 +371,11 @@

      Initialize Scrawl-canvas

    • -
    • +
    • - +

      Export selected functionalities that users can use in their scripts

      @@ -388,11 +386,11 @@

      Exp

    • -
    • +
    • - +

      core/library.js

      @@ -403,11 +401,11 @@

      Exp

    • -
    • +
    • - +

      core/animationloop.js

      @@ -419,11 +417,11 @@

      Exp

    • -
    • +
    • - +

      core/userInteraction.js

      @@ -438,11 +436,11 @@

      Exp

    • -
    • +
    • - +

      core/component.js

      @@ -453,11 +451,11 @@

      Exp

    • -
    • +
    • - +

      core/document.js

      @@ -476,11 +474,11 @@

      Exp

    • -
    • +
    • - +

      core/events.js

      @@ -501,11 +499,11 @@

      Exp

    • -
    • +
    • - +

      factory/action.js

      @@ -516,11 +514,11 @@

      Exp

    • -
    • +
    • - +

      factory/animation.js

      @@ -531,11 +529,11 @@

      Exp

    • -
    • +
    • - +

      factory/bezier.js

      @@ -546,11 +544,11 @@

      Exp

    • -
    • +
    • - +

      factory/block.js

      @@ -561,11 +559,11 @@

      Exp

    • -
    • +
    • - +

      factory/cog.js

      @@ -576,11 +574,11 @@

      Exp

    • -
    • +
    • - +

      factory/color.js

      @@ -591,11 +589,11 @@

      Exp

    • -
    • +
    • - +

      factory/coordinate.js

      @@ -607,11 +605,11 @@

      Exp

    • -
    • +
    • - +

      factory/emitter.js

      @@ -622,11 +620,11 @@

      Exp

    • -
    • +
    • - +

      factory/filter.js

      @@ -637,11 +635,11 @@

      Exp

    • -
    • +
    • - +

      factory/particleForce.js

      @@ -652,11 +650,11 @@

      Exp

    • -
    • +
    • - +

      factory/gradient.js

      @@ -667,11 +665,11 @@

      Exp

    • -
    • +
    • - +

      factory/grid.js

      @@ -682,11 +680,11 @@

      Exp

    • -
    • +
    • - +

      factory/group.js

      @@ -697,11 +695,11 @@

      Exp

    • -
    • +
    • - +

      factory/imageAsset.js

      @@ -716,11 +714,11 @@

      Exp

    • -
    • +
    • - +

      factory/line.js

      @@ -731,11 +729,11 @@

      Exp

    • -
    • +
    • - +

      factory/loom.js

      @@ -746,6 +744,21 @@

      Exp

    • +
    • +
      + +
      + +
      +

      factory/mesh.js

      + +
      + +
          makeMesh,
      + +
    • + +
    • @@ -767,11 +780,11 @@

      Exp
      -

      factory/oval.js

      +

      factory/noise.js

      -
          makeOval,
      +
          makeNoise,
    • @@ -782,11 +795,11 @@

      Exp
      -

      factory/pattern.js

      +

      factory/oval.js

      -
          makePattern,
      +
          makeOval,
      @@ -797,11 +810,11 @@

      Exp
      -

      factory/phrase.js

      +

      factory/pattern.js

      -
          makePhrase,
      +
          makePattern,
      @@ -812,11 +825,11 @@

      Exp
      -

      factory/picture.js

      +

      factory/phrase.js

      -
          makePicture,
      +
          makePhrase,
      @@ -827,11 +840,11 @@

      Exp
      -

      factory/polygon.js

      +

      factory/picture.js

      -
          makePolygon,
      +
          makePicture,
      @@ -842,11 +855,11 @@

      Exp
      -

      factory/polyline.js

      +

      factory/polygon.js

      -
          makePolyline,
      +
          makePolygon,
      @@ -857,11 +870,11 @@

      Exp
      -

      factory/quadratic.js

      +

      factory/polyline.js

      -
          makeQuadratic,
      +
          makePolyline,
      @@ -872,6 +885,21 @@

      Exp
      +

      factory/quadratic.js

      + + + +
          makeQuadratic,
      + + + + +
    • +
      + +
      + +

      factory/quaternion.js

      @@ -882,11 +910,11 @@

      Exp

    • -
    • +
    • - +

      factory/radialGradient.js

      @@ -897,11 +925,11 @@

      Exp

    • -
    • +
    • - +

      factory/rectangle.js

      @@ -912,11 +940,11 @@

      Exp

    • -
    • +
    • - +

      factory/renderAnimation.js

      @@ -927,11 +955,11 @@

      Exp

    • -
    • +
    • - +

      factory/shape.js

      @@ -942,11 +970,11 @@

      Exp

    • -
    • +
    • - +

      factory/spiral.js

      @@ -957,11 +985,11 @@

      Exp

    • -
    • +
    • - +

      factory/particleSpring.js

      @@ -972,11 +1000,11 @@

      Exp

    • -
    • +
    • - +

      factory/spriteAsset.js

      @@ -987,11 +1015,11 @@

      Exp

    • -
    • +
    • - +

      factory/star.js

      @@ -1002,11 +1030,11 @@

      Exp

    • -
    • +
    • - +

      factory/tetragon.js

      @@ -1017,11 +1045,11 @@

      Exp

    • -
    • +
    • - +

      factory/ticker.js

      @@ -1032,11 +1060,11 @@

      Exp

    • -
    • +
    • - +

      factory/tracer.js

      @@ -1047,11 +1075,11 @@

      Exp

    • -
    • +
    • - +

      factory/tween.js

      @@ -1062,11 +1090,11 @@

      Exp

    • -
    • +
    • - +

      factory/vector.js

      @@ -1078,11 +1106,11 @@

      Exp

    • -
    • +
    • - +

      factory/videoAsset.js

      @@ -1095,11 +1123,11 @@

      Exp

    • -
    • +
    • - +

      factory/wheel.js

      @@ -1110,11 +1138,11 @@

      Exp

    • -
    • +
    • - +

      factory/particleWorld.js

      diff --git a/docs/source/worker/filter-string.html b/docs/source/worker/filter-string.html new file mode 100644 index 000000000..d49542c0e --- /dev/null +++ b/docs/source/worker/filter-string.html @@ -0,0 +1,500 @@ + + + + + filter-string.js + + + + + +
      +
      + + + +
        + +
      • +
        +

        filter-string.js

        +
        +
      • + + + +
      • +
        + +
        + +
        +

        Compressed using https://javascript-minifier.com/

        + +
        + +
        +const filterCode = function () {
        +    return 'let packet,packetFiltersArray,source,work,cache,actions,workstore={},workstoreLastAccessed={};const createResultObject=function(e){return{r:new Uint8ClampedArray(e),g:new Uint8ClampedArray(e),b:new Uint8ClampedArray(e),a:new Uint8ClampedArray(e)}},unknit=function(e){let t=e.data,l=Math.floor(t.length/4);source=createResultObject(l),e.channels=source;let n=source.r,r=source.g,o=source.b,s=source.a,u=(work=createResultObject(l)).r,a=work.g,c=work.b,i=work.a,f=0;for(let e=0,l=t.length;e<l;e+=4)n[f]=t[e],r[f]=t[e+1],o[f]=t[e+2],s[f]=t[e+3],u[f]=t[e],a[f]=t[e+1],c[f]=t[e+2],i[f]=t[e+3],f++},knit=function(){let e=packet.image.data,t=work.r,l=work.g,n=work.b,r=work.a,o=0;for(let s=0,u=e.length;s<u;s+=4)e[s]=t[o],e[s+1]=l[o],e[s+2]=n[o],e[s+3]=r[o],o++};onmessage=function(e){packet=e.data,packetFiltersArray=packet.filters;let t=Object.keys(workstore),l=Date.now()-3e3;t.forEach(e=>{workstoreLastAccessed[e]<l&&(delete workstore[e],delete workstoreLastAccessed[e])}),actions=[],(cache={}).source=packet.image,packetFiltersArray.forEach(e=>actions.push(...e.actions)),actions.length&&(unknit(cache.source),actions.forEach(e=>theBigActionsObject[e.action]&&theBigActionsObject[e.action](e)),knit()),postMessage(packet)},onerror=function(e){console.log("error"+e.message),postMessage(packet)};const buildImageGrid=function(e){if(e||(e=cache.source),e&&e.width&&e.height){let t=`grid-${e.width}-${e.height}`;if(workstore[t])return workstoreLastAccessed[t]=Date.now(),workstore[t];let l=[],n=0;for(let t=0,r=e.height;t<r;t++){let t=[];for(let l=0,r=e.width;l<r;l++)t.push(n),n++;l.push(t)}return workstore[t]=l,workstoreLastAccessed[t]=Date.now(),l}return!1},buildAlphaTileSets=function(e,t,l,n,r,o,s,u){if(u||(u=cache.source),u&&u.width&&u.height){let s=u.width,a=u.height;(e=e.toFixed&&!isNaN(e)?e:1)<1&&(e=1),(t=t.toFixed&&!isNaN(t)?t:1)<1&&(t=1),e+(l=l.toFixed&&!isNaN(l)?l:1)>=s&&(e=s-l-1),t+(n=n.toFixed&&!isNaN(n)?n:1)>=a&&(t=a-n-1),e<1&&(e=1),t<1&&(t=1),e+l>=s&&(l=s-e-1),t+n>=a&&(n=a-t-1);let c=e+l,i=t+n;(r=r.toFixed&&!isNaN(r)?r:0)<0&&(r=0),r>=c&&(r=c-1),(o=o.toFixed&&!isNaN(o)?o:0)<0&&(o=0),o>=i&&(o=i-1);let f=`alphatileset-${s}-${a}-${e}-${t}-${l}-${n}-${r}-${o}`;if(workstore[f])return workstoreLastAccessed[f]=Date.now(),workstore[f];let h,g,p,d,b,k,w,R=[];for(d=o-i,b=a;d<b;d+=i)for(g=r-c,p=s;g<p;g+=c){for(h=[],k=d,w=d+t;k<w;k++)if(k>=0&&k<a)for(let t=g,l=g+e;t<l;t++)t>=0&&t<s&&h.push(k*s+t);for(R.push([].concat(h)),h=[],k=d+t,w=d+t+n;k<w;k++)if(k>=0&&k<a)for(let t=g,l=g+e;t<l;t++)t>=0&&t<s&&h.push(k*s+t);for(R.push([].concat(h)),h=[],k=d,w=d+t;k<w;k++)if(k>=0&&k<a)for(let t=g+e,n=g+e+l;t<n;t++)t>=0&&t<s&&h.push(k*s+t);for(R.push([].concat(h)),h=[],k=d+t,w=d+t+n;k<w;k++)if(k>=0&&k<a)for(let t=g+e,n=g+e+l;t<n;t++)t>=0&&t<s&&h.push(k*s+t);R.push([].concat(h))}return workstore[f]=R,workstoreLastAccessed[f]=Date.now(),R}return!1},buildImageTileSets=function(e,t,l,n,r){if(r||(r=cache.source),r&&r.width&&r.height){let o=r.width,s=r.height;(e=e.toFixed&&!isNaN(e)?e:1)<1&&(e=1),e>=o&&(e=o-1),(t=t.toFixed&&!isNaN(t)?t:1)<1&&(t=1),t>=s&&(t=s-1),(l=l.toFixed&&!isNaN(l)?l:0)<0&&(l=0),l>=e&&(l=e-1),(n=n.toFixed&&!isNaN(n)?n:0)<0&&(n=0),n>=t&&(n=t-1);let u=`imagetileset-${o}-${s}-${e}-${t}-${l}-${n}`;if(workstore[u])return workstoreLastAccessed[u]=Date.now(),workstore[u];let a=[];for(let r=n-t,u=s;r<u;r+=t)for(let n=l-e,u=o;n<u;n+=e){let l=[];for(y=r,yz=r+t;y<yz;y++)if(y>=0&&y<s)for(let t=n,r=n+e;t<r;t++)t>=0&&t<o&&l.push(y*o+t);l.length&&a.push(l)}return workstore[u]=a,workstoreLastAccessed[u]=Date.now(),a}return!1},buildHorizontalBlur=function(e,t){t&&t.toFixed&&!isNaN(t)||(t=0);let l=e.length,n=e[0].length,r=`blur-h-${n}-${l}-${t}`;if(workstore[r])return workstoreLastAccessed[r]=Date.now(),workstore[r];let o=[];for(let r=0;r<l;r++)for(let l=0;l<n;l++){let s=[];for(let o=l-t,u=l+t+1;o<u;o++)o>=0&&o<n&&s.push(e[r][o]);o[r*n+l]=s}return workstore[r]=o,workstoreLastAccessed[r]=Date.now(),o},buildVerticalBlur=function(e,t){t&&t.toFixed&&!isNaN(t)||(t=0);let l=e.length,n=e[0].length,r=`blur-v-${n}-${l}-${t}`;if(workstore[r])return workstoreLastAccessed[r]=Date.now(),workstore[r];let o=[];for(let r=0;r<n;r++)for(let s=0;s<l;s++){let u=[];for(let n=s-t,o=s+t+1;n<o;n++)n>=0&&n<l&&u.push(e[n][r]);o[s*n+r]=u}return workstore[r]=o,workstoreLastAccessed[r]=Date.now(),o},buildMatrixGrid=function(e,t,l,n,r,o){o||(o=cache.source),(null==e||e<1)&&(e=1),(null==t||t<1)&&(t=1),null==l||l<0?l=0:l>=e&&(l=e-1),null==n||n<0?n=0:n>=t&&(n=t-1);let s=o.width,u=o.height,a=`matrix-${s}-${u}-${e}-${t}-${l}-${n}`;if(workstore[a])return workstoreLastAccessed[a]=Date.now(),workstore[a];let c,i,f,h,g,p,d=o.data.length,b=[],k=[];for(f=-n,h=t-n;f<h;f++)for(c=-l,i=e-l;c<i;c++)b.push(f*s+c);for(f=0;f<u;f++)for(c=0;c<s;c++){let e=f*s+c,t=[];for(g=0,p=b.length;g<p;g++){let l=e+b[g];l<0?l+=d:l>=d&&(l-=d),t.push(l)}k.push(t)}return workstore[a]=k,workstoreLastAccessed[a]=Date.now(),k},checkChannelLevelsParameters=function(e){const t=function(e,t=!1){if(e.toFixed)return e<0?[[0,255,0]]:e>255?[[0,255,255]]:isNaN(e)?t?[[0,255,255]]:[[0,255,0]]:[[0,255,e]];if(e.substring&&(e=e.split(",")),Array.isArray(e)){if(!e.length)return e;if(Array.isArray(e[0]))return e;if((e=e.map(e=>parseInt(e,10))).sort((e,t)=>e-t),1==e.length)return[[0,255,e[0]]];let t,l,n=[];for(let r=0,o=e.length;r<o;r++)t=0,l=255,0!=r&&(t=Math.ceil(e[r-1]+(e[r]-e[r-1])/2)),r!=o-1&&(l=Math.floor(e[r]+(e[r+1]-e[r])/2)),n.push([t,l,e[r]]);return n}return t?[[0,255,255]]:[[0,255,0]]};e.red=t(e.red),e.green=t(e.green),e.blue=t(e.blue),e.alpha=t(e.alpha,!0)},cacheOutput=function(e,t,l){cache[e]=t},copyOver=function(e,t){let{r:l,g:n,b:r,a:o}=e,{r:s,g:u,b:a,a:c}=t;for(let e=0;e<l.length;e++)s[e]=l[e],u[e]=n[e],a[e]=r[e],c[e]=o[e]},getInputAndOutputDimensions=function(e){let t=cache.source,l=[];return e.lineIn&&"source"!=e.lineIn&&"source-alpha"!=e.lineIn&&cache[e.lineIn]&&(t=cache[e.lineIn]),l.push(t.width,t.height),e.lineOut&&cache[e.lineOut]&&(t=cache[e.lineOut]),l.push(t.width,t.height),t=cache.source,e.lineMix&&"source"!=e.lineMix&&"source-alpha"!=e.lineMix&&cache[e.lineMix]&&(t=cache[e.lineMix]),l.push(t.width,t.height),l},getInputAndOutputChannels=function(e){let t=work,l=t.r.length,n=cache.source;if(e.lineIn)if("source"==e.lineIn)t=n.channels;else if("source-alpha"==e.lineIn){let e=(t=createResultObject(l)).a,r=n.channels.a;for(let t=0;t<l;t++)e[t]=r[t]}else cache[e.lineIn]&&(t=(n=cache[e.lineIn]).channels);let r,o=!1;if(e.lineMix)if("source"==e.lineMix)o=cache.source.channels;else if("source-alpha"==e.lineMix){let e=(o=createResultObject(l)).a,t=cache.source.channels.a;for(let n=0;n<l;n++)e[n]=t[n]}else cache[e.lineMix]&&(o=cache[e.lineMix].channels);return e.lineOut?cache[e.lineOut]?r=cache[e.lineOut].channels:(r=createResultObject(l),cache[e.lineOut]={width:n.width,height:n.height,channels:r}):r=createResultObject(l),[t,r,o]},processResults=function(e,t,l){let n=e.r,r=e.g,o=e.b,s=e.a,u=t.r,a=t.g,c=t.b,i=t.a;if(1===l)copyOver(t,e);else if(l>0){antiRatio=1-l;for(let e=0,t=n.length;e<t;e++)n[e]=Math.floor(n[e]*antiRatio+u[e]*l),r[e]=Math.floor(r[e]*antiRatio+a[e]*l),o[e]=Math.floor(o[e]*antiRatio+c[e]*l),s[e]=Math.floor(s[e]*antiRatio+i[e]*l)}},getHSLfromRGB=function(e,t,l){let n=Math.min(e,t,l),r=Math.max(e,t,l),o=(n+r)/2,s=0;n!==r&&(s=o<=.5?(r-n)/(r+n):(r-n)/(2-r-n));let u=0;return u=r===e?(t-l)/(r-n):r===t?2+(l-e)/(r-n):4+(e-t)/(r-n),(u*=60)<0&&(u+=360),[u,s,o]},getRGBfromHSL=function(e,t,l){if(!t){let e=Math.floor(255*l);return[e,e,e]}let n=l<.5?l*(t+1):l+t-l*t,r=2*l-n;const o=function(e,t,l){return 6*e<1?l+6*(t-l)*e:2*e<1?t:2*e<2?l+6*(t-l)*(.666*e):l};let s=(e/=360)+.333,u=e,a=e-.333;return s<0&&(s+=1),s>1&&(s-=1),u<0&&(u+=1),u>1&&(u-=1),a<0&&(a+=1),a>1&&(a-=1),[255*o(s,n,r),255*o(u,n,r),255*o(a,n,r)]},theBigActionsObject={"alpha-to-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,excludeRed:a,excludeGreen:c,excludeBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!0),null==c&&(c=!0),null==i&&(i=!0);const{r:h,g:g,b:p,a:d}=t,{r:b,g:k,b:w,a:R}=l;for(let e=0;e<n;e++)b[e]=o?d[e]:a?0:h[e],k[e]=s?d[e]:c?0:g[e],w[e]=u?d[e]:i?0:p[e];R.fill(255,0,R.length-1),f?processResults(l,work,1-r):processResults(work,l,r)},"area-alpha":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,tileWidth:o,tileHeight:s,offsetX:u,offsetY:a,gutterWidth:c,gutterHeight:i,areaAlphaLevels:f,lineOut:h}=e;null==r&&(r=1),null==o&&(o=1),null==s&&(s=1),null==u&&(u=0),null==a&&(a=0),null==c&&(c=1),null==i&&(i=1),null==f&&(f=[255,0,0,0]);let g=buildAlphaTileSets(o,s,c,i,u,a);Array.isArray(f)||(f=[255,0,0,0]);const{r:p,g:d,b:b,a:k}=t,{r:w,g:R,b:O,a:A}=l;for(let e=0;e<n;e++)w[e]=p[e],R[e]=d[e],O[e]=b[e];g.forEach((e,t)=>{for(let l=0,n=e.length;l<n;l++)k[e[l]]&&(A[e[l]]=f[t%4])}),h?processResults(l,work,1-r):processResults(work,l,r)},"average-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,excludeRed:a,excludeGreen:c,excludeBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!1),null==c&&(c=!1),null==i&&(i=!1);let h=0;o&&h++,s&&h++,u&&h++;const{r:g,g:p,b:d,a:b}=t,{r:k,g:w,b:R,a:O}=l;for(let e=0;e<n;e++)if(b[e])if(h){let t=0;o&&(t+=g[e]),s&&(t+=p[e]),u&&(t+=d[e]),t=Math.floor(t/h),k[e]=a?0:t,w[e]=c?0:t,R[e]=i?0:t,O[e]=b[e]}else k[e]=a?0:g[e],w[e]=c?0:p[e],R[e]=i?0:d[e],O[e]=b[e];else k[e]=g[e],w[e]=p[e],R[e]=d[e],O[e]=b[e];f?processResults(l,work,1-r):processResults(work,l,r)},binary:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,alpha:a,lineOut:c}=e;null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=0);const{r:i,g:f,b:h,a:g}=t,{r:p,g:d,b:b,a:k}=l;for(let e=0;e<n;e++)p[e]=o?i[e]>o?255:0:i[e],d[e]=s?f[e]>s?255:0:f[e],b[e]=u?h[e]>u?255:0:h[e],k[e]=a?g[e]>a?255:0:g[e];c?processResults(l,work,1-r):processResults(work,l,r)},blend:function(e){let[t,l,n]=getInputAndOutputChannels(e),{opacity:r,blend:o,offsetX:s,offsetY:u,lineOut:a}=(l.r.length,e);null==r&&(r=1),null==o&&(o=""),null==s&&(s=0),null==u&&(u=0);const{r:c,g:i,b:f,a:h}=t,{r:g,g:p,b:d,a:b}=l,{r:k,g:w,b:R,a:O}=n;let[A,y,I,m,M,x]=getInputAndOutputDimensions(e);const B=function(e,t,l){g[t]=l.r[e],p[t]=l.g[e],d[t]=l.b[e],b[t]=l.a[e]},G=function(e,t){let l=e-s,n=t-u,r=-1;return l>=0&&l<M&&n>=0&&n<x&&(r=n*M+l),[t*A+e,r]},C=function(e,l){return[t.r[e]/255,t.g[e]/255,t.b[e]/255,t.a[e]/255,n.r[l]/255,n.g[l]/255,n.b[l]/255,n.a[l]/255]},L=(e,t)=>255*(e+t*(1-e));switch(o){case"color-burn":const e=(e,t)=>1==t?255:0==e?0:255*(1-Math.min(1,(1-t)/e));for(let l=0;l<y;l++)for(let r=0;r<A;r++){let[o,s]=G(r,l);if(s<0)B(o,o,t);else if(h[o])if(O[s]){let[t,l,n,r,u,a,c,i]=C(o,s);g[o]=e(t,u),p[o]=e(l,a),d[o]=e(n,c),b[o]=L(r,i)}else B(o,o,t);else B(s,o,n)}break;case"color-dodge":const l=(e,t)=>0==t?0:1==e?255:255*Math.min(1,t/(1-e));for(let e=0;e<y;e++)for(let r=0;r<A;r++){let[o,s]=G(r,e);if(s<0)B(o,o,t);else if(h[o])if(O[s]){let[e,t,n,r,u,a,c,i]=C(o,s);g[o]=l(e,u),p[o]=l(t,a),d[o]=l(n,c),b[o]=L(r,i)}else B(o,o,t);else B(s,o,n)}break;case"darken":const r=(e,t)=>e<t?e:t;for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[o,s]=G(l,e);s<0?B(o,o,t):h[o]?O[s]?(g[o]=r(c[o],k[s]),p[o]=r(i[o],w[s]),d[o]=r(f[o],R[s]),b[o]=L(h[o]/255,O[s]/255)):B(o,o,t):B(s,o,n)}break;case"difference":const s=(e,t)=>255*Math.abs(e-t);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,u,a,c,i]=C(r,o);g[r]=s(e,u),p[r]=s(t,a),d[r]=s(l,c),b[r]=L(n,i)}else B(o,r,n)}break;case"exclusion":const u=(e,t)=>255*(e+t-2*t*e);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,s,a,c,i]=C(r,o);g[r]=u(e,s),p[r]=u(t,a),d[r]=u(l,c),b[r]=L(n,i)}else B(o,r,n)}break;case"hard-light":const a=(e,t)=>e<=.5?e*t*255:255*(t+(e-t*e));for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,s,u,c,i]=C(r,o);g[r]=a(e,s),p[r]=a(t,u),d[r]=a(l,c),b[r]=L(n,i)}else B(o,r,n)}break;case"lighten":const I=(e,t)=>e>t?e:t;for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);o<0?B(r,r,t):h[r]?(g[r]=I(c[r],k[o]),p[r]=I(i[r],w[o]),d[r]=I(f[r],R[o]),b[r]=L(h[r]/255,O[o]/255)):B(o,r,n)}break;case"lighter":const m=(e,t)=>255*(e+t);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,s,u,a,c]=C(r,o);g[r]=m(e,s),p[r]=m(t,u),d[r]=m(l,a),b[r]=L(n,c)}else B(o,r,n)}break;case"multiply":const M=(e,t)=>e*t*255;for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o);g[r]=M(e,s),p[r]=M(t,u),d[r]=M(l,a),b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;case"overlay":const x=(e,t)=>e>=.5?e*t*255:255*(t+(e-t*e));for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,s,u,a,c]=C(r,o);g[r]=x(e,s),p[r]=x(t,u),d[r]=x(l,a),b[r]=L(n,c)}else B(o,r,n)}break;case"screen":const $=(e,t)=>255*(t+(e-t*e));for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let[e,t,l,n,s,u,a,c]=C(r,o);g[r]=$(e,s),p[r]=$(t,u),d[r]=$(l,a),b[r]=L(n,c)}else B(o,r,n)}break;case"soft-light":const N=(e,t)=>{let l=t<=.25?((16*t-12)*t+4)*t:Math.sqrt(t);return e<=.5?255*(t-(1-2*e)*t*(1-t)):255*(t+(2*e-1)*(l-t))};for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o);g[r]=N(e,s),p[r]=N(t,u),d[r]=N(l,a),b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;case"color":const v=(e,t,l,n,r,o)=>{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,u,f)};for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o),[i,f,h]=v(e,t,l,s,u,a);g[r]=i,p[r]=f,d[r]=h,b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;case"hue":const H=(e,t,l,n,r,o)=>{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,i,f)};for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o),[i,f,h]=H(e,t,l,s,u,a);g[r]=i,p[r]=f,d[r]=h,b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;case"luminosity":const D=(e,t,l,n,r,o)=>{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,i,a)};for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o),[i,f,h]=D(e,t,l,s,u,a);g[r]=i,p[r]=f,d[r]=h,b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;case"saturation":const S=(e,t,l,n,r,o)=>{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,u,f)};for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r])if(O[o]){let[e,t,l,n,s,u,a,c]=C(r,o),[i,f,h]=S(e,t,l,s,u,a);g[r]=i,p[r]=f,d[r]=h,b[r]=L(n,c)}else B(r,r,t);else B(o,r,n)}break;default:const F=(e,t,l,n)=>t*e+n*l*(1-t);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[r,o]=G(l,e);if(o<0)B(r,r,t);else if(h[r]){let e=h[r]/255,t=O[o]/255;g[r]=F(c[r],e,k[o],t),p[r]=F(i[r],e,w[o],t),d[r]=F(f[r],e,R[o],t),b[r]=L(e,t)}else B(o,r,n)}}a?processResults(l,work,1-r):processResults(work,l,r)},blur:function(e){let t,l,[n,r]=getInputAndOutputChannels(e),o=n.r.length,{opacity:s,radius:u,passes:a,processVertical:c,processHorizontal:i,includeRed:f,includeGreen:h,includeBlue:g,includeAlpha:p,step:d,lineOut:b}=e;if(null==s&&(s=1),null==u&&(u=0),null==a&&(a=1),null==c&&(c=!0),null==i&&(i=!0),null==f&&(f=!0),null==h&&(h=!0),null==g&&(g=!0),null==p&&(p=!1),null==d&&(d=1),i||c){let e=buildImageGrid();i&&(t=buildHorizontalBlur(e,u)),c&&(l=buildVerticalBlur(e,u))}const{r:k,g:w,b:R,a:O}=n,{r:A,g:y,b:I,a:m}=r,M=function(e,t,l,n,r){if(e){let e=t[l];if(null!=e){let t=e.length,l=0,o=0;if(r){for(let s=0;s<t;s+=d)r[e[s]]&&(o+=n[e[s]],l++);return o/l}for(let l=0;l<t;l++)o+=n[e[l]];return o/t}}return n[l]};if(a&&(i||c)){const e=createResultObject(o),{r:s,g:u,b:d,a:b}=e;copyOver(n,e);for(let n=0;n<a;n++){if(i){for(let e=0;e<o;e++)(p||b[e])&&(A[e]=M(f,t,e,s,b),y[e]=M(h,t,e,u,b),I[e]=M(g,t,e,d,b),m[e]=M(p,t,e,b,!1));(c||n<a-1)&&copyOver(r,e)}if(c){for(let e=0;e<o;e++)(p||b[e])&&(A[e]=M(f,l,e,s,b),y[e]=M(h,l,e,u,b),I[e]=M(g,l,e,d,b),m[e]=M(p,l,e,b,!1));n<a-1&&copyOver(r,e)}}}else copyOver(n,r);b?processResults(r,work,1-s):processResults(work,r,s)},"channels-to-alpha":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,lineOut:a}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0);let c=0;o&&c++,s&&c++,u&&c++;const{r:i,g:f,b:h,a:g}=t,{r:p,g:d,b:b,a:k}=l;for(let e=0;e<n;e++)if(p[e]=i[e],d[e]=f[e],b[e]=h[e],c){let t=0;o&&(t+=i[e]),s&&(t+=f[e]),u&&(t+=h[e]),t=Math.floor(t/c),k[e]=t}else k[e]=g[e];a?processResults(l,work,1-r):processResults(work,l,r)},chroma:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,ranges:o,lineOut:s}=e;null==r&&(r=1),null==o&&(o=[]);const{r:u,g:a,b:c,a:f}=t,{r:h,g:g,b:p,a:d}=l;for(let e=0;e<n;e++){let t=!1,l=u[e],n=a[e],r=c[e];for(i=0,iz=o.length;i<iz;i++){o[i];let[e,s,u,a,c,f]=o[i];if(l>=e&&l<=a&&n>=s&&n<=c&&r>=u&&r<=f){t=!0;break}}h[e]=u[e],g[e]=a[e],p[e]=c[e],d[e]=t?0:f[e]}s?processResults(l,work,1-r):processResults(work,l,r)},"clamp-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lowRed:o,lowGreen:s,lowBlue:u,highRed:a,highGreen:c,highBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255),null==c&&(c=255),null==i&&(i=255);const h=a-o,g=c-s,p=i-u,{r:d,g:b,b:k,a:w}=t,{r:R,g:O,b:A,a:y}=l;for(let e=0;e<n;e++)if(w[e]){let t=d[e]/255,l=b[e]/255,n=k[e]/255;R[e]=o+t*h,O[e]=s+l*g,A[e]=u+n*p,y[e]=w[e]}else R[e]=d[e],O[e]=b[e],A[e]=k[e],y[e]=w[e];f?processResults(l,work,1-r):processResults(work,l,r)},"colors-to-alpha":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,opaqueAt:a,transparentAt:c,lineOut:i}=e;null==r&&(r=1),null==o&&(o=0),null==s&&(s=255),null==u&&(u=0),null==a&&(a=1),null==c&&(c=0);const f=Math.max((o+s+u)/3,(255-o+(255-s)+(255-u))/3),h=c*f,g=a*f,p=g-h,d=function(e,t,l){let n=(Math.abs(o-e)+Math.abs(s-t)+Math.abs(u-l))/3;return n<h?0:n>g?255:(n-h)/p*255},{r:b,g:k,b:w,a:R}=t,{r:O,g:A,b:y,a:I}=l;for(let e=0;e<n;e++)O[e]=b[e],A[e]=k[e],y[e]=w[e],I[e]=d(b[e],k[e],w[e]);i?processResults(l,work,1-r):processResults(work,l,r)},compose:function(e){let[t,l,n]=getInputAndOutputChannels(e),{opacity:r,compose:o,offsetX:s,offsetY:u,lineOut:a}=(l.r.length,e);null==r&&(r=1),null==o&&(o=""),null==s&&(s=0),null==u&&(u=0);const{r:c,g:i,b:f,a:h}=t,{r:g,g:p,b:d,a:b}=l,{r:k,g:w,b:R,a:O}=n;let[A,y,I,m,M,x]=getInputAndOutputDimensions(e);const B=function(e,t,l){g[t]=l.r[e],p[t]=l.g[e],d[t]=l.b[e],b[t]=l.a[e]},G=function(e,t){let l=e-s,n=t-u,r=-1;return l>=0&&l<M&&n>=0&&n<x&&(r=n*M+l),[t*A+e,r]};switch(o){case"source-only":copyOver(t,l);break;case"source-atop":const e=(e,t,l,n)=>t*e*n+n*l*(1-t);for(let t=0;t<y;t++)for(let l=0;l<A;l++){let[n,r]=G(l,t);if(r>=0){let t=h[n]/255,l=O[r]/255;g[n]=e(c[n],t,k[r],l),p[n]=e(i[n],t,w[r],l),d[n]=e(f[n],t,R[r],l),b[n]=255*(t*l+l*(1-t))}}break;case"source-in":const r=(e,t,l)=>t*e*l;for(let e=0;e<y;e++)for(let t=0;t<A;t++){let[l,n]=G(t,e);if(n>=0){let e=h[l]/255,t=O[n]/255;g[l]=r(c[l],e,t),p[l]=r(i[l],e,t),d[l]=r(f[l],e,t),b[l]=e*t*255}}break;case"source-out":const s=(e,t,l)=>t*e*(1-l);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[n,r]=G(l,e);if(r<0)B(n,n,t);else{let e=h[n]/255,t=O[r]/255;g[n]=s(c[n],e,t),p[n]=s(i[n],e,t),d[n]=s(f[n],e,t),b[n]=e*(1-t)*255}}break;case"destination-only":for(let e=0;e<y;e++)for(let t=0;t<A;t++){let[l,r]=G(t,e);r>=0&&B(r,l,n)}break;case"destination-atop":const u=(e,t,l,n)=>t*e*(1-n)+n*l*t;for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[n,r]=G(l,e);if(r<0)B(n,n,t);else{let e=h[n]/255,t=O[r]/255;g[n]=u(c[n],e,k[r],t),p[n]=u(i[n],e,w[r],t),d[n]=u(f[n],e,R[r],t),b[n]=255*(e*(1-t)+t*e)}}break;case"destination-over":const a=(e,t,l,n)=>t*e*(1-n)+n*l;for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[n,r]=G(l,e);if(r<0)B(n,n,t);else{let e=h[n]/255,t=O[r]/255;g[n]=a(c[n],e,k[r],t),p[n]=a(i[n],e,w[r],t),d[n]=a(f[n],e,R[r],t),b[n]=255*(e*(1-t)+t)}}break;case"destination-in":const I=(e,t,l)=>t*e*l;for(let e=0;e<y;e++)for(let t=0;t<A;t++){let[l,n]=G(t,e);if(n>=0){let e=h[l]/255,t=O[n]/255;g[l]=I(k[n],t,e),p[l]=I(w[n],t,e),d[l]=I(R[n],t,e),b[l]=e*t*255}}break;case"destination-out":const m=(e,t,l)=>l*e*(1-t);for(let e=0;e<y;e++)for(let t=0;t<A;t++){let[l,n]=G(t,e);if(n>=0){let e=h[l]/255,t=O[n]/255;g[l]=m(k[n],e,t),p[l]=m(w[n],e,t),d[l]=m(R[n],e,t),b[l]=t*(1-e)*255}}break;case"clear":break;case"xor":const M=(e,t,l,n)=>t*e*(1-n)+n*l*(1-t);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[n,r]=G(l,e);if(r<0)B(n,n,t);else{let e=h[n]/255,t=O[r]/255;g[n]=M(c[n],e,k[r],t),p[n]=M(i[n],e,w[r],t),d[n]=M(f[n],e,R[r],t),b[n]=255*(e*(1-t)+t*(1-e))}}break;default:const x=(e,t,l,n)=>t*e+n*l*(1-t);for(let e=0;e<y;e++)for(let l=0;l<A;l++){let[n,r]=G(l,e);if(r<0)B(n,n,t);else{let e=h[n]/255,t=O[r]/255;g[n]=x(c[n],e,k[r],t),p[n]=x(i[n],e,w[r],t),d[n]=x(f[n],e,R[r],t),b[n]=255*(e+t*(1-e))}}}a?processResults(l,work,1-r):processResults(work,l,r)},displace:function(e){let[t,l,n]=getInputAndOutputChannels(e),{opacity:r,channelX:o,channelY:s,scaleX:u,scaleY:a,offsetX:c,offsetY:i,transparentEdges:f,lineOut:h}=(t.r.length,e);null==r&&(r=1),null==o&&(o="red"),null==s&&(s="green"),null==u&&(u=1),null==a&&(a=1),null==c&&(c=0),null==i&&(i=0),null==f&&(f=!1);const{r:g,g:p,b:d,a:b}=t,{r:k,g:w,b:R,a:O}=l,{r:A,g:y,b:I,a:m}=n;o="red"==o?A:"green"==o?y:"blue"==o?I:m,s="red"==s?A:"green"==s?y:"blue"==s?I:m;let[M,x,B,G,C,L]=getInputAndOutputDimensions(e);const $=function(e,t,l){e<0?O[t]=0:(k[t]=l.r[e],w[t]=l.g[e],R[t]=l.b[e],O[t]=l.a[e])},N=function(e,t){let l=e+c,n=t+i,r=-1;return l>=0&&l<C&&n>=0&&n<L&&(r=n*C+l),[t*M+e,r]};for(let e=0;e<x;e++)for(let l=0;l<M;l++){let[n,r]=N(l,e);if(r>=0){let c,i=Math.floor(l+(127-o[r])/127*u),h=Math.floor(e+(127-s[r])/127*a);f?c=i<0||i>=M||h<0||h>=x?-1:h*M+i:(i<0&&(i=0),i>=M&&(i=M-1),h<0&&(h=0),h>=x&&(h=x-1),c=h*M+i),$(c,n,t)}else $(n,n,t)}h?processResults(l,work,1-r):processResults(work,l,r)},emboss:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,strength:o,angle:s,tolerance:u,keepOnlyChangedAreas:a,postProcessResults:c,lineOut:i}=e;for(null==r&&(r=1),null==o&&(o=1),null==s&&(s=0),null==u&&(u=0),null==a&&(a=!1),null==c&&(c=!1),o=Math.abs(o);s<0;)s+=360;s%=360;let f=Math.floor(s/45),h=s%45/45*o,g=new Array(9);(g=g.fill(0,0,9))[4]=1,0==f?(g[5]=o-h,g[8]=h,g[3]=-g[5],g[0]=-g[8]):1==f?(g[8]=o-h,g[7]=h,g[0]=-g[8],g[1]=-g[7]):2==f?(g[7]=o-h,g[6]=h,g[1]=-g[7],g[2]=-g[6]):3==f?(g[6]=o-h,g[3]=h,g[2]=-g[6],g[5]=-g[3]):4==f?(g[3]=o-h,g[0]=h,g[5]=-g[3],g[8]=-g[0]):5==f?(g[0]=o-h,g[1]=h,g[8]=-g[0],g[7]=-g[1]):6==f?(g[1]=o-h,g[2]=h,g[7]=-g[1],g[6]=-g[2]):(g[2]=o-h,g[5]=h,g[6]=-g[2],g[3]=-g[5]);const{r:p,g:d,b:b,a:k}=t,{r:w,g:R,b:O,a:A}=l;grid=buildMatrixGrid(3,3,1,1);const y=function(e,t){let l=0;for(let n=0,r=t.length;n<r;n++)g[n]&&(l+=e[t[n]]*g[n]);return l};for(let e=0;e<n;e++)k[e]&&(w[e]=y(p,grid[e]),R[e]=y(d,grid[e]),O[e]=y(b,grid[e]),A[e]=k[e],c&&w[e]>=p[e]-u&&w[e]<=p[e]+u&&R[e]>=d[e]-u&&R[e]<=d[e]+u&&O[e]>=b[e]-u&&O[e]<=b[e]+u&&(a?A[e]=0:(w[e]=127,R[e]=127,O[e]=127)));i?processResults(l,work,1-r):processResults(work,l,r)},flood:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,alpha:a,lineOut:c}=(Math.floor,e);null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255);const{r:i,g:f,b:h,a:g}=l;i.fill(o,0,n-1),f.fill(s,0,n-1),h.fill(u,0,n-1),g.fill(a,0,n-1),c?processResults(l,work,1-r):processResults(work,l,r)},grayscale:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lineOut:o}=e;null==r&&(r=1);const{r:s,g:u,b:a,a:c}=t,{r:i,g:f,b:h,a:g}=l;for(let e=0;e<n;e++){let t=Math.floor(.2126*s[e]+.7152*u[e]+.0722*a[e]);i[e]=t,f[e]=t,h[e]=t,g[e]=c[e]}o?processResults(l,work,1-r):processResults(work,l,r)},"invert-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,includeAlpha:a,lineOut:c}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!1);const{r:i,g:f,b:h,a:g}=t,{r:p,g:d,b:b,a:k}=l;for(let e=0;e<n;e++)p[e]=o?255-i[e]:i[e],d[e]=s?255-f[e]:f[e],b[e]=u?255-h[e]:h[e],k[e]=a?255-g[e]:g[e];c?processResults(l,work,1-r):processResults(work,l,r)},"lock-channels-to-levels":function(e){checkChannelLevelsParameters(e);const t=function(e,t){if(!t.length)return e;for(let l=0,n=t.length;l<n;l++){let[n,r,o]=t[l];if(e>=n&&e<=r)return o}};let[l,n]=getInputAndOutputChannels(e),r=l.r.length,{opacity:o,red:s,green:u,blue:a,alpha:c,lineOut:i}=e;null==o&&(o=1),null==s&&(s=[0]),null==u&&(u=[0]),null==a&&(a=[0]),null==c&&(c=[255]);const{r:f,g:h,b:g,a:p}=l,{r:d,g:b,b:k,a:w}=n;for(let e=0;e<r;e++)d[e]=t(f[e],s),b[e]=t(h[e],u),k[e]=t(g[e],a),w[e]=t(p[e],c);i?processResults(n,work,1-o):processResults(work,n,o)},matrix:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,includeAlpha:a,width:c,height:i,offsetX:f,offsetY:h,weights:g,lineOut:p}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!1),(null==c||c<1)&&(c=3),(null==i||i<1)&&(i=3),null==f&&(f=1),null==h&&(h=1),null==g&&((g=[].fill(0,0,c*i-1))[Math.floor(g.length/2)+1]=1),grid=buildMatrixGrid(c,i,f,h,t.a);const d=function(e,t){let l=0;for(let n=0,r=t.length;n<r;n++)g[n]&&(l+=e[t[n]]*g[n]);return l},{r:b,g:k,b:w,a:R}=t,{r:O,g:A,b:y,a:I}=l;for(let e=0;e<n;e++)R[e]&&(O[e]=o?d(b,grid[e]):b[e],A[e]=s?d(k,grid[e]):k[e],y[e]=u?d(w,grid[e]):w[e],I[e]=a?d(R,grid[e]):R[e]);p?processResults(l,work,1-r):processResults(work,l,r)},"modulate-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,alpha:a,saturation:c,lineOut:i}=e;null==r&&(r=1),null==o&&(o=1),null==s&&(s=1),null==u&&(u=1),null==a&&(a=1),null==c&&(c=!1);const{r:f,g:h,b:g,a:p}=t,{r:d,g:b,b:k,a:w}=l;if(c)for(let e=0;e<n;e++)d[e]=127+(f[e]-127)*o,b[e]=127+(h[e]-127)*s,k[e]=127+(g[e]-127)*u,w[e]=127+(p[e]-127)*a;else for(let e=0;e<n;e++)d[e]=f[e]*o,b[e]=h[e]*s,k[e]=g[e]*u,w[e]=p[e]*a;i?processResults(l,work,1-r):processResults(work,l,r)},offset:function(e){let[t,l]=getInputAndOutputChannels(e),{opacity:n,offsetRedX:r,offsetRedY:o,offsetGreenX:s,offsetGreenY:u,offsetBlueX:a,offsetBlueY:c,offsetAlphaX:i,offsetAlphaY:f,lineOut:h}=e;null==n&&(n=1),null==r&&(r=0),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=0),null==c&&(c=0),null==i&&(i=0),null==f&&(f=0);let g=!1;r==s&&r==a&&r==i&&o==u&&o==c&&o==f&&(g=!0);const{r:p,g:d,b:b,a:k}=t,{r:w,g:R,b:O,a:A}=l;let y,I,m,M,x,B,G,C,L,$,N=buildImageGrid(),v=N[0].length,H=N.length;for(let e=0;e<H;e++)for(let t=0;t<v;t++)k[L=N[e][t]]&&(g?(I=e+o,(y=t+r)>=0&&y<v&&I>=0&&I<H&&(w[$=N[I][y]]=p[L],R[$]=d[L],O[$]=b[L],A[$]=k[L])):(I=e+o,m=t+s,M=e+u,x=t+a,B=e+c,G=t+i,C=e+f,(y=t+r)>=0&&y<v&&I>=0&&I<H&&(w[$=N[I][y]]=p[L]),m>=0&&m<v&&M>=0&&M<H&&(R[$=N[M][m]]=d[L]),x>=0&&x<v&&B>=0&&B<H&&(O[$=N[B][x]]=b[L]),G>=0&&G<v&&C>=0&&C<H&&(A[$=N[C][G]]=k[L])));h?processResults(l,work,1-n):processResults(work,l,n)},pixelate:function(e){const t=function(e,t,l){let n=l.reduce((t,l)=>t+e[l],0);n=Math.floor(n/l.length);for(let e=0,r=l.length;e<r;e++)t[l[e]]=n},l=function(e,t,l){let n;for(let r=0,o=l.length;r<o;r++)t[n=l[r]]=e[n]};let[n,r]=getInputAndOutputChannels(e),{opacity:o,tileWidth:s,tileHeight:u,offsetX:a,offsetY:c,includeRed:i,includeGreen:f,includeBlue:h,includeAlpha:g,lineOut:p}=(n.r.length,e);null==o&&(o=1),null==i&&(i=!0),null==f&&(f=!0),null==h&&(h=!0),null==g&&(g=!1),null==s&&(s=1),null==u&&(u=1),null==a&&(a=0),null==c&&(c=0);const d=buildImageTileSets(s,u,a,c),{r:b,g:k,b:w,a:R}=n,{r:O,g:A,b:y,a:I}=r;d.forEach(e=>{i?t(b,O,e):l(b,O,e),f?t(k,A,e):l(k,A,e),h?t(w,y,e):l(w,y,e),g?t(R,I,e):l(R,I,e)}),p?processResults(r,work,1-o):processResults(work,r,o)},"process-image":function(e){const{assetData:t,lineOut:l}=e;if(l&&l.substring&&l.length&&t&&t.width&&t.height&&t.data){let e=t.data,n=e.length,r=createResultObject(n/4),o=r.r,s=r.g,u=r.b,a=r.a,c=0;for(let t=0;t<n;t+=4)o[c]=e[t],s[c]=e[t+1],u[c]=e[t+2],a[c]=e[t+3],c++;t.channels=r,cache[l]=t}},"set-channel-to-level":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,includeAlpha:a,level:c,lineOut:i}=e;null==r&&(r=1),null==o&&(o=!1),null==s&&(s=!1),null==u&&(u=!1),null==a&&(a=!1),null==c&&(c=0);const{r:f,g:h,b:g,a:p}=t,{r:d,g:b,b:k,a:w}=l;for(let e=0;e<n;e++)d[e]=o?c:f[e],b[e]=s?c:h[e],k[e]=u?c:g[e],w[e]=a?c:p[e];i?processResults(l,work,1-r):processResults(work,l,r)},"step-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,r=Math.floor,{opacity:o,red:s,green:u,blue:a,lineOut:c}=e;null==o&&(o=1),null==s&&(s=1),null==u&&(u=1),null==a&&(a=1),null==s&&(s=1),null==u&&(u=1),null==a&&(a=1);const{r:i,g:f,b:h,a:g}=t,{r:p,g:d,b:b,a:k}=l;for(let e=0;e<n;e++)p[e]=r(i[e]/s)*s,d[e]=r(f[e]/u)*u,b[e]=r(h[e]/a)*a,k[e]=g[e];c?processResults(l,work,1-o):processResults(work,l,o)},threshold:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,low:o,high:s,level:u,lineOut:a}=e;null==r&&(r=1),null==o&&(o=[0,0,0]),null==s&&(s=[255,255,255]),null==u&&(u=128);const{r:c,g:i,b:f,a:h}=t,{r:g,g:p,b:d,a:b}=l;let[k,w,R]=o,[O,A,y]=s;for(let e=0;e<n;e++){Math.floor(.2126*c[e]+.7152*i[e]+.0722*f[e])<u?(g[e]=k,p[e]=w,d[e]=R):(g[e]=O,p[e]=A,d[e]=y),b[e]=h[e]}a?processResults(l,work,1-r):processResults(work,l,r)},"tint-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,redInRed:o,redInGreen:s,redInBlue:u,greenInRed:a,greenInGreen:c,greenInBlue:i,blueInRed:f,blueInGreen:h,blueInBlue:g,lineOut:p}=e;null==r&&(r=1),null==o&&(o=1),null==s&&(s=0),null==u&&(u=0),null==a&&(a=0),null==c&&(c=1),null==i&&(i=0),null==f&&(f=0),null==h&&(h=0),null==g&&(g=1);const{r:d,g:b,b:k,a:w}=t,{r:R,g:O,b:A,a:y}=l;for(let e=0;e<n;e++){let t=d[e],l=b[e],n=k[e];R[e]=Math.floor(t*o+l*a+n*f),O[e]=Math.floor(t*s+l*c+n*h),A[e]=Math.floor(t*u+l*i+n*g),y[e]=w[e]}p?processResults(l,work,1-r):processResults(work,l,r)},"user-defined-legacy":function(e){let[t,l]=getInputAndOutputChannels(e),{opacity:n,lineOut:r}=e;null==n&&(n=1),copyOver(t,l),r?processResults(l,work,1-n):processResults(work,l,n)}};'
        +};
        +
        +
        +const filterUrl = URL.createObjectURL(
        +
        +    new Blob([ filterCode() ], { type: 'text/javascript' })
        +);
        + +
      • + + +
      • +
        + +
        + +
        +

        Exports

        + +
        + +
        export {
        +    filterUrl,
        +};
        + +
      • + +
      +
      + + diff --git a/docs/source/worker/filter.html b/docs/source/worker/filter.html index 1b7433a37..a96063899 100644 --- a/docs/source/worker/filter.html +++ b/docs/source/worker/filter.html @@ -2,7 +2,7 @@ - Filter worker + Scrawl-canvas filters web worker @@ -160,11 +160,21 @@ + + ./source/factory/mesh.js + + + ./source/factory/net.js + + ./source/factory/noise.js + + + ./source/factory/oval.js @@ -420,8 +430,8 @@ - - ./source/worker/filter-stringed.js + + ./source/worker/filter-string.js @@ -429,11 +439,6 @@ ./source/worker/filter.js - - - ./source/worker/filter_canvas.js - -
    • @@ -449,14 +454,9 @@
      -

      Filter worker

      -

      A long-running web worker which, when not in use, gets stored in the filter pool defined in the filter factory

      -

      TODO: we’ve had to move all the code from this module into a new, comment-free module file because tools like CreateReactApp - which uses Webpack as its bundler of choice - breaks when we yarn add scrawl-canvas to a project.

      -
        -
      • The root of the issue is that Babel currently breaks when it encounters the import.meta attribute.
      • -
      • Babel do supply a plugin which is supposed to address this issue: babel-plugin-syntax-import-meta. But trying to add this to a Webpack configuration - particularly as implemented by create-react-app - is, at best, a nightmare.
      • -
      -

      NOTE: any changes to filter code need to be replicated in both files!

      +

      Scrawl-canvas filters web worker

      +

      This web worker code is called in a just-in-time manner; the code will not be requested from the server until a Scrawl-canvas entity, Group or Cell with a non-zero filters attribute is processed during the Display cycle.

      +

      All Scrawl-canvas filters-related image manipulation work happens in this worker code. Note that this functionality is entirely separate from the <canvas> element’s context engine’s native filter functionality, which allows us to add CSS/SVG-based filters to the canvas context

      @@ -469,16 +469,18 @@

      Filter worker

      -

      Demos:

      -
        -
      • Canvas-007 - Apply filters at the entity, group and cell level
      • -
      • Canvas-020 - Testing createImageFromXXX functionality
      • -
      • Canvas-027 - Video control and manipulation; chroma-based hit zone
      • -
      • Component-004 - Scrawl-canvas packets; save and load a range of different entitys
      • -
      +
      Road map
      +

      At some point we shall attempt to convert this code into something that can be run as WebAssembly code, possibly using Rust or AssemblyScript.

      +
      Development
      +

      Compress this file using https://javascript-minifier.com/

      +

      Copy the results of the compression into the string in worker/filter-string.js

      +

      Common variables

      +

      packet and packetFiltersArray will hold data contained in the message sent to the web worker; the packet object will be returned to the main program when processing completes (or errors) in the worker

      +
      let packet, packetFiltersArray;
      + @@ -488,11 +490,12 @@

      Demos:

      -

      Imports

      -

      None used

      +

      source - the original image data supplied in the message

      +
      let source;
      + @@ -502,18 +505,11 @@

      Imports

      -

      Polyfills

      -

      TypedArray.slice() polyfill - for blur filter

      +

      work - a copy of the source image which then gets manipulated by the functions invoked by each action object included in the filter

      -
      if (!Uint8Array.prototype.slice) {
      -    Object.defineProperty(Uint8Array.prototype, 'slice', {
      -        value: function (begin, end) {
      -            return new Uint8Array(Array.prototype.slice.call(this, begin, end));
      -        }
      -    });
      -}
      +
      let work;
      @@ -524,11 +520,12 @@

      Polyfills

      -

      Worker-wide variables

      -

      The following variables are defined here so that all filter functions - including user-defined filters - can make use of them:

      +

      cache - an Object consisting of key:Object pairs where the key is the named input of a process-image action or the output of any action object. This object is cleared and re-initialized each time the worker receives a new message

      +
      let cache;
      + @@ -538,11 +535,11 @@

      Worker-wide variables

      -

      packet - the e.data object sent to the web worker from the main code

      +

      actions - the Array of action objects that the worker needs to process - data supplied by the main thread in its message’s packetFiltersArray attribute.

      -
      let packet;
      +
      let actions;
      @@ -553,11 +550,12 @@

      Worker-wide variables

      -

      image - the ImageData object

      +

      The web worker maintains a semi-permanent storage space - the workstore - for some processing objects that are computationally expensive, for instance grids, matrix reference data objects, etc. The web worker maintains a record of when each of these processing objects was last accessed and will remove objects if they have not been accessed in the last three seconds.

      -
      let image;
      +
      let workstore = {},
      +    workstoreLastAccessed = {};
      @@ -568,12 +566,10 @@

      Worker-wide variables

      -

      iWidth - convenience variable - image.width * 4 (to cope with moving through the data array by pixel)

      +

      Result objects

      -
      let iWidth;
      - @@ -583,11 +579,19 @@

      Worker-wide variables

      -

      data - the Uint8ClampedArray data component of the ImageData object

      +

      createResultObject - to make the following code easier to maintain, the web worker will create result objects for all source image data it receives, and for each action object output. These result objects contain four arrays - one for each color channle, and one for the alpha channel.

      -
      let data;
      +
      const createResultObject = function (len) {
      +
      +    return {
      +        r: new Uint8ClampedArray(len),
      +        g: new Uint8ClampedArray(len),
      +        b: new Uint8ClampedArray(len),
      +        a: new Uint8ClampedArray(len),
      +    };
      +};
      @@ -598,11 +602,49 @@

      Worker-wide variables

      -

      cache - an Array of all the starting (ie: red channel) indexes for pixels whose alpha channel is > 0 (and thus not transparent) - speeds up things for pixel-by-pixel filters that don’t need to process transparent pixels. Generated by running the web worker’s getCache function

      +

      unknit - called at the start of each new message action chain. Creates and populates the source and work objects from the image data supplied in the message

      -
      let cache;
      +
      const unknit = function (iData) {
      +
      +    let imageData = iData.data;
      +
      +    let len = Math.floor(imageData.length / 4);
      +
      +
      +    source = createResultObject(len);
      +    iData.channels = source;
      +
      +    let sourceRed = source.r,
      +        sourceGreen = source.g,
      +        sourceBlue = source.b,
      +        sourceAlpha = source.a;
      +
      +    work = createResultObject(len);
      +
      +    let workRed = work.r,
      +        workGreen = work.g,
      +        workBlue = work.b,
      +        workAlpha = work.a;
      +
      +    let counter = 0;
      +
      +    for (let i = 0, iz = imageData.length; i < iz; i += 4) {
      +
      +        sourceRed[counter] = imageData[i];
      +        sourceGreen[counter] = imageData[i + 1];
      +        sourceBlue[counter] = imageData[i + 2];
      +        sourceAlpha[counter] = imageData[i + 3];
      +
      +        workRed[counter] = imageData[i];
      +        workGreen[counter] = imageData[i + 1];
      +        workBlue[counter] = imageData[i + 2];
      +        workAlpha[counter] = imageData[i + 3];
      +
      +        counter++;
      +    }
      +};
      @@ -613,11 +655,31 @@

      Worker-wide variables

      -

      tiles - the output from running the web worker’s getTiles function. This effectively divides the image data into a grid of blocks which can be further processed by a filter

      +

      knit - called at the end of each message action chain. Recreates the message image data in the correct format so it can be used by the main thread

      -
      let tiles;
      +
      const knit = function () {
      +
      +    let imageData = packet.image.data;
      +
      +    let workRed = work.r,
      +        workGreen = work.g,
      +        workBlue = work.b,
      +        workAlpha = work.a;
      +
      +    let counter = 0;
      +
      +    for (let i = 0, iz = imageData.length; i < iz; i += 4) {
      +
      +        imageData[i] = workRed[counter];
      +        imageData[i + 1] = workGreen[counter];
      +        imageData[i + 2] = workBlue[counter];
      +        imageData[i + 3] = workAlpha[counter];
      +
      +        counter++;
      +    }
      +};
      @@ -628,14 +690,49 @@

      Worker-wide variables

      -

      localX, localY, localWidth, localHeight - the start coordinates and dimensions of the box enclosing all the non-transparent pixels in the supplied image data array. Used mainly by the blur filter - but any filter (including user-defined filters) that needs to include transparent pixel values in its calculations, but would waste time processing non-visible areas of the image, can make use of these variables

      +

      Messaging functionality

      -
      let localX;
      -let localY;
      -let localWidth;
      -let localHeight;
      +
      onmessage = function (msg) {
      +
      +    packet = msg.data;
      +    packetFiltersArray = packet.filters;
      +
      +    let workstoreKeys = Object.keys(workstore), 
      +        workstoreChoke = Date.now() - 3000;
      +
      +    workstoreKeys.forEach(k => {
      +
      +        if (workstoreLastAccessed[k] < workstoreChoke) {
      +
      +            delete workstore[k];
      +            delete workstoreLastAccessed[k];
      +        }
      +    });
      +
      +    cache = {};
      +    actions = [];
      +
      +    cache.source = packet.image;
      +
      +    packetFiltersArray.forEach(f => actions.push(...f.actions));
      +
      +    if (actions.length) {
      +
      +        unknit(cache.source);
      +        actions.forEach(a => theBigActionsObject[a.action] && theBigActionsObject[a.action](a));
      +        knit();
      +    }
      +
      +    postMessage(packet);
      +};
      +
      +onerror = function (e) {
      +
      +    console.log('error' + e.message);
      +    postMessage(packet);
      +};
      @@ -646,11 +743,44 @@

      Worker-wide variables

      -

      filters - the filter objects Array sent to the web worker from the main code

      +

      Functions invoked by a range of different action functions

      +

      buildImageGrid creates an Array of Arrays which contain the indexes of each pixel in the data channel Arrays

      -
      let filters;
      +
      const buildImageGrid = function (data) {
      +
      +    if (!data) data = cache.source;
      +
      +    if (data && data.width && data.height) {
      +
      +        let name = `grid-${data.width}-${data.height}`;
      +        if (workstore[name]) {
      +            workstoreLastAccessed[name] = Date.now();
      +            return workstore[name];
      +        }
      +
      +
      +        let grid = [],
      +            counter = 0;
      +
      +        for (let y = 0, yz = data.height; y < yz; y++) {
      +
      +            let row = [];
      +
      +            for (let x = 0, xz = data.width; x < xz; x++) {
      +                
      +                row.push(counter);
      +                counter++;
      +            }
      +            grid.push(row);
      +        }
      +        workstore[name] = grid;
      +        workstoreLastAccessed[name] = Date.now();
      +        return grid;
      +    }
      +    return false;
      +};
      @@ -661,11 +791,105 @@

      Worker-wide variables

      -

      filter - the current filter object being processed by the web worker - holds all the settings required for that filter function, for example filter.level for the brightness, saturation and threshold filter functions

      +

      buildAlphaTileSets - creates a record of which pixels belong to which tile - used for manipulating alpha channel values. Resulting object will be cached in the store

      -
      let filter;
      +
      const buildAlphaTileSets = function (tileWidth, tileHeight, gutterWidth, gutterHeight, offsetX, offsetY, areaAlphaLevels, data) {
      +
      +    if (!data) data = cache.source;
      +
      +    if (data && data.width && data.height) {
      +
      +        let iWidth = data.width,
      +            iHeight = data.height;
      +
      +        tileWidth = (tileWidth.toFixed && !isNaN(tileWidth)) ? tileWidth : 1;
      +        tileHeight = (tileHeight.toFixed && !isNaN(tileHeight)) ? tileHeight : 1;
      +        gutterWidth = (gutterWidth.toFixed && !isNaN(gutterWidth)) ? gutterWidth : 1;
      +        gutterHeight = (gutterHeight.toFixed && !isNaN(gutterHeight)) ? gutterHeight : 1;
      +        offsetX = (offsetX.toFixed && !isNaN(offsetX)) ? offsetX : 0;
      +        offsetY = (offsetY.toFixed && !isNaN(offsetY)) ? offsetY : 0;
      +
      +        if (tileWidth < 1) tileWidth = 1;
      +        if (tileHeight < 1) tileHeight = 1;
      +        if (tileWidth + gutterWidth >= iWidth) tileWidth = iWidth - gutterWidth - 1;
      +        if (tileHeight + gutterHeight >= iHeight) tileHeight = iHeight - gutterHeight - 1;
      +
      +        if (tileWidth < 1) tileWidth = 1;
      +        if (tileHeight < 1) tileHeight = 1;
      +        if (tileWidth + gutterWidth >= iWidth) gutterWidth = iWidth - tileWidth - 1;
      +        if (tileHeight + gutterHeight >= iHeight) gutterHeight = iHeight - tileHeight - 1;
      +
      +        let aWidth = tileWidth + gutterWidth,
      +            aHeight = tileHeight + gutterHeight;
      +
      +        if (offsetX < 0) offsetX = 0;
      +        if (offsetX >= aWidth) offsetX = aWidth - 1;
      +        if (offsetY < 0) offsetY = 0;
      +        if (offsetY >= aHeight) offsetY = aHeight - 1;
      +
      +        let name = `alphatileset-${iWidth}-${iHeight}-${tileWidth}-${tileHeight}-${gutterWidth}-${gutterHeight}-${offsetX}-${offsetY}`;
      +        if (workstore[name]) {
      +            workstoreLastAccessed[name] = Date.now();
      +            return workstore[name];
      +        }
      +
      +        let tiles = [],
      +            hold, i, iz, j, jz, x, xz, y, yz;
      +
      +        for (j = offsetY - aHeight, jz = iHeight; j < jz; j += aHeight) {
      +
      +            for (i = offsetX - aWidth, iz = iWidth; i < iz; i += aWidth) {
      +
      +                hold = [];
      +                for (y = j, yz = j + tileHeight; y < yz; y++) {
      +                    if (y >= 0 && y < iHeight) {
      +                        for (let x = i, xz = i + tileWidth; x < xz; x++) {
      +                            if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x);
      +                        }
      +                    }
      +                }
      +                tiles.push([].concat(hold));
      +
      +                hold = [];
      +                for (y =  j + tileHeight, yz = j + tileHeight + gutterHeight; y < yz; y++) {
      +                    if (y >= 0 && y < iHeight) {
      +                        for (let x = i, xz = i + tileWidth; x < xz; x++) {
      +                            if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x);
      +                        }
      +                    }
      +                }
      +                tiles.push([].concat(hold));
      +
      +                hold = [];
      +                for (y = j, yz = j + tileHeight; y < yz; y++) {
      +                    if (y >= 0 && y < iHeight) {
      +                        for (let x = i + tileWidth, xz = i + tileWidth + gutterWidth; x < xz; x++) {
      +                            if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x);
      +                        }
      +                    }
      +                }
      +                tiles.push([].concat(hold));
      +
      +                hold = [];
      +                for (y =  j + tileHeight, yz = j + tileHeight + gutterHeight; y < yz; y++) {
      +                    if (y >= 0 && y < iHeight) {
      +                        for (let x = i + tileWidth, xz = i + tileWidth + gutterWidth; x < xz; x++) {
      +                            if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x);
      +                        }
      +                    }
      +                }
      +                tiles.push([].concat(hold));
      +            }
      +        }
      +        workstore[name] = tiles;
      +        workstoreLastAccessed[name] = Date.now();
      +        return tiles;
      +    }
      +    return false;
      +
      +};
      @@ -676,11 +900,66 @@

      Worker-wide variables

      -

      action - internal processing variable

      +

      buildImageTileSets - creates a record of which pixels belong to which tile - used for manipulating color channels values. Resulting object will be cached in the store

      -
      let action;
      +
      const buildImageTileSets = function (tileWidth, tileHeight, offsetX, offsetY, data) {
      +
      +    if (!data) data = cache.source;
      +
      +    if (data && data.width && data.height) {
      +
      +        let iWidth = data.width,
      +            iHeight = data.height;
      +
      +        tileWidth = (tileWidth.toFixed && !isNaN(tileWidth)) ? tileWidth : 1;
      +        tileHeight = (tileHeight.toFixed && !isNaN(tileHeight)) ? tileHeight : 1;
      +        offsetX = (offsetX.toFixed && !isNaN(offsetX)) ? offsetX : 0;
      +        offsetY = (offsetY.toFixed && !isNaN(offsetY)) ? offsetY : 0;
      +
      +        if (tileWidth < 1) tileWidth = 1;
      +        if (tileWidth >= iWidth) tileWidth = iWidth - 1;
      +        if (tileHeight < 1) tileHeight = 1;
      +        if (tileHeight >= iHeight) tileHeight = iHeight - 1;
      +        if (offsetX < 0) offsetX = 0;
      +        if (offsetX >= tileWidth) offsetX = tileWidth - 1;
      +        if (offsetY < 0) offsetY = 0;
      +        if (offsetY >= tileHeight) offsetY = tileHeight - 1;
      +
      +        let name = `imagetileset-${iWidth}-${iHeight}-${tileWidth}-${tileHeight}-${offsetX}-${offsetY}`;
      +        if (workstore[name]) {
      +            workstoreLastAccessed[name] = Date.now();
      +            return workstore[name];
      +        }
      +
      +        let tiles = [];
      +
      +        for (let j = offsetY - tileHeight, jz = iHeight; j < jz; j += tileHeight) {
      +
      +            for (let i = offsetX - tileWidth, iz = iWidth; i < iz; i += tileWidth) {
      +
      +                let hold = [];
      +                
      +                for (y = j, yz = j + tileHeight; y < yz; y++) {
      +
      +                    if (y >= 0 && y < iHeight) {
      +
      +                        for (let x = i, xz = i + tileWidth; x < xz; x++) {
      +
      +                            if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x);
      +                        }
      +                    }
      +                }
      +                if (hold.length) tiles.push(hold);
      +            }
      +        }
      +        workstore[name] = tiles;
      +        workstoreLastAccessed[name] = Date.now();
      +        return tiles;
      +    }
      +    return false;
      +};
      @@ -691,41 +970,42 @@

      Worker-wide variables

      -

      Messaging and error handling

      +

      buildHorizontalBlur - creates an Array of Arrays detailing which pixels contribute to the horizontal part of each pixel’s blur calculation. Resulting object will be cached in the store

      -
      onmessage = function (e) {
      -
      -    let i, iz;
      +            
      const buildHorizontalBlur = function (grid, radius) {
       
      -    packet = e.data;
      -    image = packet.image;
      -    iWidth = image.width * 4;
      -    data = image.data;
      -    filters = packet.filters;
      +    if (!radius || !radius.toFixed || isNaN(radius)) radius = 0;
       
      -    getCache();
      -    getLocal();
      +    let gridHeight = grid.length,
      +        gridWidth = grid[0].length;
       
      -    for (i = 0, iz = filters.length; i < iz; i++) {
      -
      -        filter = filters[i];
      +    let name = `blur-h-${gridWidth}-${gridHeight}-${radius}`;
      +    if (workstore[name]) {
      +        workstoreLastAccessed[name] = Date.now();
      +        return workstore[name];
      +    }
       
      -        if (filter.method === 'userDefined' && filter.userDefined) actions.userDefined = new Function(filter.userDefined);
      +    let horizontalBlur = [],
      +        cell;
       
      -        action = actions[filter.method];
      +    for (let y = 0; y < gridHeight; y++) {
       
      -        if (action) action();
      -    }
      +        for (let x = 0; x < gridWidth; x++) {
       
      -    postMessage(packet);
      -};
      +            let cellsToProcess = [];
       
      -onerror = function (e) {
      +            for (let c = x - radius, cz = x + radius + 1; c < cz; c++) {
       
      -    console.log('error' + e.message);
      -    postMessage(packet);
      +                if (c >= 0 && c < gridWidth) cellsToProcess.push(grid[y][c]);
      +            }
      +            horizontalBlur[(y * gridWidth) + x] = cellsToProcess;
      +        }
      +    }
      +    workstore[name] = horizontalBlur;
      +    workstoreLastAccessed[name] = Date.now();
      +    return horizontalBlur;
       };
      @@ -737,21 +1017,42 @@

      Messaging and error handling

      -

      getCache - function that fills the cache worker variable with the starting index position of each non-transparent pixel in the current image’s Uint8ClampedArray Array

      +

      buildVerticalBlur - creates an Array of Arrays detailing which pixels contribute to the vertical part of each pixel’s blur calculation. Resulting object will be cached in the store

      -
      const getCache = function () {
      +            
      const buildVerticalBlur = function (grid, radius) {
      +
      +    if (!radius || !radius.toFixed || isNaN(radius)) radius = 0;
      +
      +    let gridHeight = grid.length,
      +        gridWidth = grid[0].length;
      +
      +    let name = `blur-v-${gridWidth}-${gridHeight}-${radius}`;
      +    if (workstore[name]) {
      +        workstoreLastAccessed[name] = Date.now();
      +        return workstore[name];
      +    }
      +
      +    let verticalBlur = [],
      +        cell;
       
      -    let i, iz;
      +    for (let x = 0; x < gridWidth; x++) {
       
      -    if (Array.isArray(cache)) cache.length = 0;
      -    else cache = [];
      +        for (let y = 0; y < gridHeight; y++) {
       
      -    for (i = 0, iz = data.length; i < iz; i += 4) {
      +            let cellsToProcess = [];
       
      -        if (data[i + 3]) cache.push(i);
      +            for (let c = y - radius, cz = y + radius + 1; c < cz; c++) {
      +
      +                if (c >= 0 && c < gridHeight) cellsToProcess.push(grid[c][x]);
      +            }
      +            verticalBlur[(y * gridWidth) + x] = cellsToProcess;
      +        }
           }
      +    workstore[name] = verticalBlur;
      +    workstoreLastAccessed[name] = Date.now();
      +    return verticalBlur;
       };
      @@ -763,37 +1064,67 @@

      Messaging and error handling

      -

      getLocal - function that populates the localX, localY, localWidth, localHeight worker variables with relevant values for the current image data

      +

      buildMatrixGrid - creates an Array of Arrays detailing which pixels contribute to each pixel’s matrix calculation. Resulting object will be cached in the store

      -
      const getLocal = function () {
      +            
      const buildMatrixGrid = function (mWidth, mHeight, mX, mY, alpha, data) {
      +
      +    if (!data) data = cache.source;
      +
      +    if (mWidth == null || mWidth < 1) mWidth = 1;
      +    if (mHeight == null || mHeight < 1) mHeight = 1;
      +
      +    if (mX == null || mX < 0) mX = 0;
      +    else if (mX >= mWidth) mX = mWidth - 1;
      +
      +    if (mY == null || mY < 0) mY = 0;
      +    else if (mY >= mHeight) mY = mHeight - 1;
      +
      +    let iWidth = data.width,
      +        iHeight = data.height;
      +
      +    let name = `matrix-${iWidth}-${iHeight}-${mWidth}-${mHeight}-${mX}-${mY}`;
      +    if (workstore[name]) {
      +        workstoreLastAccessed[name] = Date.now();
      +        return workstore[name];
      +    }
       
      -    let i, iz, w, h, minX, minY, maxX, maxY, x, y, val,
      -        floor = Math.floor;
      +    let dataLength = data.data.length,
      +        x, xz, y, yz, i, iz,
      +        cellsTemplate = [],
      +        grid = [];
       
      -    w = image.width;
      -    h = image.height;
      -    minX = w;
      -    minY = h;
      -    maxX = 0;
      -    maxY = 0;
      +    for (y = -mY, yz = mHeight - mY; y < yz; y++) {
       
      -    for (i = 0, iz = cache.length; i < iz; i++) {
      +        for (x = -mX, xz = mWidth - mX; x < xz; x++) {
       
      -        val = cache[i] / 4;
      -        y = floor(val / w);
      -        x = val % w;
      -        minX = (x < minX) ? x : minX;
      -        minY = (y < minY) ? y : minY;
      -        maxX = (x > maxX) ? x : maxX;
      -        maxY = (y > maxY) ? y : maxY;
      +            cellsTemplate.push((y * iWidth) + x);
      +        }
           }
       
      -    localX = minX;
      -    localY = minY;
      -    localWidth = maxX - minX;
      -    localHeight = maxY - minY;
      +    for (y = 0; y < iHeight; y++) {
      +
      +        for (x = 0; x < iWidth; x++) {
      +            
      +            let pos = (y * iWidth) + x;
      +            let cell = [];
      +
      +            for (i = 0, iz = cellsTemplate.length; i < iz; i++) {
      +
      +                let val = pos + cellsTemplate[i];
      +
      +                if (val < 0) val += dataLength;
      +                else if (val >= dataLength) val -= dataLength;
      +
      +                cell.push(val);
      +            }
      +            grid.push(cell);
      +        }
      +    }
      +    workstore[name] = grid;
      +    workstoreLastAccessed[name] = Date.now();
      +    return grid;
       };
      @@ -805,54 +1136,55 @@

      Messaging and error handling

      -

      getTiles - function that populates the tiles worker Array with relevant values for the current image data

      +

      checkChannelLevelsParameters - divide each channel into discrete sequences of pixels

      -
      const getTiles = function () {
      +            
      const checkChannelLevelsParameters = function (f) {
       
      -    let i, iz, j, jz, x, xz, y, yz, startX, startY, pos, 
      -        hold = [],
      -        tileWidth = filter.tileWidth || 1,
      -        tileHeight = filter.tileHeight || 1,
      -        offsetX = filter.offsetX,
      -        offsetY = filter.offsetY,
      -        w = image.width,
      -        h = image.height;
      +    const doCheck = function (v, isHigh = false) {
       
      -    if (Array.isArray(tiles)) tiles.length = 0;
      -    else tiles = [];
      -
      -    offsetX = (offsetX >= tileWidth) ? tileWidth - 1 : offsetX;
      -    offsetY = (offsetY >= tileHeight) ? tileHeight - 1 : offsetY;
      +        if (v.toFixed) {
      +            if (v < 0) return [[0, 255, 0]];
      +            if (v > 255) return [[0, 255, 255]];
      +            if (isNaN(v)) return (isHigh) ? [[0, 255, 255]] : [[0, 255, 0]];
      +            return [[0, 255, v]];
      +        }
       
      -    startX = (offsetX) ? offsetX - tileWidth : 0;
      -    startY = (offsetY) ? offsetY - tileHeight : 0;
      +        if (v.substring) {
      +            v = v.split(',');
      +        }
       
      -    for (j = startY, jz = h + tileHeight; j < jz; j += tileHeight) {
      +        if (Array.isArray(v)) {
       
      -        for (i = startX, iz = w + tileWidth; i < iz; i += tileWidth) {
      +            if (!v.length) return v;
      +            if (Array.isArray(v[0])) return v;
       
      -            hold.length = 0;
      -            
      -            for (y = j, yz = j + tileHeight; y < yz; y++) {
      +            v = v.map(s => parseInt(s, 10));
      +            v.sort((a, b) => a - b);
       
      -                if (y >= 0 && y < h) {
      +            if (v.length == 1) return [[0, 255, v[0]]];
       
      -                    for (x = i, xz = i + tileWidth; x < xz; x++) {
      +            let res = [],
      +                starts, ends;
       
      -                        if (x >= 0 && x < w) {
      +            for (let i = 0, iz = v.length; i < iz; i++) {
       
      -                            pos = (y * iWidth) + (x * 4);
      +                starts = 0;
      +                ends = 255;
      +                if (i != 0) starts = Math.ceil(v[i - 1] + ((v[i] - v[i - 1]) / 2));
      +                if (i != iz - 1) ends = Math.floor(v[i] + ((v[i + 1] - v[i]) / 2));
       
      -                            if (data[pos + 3]) hold.push(pos);
      -                        }
      -                    }
      -                }
      +                res.push([starts, ends, v[i]]);
                   }
      -            if (hold.length) tiles.push([].concat(hold));
      +            return res;
               }
      +        return (isHigh) ? [[0, 255, 255]] : [[0, 255, 0]];
           }
      +    f.red = doCheck(f.red);
      +    f.green = doCheck(f.green);
      +    f.blue = doCheck(f.blue);
      +    f.alpha = doCheck(f.alpha, true);
       };
      @@ -864,25 +1196,13 @@

      Messaging and error handling

      -

      average - Returns the average of values in an array

      +

      cacheOutput - insert an action function’s output into the worker’s cache

      -
      const average = function (c) {
      -
      -    let a = 0,
      -        k, kz,
      -        l = c.length;
      -
      -    if (l) {
      -
      -        for (k = 0, kz = l; k < kz; k++) {
      +            
      const cacheOutput = function (name, obj, caller) {
       
      -            a +=c[k];
      -        }
      -        return a / l;
      -    }
      -    return 0;
      +    cache[name] = obj;
       };
      @@ -894,17 +1214,22 @@

      Messaging and error handling

      -

      checkBounds - Checks that a position cursor is within the bounds of the data array

      +

      copyOver - copy the values from one results object to another

      -
      const checkBounds = function (p) {
      +            
      const copyOver = function (f, t) {
       
      -    let len = data.length;
      +    let {r:fromR, g:fromG, b:fromB, a:fromA } = f;
      +    let {r:toR, g:toG, b:toB, a:toA } = t;
       
      -    if (p < 0) p += len;
      -    if (p >= len) p -= len;
      -    return p;
      +    for (let i = 0; i < fromR.length; i++) {
      +
      +        toR[i] = fromR[i];
      +        toG[i] = fromG[i];
      +        toB[i] = fromB[i];
      +        toA[i] = fromA[i];
      +    }
       };
      @@ -916,11 +1241,37 @@

      Messaging and error handling

      -

      All the pre-defined filter functions are held as attributes to the actions object

      +

      getInputAndOutputDimensions - determine, and return, the dimensions (width, height) for the appropriate results object for the lineIn, lineMix and lineOut values supplied to each action function when it gets invoked

      -
      const actions = {
      +
      const getInputAndOutputDimensions = function (requirements) {
      +
      +    let data = cache.source,
      +        results = [];
      +
      +    if (requirements.lineIn && requirements.lineIn != 'source' && requirements.lineIn != 'source-alpha' && cache[requirements.lineIn]) {
      +
      +        data = cache[requirements.lineIn];
      +    }
      +    results.push(data.width, data.height);
      +
      +    if (requirements.lineOut && cache[requirements.lineOut]) {
      +
      +        data = cache[requirements.lineOut];
      +    }
      +    results.push(data.width, data.height);
      +
      +    data = cache.source;
      +
      +    if (requirements.lineMix && requirements.lineMix != 'source' && requirements.lineMix != 'source-alpha' && cache[requirements.lineMix]) {
      +
      +        data = cache[requirements.lineMix];
      +    }
      +    results.push(data.width, data.height);
      +
      +    return results;
      +};
      @@ -931,11 +1282,77 @@

      Messaging and error handling

      -

      userDefined - requires a String function in the filter.userDefined attribute, which will be generated into a working function by the web worker - such filters can make use of any other filter attributes (for example: filter.level) alongside the dedicated userDefined attributes filter.udVariable0 - filter.udVariable9

      +

      getInputAndOutputChannels - determine, and return, the appropriate results object for the lineIn, lineMix and lineOut values supplied to each action function when it gets invoked

      -
          userDefined: function () {},
      +
      const getInputAndOutputChannels = function (requirements) {
      +
      +    let lineIn = work;
      +    let len = lineIn.r.length;
      +    let data = cache.source;
      +
      +    if (requirements.lineIn) {
      +
      +        if (requirements.lineIn == 'source') lineIn = data.channels;
      +        else if (requirements.lineIn == 'source-alpha') {
      +
      +            lineIn = createResultObject(len);
      +
      +            let destAlpha = lineIn.a,
      +                sourceAlpha = data.channels.a;
      +
      +            for (let i = 0; i < len; i++) {
      +
      +                destAlpha[i] = sourceAlpha[i];
      +            }
      +        }
      +        else if (cache[requirements.lineIn]) {
      +
      +            data = cache[requirements.lineIn];
      +            lineIn = data.channels;
      +        }
      +    }
      +
      +    let lineMix = false;
      +
      +    if (requirements.lineMix) {
      +
      +        if (requirements.lineMix == 'source') lineMix = cache.source.channels;
      +        else if (requirements.lineMix == 'source-alpha') {
      +
      +            lineMix = createResultObject(len);
      +
      +            let destAlpha = lineMix.a,
      +                sourceAlpha = cache.source.channels.a;
      +
      +            for (let i = 0; i < len; i++) {
      +
      +                destAlpha[i] = sourceAlpha[i];
      +            }
      +        }
      +        else if (cache[requirements.lineMix]) lineMix = cache[requirements.lineMix].channels;
      +    }
      +
      +    let lineOut;
      +
      +    if (requirements.lineOut) {
      +
      +        if (cache[requirements.lineOut]) lineOut = cache[requirements.lineOut].channels;
      +        else {
      +
      +            lineOut = createResultObject(len);
      +            cache[requirements.lineOut] = {
      +                width: data.width,
      +                height: data.height,
      +                channels: lineOut,
      +            };
      +        }
      +    }
      +    else lineOut = createResultObject(len);
      +
      +    return [lineIn, lineOut, lineMix];
      +};
      @@ -946,21 +1363,36 @@

      Messaging and error handling

      -

      grayscale - desaturates the image

      +

      processResults - at the conclusion of each action function, combine the results of the function’s manipulations back into the data supplied for manipulation, in line with the value of the action object’s opacity attribute

      -
          grayscale: function () {
      +            
      const processResults = function (store, incoming, ratio) {
       
      -        let i, iz, pos, gray;
      +    let sR = store.r,
      +        sG = store.g,
      +        sB = store.b,
      +        sA = store.a;
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +    let iR = incoming.r,
      +        iG = incoming.g,
      +        iB = incoming.b,
      +        iA = incoming.a;
       
      -            pos = cache[i];
      -            gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]);
      -            data[pos] = data[pos + 1] = data[pos + 2] = gray;
      +    if (ratio === 1) copyOver(incoming, store);
      +    else if (ratio > 0) {
      +
      +        antiRatio = 1 - ratio;
      +
      +        for (let i = 0, iz = sR.length; i < iz; i++) {
      +
      +            sR[i] = Math.floor((sR[i] * antiRatio) + (iR[i] * ratio));
      +            sG[i] = Math.floor((sG[i] * antiRatio) + (iG[i] * ratio));
      +            sB[i] = Math.floor((sB[i] * antiRatio) + (iB[i] * ratio));
      +            sA[i] = Math.floor((sA[i] * antiRatio) + (iA[i] * ratio));
               }
      -    },
      + } +};
      @@ -971,27 +1403,37 @@

      Messaging and error handling

      -

      sepia - desaturates the image, then ‘antiques’ it by adding back some yellow tone

      +

      getHSLfromRGB - convert an RGB format color into an HSL format color

      -
          sepia: function () {
      +            
      const getHSLfromRGB = function (dr, dg, db) {
       
      -        let i, iz, pos, r, g, b;
      +    let minColor = Math.min(dr, dg, db),
      +        maxColor = Math.max(dr, dg, db);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +    let lum = (minColor + maxColor) / 2;
       
      -            pos = cache[i];
      -            
      -            r = data[pos];
      -            g = data[pos + 1];
      -            b = data[pos + 2];
      -            
      -            data[pos] = (r * 0.393) + (g * 0.769) + (b * 0.189);
      -            data[pos + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168);
      -            data[pos + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131);
      -        }
      -    },
      + let sat = 0; + + if (minColor !== maxColor) { + + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } + + let hue = 0; + + if (maxColor === dr) hue = (dg - db) / (maxColor - minColor); + else if (maxColor === dg) hue = 2 + ((db - dr) / (maxColor - minColor)); + else hue = 4 + ((dr - dg) / (maxColor - minColor)); + + hue *= 60; + + if (hue < 0) hue += 360; + + return [hue, sat, lum]; +};
      @@ -1002,26 +1444,48 @@

      Messaging and error handling

      -

      invert - turns white into black, and similar across the spectrum

      +

      getRGBfromHSL - convert an HSL format color into an RGB format color

      -
          invert: function () {
      +            
      const getRGBfromHSL = function (h, s, l) {
      +
      +    if (!s) {
       
      -        let i, iz, pos;
      +        let gray = Math.floor(l * 255);
      +        return [gray, gray, gray];
      +    }
      +
      +    let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s),
      +        tempLum2 = (2 * l) - tempLum1;
      +
      +    const calculator = function (t, l1, l2) {
      +
      +        if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t);
      +        if (t * 2 < 1) return l1;
      +        if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666));
      +        return l2;
      +    };
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +    h /= 360;
       
      -            pos = cache[i];
      -            data[pos] = 255 - data[pos];
      -            
      -            pos++;
      -            data[pos] = 255 - data[pos];
      -            
      -            pos++;
      -            data[pos] = 255 - data[pos];
      -        }
      -    },
      + let tr = h + 0.333, + tg = h, + tb = h - 0.333; + + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; + + let r = calculator(tr, tempLum1, tempLum2) * 255, + g = calculator(tg, tempLum1, tempLum2) * 255, + b = calculator(tb, tempLum1, tempLum2) * 255; + + return [r, g, b]; +};
      @@ -1032,21 +1496,12 @@

      Messaging and error handling

      -

      red - suppresses the image’s green and blue channels

      +

      Filter action functions

      +

      Each function is held in the theBigActionsObject object, for convenience

      -
          red: function () {
      -
      -        let i, iz, pos;
      -
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      -
      -            pos = cache[i];
      -            data[pos + 1] = 0;
      -            data[pos + 2] = 0;
      -        }
      -    },
      +
      const theBigActionsObject = {
      @@ -1057,20 +1512,39 @@

      Messaging and error handling

      -

      green - suppresses the image’s red and blue channels

      +

      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.

      -
          green: function () {
      +            
          'alpha-to-channels': function (requirements) {
      +
      +        let [input, output] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
      +
      +        let {opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue, lineOut} = requirements;
       
      -        let i, iz, pos;
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == excludeRed) excludeRed = true;
      +        if (null == excludeGreen) excludeGreen = true;
      +        if (null == excludeBlue) excludeBlue = true;
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -            pos = cache[i];
      -            data[pos] = 0;
      -            data[pos + 2] = 0;
      +        for (let i = 0; i < len; i++) {
      +
      +            outR[i] = (includeRed) ? inA[i] : ((excludeRed) ? 0 : inR[i]);
      +            outG[i] = (includeGreen) ? inA[i] : ((excludeGreen) ? 0 : inG[i]);
      +            outB[i] = (includeBlue) ? inA[i] : ((excludeBlue) ? 0 : inB[i]);
               }
      +        outA.fill(255, 0, outA.length - 1);
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1082,20 +1556,49 @@

      Messaging and error handling

      -

      blue - suppresses the image’s red and green channels

      +

      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 zero. Can be used to create horizontal or vertical bars, or chequerboard effects.

      -
          blue: function () {
      +            
          'area-alpha': function (requirements) {
      +
      +        let [input, output] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
       
      -        let i, iz, pos;
      +        let {opacity, tileWidth, tileHeight, offsetX, offsetY, gutterWidth, gutterHeight, areaAlphaLevels, lineOut} = requirements;
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        if (null == opacity) opacity = 1;
      +        if (null == tileWidth) tileWidth = 1;
      +        if (null == tileHeight) tileHeight = 1;
      +        if (null == offsetX) offsetX = 0;
      +        if (null == offsetY) offsetY = 0;
      +        if (null == gutterWidth) gutterWidth = 1;
      +        if (null == gutterHeight) gutterHeight = 1;
      +        if (null == areaAlphaLevels) areaAlphaLevels = [255,0,0,0];
       
      -            pos = cache[i];
      -            data[pos] = 0;
      -            data[pos + 1] = 0;
      +        let tiles = buildAlphaTileSets(tileWidth, tileHeight, gutterWidth, gutterHeight, offsetX, offsetY, areaAlphaLevels);
      +
      +        if (!Array.isArray(areaAlphaLevels)) areaAlphaLevels = [255,0,0,0];
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +            outR[i] = inR[i];
      +            outG[i] = inG[i];
      +            outB[i] = inB[i];
               }
      +        tiles.forEach((t, index) => {
      +
      +            for (let j = 0, jz = t.length; j < jz; j++) {
      +
      +                if (inA[t[j]]) outA[t[j]] = areaAlphaLevels[index % 4];
      +            }
      +        });
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1107,19 +1610,71 @@

      Messaging and error handling

      -

      notred - suppresses the image’s red channel

      +

      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.

      -
          notred: function() {
      +            
          'average-channels': function (requirements) {
      +
      +        let [input, output] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
      +
      +        let {opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == excludeRed) excludeRed = false;
      +        if (null == excludeGreen) excludeGreen = false;
      +        if (null == excludeBlue) excludeBlue = false;
      +
      +        let divisor = 0;
      +        if (includeRed) divisor++;
      +        if (includeGreen) divisor++;
      +        if (includeBlue) divisor++;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +
      +            if (inA[i]) {
      +
      +                if (divisor) {
       
      -        let i, iz, pos;
      +                    let avg = 0;
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +                    if (includeRed) avg += inR[i];
      +                    if (includeGreen) avg += inG[i];
      +                    if (includeBlue) avg += inB[i];
       
      -            pos = cache[i];
      -            data[pos] = 0;
      +                    avg = Math.floor(avg / divisor);
      +
      +                    outR[i] = (excludeRed) ? 0 : avg;
      +                    outG[i] = (excludeGreen) ? 0 : avg;
      +                    outB[i] = (excludeBlue) ? 0 : avg;
      +                    outA[i] = inA[i];
      +                }
      +                else {
      +    
      +                    outR[i] = (excludeRed) ? 0 : inR[i];
      +                    outG[i] = (excludeGreen) ? 0 : inG[i];
      +                    outB[i] = (excludeBlue) ? 0 : inB[i];
      +                    outA[i] = inA[i];
      +                }
      +            }
      +            else {
      +
      +                outR[i] = inR[i];
      +                outG[i] = inG[i];
      +                outB[i] = inB[i];
      +                outA[i] = inA[i];
      +            }
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1131,19 +1686,44 @@

      Messaging and error handling

      -

      notgreen - suppresses the image’s green channel

      +

      binary - Set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the “red”, “green”, “blue” and “alpha” arguments. Setting these values to 0 disables the action for that channel.

      -
          notgreen: function () {
      +            
          'binary': function (requirements) {
       
      -        let i, iz, pos;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            data[pos + 1] = 0;
      +        let {opacity, red, green, blue, alpha, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == red) red = 0;
      +        if (null == green) green = 0;
      +        if (null == blue) blue = 0;
      +        if (null == alpha) alpha = 0;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +
      +            if (red) outR[i] = (inR[i] > red) ? 255 : 0;
      +            else outR[i] = inR[i];
      +
      +            if (green) outG[i] = (inG[i] > green) ? 255 : 0;
      +            else outG[i] = inG[i];
      +
      +            if (blue) outB[i] = (inB[i] > blue) ? 255 : 0;
      +            else outB[i] = inB[i];
      +
      +            if (alpha) outA[i] = (inA[i] > alpha) ? 255 : 0;
      +            else outA[i] = inA[i];
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1155,19 +1735,495 @@

      Messaging and error handling

      -

      notblue - suppresses the image’s blue channel

      +

      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.

      -
          notblue: function () {
      +            
          'blend': function (requirements) {
      +
      +        let [input, output, mix] = getInputAndOutputChannels(requirements);
      +
      +        let len = output.r.length;
      +
      +        let {opacity, blend, offsetX, offsetY, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == blend) blend = '';
      +        if (null == offsetX) offsetX = 0;
      +        if (null == offsetY) offsetY = 0;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +        const {r:mixR, g:mixG, b:mixB, a:mixA} = mix;
      +
      +        let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements);
      +
      +        const copyPixel = function (fromPos, toPos, channel) {
      +
      +            outR[toPos] = channel.r[fromPos];
      +            outG[toPos] = channel.g[fromPos];
      +            outB[toPos] = channel.b[fromPos];
      +            outA[toPos] = channel.a[fromPos];
      +        };
      +
      +        const getLinePositions = function (x, y) {
      +
      +            let ix = x,
      +                iy = y,
      +                mx = x - offsetX,
      +                my = y - offsetY;
      +
      +            let mPos = -1,
      +                iPos = (iy * iWidth) + ix;
      +
      +            if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx;
      +
      +            return [iPos, mPos];
      +        };
      +
      +        const getChannelNormals = function (i, m) {
      +
      +            return [
      +                input.r[i] / 255,
      +                input.g[i] / 255,
      +                input.b[i] / 255,
      +                input.a[i] / 255,
      +                mix.r[m] / 255,
      +                mix.g[m] / 255,
      +                mix.b[m] / 255,
      +                mix.a[m] / 255
      +            ];
      +        };
      +
      +        const alphaCalc = (dinA, dmixA) => (dinA + (dmixA * (1 - dinA))) * 255;
      +
      +        switch (blend) {
      +
      +            case 'color-burn' :
      +                const colorburnCalc = (din, dmix) => {
      +                    if (dmix == 1) return 255;
      +                    else if (din == 0) return 0;
      +                    return (1 - Math.min(1, ((1 - dmix) / din ))) * 255;
      +                };
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = colorburnCalc(dinR, dmixR);
      +                            outG[iPos] = colorburnCalc(dinG, dmixG);
      +                            outB[iPos] = colorburnCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'color-dodge' :
      +                const colordodgeCalc = (din, dmix) => {
      +                    if (dmix == 0) return 0;
      +                    else if (din == 1) return 255;
      +                    return Math.min(1, (dmix / (1 - din))) * 255;
      +                };
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = colordodgeCalc(dinR, dmixR);
      +                            outG[iPos] = colordodgeCalc(dinG, dmixG);
      +                            outB[iPos] = colordodgeCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'darken' :
      +                const darkenCalc = (din, dmix) => (din < dmix) ? din : dmix;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            outR[iPos] = darkenCalc(inR[iPos], mixR[mPos]);
      +                            outG[iPos] = darkenCalc(inG[iPos], mixG[mPos]);
      +                            outB[iPos] = darkenCalc(inB[iPos], mixB[mPos]);
      +                            outA[iPos] = alphaCalc(inA[iPos] / 255, mixA[mPos] / 255);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'difference' :
      +                const differenceCalc = (din, dmix) => Math.abs(din - dmix) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = differenceCalc(dinR, dmixR);
      +                            outG[iPos] = differenceCalc(dinG, dmixG);
      +                            outB[iPos] = differenceCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'exclusion' :
      +                const exclusionCalc = (din, dmix) => (din + dmix - (2 * dmix * din)) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = exclusionCalc(dinR, dmixR);
      +                            outG[iPos] = exclusionCalc(dinG, dmixG);
      +                            outB[iPos] = exclusionCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'hard-light' :
      +                const hardlightCalc = (din, dmix) => (din <= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = hardlightCalc(dinR, dmixR);
      +                            outG[iPos] = hardlightCalc(dinG, dmixG);
      +                            outB[iPos] = hardlightCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'lighten' :
      +                const lightenCalc = (din, dmix) => (din > dmix) ? din : dmix;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            outR[iPos] = lightenCalc(inR[iPos], mixR[mPos]);
      +                            outG[iPos] = lightenCalc(inG[iPos], mixG[mPos]);
      +                            outB[iPos] = lightenCalc(inB[iPos], mixB[mPos]);
      +                            outA[iPos] = alphaCalc(inA[iPos] / 255, mixA[mPos] / 255);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'lighter' :
      +                const lighterCalc = (din, dmix) => (din + dmix) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = lighterCalc(dinR, dmixR);
      +                            outG[iPos] = lighterCalc(dinG, dmixG);
      +                            outB[iPos] = lighterCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'multiply' :
      +                const multiplyCalc = (din, dmix) => din * dmix * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = multiplyCalc(dinR, dmixR);
      +                            outG[iPos] = multiplyCalc(dinG, dmixG);
      +                            outB[iPos] = multiplyCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'overlay' :
      +                const overlayCalc = (din, dmix) => (din >= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = overlayCalc(dinR, dmixR);
      +                            outG[iPos] = overlayCalc(dinG, dmixG);
      +                            outB[iPos] = overlayCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'screen' :
      +                const screenCalc = (din, dmix) => (dmix + (din - (dmix * din))) * 255;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = screenCalc(dinR, dmixR);
      +                            outG[iPos] = screenCalc(dinG, dmixG);
      +                            outB[iPos] = screenCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'soft-light' :
      +                const softlightCalc = (din, dmix) => {
      +
      +                    let d = (dmix <= 0.25) ?
      +                        ((((16 * dmix) - 12) * dmix) + 4) * dmix :
      +                        Math.sqrt(dmix);
      +
      +                    if (din <= 0.5) return (dmix - ((1 - (2 * din)) * dmix * (1 - dmix))) * 255;
      +                    return (dmix + (((2 * din) - 1) * (d - dmix))) * 255;
      +                };
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            outR[iPos] = softlightCalc(dinR, dmixR);
      +                            outG[iPos] = softlightCalc(dinG, dmixG);
      +                            outB[iPos] = softlightCalc(dinB, dmixB);
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'color' :
      +                const colorCalc = (iR, iG, iB, mR, mG, mB) => {
      +
      +                    let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB);
      +                    let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB);
      +
      +                    return getRGBfromHSL(iH, iS, mL);
      +                }
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            let [cr, cg, cb] = colorCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB);
      +                            outR[iPos] = cr;
      +                            outG[iPos] = cg;
      +                            outB[iPos] = cb;
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'hue' :
      +                const hueCalc = (iR, iG, iB, mR, mG, mB) => {
      +
      +                    let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB);
      +                    let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB);
      +
      +                    return getRGBfromHSL(iH, mS, mL);
      +                }
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
       
      -        let i, iz, pos;
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +                            let [cr, cg, cb] = hueCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB);
      +                            outR[iPos] = cr;
      +                            outG[iPos] = cg;
      +                            outB[iPos] = cb;
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'luminosity' :
      +                const luminosityCalc = (iR, iG, iB, mR, mG, mB) => {
      +
      +                    let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB);
      +                    let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB);
      +
      +                    return getRGBfromHSL(mH, mS, iL);
      +                }
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            let [cr, cg, cb] = luminosityCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB);
      +                            outR[iPos] = cr;
      +                            outG[iPos] = cg;
      +                            outB[iPos] = cb;
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'saturation' :
      +                const saturationCalc = (iR, iG, iB, mR, mG, mB) => {
      +
      +                    let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB);
      +                    let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB);
      +
      +                    return getRGBfromHSL(mH, iS, mL);
      +                }
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else if (!mixA[mPos]) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos);
      +
      +                            let [cr, cg, cb] = saturationCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB);
      +                            outR[iPos] = cr;
      +                            outG[iPos] = cg;
      +                            outB[iPos] = cb;
      +                            outA[iPos] = alphaCalc(dinA, dmixA);
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            default:
      +                const normalCalc = (Cs, As, Cb, Ab) => (As * Cs) + (Ab * Cb * (1 - As));
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else if (!inA[iPos]) copyPixel(mPos, iPos, mix);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
       
      -            pos = cache[i];
      -            data[pos + 2] = 0;
      +                            outR[iPos] = normalCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = normalCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = normalCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = alphaCalc(dinA, dmixA)
      +                        }
      +                    }
      +                }
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1179,24 +2235,119 @@

      Messaging and error handling

      -

      cyan - averages the image’s blue and green channels, and suppresses the red channel

      +

      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.

      -
          cyan: function () {
      +            
          'blur': function (requirements) {
       
      -        let i, iz, pos, gray;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            
      -            gray = (data[pos + 1] + data[pos + 2]) / 2;
      -            
      -            data[pos] = 0;
      -            data[pos + 1] = gray;
      -            data[pos + 2] = gray;
      +        let {opacity, radius, passes, processVertical, processHorizontal, includeRed, includeGreen, includeBlue, includeAlpha, step, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == radius) radius = 0;
      +        if (null == passes) passes = 1;
      +        if (null == processVertical) processVertical = true;
      +        if (null == processHorizontal) processHorizontal = true;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == includeAlpha) includeAlpha = false;
      +        if (null == step) step = 1;
      +
      +        let horizontalBlurGrid, verticalBlurGrid;
      +
      +        if (processHorizontal || processVertical) {
      +
      +            let grid = buildImageGrid();
      +
      +            if (processHorizontal)  horizontalBlurGrid = buildHorizontalBlur(grid, radius);
      +
      +            if (processVertical) verticalBlurGrid = buildVerticalBlur(grid, radius);
               }
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        const getValue = function (flag, gridStore, pos, holdChannel, alpha) {
      +
      +            if (flag) {
      +
      +                let h = gridStore[pos];
      +
      +                if (h != null) {
      +
      +                    let l = h.length,
      +                        counter = 0,
      +                        total = 0;
      +
      +                    if (alpha) {
      +
      +                        for (let t = 0; t < l; t += step) {
      +
      +                            if (alpha[h[t]]) {
      +
      +                                total += holdChannel[h[t]];
      +                                counter++;
      +                            }
      +                        }
      +                        return total / counter;
      +                    }
      +                    for (let t = 0; t < l; t++) {
      +                        total += holdChannel[h[t]];
      +                    }
      +                    return total / l;
      +                }
      +            }
      +            return holdChannel[pos];
      +        }
      +
      +        if (!passes || (!processHorizontal && !processVertical)) copyOver(input, output);
      +        else {
      +
      +            const hold = createResultObject(len);
      +            const {r:holdR, g:holdG, b:holdB, a:holdA} = hold;
      +
      +            copyOver(input, hold);
      +
      +            for (let pass = 0; pass < passes; pass++) {
      +
      +                if (processHorizontal) {
      +
      +                    for (let k = 0; k < len; k++) {
      +
      +                        if (includeAlpha || holdA[k]) {
      +
      +                            outR[k] = getValue(includeRed, horizontalBlurGrid, k, holdR, holdA);
      +                            outG[k] = getValue(includeGreen, horizontalBlurGrid, k, holdG, holdA);
      +                            outB[k] = getValue(includeBlue, horizontalBlurGrid, k, holdB, holdA);
      +                            outA[k] = getValue(includeAlpha, horizontalBlurGrid, k, holdA, false);
      +                        }
      +                    }
      +                    if (processVertical || pass < passes - 1) copyOver(output, hold);
      +                }
      +
      +                if (processVertical) {
      +
      +                    for (let k = 0; k < len; k++) {
      +
      +                        if (includeAlpha || holdA[k]) {
      +
      +                            outR[k] = getValue(includeRed, verticalBlurGrid, k, holdR, holdA);
      +                            outG[k] = getValue(includeGreen, verticalBlurGrid, k, holdG, holdA);
      +                            outB[k] = getValue(includeBlue, verticalBlurGrid, k, holdB, holdA);
      +                            outA[k] = getValue(includeAlpha, verticalBlurGrid, k, holdA, false);
      +                        }
      +                    }
      +                    if (pass < passes - 1) copyOver(output, hold);
      +                }
      +            }
      +        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1208,24 +2359,53 @@

      Messaging and error handling

      -

      magenta - averages the image’s red and blue channels, and suppresses the green channel

      +

      channels-to-alpha - Calculates an average value from each pixel’s included channels and applies that value to the alpha channel.

      -
          magenta: function () {
      +            
          'channels-to-alpha': function (requirements) {
      +
      +        let [input, output] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
      +
      +        let {opacity, includeRed, includeGreen, includeBlue, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +
      +        let divisor = 0;
      +        if (includeRed) divisor++;
      +        if (includeGreen) divisor++;
      +        if (includeBlue) divisor++;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -        let i, iz, pos, gray;
      +        for (let i = 0; i < len; i++) {
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +            outR[i] = inR[i];
      +            outG[i] = inG[i];
      +            outB[i] = inB[i];
       
      -            pos = cache[i];
      +            if (divisor) {
       
      -            gray = (data[pos] + data[pos + 2]) / 2;
      +                let avg = 0;
       
      -            data[pos] = gray;
      -            data[pos + 1] = 0;
      -            data[pos + 2] = gray;
      +                if (includeRed) avg += inR[i];
      +                if (includeGreen) avg += inG[i];
      +                if (includeBlue) avg += inB[i];
      +
      +                avg = Math.floor(avg / divisor);
      +
      +                outA[i] = avg;
      +            }
      +            else outA[i] = inA[i];
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1237,24 +2417,52 @@

      Messaging and error handling

      -

      yellow - averages the image’s red and green channels, and suppresses the blue channel

      +

      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.

      -
          yellow: function () {
      +            
          'chroma': function (requirements) {
       
      -        let i, iz, pos, gray;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            
      -            gray = (data[pos] + data[pos + 1]) / 2;
      -            
      -            data[pos] = gray;
      -            data[pos + 1] = gray;
      -            data[pos + 2] = 0;
      +        let {opacity, ranges, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == ranges) ranges = [];
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let j = 0; j < len; j++) {
      +
      +            let flag = false;
      +
      +            let r = inR[j],
      +                g = inG[j],
      +                b = inB[j];
      +
      +            for (i = 0, iz = ranges.length; i < iz; i++) {
      +
      +                let range = ranges[i];
      +
      +                let [minR, minG, minB, maxR, maxG, maxB] = ranges[i];
      +
      +                if (r >= minR && r <= maxR && g >= minG && g <= maxG && b >= minB && b <= maxB) {
      +                    flag = true;
      +                    break;
      +                }
      +
      +            }
      +            outR[j] = inR[j];
      +            outG[j] = inG[j];
      +            outB[j] = inB[j];
      +            outA[j] = (flag) ? 0 : inA[j];
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1266,26 +2474,55 @@

      Messaging and error handling

      -

      brightness - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute

      +

      clamp-channels - Clamp each color channel to a range set by lowColor and highColor values

      -
          brightness: function () {
      +            
          'clamp-channels': function (requirements) {
       
      -        let i, iz, pos, 
      -            level = filter.level || 0;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            data[pos] *= level;
      -            
      -            pos++;
      -            data[pos] *= level;
      -            
      -            pos++;
      -            data[pos] *= level;
      +        let {opacity, lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == lowRed) lowRed = 0;
      +        if (null == lowGreen) lowGreen = 0;
      +        if (null == lowBlue) lowBlue = 0;
      +        if (null == highRed) highRed = 255;
      +        if (null == highGreen) highGreen = 255;
      +        if (null == highBlue) highBlue = 255;
      +
      +        const dR = highRed - lowRed,
      +            dG = highGreen - lowGreen,
      +            dB = highBlue - lowBlue;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +
      +            if (inA[i]) {
      +
      +                let r = inR[i] / 255,
      +                    g = inG[i] / 255,
      +                    b = inB[i] / 255;
      +
      +                outR[i] = lowRed + (r * dR);
      +                outG[i] = lowGreen + (g * dG);
      +                outB[i] = lowBlue + (b * dB);
      +                outA[i] = inA[i];
      +            }
      +            else {
      +                outR[i] = inR[i];
      +                outG[i] = inG[i];
      +                outB[i] = inB[i];
      +                outA[i] = inA[i];
      +            }
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1297,26 +2534,51 @@

      Messaging and error handling

      -

      saturation - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute, then normalizes the result

      +

      colors-to-alpha - 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.

      -
          saturation: function () {
      +            
          'colors-to-alpha': function (requirements) {
       
      -        let i, iz, pos, 
      -            level = filter.level || 0;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            data[pos] = 127 + ((data[pos] - 127) * level);
      -            
      -            pos++;
      -            data[pos] = 127 + ((data[pos] - 127) * level);
      -            
      -            pos++;
      -            data[pos] = 127 + ((data[pos] - 127) * level);
      +        let {opacity, red, green, blue, opaqueAt, transparentAt, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == red) red = 0;
      +        if (null == green) green = 255;
      +        if (null == blue) blue = 0;
      +        if (null == opaqueAt) opaqueAt = 1;
      +        if (null == transparentAt) transparentAt = 0;
      +
      +        const maxDiff = Math.max(((red + green + blue) / 3), (((255 - red) + (255 - green) + (255 - blue)) / 3)),
      +            transparent = transparentAt * maxDiff,
      +            opaque = opaqueAt * maxDiff,
      +            range = opaque - transparent;
      +
      +        const getValue = function (r, g, b) {
      +
      +            let diff = (Math.abs(red - r) + Math.abs(green - g) + Math.abs(blue - b)) / 3;
      +
      +            if (diff < transparent) return 0;
      +            if (diff > opaque) return 255;
      +            return ((diff - transparent) / range) * 255;
      +        };
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +            outR[i] = inR[i];
      +            outG[i] = inG[i];
      +            outB[i] = inB[i];
      +            outA[i] = getValue(inR[i], inG[i], inB[i]);
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
      @@ -1328,186 +2590,687 @@

      Messaging and error handling

      -

      threshold - desaturates each pixel then tests it against filter.level value; those pixels below the level are set to the filter.lowRGB values while the rest are set to the filter.highRGB values

      +

      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.

      -
          threshold: function () {
      +            
          'compose': function (requirements) {
      +
      +        let [input, output, mix] = getInputAndOutputChannels(requirements);
      +
      +        let len = output.r.length;
      +
      +        let {opacity, compose, offsetX, offsetY, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == compose) compose = '';
      +        if (null == offsetX) offsetX = 0;
      +        if (null == offsetY) offsetY = 0;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +        const {r:mixR, g:mixG, b:mixB, a:mixA} = mix;
      +
      +        let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements);
      +
      +        const copyPixel = function (fromPos, toPos, channel) {
      +
      +            outR[toPos] = channel.r[fromPos];
      +            outG[toPos] = channel.g[fromPos];
      +            outB[toPos] = channel.b[fromPos];
      +            outA[toPos] = channel.a[fromPos];
      +        };
      +
      +        const getLinePositions = function (x, y) {
      +
      +            let ix = x,
      +                iy = y,
      +                mx = x - offsetX,
      +                my = y - offsetY;
      +
      +            let mPos = -1,
      +                iPos = (iy * iWidth) + ix;
      +
      +            if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx;
      +
      +            return [iPos, mPos];
      +        };
      +
      +        switch (compose) {
      +
      +            case 'source-only' :
      +                copyOver(input, output);
      +                break;
      +
      +            case 'source-atop' :
      +                const sAtopCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * mAlpha) + (mAlpha * mColor * (1 - iAlpha));
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos >= 0) {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = sAtopCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = sAtopCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = sAtopCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = ((dinA * dmixA) + (dmixA * (1 - dinA))) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'source-in' :
      +                const sInCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * mAlpha;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos >= 0) {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = sInCalc(inR[iPos], dinA, dmixA);
      +                            outG[iPos] = sInCalc(inG[iPos], dinA, dmixA);
      +                            outB[iPos] = sInCalc(inB[iPos], dinA, dmixA);
      +                            outA[iPos] = dinA * dmixA * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'source-out' :
      +                const sOutCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * (1 - mAlpha);
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = sOutCalc(inR[iPos], dinA, dmixA);
      +                            outG[iPos] = sOutCalc(inG[iPos], dinA, dmixA);
      +                            outB[iPos] = sOutCalc(inB[iPos], dinA, dmixA);
      +                            outA[iPos] = dinA * (1 - dmixA) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'destination-only' :
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos >= 0) copyPixel(mPos, iPos, mix);
      +                    }
      +                }
      +                break;
      +
      +            case 'destination-atop' :
      +                const dAtopCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor * iAlpha);
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = dAtopCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = dAtopCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = dAtopCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = ((dinA * (1 - dmixA)) + (dmixA * dinA)) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'destination-over' :
      +                const dOverCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor);
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = dOverCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = dOverCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = dOverCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = ((dinA * (1 - dmixA)) + dmixA) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'destination-in' :
      +                const dInCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * mAlpha;
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos >= 0) {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = dInCalc(mixR[mPos], dmixA, dinA);
      +                            outG[iPos] = dInCalc(mixG[mPos], dmixA, dinA);
      +                            outB[iPos] = dInCalc(mixB[mPos], dmixA, dinA);
      +                            outA[iPos] = dinA * dmixA * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'destination-out' :
      +                const dOutCalc = (mColor, iAlpha, mAlpha) => mAlpha * mColor * (1 - iAlpha);
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos >= 0) {
      +        
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = dOutCalc(mixR[mPos], dinA, dmixA);
      +                            outG[iPos] = dOutCalc(mixG[mPos], dinA, dmixA);
      +                            outB[iPos] = dOutCalc(mixB[mPos], dinA, dmixA);
      +                            outA[iPos] = dmixA * (1 - dinA) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            case 'clear' :
      +                break;
      +
      +            case 'xor' :
      +                const xorCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor * (1 - iAlpha));
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = xorCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = xorCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = xorCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = ((dinA * (1 - dmixA)) + (dmixA * (1 - dinA))) * 255;
      +                        }
      +                    }
      +                }
      +                break;
      +
      +            default:
      +                const sOverCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor) + (mAlpha * mColor * (1 - iAlpha));
      +                for (let y = 0; y < iHeight; y++) {
      +                    for (let x = 0; x < iWidth; x++) {
      +
      +                        let [iPos, mPos] = getLinePositions(x, y);
      +
      +                        if (mPos < 0) copyPixel(iPos, iPos, input);
      +                        else {
      +
      +                            let dinA = inA[iPos] / 255,
      +                                dmixA = mixA[mPos] / 255;
      +
      +                            outR[iPos] = sOverCalc(inR[iPos], dinA, mixR[mPos], dmixA);
      +                            outG[iPos] = sOverCalc(inG[iPos], dinA, mixG[mPos], dmixA);
      +                            outB[iPos] = sOverCalc(inB[iPos], dinA, mixB[mPos], dmixA);
      +                            outA[iPos] = (dinA + (dmixA * (1 - dinA))) * 255;
      +                        }
      +                    }
      +                }
      +        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + + + + +
    • +
      + +
      + +
      +

      displace - Shift pixels around the image, based on the values supplied in a displacement image

      + +
      + +
          'displace': function (requirements) {
      +
      +        let [input, output, mix] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
      +
      +        let {opacity, channelX, channelY, scaleX, scaleY, offsetX, offsetY, transparentEdges, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == channelX) channelX = 'red';
      +        if (null == channelY) channelY = 'green';
      +        if (null == scaleX) scaleX = 1;
      +        if (null == scaleY) scaleY = 1;
      +        if (null == offsetX) offsetX = 0;
      +        if (null == offsetY) offsetY = 0;
      +        if (null == transparentEdges) transparentEdges = false;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +        const {r:mixR, g:mixG, b:mixB, a:mixA} = mix;
      +
      +        if (channelX == 'red') channelX = mixR;
      +        else if (channelX == 'green') channelX = mixG;
      +        else if (channelX == 'blue') channelX = mixB;
      +        else channelX = mixA;
      +
      +        if (channelY == 'red') channelY = mixR;
      +        else if (channelY == 'green') channelY = mixG;
      +        else if (channelY == 'blue') channelY = mixB;
      +        else channelY = mixA;
      +
      +        let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements);
      +
      +        const copyPixel = function (fromPos, toPos, channel) {
      +
      +            if (fromPos < 0) outA[toPos] = 0;
      +            else {
      +
      +                outR[toPos] = channel.r[fromPos];
      +                outG[toPos] = channel.g[fromPos];
      +                outB[toPos] = channel.b[fromPos];
      +                outA[toPos] = channel.a[fromPos];
      +            }
      +        };
      +
      +        const getLinePositions = function (x, y) {
      +
      +            let ix = x,
      +                iy = y,
      +                mx = x + offsetX,
      +                my = y + offsetY;
      +
      +            let mPos = -1,
      +                iPos = (iy * iWidth) + ix;
      +
      +            if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx;
      +
      +            return [iPos, mPos];
      +        };
      +
      +        for (let y = 0; y < iHeight; y++) {
      +            for (let x = 0; x < iWidth; x++) {
      +
      +                let [iPos, mPos] = getLinePositions(x, y);
      +
      +                if (mPos >= 0) {
      +
      +                    let dx = Math.floor(x + ((127 - channelX[mPos]) / 127) * scaleX);
      +                    let dy = Math.floor(y + ((127 - channelY[mPos]) / 127) * scaleY);
      +                    let dPos;
      +
      +                    if (!transparentEdges) {
      +
      +                        if (dx < 0) dx = 0;
      +                        if (dx >= iWidth) dx = iWidth - 1;
      +                        if (dy < 0) dy = 0;
      +                        if (dy >= iHeight) dy = iHeight - 1;
      +
      +                        dPos = (dy * iWidth) + dx;
      +                    }
      +                    else {
      +
      +                        if (dx < 0 || dx >= iWidth || dy < 0 || dy >= iHeight) dPos = -1;
      +                        else dPos = (dy * iWidth) + dx;
      +                    }
      +
      +                    copyPixel(dPos, iPos, input);
      +                }
      +                else copyPixel(iPos, iPos, input);
      +            }
      +        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      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

      + +
      + +
          'emboss': function (requirements) {
      +
      +        let [input, output] = getInputAndOutputChannels(requirements);
      +
      +        let len = input.r.length;
      +
      +        let {opacity, strength, angle, tolerance, keepOnlyChangedAreas, postProcessResults, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == strength) strength = 1;
      +        if (null == angle) angle = 0;
      +        if (null == tolerance) tolerance = 0;
      +        if (null == keepOnlyChangedAreas) keepOnlyChangedAreas = false;
      +        if (null == postProcessResults) postProcessResults = false;
      +
      +        strength = Math.abs(strength);
      +
      +        while (angle < 0) {
      +            angle += 360;
      +        }
      +
      +        angle = angle % 360;
      +
      +        let slices = Math.floor(angle / 45),
      +            remains = ((angle % 45) / 45) * strength,
      +            weights = new Array(9);
      +
      +        weights = weights.fill(0, 0, 9); 
      +        weights[4] = 1;
      +
      +        if (slices == 0) {
      +            weights[5] = strength - remains;
      +            weights[8] = remains;
      +            weights[3] = -weights[5];
      +            weights[0] = -weights[8];
      +        }
      +        else if (slices == 1) {
      +            weights[8] = strength - remains;
      +            weights[7] = remains;
      +            weights[0] = -weights[8];
      +            weights[1] = -weights[7];
      +        }
      +        else if (slices == 2) {
      +            weights[7] = strength - remains;
      +            weights[6] = remains;
      +            weights[1] = -weights[7];
      +            weights[2] = -weights[6];
      +        }
      +        else if (slices == 3) {
      +            weights[6] = strength - remains;
      +            weights[3] = remains;
      +            weights[2] = -weights[6];
      +            weights[5] = -weights[3];
      +        }
      +        else if (slices == 4) {
      +            weights[3] = strength - remains;
      +            weights[0] = remains;
      +            weights[5] = -weights[3];
      +            weights[8] = -weights[0];
      +        }
      +        else if (slices == 5) {
      +            weights[0] = strength - remains;
      +            weights[1] = remains;
      +            weights[8] = -weights[0];
      +            weights[7] = -weights[1];
      +        }
      +        else if (slices == 6) {
      +            weights[1] = strength - remains;
      +            weights[2] = remains;
      +            weights[7] = -weights[1];
      +            weights[6] = -weights[2];
      +        }
      +        else {
      +            weights[2] = strength - remains;
      +            weights[5] = remains;
      +            weights[6] = -weights[2];
      +            weights[3] = -weights[5];
      +        }
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        grid = buildMatrixGrid(3, 3, 1, 1, inA);
      +
      +        const doCalculations = function (inChannel, matrix) {
      +
      +            let val = 0;
      +
      +            for (let m = 0, mz = matrix.length; m < mz; m++) {
      +
      +                if (weights[m]) val += (inChannel[matrix[m]] * weights[m]);
      +            }
      +            return val;
      +        }
       
      -        let i, iz, pos, gray, test,
      -            level = filter.level || 0,
      -            lowRed = filter.lowRed,
      -            lowGreen = filter.lowGreen,
      -            lowBlue = filter.lowBlue,
      -            highRed = filter.highRed,
      -            highGreen = filter.highGreen,
      -            highBlue = filter.highBlue;
      +        for (let i = 0; i < len; i++) {
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +            if (inA[i]) {
       
      -            pos = cache[i];
      -            
      -            gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]);
      -            test = (gray > level) ? true : false;
      -            
      -            if (test) {
      +                outR[i] = doCalculations(inR, grid[i]);
      +                outG[i] = doCalculations(inG, grid[i]);
      +                outB[i] = doCalculations(inB, grid[i]);
      +                outA[i] = inA[i];
       
      -                data[pos] = highRed;
      -                data[pos + 1] = highGreen;
      -                data[pos + 2] = highBlue;
      -            }
      -            else {
      +                if (postProcessResults) {
       
      -                data[pos] = lowRed;
      -                data[pos + 1] = lowGreen;
      -                data[pos + 2] = lowBlue;
      +                    if (outR[i] >= inR[i] - tolerance && outR[i] <= inR[i] + tolerance && 
      +                        outG[i] >= inG[i] - tolerance && outG[i] <= inG[i] + tolerance && 
      +                        outB[i] >= inB[i] - tolerance && outB[i] <= inB[i] + tolerance) {
      +
      +                        if (keepOnlyChangedAreas) outA[i] = 0;
      +                        else {
      +                            outR[i] = 127;
      +                            outG[i] = 127;
      +                            outB[i] = 127;
      +                        }
      +                    }
      +                }
                   }
      -            
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      channels - multiply each pixel’s channel values by the values set in the filter.RGB attributes

      +

      flood - Set all pixels to the channel values supplied in the “red”, “green”, “blue” and “alpha” arguments

      -
          channels: function () {
      +            
          'flood': function (requirements) {
       
      -        let i, iz, pos,
      -            red = filter.red || 0,
      -            green = filter.green || 0,
      -            blue = filter.blue || 0;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length,
      +            floor = Math.floor;
       
      -            pos = cache[i];
      -            
      -            data[pos] *= red;
      -            data[pos + 1] *= green;
      -            data[pos + 2] *= blue;
      -        }
      +        let {opacity, red, green, blue, alpha, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == red) red = 0;
      +        if (null == green) green = 0;
      +        if (null == blue) blue = 0;
      +        if (null == alpha) alpha = 255;
      +
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        outR.fill(red, 0, len - 1);
      +        outG.fill(green, 0, len - 1);
      +        outB.fill(blue, 0, len - 1);
      +        outA.fill(alpha, 0, len - 1);
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      channelstep - divide, floor, and then multiply each pixel’s channel values by the values set in the filter.RGB attributes

      +

      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.

      -
          channelstep: function () {
      +            
          'grayscale': function (requirements) {
       
      -        let i, iz, pos,
      -            red = filter.red || 1,
      -            green = filter.green || 1,
      -            blue = filter.blue || 1,
      -            floor = Math.floor;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            data[pos] = floor(data[pos] / red) * red;
      -            
      -            pos++;
      -            data[pos] = floor(data[pos] / green) * green;
      -            
      -            pos++;
      -            data[pos] = floor(data[pos] / blue) * blue;
      +        let {opacity, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +
      +            let gray = Math.floor((0.2126 * inR[i]) + (0.7152 * inG[i]) + (0.0722 * inB[i]));
      +
      +            outR[i] = gray;
      +            outG[i] = gray;
      +            outB[i] = gray;
      +            outA[i] = inA[i];
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      tint - a more fine-grained form of the channels filter

      +

      invert-channels - For each pixel, subtracts its current channel values - when included - from 255.

      -
          tint: function () {
      +            
          'invert-channels': function (requirements) {
       
      -        let i, iz, pos, r, g, b,
      -            redInRed = filter.redInRed || 0,
      -            redInGreen = filter.redInGreen || 0,
      -            redInBlue = filter.redInBlue || 0,
      -            greenInRed = filter.greenInRed || 0,
      -            greenInGreen = filter.greenInGreen || 0,
      -            greenInBlue = filter.greenInBlue || 0,
      -            blueInRed = filter.blueInRed || 0,
      -            blueInGreen = filter.blueInGreen || 0,
      -            blueInBlue = filter.blueInBlue || 0;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let len = input.r.length;
       
      -            pos = cache[i];
      -            
      -            r = data[pos];
      -            g = data[pos + 1];
      -            b = data[pos + 2];
      -            
      -            data[pos] = (r * redInRed) + (g * greenInRed) + (b * blueInRed);
      -            data[pos + 1] = (r * redInGreen) + (g * greenInGreen) + (b * blueInGreen);
      -            data[pos + 2] = (r * redInBlue) + (g * greenInBlue) + (b * blueInBlue);
      +        let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, lineOut} = requirements;
      +
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == includeAlpha) includeAlpha = false;
      +
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
      +
      +        for (let i = 0; i < len; i++) {
      +
      +            outR[i] = (includeRed) ? 255 - inR[i] : inR[i];
      +            outG[i] = (includeGreen) ? 255 - inG[i] : inG[i];
      +            outB[i] = (includeBlue) ? 255 - inB[i] : inB[i];
      +            outA[i] = (includeAlpha) ? 255 - inA[i] : inA[i];
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      chroma - will evaluate each pixel against a range array; pixels that fall within the range are set to transparent

      +

      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.

      -
    • - - -
    • -
      - -
      - -
      -

      The ranges attribute needs to be an array of arrays with the following format:

      +
          'lock-channels-to-levels': function (requirements) {
       
      -            
      - -
    • - - -
    • -
      - -
      - -
      -
      [[minRed, minGreen, minBlue, maxRed, maxGreen, maxBlue], etc]
      + checkChannelLevelsParameters(requirements) -
      + const getValue = function (val, levels) { + + if (!levels.length) return val; + + for (let i = 0, iz = levels.length; i < iz; i++) { + + let [start, end, level] = levels[i]; + if (val >= start && val <= end) return level; + } + }; + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, red, green, blue, alpha, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = [0]; + if (null == green) green = [0]; + if (null == blue) blue = [0]; + if (null == alpha) alpha = [255]; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + outR[i] = getValue(inR[i], red); + outG[i] = getValue(inG[i], green); + outB[i] = getValue(inB[i], blue); + outA[i] = getValue(inA[i], alpha); + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + },
    • @@ -1518,622 +3281,568 @@

      Messaging and error handling

      -

      … multiple ranges can be defined - for instance to key out the lightest and darkest hues:

      - -
      - - - - -
    • -
      - -
      - -
      -
      ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]]
      +

      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.

      -
          chroma: function () {
      +            
          'matrix': function (requirements) {
       
      -        let pos, posA,
      -            ranges = filter.ranges,
      -            range, min, max, val,
      -            i, iz, j, jz, flag;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -        for (j = 0, jz = cache.length; j < jz; j++) {
      +        let len = input.r.length;
       
      -            flag = false;
      +        let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, width, height, offsetX, offsetY, weights, lineOut} = requirements;
       
      -            for (i = 0, iz = ranges.length; i < iz; i++) {
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == includeAlpha) includeAlpha = false;
      +        if (null == width || width < 1) width = 3;
      +        if (null == height || height < 1) height = 3;
      +        if (null == offsetX) offsetX = 1;
      +        if (null == offsetY) offsetY = 1;
      +        if (null == weights) {
      +            weights = [].fill(0, 0, (width * height) - 1);
      +            weights[Math.floor(weights.length / 2) + 1] = 1;
      +        }
       
      -                posA = cache[j] + 3;
      -                range = ranges[i];
      -                min = range[2];
      -                pos = posA - 1;
      -                val = data[pos];
      +        grid = buildMatrixGrid(width, height, offsetX, offsetY, input.a);
       
      -                if (val >= min) {
      +        const doCalculations = function (inChannel, matrix) {
       
      -                    max = range[5];
      +            let val = 0;
       
      -                    if (val <= max) {
      +            for (let m = 0, mz = matrix.length; m < mz; m++) {
       
      -                        min = range[1];
      -                        pos--;
      -                        val = data[pos];
      +                if (weights[m]) val += (inChannel[matrix[m]] * weights[m]);
      +            }
      +            return val;
      +        }
       
      -                        if (val >= min) {
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                            max = range[4];
      +        for (let i = 0; i < len; i++) {
       
      -                            if (val <= max) {
      +            if (inA[i]) {
       
      -                                min = range[0];
      -                                pos--;
      -                                val = data[pos];
      +                if (includeRed) outR[i] = doCalculations(inR, grid[i]);
      +                else outR[i] = inR[i];
       
      -                                if (val >= min) {
      +                if (includeGreen) outG[i] = doCalculations(inG, grid[i]);
      +                else outG[i] = inG[i];
       
      -                                    max = range[3];
      +                if (includeBlue) outB[i] = doCalculations(inB, grid[i]);
      +                else outB[i] = inB[i];
       
      -                                    if (val <= max) {
      -                                        flag = true;
      -                                        break;
      -                                    }
      -                                }
      -                            }
      -                        }
      -                    }
      -                }
      +                if (includeAlpha) outA[i] = doCalculations(inA, grid[i]);
      +                else outA[i] = inA[i];
                   }
      -            if (flag) data[posA] = 0;
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      pixelate - create tiles - whose dimensions and positions are determined by values set in the filter tileWidth, tileHeight, offsetX and offsetY attributes - across the image and then average the pixels in each tile to a single color

      +

      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.

      -
          pixelate: function () {
      -
      -        let i, iz, j, jz, pos, r, g, b, a, tile, len;
      -
      -        getTiles();
      +            
          'modulate-channels': function (requirements) {
       
      -        for (i = 0, iz = tiles.length; i < iz; i++) {
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -            tile = tiles[i];
      -            r = g = b = a = 0;
      -            len = tile.length;
      +        let len = input.r.length;
       
      -            if (len) {
      +        let {opacity, red, green, blue, alpha, saturation, lineOut} = requirements;
       
      -                for (j = 0, jz = len; j < jz; j++) {
      +        if (null == opacity) opacity = 1;
      +        if (null == red) red = 1;
      +        if (null == green) green = 1;
      +        if (null == blue) blue = 1;
      +        if (null == alpha) alpha = 1;
      +        if (null == saturation) saturation = false;
       
      -                    pos = tile[j];
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                    r += data[pos];
      -                    g += data[pos + 1];
      -                    b += data[pos + 2];
      -                    a += data[pos + 3];
      -                }
      -
      -                r /= len;
      -                g /= len;
      -                b /= len;
      -                a /= len;
      +        if (saturation) {
       
      -                for (j = 0, jz = len; j < jz; j++) {
      -
      -                    pos = tile[j];
      +            for (let i = 0; i < len; i++) {
      +                outR[i] = 127 + ((inR[i] - 127) * red);
      +                outG[i] = 127 + ((inG[i] - 127) * green);
      +                outB[i] = 127 + ((inB[i] - 127) * blue);
      +                outA[i] = 127 + ((inA[i] - 127) * alpha);
      +            }
      +        }
      +        else {
       
      -                    data[pos] = r;
      -                    data[pos + 1] = g;
      -                    data[pos + 2] = b;
      -                    data[pos + 3] = a;
      -                }
      +            for (let i = 0; i < len; i++) {
      +                outR[i] = inR[i] * red;
      +                outG[i] = inG[i] * green;
      +                outB[i] = inB[i] * blue;
      +                outA[i] = inA[i] * alpha;
                   }
               }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      blur - creates a blurred image. Note: can be slow across larger images! The degree of the blur - which does not follow conventional algorithms such as gaussian - is determined by the filter attribute values for radius (number), passes (number) and shrink (boolean)

      +

      offset - Offset the input image in the output image.

      -
          blur: function () {
      +            
          'offset': function (requirements) {
       
      -        if (data.slice) {
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -            let radius = filter.radius || 1,
      -                alpha = filter.includeAlpha || false,
      -                shrink = filter.shrinkingRadius || false,
      -                passes = filter.passes || 1,
      -                vertical = filter.processVertical,
      -                horizontal = filter.processHorizontal,
      -                len = data.length,
      -                imageWidth = image.width,
      -                imageHeight = image.height,
      -                tempDataTo, tempDataFrom,
      -                i, iz, index;
      +        let {opacity, offsetRedX, offsetRedY, offsetGreenX, offsetGreenY, offsetBlueX, offsetBlueY, offsetAlphaX, offsetAlphaY, lineOut} = requirements;
       
      -            let processPass = function () {
      +        if (null == opacity) opacity = 1;
      +        if (null == offsetRedX) offsetRedX = 0;
      +        if (null == offsetRedY) offsetRedY = 0;
      +        if (null == offsetGreenX) offsetGreenX = 0;
      +        if (null == offsetGreenY) offsetGreenY = 0;
      +        if (null == offsetBlueX) offsetBlueX = 0;
      +        if (null == offsetBlueY) offsetBlueY = 0;
      +        if (null == offsetAlphaX) offsetAlphaX = 0;
      +        if (null == offsetAlphaY) offsetAlphaY = 0;
       
      -                let j, jz;
      +        let simpleoffset = false;
       
      -                if (vertical) {
      +        if (offsetRedX == offsetGreenX && offsetRedX == offsetBlueX && offsetRedX == offsetAlphaX && offsetRedY == offsetGreenY && offsetRedY == offsetBlueY && offsetRedY == offsetAlphaY) simpleoffset = true;
       
      -                    tempDataFrom = tempDataTo.slice(); 
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                    for (j = localX * 4, jz = (localX + localWidth) * 4; j < jz; j++) {
      +        let grid = buildImageGrid(),
      +            gWidth = grid[0].length,
      +            gHeight = grid.length,
      +            drx, dry, dgx, dgy, dbx, dby, dax, day, inCell, outCell;
       
      -                        if (alpha) processColumn(j);
      -                        else {
      +        for (let y = 0; y < gHeight; y++) {
      +            for (let x = 0; x < gWidth; x++) {
       
      -                            if (j % 4 !== 3) processColumn(j);
      -                        }
      -                    }
      -                }
      +                inCell = grid[y][x];
       
      -                if (horizontal) {
      +                if (inA[inCell]) {
       
      -                    tempDataFrom = tempDataTo.slice(); 
      +                    if (simpleoffset) {
       
      -                    for (j = localY, jz = localY + localHeight; j < jz; j++) {
      +                        drx = x + offsetRedX;
      +                        dry = y + offsetRedY;
       
      -                        if (alpha) processRowWithAlpha(j);
      -                        else processRowNoAlpha(j);
      -                    }
      -                }
      -            };
      +                        if (drx >= 0 && drx < gWidth && dry >= 0 && dry < gHeight) {
       
      -            let processColumn = function (col) {
      +                            outCell = grid[dry][drx];
      +                            outR[outCell] = inR[inCell];
      +                            outG[outCell] = inG[inCell];
      +                            outB[outCell] = inB[inCell];
      +                            outA[outCell] = inA[inCell];
      +                        }
      +                    }
      +                    else {
       
      -                let pos, avg, val, cagePointer, y, yz, q, dataPointer,
      -                    vLead = radius * iWidth, 
      -                    cage = [],
      -                    cageLen;
      +                        drx = x + offsetRedX;
      +                        dry = y + offsetRedY;
      +                        dgx = x + offsetGreenX;
      +                        dgy = y + offsetGreenY;
      +                        dbx = x + offsetBlueX;
      +                        dby = y + offsetBlueY;
      +                        dax = x + offsetAlphaX;
      +                        day = y + offsetAlphaY;
       
      -                for (y = -radius, yz = radius; y < yz; y++) {
      +                        if (drx >= 0 && drx < gWidth && dry >= 0 && dry < gHeight) {
       
      -                    pos = col + (y * iWidth);
      -                    pos = checkBounds(pos, len);
      -                    cage.push(tempDataFrom[pos]);
      -                }
      +                            outCell = grid[dry][drx];
      +                            outR[outCell] = inR[inCell];
      +                        }
       
      -                tempDataTo[col] = avg = average(cage);
      +                        if (dgx >= 0 && dgx < gWidth && dgy >= 0 && dgy < gHeight) {
       
      -                cageLen = cage.length;
      +                            outCell = grid[dgy][dgx];
      +                            outG[outCell] = inG[inCell];
      +                        }
       
      -                for (q = 0; q < cageLen; q++) {
      +                        if (dbx >= 0 && dbx < gWidth && dby >= 0 && dby < gHeight) {
       
      -                    cage[q] /= cageLen;
      -                }
      +                            outCell = grid[dby][dbx];
      +                            outB[outCell] = inB[inCell];
      +                        }
       
      -                cagePointer = 0;
      +                        if (dax >= 0 && dax < gWidth && day >= 0 && day < gHeight) {
       
      -                for (y = 1; y < imageHeight; y++) {
      +                            outCell = grid[day][dax];
      +                            outA[outCell] = inA[inCell];
      +                        }
      +                    }
      +                }
      +            }
      +        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      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.

      - avg -= cage[cagePointer]; +
      + +
          'pixelate': function (requirements) {
       
      -                    dataPointer = col + (y * iWidth);
      -                    pos = dataPointer + vLead;
      -                    pos = checkBounds(pos, len);
      -                    val = tempDataFrom[pos] / cageLen;
      +        const doCalculations = function (inChannel, outChannel, tile) {
       
      -                    avg += val;
      -                    cage[cagePointer] = val;
      -                    tempDataTo[dataPointer] = avg;
      +            let avg = tile.reduce((a, v) => a + inChannel[v], 0);
       
      -                    cagePointer++;
      +            avg = Math.floor(avg / tile.length);
       
      -                    if (cagePointer === cageLen) cagePointer = 0;
      -                }
      -            };
      +            for (let i = 0, iz = tile.length; i < iz; i++) {
       
      -            let processRowWithAlpha = function (row) {
      +                outChannel[tile[i]] = avg;
      +            }
      +        }
       
      -                let pos, val, x, xz, q, avgQ, cageQ, rowPosX,
      -                    avg = [],
      -                    cage = [[], [], [], []],
      -                    rowPos = row * iWidth, 
      -                    hLead = radius * 4,
      -                    dataPointer, cagePointer, cageLen;
      +        const setOutValueToInValue = function (inChannel, outChannel, tile) {
       
      -                q = 0;
      +            let cell;
       
      -                for (x = -radius * 4, xz = radius * 4; x < xz; x++) {
      +            for (let i = 0, iz = tile.length; i < iz; i++) {
       
      -                    pos = rowPos + x;
      -                    pos = checkBounds(pos, len);
      -                    
      -                    cage[q].push(tempDataFrom[pos]);
      -                    
      -                    q++;
      -                    if (q === 4) q = 0;
      -                }
      +                cell = tile[i];
      +                outChannel[cell] = inChannel[cell];
      +            }
      +        };
       
      -                tempDataTo[rowPos] = avg[0] = average(cage[0]);
      -                tempDataTo[rowPos + 1] = avg[1] = average(cage[1]);
      -                tempDataTo[rowPos + 2] = avg[2] = average(cage[2]);
      -                tempDataTo[rowPos + 3] = avg[3] = average(cage[3]);
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -                cageLen = cage[0].length;
      +        let len = input.r.length;
       
      -                for (q = 0; q < 4; q++) {
      +        let {opacity, tileWidth, tileHeight, offsetX, offsetY, includeRed, includeGreen, includeBlue, includeAlpha, lineOut} = requirements;
       
      -                    for (x = 0; x < cageLen; x++) {
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = true;
      +        if (null == includeGreen) includeGreen = true;
      +        if (null == includeBlue) includeBlue = true;
      +        if (null == includeAlpha) includeAlpha = false;
      +        if (null == tileWidth) tileWidth = 1;
      +        if (null == tileHeight) tileHeight = 1;
      +        if (null == offsetX) offsetX = 0;
      +        if (null == offsetY) offsetY = 0;
       
      -                        cage[q][x] /= cageLen;
      -                    }
      -                }
      -                cagePointer = 0;
      +        const tiles = buildImageTileSets(tileWidth, tileHeight, offsetX, offsetY);
       
      -                for (x = 1; x < imageWidth; x++) {
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                    rowPosX = rowPos + (x * 4);
      +        tiles.forEach(t => {
      +            if (includeRed) doCalculations(inR, outR, t);
      +            else setOutValueToInValue(inR, outR, t);
       
      -                    for (q = 0; q < 4; q++) {
      +            if (includeGreen) doCalculations(inG, outG, t);
      +            else setOutValueToInValue(inG, outG, t);
       
      -                        avgQ = avg[q];
      -                        cageQ = cage[q];
      -                        avgQ -= cageQ[cagePointer];
      +            if (includeBlue) doCalculations(inB, outB, t);
      +            else setOutValueToInValue(inB, outB, t);
       
      -                        dataPointer = rowPosX + q;
      -                        pos = dataPointer + hLead;
      -                        pos = checkBounds(pos, len);
      -                        val = tempDataFrom[pos] / cageLen;
      +            if (includeAlpha) doCalculations(inA, outA, t);
      +            else setOutValueToInValue(inA, outA, t);
      +        })
       
      -                        avgQ += val;
      -                        tempDataTo[dataPointer] = avgQ;
      -                        avg[q] = avgQ;
      -                        cageQ[cagePointer] = val;
      -                    }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      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. -

      - cagePointer++; +
      + +
          'process-image': function (requirements) {
       
      -                    if (cagePointer === cageLen) cagePointer = 0;
      -                }
      -            };
      +        const {assetData, lineOut} = requirements;
       
      -            let processRowNoAlpha = function (row) {
      +        if (lineOut && lineOut.substring && lineOut.length && assetData && assetData.width && assetData.height && assetData.data) {
       
      -                let pos, val, x, xz, q, avgQ, cageQ, rowPosX,
      -                    avg = [],
      -                    hLead = radius * 4,
      -                    cage = [[], [], []],
      -                    rowPos = row * iWidth, 
      -                    dataPointer, cagePointer, cageLen;
      +            let d = assetData.data;
      +            let len = d.length;
       
      -                q = 0;
      +            let res = createResultObject(len / 4);
       
      -                for (x = -radius * 4, xz = radius * 4; x < xz; x++) {
      +            let r = res.r,
      +                g = res.g,
      +                b = res.b,
      +                a = res.a;
       
      -                    if (q < 3) {
      +            let counter = 0;
       
      -                        pos = rowPos + x;
      -                        pos = checkBounds(pos, len);
      -                        cage[q].push(tempDataFrom[pos]);
      -                        q++;
      -                    }
      -                    else q = 0;
      -                }
      +            for (let i = 0; i < len; i += 4) {
       
      -                tempDataTo[rowPos] = avg[0] = average(cage[0]);
      -                tempDataTo[rowPos + 1] = avg[1] = average(cage[1]);
      -                tempDataTo[rowPos + 2] = avg[2] = average(cage[2]);
      +                r[counter] = d[i];
      +                g[counter] = d[i + 1];
      +                b[counter] = d[i + 2];
      +                a[counter] = d[i + 3];
       
      -                cageLen = cage[0].length;
      +                counter++;
      +            }
      +            assetData.channels = res;
       
      -                for (q = 0; q < 3; q++) {
      +            cache[lineOut] = assetData;
      +        }
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      set-channel-to-level - Sets the value of each pixel’s included channel to the value supplied in the “level” argument.

      - cageQ = cage[q]; - - for (x = 0; x < cageLen; x++) { +
      + +
          'set-channel-to-level': function (requirements) {
       
      -                        cageQ[x] /= cageLen;
      -                    }
      -                }
      -                cagePointer = 0;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -                for (x = 1; x < imageWidth; x++) {
      +        let len = input.r.length;
       
      -                    rowPosX = rowPos + (x * 4);
      +        let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, level, lineOut} = requirements;
       
      -                    for (q = 0; q < 3; q++) {
      +        if (null == opacity) opacity = 1;
      +        if (null == includeRed) includeRed = false;
      +        if (null == includeGreen) includeGreen = false;
      +        if (null == includeBlue) includeBlue = false;
      +        if (null == includeAlpha) includeAlpha = false;
      +        if (null == level) level = 0;
       
      -                        avgQ = avg[q];
      -                        cageQ = cage[q];
      -                        avgQ -= cageQ[cagePointer];
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                        dataPointer = rowPosX + q;
      -                        pos = dataPointer + hLead;
      -                        pos = checkBounds(pos, len);
      -                        val = tempDataFrom[pos] / cageLen;
      +        for (let i = 0; i < len; i++) {
       
      -                        avgQ += val;
      -                        tempDataTo[dataPointer] = avgQ;
      -                        avg[q] = avgQ;
      -                        cageQ[cagePointer] = val;
      -                    }
      +            outR[i] = (includeRed) ? level : inR[i];
      +            outG[i] = (includeGreen) ? level : inG[i];
      +            outB[i] = (includeBlue) ? level : inB[i];
      +            outA[i] = (includeAlpha) ? level : inA[i];
      +        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      step-channels - Takes three divisor values - “red”, “green”, “blue”. For each pixel, its color channel values are divided by the corresponding color divisor, floored 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’. The output is a form of posterization.

      - cagePointer++; - if (cagePointer === cageLen) cagePointer = 0; - } - }; +
      + +
          'step-channels': function (requirements) {
       
      -            tempDataTo = data.slice();
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -            for (i = 0; i < passes; i++) {
      +        let len = input.r.length,
      +            floor = Math.floor;
       
      -                processPass();
      -                
      -                if (shrink) {
      +        let {opacity, red, green, blue, lineOut} = requirements;
       
      -                    radius = Math.ceil(radius * 0.3);
      -                    radius = (radius < 1) ? 1 : radius;
      -                }
      -            }
      +        if (null == opacity) opacity = 1;
      +        if (null == red) red = 1;
      +        if (null == green) green = 1;
      +        if (null == blue) blue = 1;
       
      -            for (i = 0, iz = cache.length; i < iz; i++) {
      +        if (red == null) red = 1;
      +        if (green == null) green = 1;
      +        if (blue == null) blue = 1;
       
      -                index = cache[i];
      -                data[index] = tempDataTo[index];
      -                
      -                index++;
      -                data[index] = tempDataTo[index];
      -                
      -                index++;
      -                data[index] = tempDataTo[index];
      -                
      -                if (alpha) {
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -                    index++;
      -                    data[index] = tempDataTo[index];
      -                }
      -            }
      +        for (let i = 0; i < len; i++) {
      +            outR[i] = floor(inR[i] / red) * red;
      +            outG[i] = floor(inG[i] / green) * green;
      +            outB[i] = floor(inB[i] / blue) * blue;
      +            outA[i] = inA[i];
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      matrix - apply a 3x3 matrix transform to each of the image’s pixels

      +

      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.

      -
          matrix: function () {
      -
      -        let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos,
      -            len = data.length,
      -            alpha = filter.includeAlpha || false,
      -            offset = [],
      -            weights = filter.weights || [0, 0, 0, 0, 1, 0, 0, 0, 0],
      -            tempCache = [],
      -            cursor = 0;
      -
      -        offset[0] = -iWidth - 4;
      -        offset[1] = -iWidth;
      -        offset[2] = -iWidth + 4;
      -        offset[3] = -4;
      -        offset[4] = 0;
      -        offset[5] = 4;
      -        offset[6] = iWidth - 4;
      -        offset[7] = iWidth;
      -        offset[8] = iWidth + 4;
      -
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      -
      -            homePos = cache[i];
      -            sumR = sumG = sumB = sumA = 0;
      -            
      -            for (j = 0, jz = offset.length; j < jz; j++) {
      -
      -                pos = homePos + offset[j];
      -                
      -                if (pos >= 0 && pos < len) {
      -
      -                    weight = weights[j];
      -                    sumR += data[pos] * weight;
      -                    
      -                    pos++;
      -                    sumG += data[pos] * weight;
      -                    
      -                    pos++;
      -                    sumB += data[pos] * weight;
      -                    
      -                    if (alpha) {
      -
      -                        pos++;
      -                        sumA += data[pos] * weight;
      -                    }
      -                }
      -            }
      -
      -            tempCache[cursor] = sumR;
      -            cursor++;
      +            
          'threshold': function (requirements) {
       
      -            tempCache[cursor] = sumG;
      -            cursor++;
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -            tempCache[cursor] = sumB;
      -            cursor++;
      +        let len = input.r.length;
       
      -            if (alpha) {
      +        let {opacity, low, high, level, lineOut} = requirements;
       
      -                tempCache[cursor] = sumA;
      -                cursor++;
      -            }
      -        }
      +        if (null == opacity) opacity = 1;
      +        if (null == low) low = [0,0,0];
      +        if (null == high) high = [255,255,255];
      +        if (null == level) level = 128;
       
      -        cursor = 0;
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let [lowR, lowG, lowB] = low;
      +        let [highR, highG, highB] = high;
       
      -            homePos = cache[i];
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +        for (let i = 0; i < len; i++) {
       
      -            homePos++;
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +            let gray = Math.floor((0.2126 * inR[i]) + (0.7152 * inG[i]) + (0.0722 * inB[i]));
       
      -            homePos++;
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +            if (gray < level) {
       
      -            if (alpha) {
      +                outR[i] = lowR;
      +                outG[i] = lowG;
      +                outB[i] = lowB;
      +            }
      +            else {
       
      -                homePos++;
      -                data[homePos] = tempCache[cursor];
      -                cursor++;
      +                outR[i] = highR;
      +                outG[i] = highG;
      +                outB[i] = highB;
                   }
      +            outA[i] = inA[i];
               }
      +
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
    • -
    • +
    • - +
      -

      matrix5 - apply a 5x5 matrix transform to each of the image’s pixels

      +

      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.

      -
      -    matrix5: function () {
      -
      -        let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos,
      -            len = data.length,
      -            alpha = filter.includeAlpha || false,
      -            offset = [],
      -            weights = filter.weights || [0, 0, 0, 0, 0,  0, 0, 0, 0, 0,  0, 0, 1, 0, 0,  0, 0, 0, 0, 0,  0, 0, 0, 0, 0],
      -            tempCache = [],
      -            iWidth2 = iWidth * 2,
      -            cursor = 0;
      -
      -        offset[0] = -iWidth2 - 8;
      -        offset[1] = -iWidth2 - 4;
      -        offset[2] = -iWidth2;
      -        offset[3] = -iWidth2 + 4;
      -        offset[4] = -iWidth2 + 8;
      -        offset[5] = -iWidth - 8;
      -        offset[6] = -iWidth - 4;
      -        offset[7] = -iWidth;
      -        offset[8] = -iWidth + 4;
      -        offset[9] = -iWidth + 8;
      -        offset[10] = -8;
      -        offset[11] = -4;
      -        offset[12] = 0;
      -        offset[13] = 4;
      -        offset[14] = 8;
      -        offset[15] = iWidth - 8;
      -        offset[16] = iWidth - 4;
      -        offset[17] = iWidth;
      -        offset[18] = iWidth + 4;
      -        offset[19] = iWidth + 8;
      -        offset[20] = iWidth2 - 8;
      -        offset[21] = iWidth2 - 4;
      -        offset[22] = iWidth2;
      -        offset[23] = iWidth2 + 4;
      -        offset[24] = iWidth2 + 8;
      -
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      -
      -            homePos = cache[i];
      -            sumR = sumG = sumB = sumA = 0;
      -            
      -            for (j = 0, jz = offset.length; j < jz; j++) {
      -
      -                pos = homePos + offset[j];
      -                
      -                if (pos >= 0 && pos < len) {
      -
      -                    weight = weights[j];
      -                    sumR += data[pos] * weight;
      -
      -                    pos++;
      -                    sumG += data[pos] * weight;
      -
      -                    pos++;
      -                    sumB += data[pos] * weight;
      +            
          'tint-channels': function (requirements) {
       
      -                    if (alpha) {
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -                        pos++;
      -                        sumA += data[pos] * weight;
      -                    }
      -                }
      -            }
      +        let len = input.r.length;
       
      -            tempCache[cursor] = sumR;
      -            cursor++;
      +        let {opacity, redInRed, redInGreen, redInBlue, greenInRed, greenInGreen, greenInBlue, blueInRed, blueInGreen, blueInBlue, lineOut} = requirements;
       
      -            tempCache[cursor] = sumG;
      -            cursor++;
      +        if (null == opacity) opacity = 1;
      +        if (null == redInRed) redInRed = 1;
      +        if (null == redInGreen) redInGreen = 0;
      +        if (null == redInBlue) redInBlue = 0;
      +        if (null == greenInRed) greenInRed = 0;
      +        if (null == greenInGreen) greenInGreen = 1;
      +        if (null == greenInBlue) greenInBlue = 0;
      +        if (null == blueInRed) blueInRed = 0;
      +        if (null == blueInGreen) blueInGreen = 0;
      +        if (null == blueInBlue) blueInBlue = 1;
       
      -            tempCache[cursor] = sumB;
      -            cursor++;
      +        const {r:inR, g:inG, b:inB, a:inA} = input;
      +        const {r:outR, g:outG, b:outB, a:outA} = output;
       
      -            if (alpha) {
      +        for (let i = 0; i < len; i++) {
       
      -                tempCache[cursor] = sumA;
      -                cursor++;
      -            }
      +            let r = inR[i],
      +                g = inG[i],
      +                b = inB[i];
      +
      +            outR[i] = Math.floor((r * redInRed) + (g * greenInRed) + (b * blueInRed));
      +            outG[i] = Math.floor((r * redInGreen) + (g * greenInGreen) + (b * blueInGreen));
      +            outB[i] = Math.floor((r * redInBlue) + (g * greenInBlue) + (b * blueInBlue));
      +            outA[i] = inA[i];
               }
       
      -        cursor = 0;
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
      +    },
      + +
    • + + +
    • +
      + +
      + +
      +

      user-defined-legacy - Previous to version 8.4, filters could be defined with an argument which passed a function string to the filter 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.

      + +
      + +
      +    'user-defined-legacy': function (requirements) {
       
      -        for (i = 0, iz = cache.length; i < iz; i++) {
      +        let [input, output] = getInputAndOutputChannels(requirements);
       
      -            homePos = cache[i];
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +        let {opacity, lineOut} = requirements;
       
      -            homePos++;
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +        if (null == opacity) opacity = 1;
       
      -            homePos++;
      -            data[homePos] = tempCache[cursor];
      -            cursor++;
      +        copyOver(input, output);
       
      -            if (alpha) {
      -                
      -                homePos++;
      -                data[homePos] = tempCache[cursor];
      -                cursor++;
      -            }
      -        }
      +        if (lineOut) processResults(output, work, 1 - opacity);
      +        else processResults(work, output, opacity);
           },
       };
      diff --git a/min/scrawl.js b/min/scrawl.js index 4c67bc010..daa21c9a1 100644 --- a/min/scrawl.js +++ b/min/scrawl.js @@ -1 +1 @@ -const t={},e={},i={},s=[],n={},r=[],o={},a={},h=[],c={},l={},u={},d={},f={},p={},m=[],g={},y={},b={},S=[],P={},k={},v=Math.PI/180,x=new Set(["all","background","backgroundAttachment","backgroundBlendMode","backgroundClip","backgroundColor","backgroundOrigin","backgroundPosition","backgroundRepeat","border","borderBottom","borderBottomColor","borderBottomStyle","borderBottomWidth","borderCollapse","borderColor","borderLeft","borderLeftColor","borderLeftStyle","borderLeftWidth","borderRight","borderRightColor","borderRightStyle","borderRightWidth","borderSpacing","borderStyle","borderTop","borderTopColor","borderTopStyle","borderTopWidth","borderWidth","clear","color","columns","content","counterIncrement","counterReset","cursor","direction","display","emptyCells","float","font","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontSynthesis","fontVariant","fontVariantAlternates","fontVariantCaps","fontVariantEastAsian","fontVariantLigatures","fontVariantNumeric","fontVariantPosition","fontWeight","grid","gridArea","gridAutoColumns","gridAutoFlow","gridAutoPosition","gridAutoRows","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","gridTemplate","gridTemplateAreas","gridTemplateRows","gridTemplateColumns","imageResolution","imeMode","inherit","inlineSize","isolation","letterSpacing","lineBreak","lineHeight","listStyle","listStyleImage","listStylePosition","listStyleType","margin","marginBlockStart","marginBlockEnd","marginInlineStart","marginInlineEnd","marginBottom","marginLeft","marginRight","marginTop","marks","mask","maskType","maxWidth","maxHeight","maxBlockSize","maxInlineSize","maxZoom","minWidth","minHeight","minBlockSize","minInlineSize","minZoom","mixBlendMode","objectFit","objectPosition","offsetBlockStart","offsetBlockEnd","offsetInlineStart","offsetInlineEnd","orphans","overflow","overflowWrap","overflowX","overflowY","pad","padding","paddingBlockStart","paddingBlockEnd","paddingInlineStart","paddingInlineEnd","paddingBottom","paddingLeft","paddingRight","paddingTop","pageBreakAfter","pageBreakBefore","pageBreakInside","pointerEvents","position","prefix","quotes","rubyAlign","rubyMerge","rubyPosition","scrollBehavior","scrollSnapCoordinate","scrollSnapDestination","scrollSnapPointsX","scrollSnapPointsY","scrollSnapType","scrollSnapTypeX","scrollSnapTypeY","shapeImageThreshold","shapeMargin","shapeOutside","tableLayout","textAlign","textDecoration","textIndent","textOrientation","textOverflow","textRendering","textShadow","textTransform","textUnderlinePosition","unicodeRange","unset","verticalAlign","widows","willChange","wordBreak","wordSpacing","wordWrap","zIndex"]),C=new Set(["alignContent","alignItems","alignSelf","animation","animationDelay","animationDirection","animationDuration","animationFillMode","animationIterationCount","animationName","animationPlayState","animationTimingFunction","backfaceVisibility","backgroundImage","backgroundSize","borderBottomLeftRadius","borderBottomRightRadius","borderImage","borderImageOutset","borderImageRepeat","borderImageSlice","borderImageSource","borderImageWidth","borderRadius","borderTopLeftRadius","borderTopRightRadius","boxDecorationBreak","boxShadow","boxSizing","columnCount","columnFill","columnGap","columnRule","columnRuleColor","columnRuleStyle","columnRuleWidth","columnSpan","columnWidth","filter","flex","flexBasis","flexDirection","flexFlow","flexGrow","flexShrink","flexWrap","fontFeatureSettings","fontKerning","fontLanguageOverride","hyphens","imageRendering","imageOrientation","initial","justifyContent","linearGradient","opacity","order","orientation","outline","outlineColor","outlineOffset","outlineStyle","outlineWidth","resize","tabSize","textAlignLast","textCombineUpright","textDecorationColor","textDecorationLine","textDecorationStyle","touchAction","transformStyle","transition","transitionDelay","transitionDuration","transitionProperty","transitionTimingFunction","unicodeBidi","whiteSpace","writingMode"]);var A=Object.freeze({__proto__:null,version:"8.3.4",anchor:{},anchornames:[],animation:t,animationnames:[],asset:n,assetnames:r,animationtickers:e,animationtickersnames:[],artefact:i,artefactnames:s,canvas:o,canvasnames:[],cell:a,cellnames:h,element:{},elementnames:[],entity:c,entitynames:[],filter:l,filternames:[],fontattribute:{},fontattributenames:[],force:f,forcenames:[],group:u,groupnames:[],palette:{},palettenames:[],particle:d,particlenames:[],spring:p,springnames:m,stack:{},stacknames:[],styles:b,stylesnames:S,tween:y,tweennames:[],unstackedelement:P,unstackedelementnames:[],world:g,worldnames:[],constructors:k,radian:v,css:x,xcss:C});let w=!1,D=!0,O=[],E=[];const T=function(){D=!0},R=function(){let e=[];D&&function(){if(D){D=!1;let e,i,s=Math.floor,n=[];E.forEach(r=>{e=t[r],e&&(i=s(e.order)||0,n[i]||(n[i]=[]),n[i].push(e))}),O=n.reduce((t,e)=>t.concat(e),[])}}(),O.forEach(t=>{t&&t.fn&&e.push(t.fn())}),Promise.all(e).then(()=>{w&&window.requestAnimationFrame(()=>R())}).catch(t=>console.log("animationLoop error: ",t))},F=function(){w=!0,R()},H=(t,e)=>{if(!Q(e))throw new Error(`core/utilities addStrings() error - no delta argument supplied ${t}, ${e}`);if(null!=e){let i=!(!t.substring&&!e.substring);return W(t)?t+=W(e)?e:parseFloat(e):t=parseFloat(t)+(W(e)?e:parseFloat(e)),i?t+"%":t}return t},L=t=>{let e,i,s;if(!Q(t))throw new Error("core/utilities convertTime() error - no argument supplied");if(W(t))return["ms",t];if(!t.substring)throw new Error("core/utilities convertTime() error - invalid argument: "+t);if(e=t.match(/^\d+\.?\d*(\D*)/),i=e[1].toLowerCase?e[1].toLowerCase():"ms",s=parseFloat(t),!W(s))throw new Error("core/base error - convertTime() argument converts to NaN: "+t);switch(i){case"s":s*=1e3;break;case"%":break;default:i="ms"}return[i,s]},j=t=>t.toFixed?0==t?t:isNaN(t)?0:t<-1e-6||t>1e-6?t:0:t,B=()=>{},$=function(){return this},M=()=>Promise.resolve(!0),z=()=>performance.now().toString(36)+Math.random().toString(36).substr(2),I=t=>"boolean"==typeof t,X=t=>"[object HTMLCanvasElement]"===Object.prototype.toString.call(t),Y=t=>!!(t&&t.querySelector&&t.dispatchEvent),N=t=>"function"==typeof t,W=t=>!(void 0===t||!t.toFixed||Number.isNaN(t)),V=t=>"[object Object]"===Object.prototype.toString.call(t),q=t=>!(!t||!t.type||"Quaternion"!==t.type),G=(t,e)=>{if(!V(t)||!V(e))throw new Error(`core/utilities mergeOver() error - insufficient arguments supplied ${t}, ${e}`);for(let i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},U=(t,e)=>{if(!V(t)||!V(e))throw new Error(`core/utilities mergeDiscard() error - insufficient arguments supplied ${t}, ${e}`);return Object.entries(e).forEach(([i,s])=>{null===s?delete t[i]:t[i]=e[i]}),t},Z=(t,e)=>{if(!K(t,e))throw new Error(`core/utilities pushUnique() error - insufficient arguments supplied ${t}, ${e}`);if(!Array.isArray(t))throw new Error("core/utilities pushUnique() error - argument not an array "+t);return Array.isArray(e)?e.forEach(e=>Z(t,e)):t.indexOf(e)<0&&t.push(e),t},_=(t,e)=>{if(!K(t,e))throw new Error(`core/utilities removeItem() error - insufficient arguments supplied ${t}, ${e}`);if(!Array.isArray(t))throw new Error("core/utilities removeItem() error - argument not an array "+t);let i=t.indexOf(e);return i>=0&&t.splice(i,1),t},Q=t=>void 0!==t,K=(...t)=>t.every(t=>void 0!==t),J=(...t)=>t.find(t=>void 0!==t),tt=(...t)=>!!t.find(t=>void 0!==t),et=function(t,e,i={}){if("function"==typeof window.IntersectionObserver&&t&&t.run){let s=new IntersectionObserver((e,i)=>{e.forEach(e=>{e.isIntersecting?!t.isRunning()&&t.run():e.isIntersecting||t.isRunning()&&t.halt()})},i);return e&&e.domElement&&s.observe(e.domElement),function(){s.disconnect()}}return B},it=function(t,e,i){if(!N(e))throw new Error(`core/document addListener() error - no function supplied: ${t}, ${i}`);return nt(t,e,i,"removeEventListener"),nt(t,e,i,"addEventListener"),function(){st(t,e,i)}},st=function(t,e,i){if(!N(e))throw new Error(`core/document removeListener() error - no function supplied: ${t}, ${i}`);nt(t,e,i,"removeEventListener")},nt=function(t,e,i,s){let n,r=[].concat(t);n=i.substring?document.body.querySelectorAll(i):Array.isArray(i)?i:[i],navigator.pointerEnabled||navigator.msPointerEnabled?ot(r,e,n,s):rt(r,e,n,s)},rt=function(t,e,i,s){t.forEach(t=>{i.forEach(i=>{if(!(Y(i)||i.document||i.characterSet))throw new Error(`core/document actionMouseListener() error - bad target: ${t}, ${i}`);switch(t){case"move":i[s]("mousemove",e,!1),i[s]("touchmove",e,!1),i[s]("touchfollow",e,!1);break;case"up":i[s]("mouseup",e,!1),i[s]("touchend",e,!1);break;case"down":i[s]("mousedown",e,!1),i[s]("touchstart",e,!1);break;case"leave":i[s]("mouseleave",e,!1),i[s]("touchleave",e,!1);break;case"enter":i[s]("mouseenter",e,!1),i[s]("touchenter",e,!1)}})})},ot=function(t,e,i,s){t.forEach(t=>{i.forEach(i=>{if(!(Y(i)||i.document||i.characterSet))throw new Error(`core/document actionPointerListener() error - bad target: ${t}, ${i}`);switch(t){case"move":i[s]("pointermove",e,!1);break;case"up":i[s]("pointerup",e,!1);break;case"down":i[s]("pointerdown",e,!1);break;case"leave":i[s]("pointerleave",e,!1);break;case"enter":i[s]("pointerenter",e,!1)}})})},at=function(t,e,i){if(!N(e))throw new Error(`core/document addNativeListener() error - no function supplied: ${t}, ${i}`);return ct(t,e,i,"removeEventListener"),ct(t,e,i,"addEventListener"),function(){ht(t,e,i)}},ht=function(t,e,i){if(!N(e))throw new Error(`core/document removeNativeListener() error - no function supplied: ${t}, ${i}`);ct(t,e,i,"removeEventListener")},ct=function(t,e,i,s){let n,r=[].concat(t);n=i.substring?document.body.querySelectorAll(i):Array.isArray(i)?i:[i],r.forEach(t=>{n.forEach(i=>{if(!(Y(i)||i.document||i.characterSet))throw new Error(`core/document actionNativeListener() error - bad target: ${t}, ${i}`);i[s](t,e,!1)})})},lt=window.matchMedia("(prefers-reduced-motion: reduce)");let ut=lt.matches,dt=B,ft=B;const pt=()=>{ut?dt():ft()};lt.addEventListener("change",()=>{ut=lt.matches,pt()});const mt=window.matchMedia("(prefers-color-scheme: dark)");let gt=mt.matches,yt=B,bt=B;const St=()=>{gt?yt():bt()};mt.addEventListener("change",()=>{gt=mt.matches,St()});function Pt(t={}){t.defs={},t.getters={},t.setters={},t.deltaSetters={},t.get=function(t){if(Q(t)){let e=this.getters[t];if(e)return e.call(this);{let e=this.defs[t];if(void 0!==e){let i=this[t];return void 0!==i?i:e}}}return null},t.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e,i=this.deltaSetters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=H(this[t],n)))},this)}return this};return t.defs=G(t.defs,{name:""}),t.packetExclusions=[],t.packetExclusionsByRegex=[],t.packetCoordinates=[],t.packetObjects=[],t.packetFunctions=[],t.saveAsPacket=function(t={}){I(t)&&t&&(t={includeDefaults:!0});let e=this.defs,i=Object.keys(e),s=this.packetExclusions,n=this.packetExclusionsByRegex,r=this.packetCoordinates,o=this.packetObjects,a=this.packetFunctions,h=t.includeDefaults||!1,c={};return h&&!Array.isArray(h)?h=Object.keys(e):h||(h=[]),Object.entries(this).forEach(([t,e])=>{let l,u=!0;if(i.indexOf(t)<0&&(u=!1),u&&s.indexOf(t)>=0&&(u=!1),u&&(l=n.some(e=>new RegExp(e).test(t)),l&&(u=!1)),u)if(a.indexOf(t)>=0){if(Q(e)&&null!==e){let i=this.stringifyFunction(e);i&&i.length&&(c[t]=i)}}else o.indexOf(t)>=0&&this[t]&&this[t].name?c[t]=this[t].name:r.indexOf(t)>=0?(h.indexOf(t)>=0||e[0]||e[1])&&(c[t]=e):(l=this.processPacketOut(t,e,h),l&&(c[t]=e))},this),c=this.finalizePacketOut(c,t),JSON.stringify([this.name,this.type,this.lib,c])},t.stringifyFunction=function(t){let e=t.toString().match(/\(([\s\S]*?)\)[\s\S]*?\{([\s\S]*)\}/),i=e[1],s=e[2];return!!K(i,s)&&`${i}~~~${s}`},t.processPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){return t},t.importPacket=function(t){let e=this;const i=function(t){return new Promise((i,s)=>{let n;t.substring||s(new Error("Packet url supplied for import is not a string")),"["===t[0]?(n=e.actionPacket(t),n&&n.lib?i(n):s(n)):t.indexOf('"name":')>=0?s(new Error("Bad packet supplied for import")):fetch(t).then(t=>{if(!t.ok)throw new Error(`Packet import from server failed - ${t.status}: ${t.statusText} - ${t.url}`);return t.text()}).then(t=>{if(n=e.actionPacket(t),!n||!n.lib)throw n;i(n)}).catch(t=>s(t))})};if(Array.isArray(t)){let e=[];return t.forEach(t=>e.push(i(t))),new Promise((t,i)=>{Promise.all(e).then(e=>t(e)).catch(t=>i(t))})}if(t.substring)return i(t);Promise.reject(new Error("Argument supplied for packet import is not a string or array of strings"))},t.actionPacketExclusions=["Image","Sprite","Video","Canvas","Stack"],t.actionPacket=function(t){try{if(t&&t.substring){if("["===t[0]){let e,i,s,n;try{[e,i,s,n]=JSON.parse(t)}catch(t){throw new Error("Failed to process packet due to JSON parsing error - "+t.message)}if(K(e,i,s,n)){if(this.actionPacketExclusions.indexOf(i)>=0)throw new Error("Failed to process packet - Stacks, Canvases and visual assets are excluded from the packet system");let t=A[s][e];if(t)t.set(n);else{if(n.outerHTML&&n.host){let t=document.querySelector("#"+n.host);if(t){let i=document.createElement("div");i.innerHTML=n.outerHTML;let s=i.firstElementChild;s&&(s.id=e,t.appendChild(s),n.domElement=s)}}if(t=new k[i](n),!t)throw new Error("Failed to create Scrawl-canvas object from supplied packet")}if(t.packetFunctions.forEach(e=>this.actionPacketFunctions(t,e)),n.anchor&&t.anchor&&t.anchor.packetFunctions.forEach(e=>{t.anchor[e]=n.anchor[e],this.actionPacketFunctions(t.anchor,e),t.anchor.build()}),n.glyphStyles&&t.glyphStyles&&n.glyphStyles.forEach((e,i)=>{V(e)&&t.setGlyphStyles(e,i)}),t)return t;throw new Error("Failed to process supplied packet")}throw new Error("Failed to process packet - JSON string holds incomplete data")}throw new Error("Failed to process packet - JSON string does not represent an array")}throw new Error("Failed to process packet - not a JSON string")}catch(t){return console.log(t),t}},t.actionPacketFunctions=function(t,e){let i=t[e];if(Q(i)&&null!==i&&i.substring)if("~~~"===i)t[e]=B;else{let s,n,r;[s,n]=i.split("~~~"),s=s.split(","),s=s.map(t=>t.trim()),n.indexOf("[native code]")<0?(r=new Function(...s,n),t[e]=r.bind(t)):t[e]=B}},t.clone=function(t={}){let e,i,s=this.name;this.name=t.name||"",t.useNewTicker?(i=this.ticker,this.ticker=null,e=this.saveAsPacket(),this.ticker=i):e=this.saveAsPacket(),this.name=s;let n=this.actionPacket(e);return this.packetFunctions.forEach(t=>{this[t]&&(n[t]=this[t])}),n=this.postCloneAction(n,t),n.set(t),n},t.postCloneAction=function(t,e){return t},t.kill=function(){return this.deregister()},t.makeName=function(t){return t&&t.substring&&A[this.lib+"names"].indexOf(t)<0?this.name=t:this.name=z(),this},t.register=function(){if(!Q(this.name))throw new Error("core/base error - register() name not set: "+this);let t=A[this.lib+"names"],e=A[this.lib];return this.isArtefact&&(Z(s,this.name),i[this.name]=this),this.isAsset&&(Z(r,this.name),n[this.name]=this),Z(t,this.name),e[this.name]=this,this},t.deregister=function(){if(!Q(this.name))throw new Error("core/base error - deregister() name not set: "+this);let t=A[this.lib+"names"],e=A[this.lib];return this.isArtefact&&(_(s,this.name),delete i[this.name]),this.isAsset&&(_(r,this.name),delete n[this.name]),_(t,this.name),delete e[this.name],this},t}const Animation=function(t={}){return this.makeName(t.name),this.order=Q(t.order)?t.order:this.defs.order,this.fn=t.fn||M,this.onRun=t.onRun||B,this.onHalt=t.onHalt||B,this.onKill=t.onKill||B,this.register(),t.delay||this.run(),this};let kt=Animation.prototype=Object.create(Object.prototype);kt.type="Animation",kt.lib="animation",kt.isArtefact=!1,kt.isAsset=!1,kt=Pt(kt);kt.defs=G(kt.defs,{order:1,fn:null,onRun:null,onHalt:null,onKill:null}),kt.stringifyFunction=B,kt.processPacketOut=B,kt.finalizePacketOut=B,kt.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},kt.clone=$,kt.run=function(){return this.onRun(),Z(E,this.name),T(),this},kt.isRunning=function(){return E.indexOf(this.name)>=0},kt.halt=function(){return this.onHalt(),_(E,this.name),T(),this},kt.kill=function(){return this.onKill(),_(E,this.name),T(),this.deregister(),!0};const vt=function(t){return new Animation(t)};k.Animation=Animation;const Ct=[];let At=!1,wt=!1;const Dt={x:0,y:0,scrollX:0,scrollY:0,w:0,h:0,type:"mouse"},Ot=function(t){let e=document.documentElement.clientWidth,i=document.documentElement.clientHeight;Dt.w===e&&Dt.h===i||(Dt.w=e,Dt.h=i,wt=!0)},Et=function(t){let e=window.pageXOffset,i=window.pageYOffset;Dt.scrollX===e&&Dt.scrollY===i||(Dt.x+=e-Dt.scrollX,Dt.y+=i-Dt.scrollY,Dt.scrollX=e,Dt.scrollY=i,wt=!0)},Tt=function(t){let e=Math.round(t.pageX),i=Math.round(t.pageY);Dt.x===e&&Dt.y===i||(Dt.type=navigator.pointerEnabled?"pointer":"mouse",Dt.x=e,Dt.y=i,wt=!0)},Rt=function(t){if(("touchstart"===t.type||"touchmove"===t.type)&&t.touches&&t.touches[0]){let e=t.touches[0],i=Math.round(e.pageX),s=Math.round(e.pageY);Dt.x===i&&Dt.y===s||(Dt.type="touch",Dt.x=i,Dt.y=s,Ft())}},Ft=function(){Ct.forEach(t=>Ht(t))},Ht=function(t){let e=i[t];if(!Q(e))throw new Error("core/userInteraction updateUiSubscribedElement() error - artefact does not exist: "+t);let s=e.domElement;if(!Y(s))throw new Error("core/userInteraction updateUiSubscribedElement() error - DOM element missing: "+t);let n=s.getBoundingClientRect(),r=Math.round(n.left+window.pageXOffset),o=Math.round(n.top+window.pageYOffset);e.here||(e.here={});let a=e.here;if(a.w=Math.round(n.width),a.h=Math.round(n.height),a.type=Dt.type,e.localMouseListener?(a.localListener=!0,a.active=!1,a.normX=!!a.originalWidth&&a.x/a.originalWidth,a.normY=!!a.originalHeight&&a.y/a.originalHeight,a.offsetX=r,a.offsetY=o,a.x>e.activePadding&&a.x0+e.activePadding&&a.y1||a.normY<0||a.normY>1)&&(a.active=!1)),"Canvas"===e.type&&e.updateBaseHere(a,e.fit),e.checkForResize&&!e.dirtyDimensions&&!e.dirtyDomDimensions){let[t,i]=e.currentDimensions;if("Canvas"===e.type){e.computedStyles||(e.computedStyles=window.getComputedStyle(e.domElement));let s=e.computedStyles,n=Math.floor(a.w-parseFloat(s.borderLeftWidth)-parseFloat(s.borderRightWidth)-parseFloat(s.paddingLeft)-parseFloat(s.paddingRight)),r=Math.floor(a.h-parseFloat(s.borderTopWidth)-parseFloat(s.borderBottomWidth)-parseFloat(s.paddingTop)-parseFloat(s.paddingBottom));t===n&&i===r||e.set({dimensions:[n,r]})}else t===a.w&&i===a.h||e.set({dimensions:[a.w,a.h]})}},Lt=vt({name:"coreListenersTracker",order:0,delay:!0,fn:function(){return new Promise(t=>{Ct.length||t(!1),At&&wt&&(wt=!1,Ft()),t(!0)})}}),jt=function(){Bt("removeEventListener"),Bt("addEventListener"),At=!0,wt=!0,Lt.run()},Bt=function(t){navigator.pointerEnabled||navigator.msPointerEnabled?(window[t]("pointermove",Tt,!1),window[t]("pointerup",Tt,!1),window[t]("pointerdown",Tt,!1),window[t]("pointerleave",Tt,!1),window[t]("pointerenter",Tt,!1)):(window[t]("mousemove",Tt,!1),window[t]("mouseup",Tt,!1),window[t]("mousedown",Tt,!1),window[t]("mouseleave",Tt,!1),window[t]("mouseenter",Tt,!1),window[t]("touchmove",Rt,!1),window[t]("touchstart",Rt,!1),window[t]("touchend",Rt,!1),window[t]("touchcancel",Rt,!1)),window[t]("scroll",Et,!1),window[t]("resize",Ot,!1)},$t=function(){Ot(),wt=!0},Mt=URL.createObjectURL(new Blob(["\nif (!Uint8Array.prototype.slice) {\n Object.defineProperty(Uint8Array.prototype, 'slice', {\n value: function (begin, end) {\n return new Uint8Array(Array.prototype.slice.call(this, begin, end));\n }\n });\n}\n\nlet packet;\nlet image;\nlet iWidth;\nlet data;\nlet cache;\nlet tiles;\nlet localX;\nlet localY;\nlet localWidth;\nlet localHeight;\nlet filters;\nlet filter;\nlet action;\n\nonmessage = function (e) {\n\n let i, iz;\n\n packet = e.data;\n image = packet.image;\n iWidth = image.width * 4;\n data = image.data;\n filters = packet.filters;\n\n getCache();\n getLocal();\n\n for (i = 0, iz = filters.length; i < iz; i++) {\n\n filter = filters[i];\n\n if (filter.method === 'userDefined' && filter.userDefined) actions.userDefined = new Function(filter.userDefined);\n\n action = actions[filter.method];\n\n if (action) action();\n }\n\n postMessage(packet);\n};\n\nonerror = function (e) {\n\n console.log('error' + e.message);\n postMessage(packet);\n};\n\nconst getCache = function () {\n\n let i, iz;\n\n if (Array.isArray(cache)) cache.length = 0;\n else cache = [];\n\n for (i = 0, iz = data.length; i < iz; i += 4) {\n\n if (data[i + 3]) cache.push(i);\n }\n};\n\nconst getLocal = function () {\n\n let i, iz, w, h, minX, minY, maxX, maxY, x, y, val,\n floor = Math.floor;\n\n w = image.width;\n h = image.height;\n minX = w;\n minY = h;\n maxX = 0;\n maxY = 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n val = cache[i] / 4;\n y = floor(val / w);\n x = val % w;\n minX = (x < minX) ? x : minX;\n minY = (y < minY) ? y : minY;\n maxX = (x > maxX) ? x : maxX;\n maxY = (y > maxY) ? y : maxY;\n }\n\n localX = minX;\n localY = minY;\n localWidth = maxX - minX;\n localHeight = maxY - minY;\n};\n\nconst getTiles = function () {\n\n let i, iz, j, jz, x, xz, y, yz, startX, startY, pos, \n hold = [],\n tileWidth = filter.tileWidth || 1,\n tileHeight = filter.tileHeight || 1,\n offsetX = filter.offsetX,\n offsetY = filter.offsetY,\n w = image.width,\n h = image.height;\n\n if (Array.isArray(tiles)) tiles.length = 0;\n else tiles = [];\n\n offsetX = (offsetX >= tileWidth) ? tileWidth - 1 : offsetX;\n offsetY = (offsetY >= tileHeight) ? tileHeight - 1 : offsetY;\n\n startX = (offsetX) ? offsetX - tileWidth : 0;\n startY = (offsetY) ? offsetY - tileHeight : 0;\n\n for (j = startY, jz = h + tileHeight; j < jz; j += tileHeight) {\n\n for (i = startX, iz = w + tileWidth; i < iz; i += tileWidth) {\n\n hold.length = 0;\n \n for (y = j, yz = j + tileHeight; y < yz; y++) {\n\n if (y >= 0 && y < h) {\n\n for (x = i, xz = i + tileWidth; x < xz; x++) {\n\n if (x >= 0 && x < w) {\n\n pos = (y * iWidth) + (x * 4);\n\n if (data[pos + 3]) hold.push(pos);\n }\n }\n }\n }\n if (hold.length) tiles.push([].concat(hold));\n }\n }\n};\n\nconst average = function (c) {\n\n let a = 0,\n k, kz,\n l = c.length;\n\n if (l) {\n\n for (k = 0, kz = l; k < kz; k++) {\n\n a +=c[k];\n }\n return a / l;\n }\n return 0;\n};\n\nconst checkBounds = function (p) {\n\n let len = data.length;\n\n if (p < 0) p += len;\n if (p >= len) p -= len;\n return p;\n};\n\nconst actions = {\n\n\n userDefined: function () {},\n\n grayscale: function () {\n\n let i, iz, pos, gray;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]);\n data[pos] = data[pos + 1] = data[pos + 2] = gray;\n }\n },\n\n sepia: function () {\n\n let i, iz, pos, r, g, b;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n r = data[pos];\n g = data[pos + 1];\n b = data[pos + 2];\n \n data[pos] = (r * 0.393) + (g * 0.769) + (b * 0.189);\n data[pos + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168);\n data[pos + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131);\n }\n },\n\n invert: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = 255 - data[pos];\n \n pos++;\n data[pos] = 255 - data[pos];\n \n pos++;\n data[pos] = 255 - data[pos];\n }\n },\n\n red: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos + 1] = 0;\n data[pos + 2] = 0;\n }\n },\n\n green: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = 0;\n data[pos + 2] = 0;\n }\n },\n\n blue: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = 0;\n data[pos + 1] = 0;\n }\n },\n\n notred: function() {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = 0;\n }\n },\n\n notgreen: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos + 1] = 0;\n }\n },\n\n notblue: function () {\n\n let i, iz, pos;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos + 2] = 0;\n }\n },\n\n cyan: function () {\n\n let i, iz, pos, gray;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n gray = (data[pos + 1] + data[pos + 2]) / 2;\n \n data[pos] = 0;\n data[pos + 1] = gray;\n data[pos + 2] = gray;\n }\n },\n\n magenta: function () {\n\n let i, iz, pos, gray;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n\n gray = (data[pos] + data[pos + 2]) / 2;\n\n data[pos] = gray;\n data[pos + 1] = 0;\n data[pos + 2] = gray;\n }\n },\n\n yellow: function () {\n\n let i, iz, pos, gray;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n gray = (data[pos] + data[pos + 1]) / 2;\n \n data[pos] = gray;\n data[pos + 1] = gray;\n data[pos + 2] = 0;\n }\n },\n\n brightness: function () {\n\n let i, iz, pos, \n level = filter.level || 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] *= level;\n \n pos++;\n data[pos] *= level;\n \n pos++;\n data[pos] *= level;\n }\n },\n\n saturation: function () {\n\n let i, iz, pos, \n level = filter.level || 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = 127 + ((data[pos] - 127) * level);\n \n pos++;\n data[pos] = 127 + ((data[pos] - 127) * level);\n \n pos++;\n data[pos] = 127 + ((data[pos] - 127) * level);\n }\n },\n\n threshold: function () {\n\n let i, iz, pos, gray, test,\n level = filter.level || 0,\n lowRed = filter.lowRed,\n lowGreen = filter.lowGreen,\n lowBlue = filter.lowBlue,\n highRed = filter.highRed,\n highGreen = filter.highGreen,\n highBlue = filter.highBlue;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]);\n test = (gray > level) ? true : false;\n \n if (test) {\n\n data[pos] = highRed;\n data[pos + 1] = highGreen;\n data[pos + 2] = highBlue;\n }\n else {\n\n data[pos] = lowRed;\n data[pos + 1] = lowGreen;\n data[pos + 2] = lowBlue;\n }\n \n }\n },\n\n channels: function () {\n\n let i, iz, pos,\n red = filter.red || 0,\n green = filter.green || 0,\n blue = filter.blue || 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n data[pos] *= red;\n data[pos + 1] *= green;\n data[pos + 2] *= blue;\n }\n },\n\n channelstep: function () {\n\n let i, iz, pos,\n red = filter.red || 1,\n green = filter.green || 1,\n blue = filter.blue || 1,\n floor = Math.floor;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n data[pos] = floor(data[pos] / red) * red;\n \n pos++;\n data[pos] = floor(data[pos] / green) * green;\n \n pos++;\n data[pos] = floor(data[pos] / blue) * blue;\n }\n },\n\n tint: function () {\n\n let i, iz, pos, r, g, b,\n redInRed = filter.redInRed || 0,\n redInGreen = filter.redInGreen || 0,\n redInBlue = filter.redInBlue || 0,\n greenInRed = filter.greenInRed || 0,\n greenInGreen = filter.greenInGreen || 0,\n greenInBlue = filter.greenInBlue || 0,\n blueInRed = filter.blueInRed || 0,\n blueInGreen = filter.blueInGreen || 0,\n blueInBlue = filter.blueInBlue || 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n pos = cache[i];\n \n r = data[pos];\n g = data[pos + 1];\n b = data[pos + 2];\n \n data[pos] = (r * redInRed) + (g * greenInRed) + (b * blueInRed);\n data[pos + 1] = (r * redInGreen) + (g * greenInGreen) + (b * blueInGreen);\n data[pos + 2] = (r * redInBlue) + (g * greenInBlue) + (b * blueInBlue);\n }\n },\n\n chroma: function () {\n\n let pos, posA,\n ranges = filter.ranges,\n range, min, max, val,\n i, iz, j, jz, flag;\n\n for (j = 0, jz = cache.length; j < jz; j++) {\n\n flag = false;\n\n for (i = 0, iz = ranges.length; i < iz; i++) {\n\n posA = cache[j] + 3;\n range = ranges[i];\n min = range[2];\n pos = posA - 1;\n val = data[pos];\n\n if (val >= min) {\n\n max = range[5];\n\n if (val <= max) {\n\n min = range[1];\n pos--;\n val = data[pos];\n\n if (val >= min) {\n\n max = range[4];\n\n if (val <= max) {\n\n min = range[0];\n pos--;\n val = data[pos];\n\n if (val >= min) {\n\n max = range[3];\n\n if (val <= max) {\n flag = true;\n break;\n }\n }\n }\n }\n }\n }\n }\n if (flag) data[posA] = 0;\n }\n },\n\n pixelate: function () {\n\n let i, iz, j, jz, pos, r, g, b, a, tile, len;\n\n getTiles();\n\n for (i = 0, iz = tiles.length; i < iz; i++) {\n\n tile = tiles[i];\n r = g = b = a = 0;\n len = tile.length;\n\n if (len) {\n\n for (j = 0, jz = len; j < jz; j++) {\n\n pos = tile[j];\n\n r += data[pos];\n g += data[pos + 1];\n b += data[pos + 2];\n a += data[pos + 3];\n }\n\n r /= len;\n g /= len;\n b /= len;\n a /= len;\n\n for (j = 0, jz = len; j < jz; j++) {\n\n pos = tile[j];\n\n data[pos] = r;\n data[pos + 1] = g;\n data[pos + 2] = b;\n data[pos + 3] = a;\n }\n }\n }\n },\n\n blur: function () {\n\n if (data.slice) {\n\n let radius = filter.radius || 1,\n alpha = filter.includeAlpha || false,\n shrink = filter.shrinkingRadius || false,\n passes = filter.passes || 1,\n vertical = filter.processVertical,\n horizontal = filter.processHorizontal,\n len = data.length,\n imageWidth = image.width,\n imageHeight = image.height,\n tempDataTo, tempDataFrom,\n i, iz, index;\n\n let processPass = function () {\n\n let j, jz;\n\n if (vertical) {\n\n tempDataFrom = tempDataTo.slice(); \n\n for (j = localX * 4, jz = (localX + localWidth) * 4; j < jz; j++) {\n\n if (alpha) processColumn(j);\n else {\n\n if (j % 4 !== 3) processColumn(j);\n }\n }\n }\n\n if (horizontal) {\n\n tempDataFrom = tempDataTo.slice(); \n\n for (j = localY, jz = localY + localHeight; j < jz; j++) {\n\n if (alpha) processRowWithAlpha(j);\n else processRowNoAlpha(j);\n }\n }\n };\n\n let processColumn = function (col) {\n\n let pos, avg, val, cagePointer, y, yz, q, dataPointer,\n vLead = radius * iWidth, \n cage = [],\n cageLen;\n\n for (y = -radius, yz = radius; y < yz; y++) {\n\n pos = col + (y * iWidth);\n pos = checkBounds(pos, len);\n cage.push(tempDataFrom[pos]);\n }\n\n tempDataTo[col] = avg = average(cage);\n\n cageLen = cage.length;\n\n for (q = 0; q < cageLen; q++) {\n\n cage[q] /= cageLen;\n }\n\n cagePointer = 0;\n\n for (y = 1; y < imageHeight; y++) {\n\n avg -= cage[cagePointer];\n\n dataPointer = col + (y * iWidth);\n pos = dataPointer + vLead;\n pos = checkBounds(pos, len);\n val = tempDataFrom[pos] / cageLen;\n\n avg += val;\n cage[cagePointer] = val;\n tempDataTo[dataPointer] = avg;\n\n cagePointer++;\n\n if (cagePointer === cageLen) cagePointer = 0;\n }\n };\n\n let processRowWithAlpha = function (row) {\n\n let pos, val, x, xz, q, avgQ, cageQ, rowPosX,\n avg = [],\n cage = [[], [], [], []],\n rowPos = row * iWidth, \n hLead = radius * 4,\n dataPointer, cagePointer, cageLen;\n\n q = 0;\n\n for (x = -radius * 4, xz = radius * 4; x < xz; x++) {\n\n pos = rowPos + x;\n pos = checkBounds(pos, len);\n \n cage[q].push(tempDataFrom[pos]);\n \n q++;\n if (q === 4) q = 0;\n }\n\n tempDataTo[rowPos] = avg[0] = average(cage[0]);\n tempDataTo[rowPos + 1] = avg[1] = average(cage[1]);\n tempDataTo[rowPos + 2] = avg[2] = average(cage[2]);\n tempDataTo[rowPos + 3] = avg[3] = average(cage[3]);\n\n cageLen = cage[0].length;\n\n for (q = 0; q < 4; q++) {\n\n for (x = 0; x < cageLen; x++) {\n\n cage[q][x] /= cageLen;\n }\n }\n cagePointer = 0;\n\n for (x = 1; x < imageWidth; x++) {\n\n rowPosX = rowPos + (x * 4);\n\n for (q = 0; q < 4; q++) {\n\n avgQ = avg[q];\n cageQ = cage[q];\n avgQ -= cageQ[cagePointer];\n\n dataPointer = rowPosX + q;\n pos = dataPointer + hLead;\n pos = checkBounds(pos, len);\n val = tempDataFrom[pos] / cageLen;\n\n avgQ += val;\n tempDataTo[dataPointer] = avgQ;\n avg[q] = avgQ;\n cageQ[cagePointer] = val;\n }\n\n cagePointer++;\n\n if (cagePointer === cageLen) cagePointer = 0;\n }\n };\n\n let processRowNoAlpha = function (row) {\n\n let pos, val, x, xz, q, avgQ, cageQ, rowPosX,\n avg = [],\n hLead = radius * 4,\n cage = [[], [], []],\n rowPos = row * iWidth, \n dataPointer, cagePointer, cageLen;\n\n q = 0;\n\n for (x = -radius * 4, xz = radius * 4; x < xz; x++) {\n\n if (q < 3) {\n\n pos = rowPos + x;\n pos = checkBounds(pos, len);\n cage[q].push(tempDataFrom[pos]);\n q++;\n }\n else q = 0;\n }\n\n tempDataTo[rowPos] = avg[0] = average(cage[0]);\n tempDataTo[rowPos + 1] = avg[1] = average(cage[1]);\n tempDataTo[rowPos + 2] = avg[2] = average(cage[2]);\n\n cageLen = cage[0].length;\n\n for (q = 0; q < 3; q++) {\n\n cageQ = cage[q];\n \n for (x = 0; x < cageLen; x++) {\n\n cageQ[x] /= cageLen;\n }\n }\n cagePointer = 0;\n\n for (x = 1; x < imageWidth; x++) {\n\n rowPosX = rowPos + (x * 4);\n\n for (q = 0; q < 3; q++) {\n\n avgQ = avg[q];\n cageQ = cage[q];\n avgQ -= cageQ[cagePointer];\n\n dataPointer = rowPosX + q;\n pos = dataPointer + hLead;\n pos = checkBounds(pos, len);\n val = tempDataFrom[pos] / cageLen;\n\n avgQ += val;\n tempDataTo[dataPointer] = avgQ;\n avg[q] = avgQ;\n cageQ[cagePointer] = val;\n }\n\n cagePointer++;\n if (cagePointer === cageLen) cagePointer = 0;\n }\n };\n\n tempDataTo = data.slice();\n\n for (i = 0; i < passes; i++) {\n\n processPass();\n \n if (shrink) {\n\n radius = Math.ceil(radius * 0.3);\n radius = (radius < 1) ? 1 : radius;\n }\n }\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n index = cache[i];\n data[index] = tempDataTo[index];\n \n index++;\n data[index] = tempDataTo[index];\n \n index++;\n data[index] = tempDataTo[index];\n \n if (alpha) {\n\n index++;\n data[index] = tempDataTo[index];\n }\n }\n }\n },\n\n matrix: function () {\n\n let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos,\n len = data.length,\n alpha = filter.includeAlpha || false,\n offset = [],\n weights = filter.weights || [0, 0, 0, 0, 1, 0, 0, 0, 0],\n tempCache = [],\n cursor = 0;\n\n offset[0] = -iWidth - 4;\n offset[1] = -iWidth;\n offset[2] = -iWidth + 4;\n offset[3] = -4;\n offset[4] = 0;\n offset[5] = 4;\n offset[6] = iWidth - 4;\n offset[7] = iWidth;\n offset[8] = iWidth + 4;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n homePos = cache[i];\n sumR = sumG = sumB = sumA = 0;\n \n for (j = 0, jz = offset.length; j < jz; j++) {\n\n pos = homePos + offset[j];\n \n if (pos >= 0 && pos < len) {\n\n weight = weights[j];\n sumR += data[pos] * weight;\n \n pos++;\n sumG += data[pos] * weight;\n \n pos++;\n sumB += data[pos] * weight;\n \n if (alpha) {\n\n pos++;\n sumA += data[pos] * weight;\n }\n }\n }\n\n tempCache[cursor] = sumR;\n cursor++;\n\n tempCache[cursor] = sumG;\n cursor++;\n\n tempCache[cursor] = sumB;\n cursor++;\n\n if (alpha) {\n\n tempCache[cursor] = sumA;\n cursor++;\n }\n }\n\n cursor = 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n homePos = cache[i];\n data[homePos] = tempCache[cursor];\n cursor++;\n\n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n\n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n\n if (alpha) {\n\n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n }\n }\n },\n\n matrix5: function () {\n\n let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos,\n len = data.length,\n alpha = filter.includeAlpha || false,\n offset = [],\n weights = filter.weights || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n tempCache = [],\n iWidth2 = iWidth * 2,\n cursor = 0;\n\n offset[0] = -iWidth2 - 8;\n offset[1] = -iWidth2 - 4;\n offset[2] = -iWidth2;\n offset[3] = -iWidth2 + 4;\n offset[4] = -iWidth2 + 8;\n offset[5] = -iWidth - 8;\n offset[6] = -iWidth - 4;\n offset[7] = -iWidth;\n offset[8] = -iWidth + 4;\n offset[9] = -iWidth + 8;\n offset[10] = -8;\n offset[11] = -4;\n offset[12] = 0;\n offset[13] = 4;\n offset[14] = 8;\n offset[15] = iWidth - 8;\n offset[16] = iWidth - 4;\n offset[17] = iWidth;\n offset[18] = iWidth + 4;\n offset[19] = iWidth + 8;\n offset[20] = iWidth2 - 8;\n offset[21] = iWidth2 - 4;\n offset[22] = iWidth2;\n offset[23] = iWidth2 + 4;\n offset[24] = iWidth2 + 8;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n homePos = cache[i];\n sumR = sumG = sumB = sumA = 0;\n \n for (j = 0, jz = offset.length; j < jz; j++) {\n\n pos = homePos + offset[j];\n \n if (pos >= 0 && pos < len) {\n\n weight = weights[j];\n sumR += data[pos] * weight;\n\n pos++;\n sumG += data[pos] * weight;\n\n pos++;\n sumB += data[pos] * weight;\n\n if (alpha) {\n\n pos++;\n sumA += data[pos] * weight;\n }\n }\n }\n\n tempCache[cursor] = sumR;\n cursor++;\n\n tempCache[cursor] = sumG;\n cursor++;\n\n tempCache[cursor] = sumB;\n cursor++;\n\n if (alpha) {\n\n tempCache[cursor] = sumA;\n cursor++;\n }\n }\n\n cursor = 0;\n\n for (i = 0, iz = cache.length; i < iz; i++) {\n\n homePos = cache[i];\n data[homePos] = tempCache[cursor];\n cursor++;\n\n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n\n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n\n if (alpha) {\n \n homePos++;\n data[homePos] = tempCache[cursor];\n cursor++;\n }\n }\n },\n};"],{type:"text/javascript"})),Filter=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this};let zt=Filter.prototype=Object.create(Object.prototype);zt.type="Filter",zt.lib="filter",zt.isArtefact=!1,zt.isAsset=!1,zt=Pt(zt);zt.defs=G(zt.defs,{method:"",level:0,lowRed:0,lowGreen:0,lowBlue:0,highRed:255,highGreen:255,highBlue:255,red:0,green:0,blue:0,redInRed:0,redInGreen:0,redInBlue:0,greenInRed:0,greenInGreen:0,greenInBlue:0,blueInRed:0,blueInGreen:0,blueInBlue:0,offsetX:0,offsetY:0,tileWidth:1,tileHeight:1,radius:1,passes:1,shrinkingRadius:!1,includeAlpha:!1,processVertical:!0,processHorizontal:!0,weights:null,ranges:null,userDefined:"",udVariable0:"",udVariable1:"",udVariable2:"",udVariable3:"",udVariable4:"",udVariable5:"",udVariable6:"",udVariable7:"",udVariable8:"",udVariable9:""}),zt.kill=function(){let t=this.name;return Object.entries(c).forEach(([e,i])=>{let s=i.filters;s&&s.indexOf(t)>=0&&_(s,t)}),Object.entries(u).forEach(([e,i])=>{let s=i.filters;s&&s.indexOf(t)>=0&&_(s,t)}),Object.entries(a).forEach(([e,i])=>{let s=i.filters;s&&s.indexOf(t)>=0&&_(s,t)}),this.deregister(),this};const It=[],Xt=function(){return It.length||It.push(Nt()),It.shift()},Yt=function(t){It.push(t)},Nt=function(){return new Worker(Mt)},Wt=function(t,e){return new Promise((i,s)=>{t.onmessage=t=>{t&&t.data&&t.data.image?i(t.data.image):i(!1)},t.onerror=t=>{console.log("error",t.lineno,t.message),i(!1)},t.postMessage(e)})};k.Filter=Filter;const State=function(t={}){return this.set(this.defs),this.lineDash=[],this.set(t),this};let Vt=State.prototype=Object.create(Object.prototype);Vt.type="State",Vt=Pt(Vt),Vt.defs={fillStyle:"rgba(0,0,0,1)",strokeStyle:"rgba(0,0,0,1)",globalAlpha:1,globalCompositeOperation:"source-over",lineWidth:1,lineCap:"butt",lineJoin:"miter",lineDash:null,lineDashOffset:0,miterLimit:10,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,shadowColor:"rgba(0,0,0,0)",font:"12px sans-serif",textAlign:"left",textBaseline:"top",filter:"none"},Vt.processPacketOut=function(t,e,i){let s=!0;switch(t){case"lineDash":e.length||(s=i.indexOf("lineDash")>=0);break;default:i.indexOf(t)<0&&e===this.defs[t]&&(s=!1)}return s},Vt.finalizePacketOut=function(t,e){let i=t.fillStyle,s=t.strokeStyle;return i&&!i.substring&&(t.fillStyle=i.name),s&&!s.substring&&(t.strokeStyle=s.name),t},Vt.set=function(t={}){if(Object.keys(t).length){let e,i,s,n=Object.keys(t),r=this.defs;for(i=0,s=n.length;i0&&(this[0]/=t,this[1]/=t),this};const Zt=[],_t=function(t,e){Zt.length||Zt.push(new Coordinate);let i=Zt.shift();return i.set(t,e),i},Qt=function(t){t&&"Coordinate"===t.type&&Zt.push(t.zero())},Kt=function(t,e){return new Coordinate(t,e)};function Jt(t={}){t.defs=G(t.defs,{sourceLoaded:!1,source:null,subscribers:null}),t.packetExclusions=Z(t.packetExclusions,["sourceLoaded","source","subscribers"]),t.finalizePacketOut=function(t,e){return this.subscribers&&this.subscribers.length&&(t.subscribers=this.subscribers.map(t=>t.name)),t},t.kill=function(t=!1){return t&&this.source&&this.source.remove(),this.deregister()};t.getters;let e=t.setters;t.deltaSetters;return e.source=function(t={}){t&&this.sourceLoaded&&this.notifySubscribers()},e.subscribers=B,t.assetConstructor=function(t={}){return this.makeName(t.name),this.register(),this.subscribers=[],this.set(this.defs),this.set(t),t.subscribe&&this.subscribers.push(t.subscribe),this},t.subscribe=function(t={}){if(t&&t.name){let e=t.name;this.subscribers.every(t=>t.name!==e)&&this.subscribeAction(t)}},t.subscribeAction=function(t={}){this.subscribers.push(t),t.asset=this,t.source=this.source,this.notifySubscriber(t)},t.unsubscribe=function(t={}){if(t.name){let e=t.name,i=this.subscribers.findIndex(t=>t.name===e);i>=0&&(t.source=null,t.asset=null,t.sourceNaturalHeight=0,t.sourceNaturalWidth=0,t.sourceLoaded=!1,this.subscribers.splice(i,1))}},t.notifySubscribers=function(){this.subscribers.forEach(t=>this.notifySubscriber(t),this)},t.notifySubscriber=function(t){t.sourceNaturalWidth=this.sourceNaturalWidth,t.sourceNaturalHeight=this.sourceNaturalHeight,t.sourceLoaded=this.sourceLoaded,t.source=this.source,t.dirtyImage=!0,t.dirtyCopyStart=!0,t.dirtyCopyDimensions=!0,t.dirtyImageSubscribers=!0},t}k.Coordinate=Coordinate;const ImageAsset=function(t={}){return this.assetConstructor(t)};let te=ImageAsset.prototype=Object.create(Object.prototype);te.type="Image",te.lib="asset",te.isArtefact=!1,te.isAsset=!0,te=Pt(te),te=Jt(te);te.defs=G(te.defs,{intrinsicDimensions:null}),te.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},te.stringifyFunction=B,te.processPacketOut=B,te.finalizePacketOut=B,te.clone=$;te.getters;let ee=te.setters;te.deltaSetters;ee.source=function(t={}){t&&(["IMG","PICTURE"].indexOf(t.tagName.toUpperCase())>=0&&(this.source=t,this.sourceNaturalWidth=t.naturalWidth,this.sourceNaturalHeight=t.naturalHeight,this.sourceLoaded=t.complete),this.sourceLoaded&&this.notifySubscribers())},ee.currentSrc=function(t){this.currentSrc=t,this.currentFile=this.currentSrc.split("/").pop()},te.checkSource=function(t,e){let i=this.source,s="element";if(this.sourceLoaded){let n=this.intrinsicDimensions[this.currentFile];switch(this.currentSrc!==i.currentSrc?(this.set({currentSrc:i.currentSrc}),n=this.intrinsicDimensions[this.currentFile],s=n?"intrinsic":"zero"):n&&(s="intrinsic"),s){case"zero":this.sourceNaturalWidth=0,this.sourceNaturalHeight=0,this.notifySubscribers();break;case"intrinsic":this.sourceNaturalWidth===n[0]&&this.sourceNaturalHeight===n[1]||(this.sourceNaturalWidth=n[0],this.sourceNaturalHeight=n[1],this.notifySubscribers());break;default:this.sourceNaturalWidth===i.naturalWidth&&this.sourceNaturalHeight===i.naturalHeight&&this.sourceNaturalWidth===t&&this.sourceNaturalHeight===e||(this.sourceNaturalWidth=i.naturalWidth,this.sourceNaturalHeight=i.naturalHeight,this.notifySubscribers())}}};const ie=[],se=[],ne=function(...t){let e=/.*\/(.*?)\./,i=[];return t.forEach(t=>{let s,n,r,o,a=!1,h=!1;if(t.substring){let i=e.exec(t);s=i&&i[1]?i[1]:"",n=t,r="",o=!1,h=!0}else(t=!!V(t)&&t)&&t.src&&(s=t.name||"",n=t.src,r=t.className||"",o=t.visibility||!1,t.parent&&(a=document.querySelector(t.parent)),h=!0);if(h){let t=oe({name:s,intrinsicDimensions:{}}),e=document.createElement("img");e.name=s,e.className=r,e.crossorigin="anonymous",e.style.display=o?"block":"none",a&&a.appendChild(e),e.onload=()=>{t.set({source:e})},e.src=n,t.set({source:e}),i.push(s)}else i.push(!1)}),i},re=function(t){let e=/.*\/(.*?)\./;document.querySelectorAll(t).forEach(t=>{let i;if(["IMG","PICTURE"].indexOf(t.tagName.toUpperCase())>=0){if(t.id||t.name)i=t.id||t.name;else{let s=e.exec(t.src);i=s&&s[1]?s[1]:""}let s=t.dataset.dimensions||{};s.substring&&(s=JSON.parse(s));let n=oe({name:i,source:t,intrinsicDimensions:s,currentSrc:t.currentSrc});t.onload=()=>{n.set({source:t})}}})},oe=function(t){return new ImageAsset(t)};function ae(t={}){t.defs=G(t.defs,{group:null,visibility:!0,order:0,start:null,handle:null,offset:null,dimensions:null,pivoted:null,mimicked:null,particle:null,lockTo:null,bringToFrontOnDrag:!0,scale:1,roll:0,noUserInteraction:!1,noPositionDependencies:!1,noCanvasEngineUpdates:!1,noFilters:!1,noPathUpdates:!1,purge:null}),t.packetExclusions=Z(t.packetExclusions,["pathObject","mimicked","pivoted"]),t.packetExclusionsByRegex=Z(t.packetExclusionsByRegex,["^(local|dirty|current)","Subscriber$"]),t.packetCoordinates=Z(t.packetCoordinates,["start","handle","offset"]),t.packetObjects=Z(t.packetObjects,["group"]),t.packetFunctions=Z(t.packetFunctions,[]),t.processPacketOut=function(t,e,i){let s=!0;switch(t){case"lockTo":"start"===e[0]&&"start"===e[1]&&(s=i.indexOf("lockTo")>=0);break;default:"entity"===this.lib?s=this.processEntityPacketOut(t,e,i):this.isArtefact&&(s=this.processDOMPacketOut(t,e,i))}return s},t.handlePacketAnchor=function(t,e){if(this.anchor){let i=JSON.parse(this.anchor.saveAsPacket(e))[3];t.anchor=i}return t},t.kill=function(t=!1,e=!1){let s=this.name;return Object.entries(u).forEach(([t,e])=>{e.artefacts.indexOf(s)>=0&&e.removeArtefacts(s)}),this.anchor&&this.demolishAnchor(),Object.entries(i).forEach(([t,e])=>{e.name!==s&&(e.pivot&&e.pivot.name===s&&e.set({pivot:!1}),e.mimic&&e.mimic.name===s&&e.set({mimic:!1}),e.path&&e.path.name===s&&e.set({path:!1}),e.generateAlongPath&&e.generateAlongPath.name===s&&e.set({generateAlongPath:!1}),e.generateInArea&&e.generateInArea.name===s&&e.set({generateInArea:!1}),e.artefact&&e.artefact.name===s&&e.set({artefact:!1}),Array.isArray(e.pins)&&e.pins.forEach((t,i)=>{V(t)&&t.name===s&&e.removePinAt(i)}))}),Object.entries(y).forEach(([t,e])=>{e.checkForTarget(s)&&e.removeFromTargets(this)}),this.factoryKill(t,e),this.deregister(),this},t.factoryKill=B;let e=t.getters,s=t.setters,n=t.deltaSetters;return e.positionX=function(){return this.currentStampPosition[0]},e.positionY=function(){return this.currentStampPosition[1]},e.position=function(){return[].concat(this.currentStampPosition)},e.startX=function(){return this.currentStart[0]},e.startY=function(){return this.currentStart[1]},e.start=function(){return[].concat(this.currentStart)},s.startX=function(t){null!=t&&(this.start[0]=t,this.dirtyStart=!0)},s.startY=function(t){null!=t&&(this.start[1]=t,this.dirtyStart=!0)},s.start=function(t,e){this.setCoordinateHelper("start",t,e),this.dirtyStart=!0},n.startX=function(t){let e=this.start;e[0]=H(e[0],t),this.dirtyStart=!0},n.startY=function(t){let e=this.start;e[1]=H(e[1],t),this.dirtyStart=!0},n.start=function(t,e){this.setDeltaCoordinateHelper("start",t,e),this.dirtyStart=!0},e.handleX=function(){return this.currentHandle[0]},e.handleY=function(){return this.currentHandle[1]},e.handle=function(){return[].concat(this.currentHandle)},s.handleX=function(t){null!=t&&(this.handle[0]=t,this.dirtyHandle=!0)},s.handleY=function(t){null!=t&&(this.handle[1]=t,this.dirtyHandle=!0)},s.handle=function(t,e){this.setCoordinateHelper("handle",t,e),this.dirtyHandle=!0},n.handleX=function(t){let e=this.handle;e[0]=H(e[0],t),this.dirtyHandle=!0},n.handleY=function(t){let e=this.handle;e[1]=H(e[1],t),this.dirtyHandle=!0},n.handle=function(t,e){this.setDeltaCoordinateHelper("handle",t,e),this.dirtyHandle=!0},e.offsetX=function(){return this.currentOffset[0]},e.offsetY=function(){return this.currentOffset[1]},e.offset=function(){return[].concat(this.currentOffset)},s.offsetX=function(t){null!=t&&(this.offset[0]=t,this.dirtyOffset=!0)},s.offsetY=function(t){null!=t&&(this.offset[1]=t,this.dirtyOffset=!0)},s.offset=function(t,e){this.setCoordinateHelper("offset",t,e),this.dirtyOffset=!0},n.offsetX=function(t){let e=this.offset;e[0]=H(e[0],t),this.dirtyOffset=!0},n.offsetY=function(t){let e=this.offset;e[1]=H(e[1],t),this.dirtyOffset=!0},n.offset=function(t,e){this.setDeltaCoordinateHelper("offset",t,e),this.dirtyOffset=!0},e.width=function(){return this.currentDimensions[0]},e.height=function(){return this.currentDimensions[1]},e.dimensions=function(){return[].concat(this.currentDimensions)},s.width=function(t){null!=t&&(this.dimensions[0]=t,this.dirtyDimensions=!0)},s.height=function(t){null!=t&&(this.dimensions[1]=t,this.dirtyDimensions=!0)},s.dimensions=function(t,e){this.setCoordinateHelper("dimensions",t,e),this.dirtyDimensions=!0},n.width=function(t){let e=this.dimensions;e[0]=H(e[0],t),this.dirtyDimensions=!0},n.height=function(t){let e=this.dimensions;e[1]=H(e[1],t),this.dirtyDimensions=!0},n.dimensions=function(t,e){this.setDeltaCoordinateHelper("dimensions",t,e),this.dirtyDimensions=!0},s.particle=function(t){I(t)&&!t?(this.particle=null,"particle"===this.lockTo[0]&&(this.lockTo[0]="start"),"particle"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0):(this.particle=t,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)},s.lockTo=function(t){Array.isArray(t)?(this.lockTo[0]=t[0],this.lockTo[1]=t[1]):(this.lockTo[0]=t,this.lockTo[1]=t),this.dirtyLock=!0,this.dirtyStampPositions=!0},s.lockXTo=function(t){this.lockTo[0]=t,this.dirtyLock=!0,this.dirtyStampPositions=!0},s.lockYTo=function(t){this.lockTo[1]=t,this.dirtyLock=!0,this.dirtyStampPositions=!0},e.roll=function(){return this.currentRotation},s.roll=function(t){this.roll=t,this.dirtyRotation=!0},n.roll=function(t){this.roll+=t,this.dirtyRotation=!0},e.scale=function(){return this.currentScale},s.scale=function(t){this.scale=t,this.dirtyScale=!0},n.scale=function(t){this.scale+=t,this.dirtyScale=!0},s.host=function(t){if(t){let e=i[t];e&&e.here?this.host=e.name:this.host=t}else this.host="";this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyStart=!0,this.dirtyOffset=!0},s.group=function(t){let e;t&&(this.group&&"Group"===this.group.type&&this.group.removeArtefacts(this.name),t.substring?(e=u[t],this.group=e||t):this.group=t),this.group&&"Group"===this.group.type&&this.group.addArtefacts(this.name)},t.purgeArtefact=function(t){return t.substring&&(t="all"===t?["pivot","mimic","path","filter"]:[t]),Array.isArray(t)&&t.forEach(t=>function(t,e){switch(e){case"pivot":delete t.pivot,delete t.pivotCorner,delete t.pivotPin,delete t.addPivotHandle,delete t.addPivotOffset,delete t.addPivotRotation;break;case"mimic":delete t.mimic,delete t.useMimicDimensions,delete t.useMimicScale,delete t.useMimicStart,delete t.useMimicHandle,delete t.useMimicOffset,delete t.useMimicRotation,delete t.useMimicFlip,delete t.addOwnDimensionsToMimic,delete t.addOwnScaleToMimic,delete t.addOwnStartToMimic,delete t.addOwnHandleToMimic,delete t.addOwnOffsetToMimic,delete t.addOwnRotationToMimic;break;case"path":delete t.path,delete t.pathPosition,delete t.addPathHandle,delete t.addPathOffset,delete t.addPathRotation,delete t.constantPathSpeed;break;case"filter":delete t.filter,delete t.filters,delete t.isStencil}}(this,t)),this},t.initializePositions=function(){this.dimensions=Kt(),this.start=Kt(),this.handle=Kt(),this.offset=Kt(),this.currentDimensions=Kt(),this.currentStart=Kt(),this.currentHandle=Kt(),this.currentOffset=Kt(),this.currentDragOffset=Kt(),this.currentDragCache=Kt(),this.currentStartCache=Kt(),this.currentStampPosition=Kt(),this.currentStampHandlePosition=Kt(),this.delta={},this.lockTo=["start","start"],this.pivoted=[],this.mimicked=[],this.dirtyScale=!0,this.dirtyDimensions=!0,this.dirtyLock=!0,this.dirtyStart=!0,this.dirtyOffset=!0,this.dirtyHandle=!0,this.dirtyRotation=!0,this.isBeingDragged=!1,this.initializeDomPositions()},t.initializeDomPositions=B,t.setCoordinateHelper=function(t,e,i){let s=this[t];Array.isArray(e)?(s[0]=e[0],s[1]=e[1]):V(e)?tt(e.x,e.y)?(s[0]=J(e.x,s[0]),s[1]=J(e.y,s[1])):(s[0]=J(e.width,e.w,s[0]),s[1]=J(e.height,e.h,s[1])):(s[0]=e,s[1]=i)},t.setDeltaCoordinateHelper=function(t,e,i){let s=this[t],n=s[0],r=s[1];Array.isArray(e)?(s[0]=H(n,e[0]),s[1]=H(r,e[1])):V(e)?tt(e.x,e.y)?(s[0]=H(n,J(e.x,0)),s[1]=H(r,J(e.y,0))):(s[0]=H(n,J(e.width,e.w,0)),s[1]=H(r,J(e.height,e.h,0))):(s[0]=H(n,e),s[1]=H(r,i))},t.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=i[this.host];if(t)return this.currentHost=t,this.dirtyHost=!0,this.currentHost}return Dt},t.getHere=function(){let t=this.getHost();if(t){if(t.here&&Object.keys(t.here))return t.here;if(t.currentDimensions){let e=t.currentDimensions;if(e)return{w:e[0],h:e[1]}}}return Dt},t.cleanPosition=function(t,e,i){let s,n;for(let r=0;r<2;r++)s=e[r],n=i[r],s.toFixed?t[r]=s:t[r]="left"===s||"top"===s?0:"right"===s||"bottom"===s?n:"center"===s?n/2:parseFloat(s)/100*n},t.cleanScale=function(){this.dirtyScale=!1;let t,e=this.scale,i=this.mimic,s=this.currentScale;i&&this.useMimicScale?i.currentScale?(t=i.currentScale,this.addOwnScaleToMimic&&(t+=e)):(t=e,this.dirtyMimicScale=!0):t=e,this.currentScale=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,s!==this.currentScale&&(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicScale=!0)},t.cleanDimensions=function(){this.dirtyDimensions=!1;let t=this.getHost(),e=this.dimensions,i=this.currentDimensions;if(t){let s=t.currentDimensions?t.currentDimensions:[t.w,t.h],[n,r]=e,o=i[0],a=i[1];n.substring&&(n=parseFloat(n)/100*s[0]),r.substring&&(r="auto"===r?0:parseFloat(r)/100*s[1]);let h,c=this.mimic;c&&c.name&&this.useMimicDimensions&&(h=c.currentDimensions),h?(i[0]=this.addOwnDimensionsToMimic?h[0]+n:h[0],i[1]=this.addOwnDimensionsToMimic?h[1]+r:h[1]):(i[0]=n,i[1]=r),this.cleanDimensionsAdditionalActions(),this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0,o===i[0]&&a===i[1]||(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicDimensions=!0)}else this.dirtyDimensions=!0},t.cleanDimensionsAdditionalActions=B,t.cleanLock=function(){this.dirtyLock=!1,this.dirtyStart=!0,this.dirtyHandle=!0},t.cleanStart=function(){let t,e,i=this.getHost();i&&(this.dirtyStart=!1,K(i.w,i.h)?(t=i.w,e=i.h):i.currentDimensions?[t,e]=i.currentDimensions:this.dirtyStart=!0),this.dirtyStart||(this.cleanPosition(this.currentStart,this.start,[t,e]),this.dirtyStampPositions=!0)},t.cleanOffset=function(){let t,e,i=this.getHost();i&&(this.dirtyOffset=!1,K(i.w,i.h)?(t=i.w,e=i.h):i.currentDimensions?[t,e]=i.currentDimensions:this.dirtyOffset=!0),this.dirtyStart||(this.cleanPosition(this.currentOffset,this.offset,[t,e]),this.dirtyStampPositions=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicOffset=!0))},t.cleanHandle=function(){this.dirtyHandle=!1;let t=this.currentHandle;this.cleanPosition(t,this.handle,this.currentDimensions),this.dirtyStampHandlePositions=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicHandle=!0)},t.cleanRotation=function(){this.dirtyRotation=!1;let t,e=this.roll,i=this.currentRotation,s=this.path,n=this.mimic,r=this.pivot,o=this.lockTo;if(s&&o.indexOf("path")>=0){if(t=e,this.addPathRotation){let e=this.getPathData();e&&(t+=e.angle)}}else n&&this.useMimicRotation&&o.indexOf("mimic")>=0?Q(n.currentRotation)?(t=n.currentRotation,this.addOwnRotationToMimic&&(t+=e)):this.dirtyMimicRotation=!0:(t=e,r&&this.addPivotRotation&&o.indexOf("pivot")>=0&&(Q(r.currentRotation)?t+=r.currentRotation:this.dirtyPivotRotation=!0));this.currentRotation=t,t!==i&&(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicRotation=!0)},t.cleanStampPositions=function(){this.dirtyStampPositions=!1;let{currentStampPosition:t,currentStart:e,currentOffset:i,currentStartCache:s,currentDragOffset:n}=this,[r,o]=t;if(this.noPositionDependencies)t[0]=e[0],t[1]=e[1];else{let{isBeingDragged:r,lockTo:o,pivot:a,pivotCorner:h,pivotPin:c,addPivotOffset:l,path:u,addPathOffset:f,mimic:p,useMimicStart:m,useMimicOffset:g,addOwnStartToMimic:y,addOwnOffsetToMimic:b,particle:S}=this;const P={start:function(t){t.setFromArray(e).add(i)},path:function(t){C?(t.setFromVector(C),f||t.subtract(u.currentOffset)):t.setFromArray(e).add(i)},pivot:function(t){h&&a.getCornerCoordinate?t.setFromArray(a.getCornerCoordinate(h)):"Polyline"==a.type?t.setFromArray(a.getPinAt(c)):t.setFromArray(a.currentStampPosition),l||t.subtract(a.currentOffset),t.add(i)},mimic:function(t){m||g?(t.setFromArray(p.currentStampPosition),m&&y&&t.add(e),g&&b&&t.add(i),m||t.subtract(p.currentStart).add(e),g||t.subtract(p.currentOffset).add(i)):t.setFromArray(e).add(i)},particle:function(t){S.substring&&(S=d[S]),S?t.setFromVector(S.position):t.setFromArray(e).add(i)},mouse:function(t){t.setFromVector(x),r&&(s.setFromArray(t),t.add(n)),t.add(i)}};let k,v,x,C,A=_t(),w=!1;if(A.length=0,r)A.push("mouse","mouse"),w=!0,this.getCornerCoordinate&&this.cleanPathObject();else for(k=0;k<2;k++)v=o[k],("pivot"!==v||a)&&("path"!==v||u)&&("mimic"!==v||p)&&("particle"!==v||d)||(v="start"),"mouse"===v&&(w=!0),A.push(v);w&&(x=this.getHere()),A.indexOf("path")>=0&&(C=this.getPathData());let[D,O]=A,E=_t(),T=_t();P[D](E),D==O?T.setFromArray(E):P[O](T),t[0]=E[0],t[1]=T[1],Qt(A)}r===t[0]&&o===t[1]||(this.dirtyPositionSubscribers=!0)},t.cleanStampHandlePositions=function(){this.dirtyStampHandlePositions=!1;let t=this.currentStampHandlePosition,e=this.currentHandle,i=t[0],s=t[1];if(this.noPositionDependencies)t[0]=e[0],t[1]=e[1];else{let i,s,n,r=this.lockTo,o=this.pivot,a=this.path,h=this.mimic;for(s=0;s<2;s++){switch(i=r[s],"pivot"!==i||o||(i="start"),"path"!==i||a||(i="start"),"mimic"!==i||h||(i="start"),n=e[s],i){case"pivot":this.addPivotHandle&&(n+=o.currentHandle[s]);break;case"path":this.addPathHandle&&(n+=a.currentHandle[s]);break;case"mimic":this.useMimicHandle&&(n=h.currentHandle[s],this.addOwnHandleToMimic&&(n+=e[s]))}t[s]=n}}this.cleanStampHandlePositionsAdditionalActions(),i===t[0]&&s===t[1]||(this.dirtyPositionSubscribers=!0)},t.cleanStampHandlePositionsAdditionalActions=B,t.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=xe(),s=!0);let n,r,o=e.engine,a=this.currentStampPosition,h=a[0],c=a[1];if(i.some(t=>{if(Array.isArray(t))n=t[0],r=t[1];else{if(!K(t,t.x,t.y))return!1;n=t.x,r=t.y}return!(!n.toFixed||!r.toFixed||isNaN(n)||isNaN(r))&&(e.rotateDestination(o,h,c,this),o.isPointInPath(this.pathObject,n,r,this.winding))},this)){let t=this.checkHitReturn(n,r,e);return s&&Ce(e),t}return s&&Ce(e),!1},t.checkHitReturn=function(t,e,i){return{x:t,y:e,artefact:this}},t.pickupArtefact=function(t={}){let{x:e,y:i}=t;return K(e,i)&&(this.isBeingDragged=!0,this.currentDragCache.set(this.currentDragOffset),"start"===this.lockTo[0]?this.currentDragOffset[0]=this.currentStart[0]-e:"pivot"===this.lockTo[0]&&this.pivot?this.currentDragOffset[0]=this.pivot.get("startX")-e:"mimic"===this.lockTo[0]&&this.mimic&&(this.currentDragOffset[0]=this.mimic.get("startX")-e),"start"===this.lockTo[1]?this.currentDragOffset[1]=this.currentStart[1]-i:"pivot"===this.lockTo[1]&&this.pivot?this.currentDragOffset[1]=this.pivot.get("startY")-i:"mimic"===this.lockTo[1]&&this.mimic&&(this.currentDragOffset[1]=this.mimic.get("startY")-i),this.bringToFrontOnDrag&&(this.order+=9999,this.group.batchResort=!0),Q(this.dirtyPathObject)&&(this.dirtyPathObject=!0)),this},t.dropArtefact=function(){return this.start.set(this.currentStartCache).add(this.currentDragOffset),this.dirtyStart=!0,this.currentDragOffset.set(this.currentDragCache),this.bringToFrontOnDrag&&(this.order-=9999,this.order<0&&(this.order=0),this.group.batchResort=!0),Q(this.dirtyPathObject)&&(this.dirtyPathObject=!0),this.isBeingDragged=!1,this},t.updatePositionSubscribers=function(){this.dirtyPositionSubscribers=!1,this.pivoted&&this.pivoted.length&&this.updatePivotSubscribers(),this.mimicked&&this.mimicked.length&&this.updateMimicSubscribers(),this.pathed&&this.pathed.length&&this.updatePathSubscribers()},t.updatePivotSubscribers=B,t.updateMimicSubscribers=B,t.updatePathSubscribers=B,t.updateImageSubscribers=B,t}function he(t={}){let e={delta:null,noDeltaUpdates:!1};t.defs=G(t.defs,e),G(t,e);let i=t.setters;t.deltaSetters;return i.delta=function(t={}){t&&(this.delta=U(this.delta,t))},t.updateByDelta=function(){return"kaliedoscope-clock-background"==this.name&&console.log(this.name,"updateByDelta"),this.setDelta(this.delta),this},t.reverseByDelta=function(){let t={};return Object.entries(this.delta).forEach(([e,i])=>{i=i.substring?-parseFloat(i)+"%":-i,t[e]=i}),this.setDelta(t),this},t.setDeltaValues=function(t={}){let e,i,s=this.delta;return Object.entries(t).forEach(([t,n])=>{if(Q(s[t]))switch(i=n,e=s[t],i){case"reverse":e.toFixed&&(s[t]=-e);break;case"zero":e.toFixed&&(s[t]=0)}}),this},t}function ce(t={}){let e={pivot:"",pivotCorner:"",pivotPin:0,addPivotHandle:!1,addPivotOffset:!0,addPivotRotation:!1};t.defs=G(t.defs,e),G(t,e),t.packetObjects=Z(t.packetObjects,["pivot"]);t.getters;let s=t.setters;t.deltaSetters;return s.pivot=function(t){if(I(t)&&!t)this.pivot=null,"pivot"===this.lockTo[0]&&(this.lockTo[0]="start"),"pivot"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.pivot,s=this.name,r=t.substring?i[t]:t;r||(r=n[t],r&&"Cell"!==r.type&&(r=!1)),r&&r.name&&(e&&e.name!==r.name&&_(e.pivoted,s),Z(r.pivoted,s),this.pivot=r,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},t.pivotCorners=["topLeft","topRight","bottomRight","bottomLeft"],s.pivotCorner=function(t){this.pivotCorners.indexOf(t)>=0&&(this.pivotCorner=t)},s.addPivotHandle=function(t){this.addPivotHandle=t,this.dirtyHandle=!0},s.addPivotOffset=function(t){this.addPivotOffset=t,this.dirtyOffset=!0},s.addPivotRotation=function(t){this.addPivotRotation=t,this.dirtyRotation=!0},t.updatePivotSubscribers=function(){this.pivoted.forEach(t=>{let e=i[t];e||(e=n[t],e&&"Cell"===e.type||(e=!1)),e&&(e.dirtyStart=!0,e.addPivotHandle&&(e.dirtyHandle=!0),e.addPivotOffset&&(e.dirtyOffset=!0),e.addPivotRotation&&(e.dirtyRotation=!0),"Polyline"===e.type?e.dirtyPins=!0:"Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||e.dirtyPins.push(this.name))},this)},t}function le(t={}){let e={mimic:"",useMimicDimensions:!1,useMimicScale:!1,useMimicStart:!1,useMimicHandle:!1,useMimicOffset:!1,useMimicRotation:!1,useMimicFlip:!1,addOwnDimensionsToMimic:!1,addOwnScaleToMimic:!1,addOwnStartToMimic:!1,addOwnHandleToMimic:!1,addOwnOffsetToMimic:!1,addOwnRotationToMimic:!1};t.defs=G(t.defs,e),G(t,e),t.packetObjects=Z(t.packetObjects,["mimic"]);t.getters;let s=t.setters;t.deltaSetters;return s.mimic=function(t){if(I(t)&&!t)this.mimic=null,"mimic"===this.lockTo[0]&&(this.lockTo[0]="start"),"mimic"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.mimic,s=this.name,r=t.substring?i[t]:t;r||(r=n[t],r&&"Cell"!==r.type&&(r=!1)),r&&r.name&&(e&&e.name!==r.name&&removeItem(e.mimicked,s),Z(r.mimicked,s),this.mimic=r,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},s.useMimicDimensions=function(t){this.useMimicDimensions=t,this.dirtyDimensions=!0},s.useMimicScale=function(t){this.useMimicScale=t,this.dirtyScale=!0},s.useMimicStart=function(t){this.useMimicStart=t,this.dirtyStart=!0},s.useMimicHandle=function(t){this.useMimicHandle=t,this.dirtyHandle=!0},s.useMimicOffset=function(t){this.useMimicOffset=t,this.dirtyOffset=!0},s.useMimicRotation=function(t){this.useMimicRotation=t,this.dirtyRotation=!0},s.addOwnDimensionsToMimic=function(t){this.addOwnDimensionsToMimic=t,this.dirtyDimensions=!0},s.addOwnScaleToMimic=function(t){this.addOwnScaleToMimic=t,this.dirtyScale=!0},s.addOwnStartToMimic=function(t){this.addOwnStartToMimic=t,this.dirtyStart=!0},s.addOwnHandleToMimic=function(t){this.addOwnHandleToMimic=t,this.dirtyHandle=!0},s.addOwnOffsetToMimic=function(t){this.addOwnOffsetToMimic=t,this.dirtyOffset=!0},s.addOwnRotationToMimic=function(t){this.addOwnRotationToMimic=t,this.dirtyRotation=!0},t.updateMimicSubscribers=function(){let t=this.dirtyMimicHandle,e=this.dirtyMimicOffset,s=this.dirtyMimicRotation,r=this.dirtyMimicScale,o=this.dirtyMimicDimensions;this.mimicked.forEach(a=>{let h=i[a];h||(h=n[a],h&&"Cell"===h.type||(h=!1)),h&&(h.useMimicStart&&(h.dirtyStart=!0),t&&h.useMimicHandle&&(h.dirtyHandle=!0),e&&h.useMimicOffset&&(h.dirtyOffset=!0),s&&h.useMimicRotation&&(h.dirtyRotation=!0),r&&h.useMimicScale&&(h.dirtyScale=!0),o&&h.useMimicDimensions&&(h.dirtyDimensions=!0))}),this.dirtyMimicHandle=!1,this.dirtyMimicOffset=!1,this.dirtyMimicRotation=!1,this.dirtyMimicScale=!1,this.dirtyMimicDimensions=!1},t}function ue(t={}){let e={path:"",pathPosition:0,addPathHandle:!1,addPathOffset:!0,addPathRotation:!1,constantPathSpeed:!1};t.defs=G(t.defs,e),G(t,e),t.packetObjects=Z(t.packetObjects,["path"]);let s=t.setters,n=t.deltaSetters;return s.path=function(t){if(I(t)&&!t)this.path=null,"path"===this.lockTo[0]&&(this.lockTo[0]="start"),"path"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.path,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&_(e.pathed,n),Z(s.pathed,n),this.path=s,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},s.pathPosition=function(t){t<0&&(t=Math.abs(t)),t>1&&(t%=1),this.pathPosition=parseFloat(t.toFixed(6)),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0,this.currentPathData=!1},n.pathPosition=function(t){let e=this.pathPosition+t;e<0&&(e+=1),e>1&&(e%=1),this.pathPosition=parseFloat(e.toFixed(6)),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0,this.currentPathData=!1},s.addPathHandle=function(t){this.addPathHandle=t,this.dirtyHandle=!0},s.addPathOffset=function(t){this.addPathOffset=t,this.dirtyOffset=!0},s.addPathRotation=function(t){this.addPathRotation=t,this.dirtyRotation=!0},t.getPathData=function(){if(this.currentPathData)return this.currentPathData;let t,e=this.pathPosition,i=this.path;return!!i&&(t=i.getPathPositionData(e,this.constantPathSpeed),this.addPathRotation&&(this.dirtyRotation=!0),this.currentPathData=t,t)},t}k.ImageAsset=ImageAsset;const Anchor=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.build(),this};let de=Anchor.prototype=Object.create(Object.prototype);de.type="Anchor",de.lib="anchor",de.isArtefact=!1,de.isAsset=!1,de=Pt(de);de.defs=G(de.defs,{host:null,description:"",download:"",href:"",hreflang:"",ping:"",referrerpolicy:"",rel:"noreferrer",target:"_blank",anchorType:"",clickAction:null,focusAction:!1,blurAction:!1}),de.packetExclusions=Z(de.packetExclusions,["domElement"]),de.packetObjects=Z(de.packetExclusions,["host"]),de.packetFunctions=Z(de.packetFunctions,["clickAction"]),de.demolish=function(){this.domElement&&this.hold&&this.hold.removeChild(this.domElement),this.deregister()};let fe=de.setters;fe.host=function(t){let e=t.substring?i[t]:t;e&&e.name&&(this.host=e)},fe.hold=function(t){Y(t)&&(this.domElement&&this.hold&&this.hold.removeChild(this.domElement),this.hold=t,this.domElement&&this.hold.appendChild(this.domElement))},fe.download=function(t){this.download=t,this.domElement&&this.update("download")},fe.href=function(t){this.href=t,this.domElement&&this.update("href")},fe.hreflang=function(t){this.hreflang=t,this.domElement&&this.update("hreflang")},fe.ping=function(t){this.ping=t,this.domElement&&this.update("ping")},fe.referrerpolicy=function(t){this.referrerpolicy=t,this.domElement&&this.update("referrerpolicy")},fe.rel=function(t){this.rel=t,this.domElement&&this.update("rel")},fe.target=function(t){this.target=t,this.domElement&&this.update("target")},fe.anchorType=function(t){this.anchorType=t,this.domElement&&this.update("type")},fe.description=function(t){this.description=t,this.domElement&&(this.domElement.textContent=t)},fe.clickAction=function(t){N(t)&&(this.clickAction=t,this.domElement&&this.domElement.setAttribute("onclick",t()))},de.build=function(){this.domElement&&this.hold&&this.hold.removeChild(this.domElement);let t=document.createElement("a");t.id=this.name,this.download&&t.setAttribute("download",this.download),this.href&&t.setAttribute("href",this.href),this.hreflang&&t.setAttribute("hreflang",this.hreflang),this.ping&&t.setAttribute("ping",this.ping),this.referrerpolicy&&t.setAttribute("referrerpolicy",this.referrerpolicy),this.rel&&t.setAttribute("rel",this.rel),this.target&&t.setAttribute("target",this.target),this.anchorType&&t.setAttribute("type",this.anchorType),this.clickAction&&N(this.clickAction)&&t.setAttribute("onclick",this.clickAction()),this.description&&(t.textContent=this.description),this.focusAction&&t.addEventListener("focus",t=>this.host.onEnter(),!1),this.blurAction&&t.addEventListener("blur",t=>this.host.onLeave(),!1),this.domElement=t,this.hold&&this.hold.appendChild(t)},de.update=function(t){this.domElement&&this.domElement.setAttribute(t,this[t])},de.click=function(){if(this.hasBeenRecentlyClicked)return!1;{let t=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0});this.hasBeenRecentlyClicked=!0;let e=this;return setTimeout(()=>e.hasBeenRecentlyClicked=!1,200),this.domElement.dispatchEvent(t)}};function pe(t={}){t.defs=G(t.defs,{anchor:null}),t.demolishAnchor=function(){this.anchor&&this.anchor.demolish()};let e=t.getters,i=t.setters;t.deltaSetters;return e.anchorDescription=function(){return this.anchor?this.anchor.get("description"):""},i.anchorDescription=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.description(t)},e.anchorType=function(){return this.anchor?this.anchor.get("type"):""},i.anchorType=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.anchorType(t)},e.anchorTarget=function(){return this.anchor?this.anchor.get("target"):""},i.anchorTarget=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.target(t)},e.anchorRel=function(){return this.anchor?this.anchor.get("rel"):""},i.anchorRel=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.rel(t)},e.anchorReferrerPolicy=function(){return this.anchor?this.anchor.get("referrerpolicy"):""},i.anchorReferrerPolicy=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.referrerpolicy(t)},e.anchorPing=function(){return this.anchor?this.anchor.get("ping"):""},i.anchorPing=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.ping(t)},e.anchorHreflang=function(){return this.anchor?this.anchor.get("hreflang"):""},i.anchorHreflang=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.hreflang(t)},e.anchorHref=function(){return this.anchor?this.anchor.get("href"):""},i.anchorHref=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.href(t)},e.anchorDownload=function(){return this.anchor?this.anchor.get("download"):""},i.anchorDownload=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.download(t)},i.anchorFocusAction=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.focusAction(t)},i.anchorBlurAction=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.blurAction(t)},i.anchor=function(t={}){this.anchor?this.anchor.set(t):this.buildAnchor(t)},t.buildAnchor=function(t={}){this.anchor&&this.anchor.demolish(),t.name||(t.name=this.name+"-anchor"),t.description||(t.description=`Anchor link for ${this.name} ${this.type}`),t.host=this,t.hold=this.getAnchorHold(),this.anchor=function(t){return new Anchor(t)}(t)},t.getAnchorHold=function(){let t=this.currentHost;if(t){if("Canvas"===t.type)return t.navigation;if("Cell"===t.type){let e=t.currentHost?t.currentHost:o[t.host];if(e&&"Canvas"===e.type)return e.navigation}}return this.dirtyAnchorHold=!0,vi},t.rebuildAnchor=function(){this.anchor&&this.anchor.build()},t.clickAnchor=function(){this.anchor&&this.anchor.click()},t}function me(t={}){t.defs=G(t.defs,{groups:null,groupBuckets:null,batchResort:!0});let e=t.getters,i=t.setters;return e.groups=function(){return[].concat(this.groups)},i.groups=function(t){this.groups=[],this.addGroups(t)},t.sortGroups=function(t=!1){if(this.batchResort){this.batchResort=!1;let t,e,i=Math.floor,s=this.groups,n=[];s.forEach(s=>{t=u[s],e=t?i(t.order):0,n[e]||(n[e]=[]),n[e].push(t)}),this.groupBuckets=n.reduce((t,e)=>t.concat(e),[])}},t.initializeCascade=function(){this.groups=[],this.groupBuckets=[]},t.addGroups=function(...t){return t.forEach(t=>{t&&t.substring?Z(this.groups,t):u[t]&&Z(this.groups,t.name)},this),this.batchResort=!0,this},t.removeGroups=function(...t){return t.forEach(t=>{t&&t.substring?_(this.groups,t):u[t]&&_(this.groups,t.name)},this),this.batchResort=!0,this},t.cascadeAction=function(t,e){let i;return console.log("cascadeAction"),this.groups.forEach(s=>{i=u[s],i&&i[e](t)},this),this},t.updateArtefacts=function(t){return this.cascadeAction(t,"updateArtefacts"),this},t.setArtefacts=function(t){return this.cascadeAction(t,"setArtefacts"),this},t.addArtefactClasses=function(t){return this.cascadeAction(t,"addArtefactClasses"),this},t.removeArtefactClasses=function(t){return this.cascadeAction(t,"removeArtefactClasses"),this},t.updateByDelta=function(){return this.cascadeAction(!1,"updateByDelta"),this},t.reverseByDelta=function(){return this.cascadeAction(!1,"reverseByDelta"),this},t.getArtefactAt=function(t){if(t=J(t,this.here,!1)){let e,i;for(let s=this.groups.length-1;s>=0;s--)if(e=u[this.groups[s]],e&&(i=e.getArtefactAt(t),i))return i}return!1},t.getAllArtefactsAt=function(t){if(t=J(t,this.here,!1)){let e,i,s=[];for(let n=this.groups.length-1;n>=0;n--)e=u[this.groups[n]],e&&(i=e.getAllArtefactsAt(t),i&&(s=s.concat(i)));return s}return[]},t}function ge(t={}){t.defs=G(t.defs,{repeat:"repeat",patternMatrix:null});let e=t.setters;return t.repeatValues=["repeat","repeat-x","repeat-y","no-repeat"],e.repeat=function(t){this.repeatValues.indexOf(t)>=0?this.repeat=t:this.repeat=this.defs.repeat},t.matrixNumberPosCheck=["a","b","c","d","e","f"],t.updateMatrixNumber=function(t,e){this.patternMatrix||(this.patternMatrix=new DOMMatrix),t=t.substring?parseFloat(t):t;let i=this.matrixNumberPosCheck.indexOf(e);W(t)&&i>=0&&(this.patternMatrix[e]=t)},e.matrixA=function(t){this.updateMatrixNumber(t,"a")},e.matrixB=function(t){this.updateMatrixNumber(t,"b")},e.matrixC=function(t){this.updateMatrixNumber(t,"c")},e.matrixD=function(t){this.updateMatrixNumber(t,"d")},e.matrixE=function(t){this.updateMatrixNumber(t,"e")},e.matrixF=function(t){this.updateMatrixNumber(t,"f")},e.patternMatrix=function(t){if(Array.isArray(t)){let e=this.updateMatrixNumber;e(t[0],"a"),e(t[1],"b"),e(t[2],"c"),e(t[3],"d"),e(t[4],"e"),e(t[5],"f")}},t.buildStyle=function(t={}){if(t){t.substring&&(t=a[t]);let e=this.source,i=this.sourceLoaded,s=this.repeat,n=t.engine;if("Cell"===this.type&&(e=this.element,i=!0),n&&i){let t=n.createPattern(e,s);return t.setTransform(this.patternMatrix),t}}return"rgba(0,0,0,0)"},t}function ye(t={}){return t.defs=G(t.defs,{filters:null,isStencil:!1}),t.setters.filters=function(t){Array.isArray(this.filters)||(this.filters=[]),t&&(Array.isArray(t)?(this.filters=t,this.dirtyFilters=!0,this.dirtyImageSubscribers=!0):t.substring&&(Z(this.filters,t),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0))},t.cleanFilters=function(){this.dirtyFilters=!1,this.filters||(this.filters=[]);let t,e,i=this.filters,s=Math.floor,n=[];i.forEach(i=>{t=l[i],e=s(t.order)||0,n[e]||(n[e]=[]),n[e].push(t)}),this.currentFilters=n.reduce((t,e)=>t.concat(e),[])},t.addFilters=function(...t){return Array.isArray(this.filters)||(this.filters=[]),t.forEach(t=>{this.name,t&&(t.substring?Z(this.filters,t):"Filter"===t.type&&Z(this.filters,t.name))},this),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t.removeFilters=function(...t){return Array.isArray(this.filters)||(this.filters=[]),t.forEach(t=>{t&&(t.substring?_(this.filters,t):"Filter"===t.type&&_(this.filters,t.name))},this),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t.clearFilters=function(){return Array.isArray(this.filters)||(this.filters=[]),this.filters.length=0,this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t}k.Anchor=Anchor;const Cell=function(t={}){if(this.makeName(t.name),t.isPool||this.register(),this.initializePositions(),this.initializeCascade(),!X(t.element)){let e=document.createElement("canvas");e.id=this.name,e.width=300,e.height=150,t.element=e}return this.installElement(t.element),t.isPool?this.set(this.poolDefs):this.set(this.defs),this.set(t),this.state.setStateFromEngine(this.engine),t.isPool||Ee({name:this.name,host:this.name}),this.subscribers=[],this.sourceNaturalDimensions=Kt(),this.sourceLoaded=!0,this.here={},this};let be=Cell.prototype=Object.create(Object.prototype);be.type="Cell",be.lib="cell",be.isArtefact=!1,be.isAsset=!0,be=Pt(be),be=Jt(be),be=ae(be),be=he(be),be=ce(be),be=le(be),be=ue(be),be=pe(be),be=me(be),be=ge(be),be=ye(be);be.defs=G(be.defs,{cleared:!0,compiled:!0,shown:!0,compileOrder:0,showOrder:0,backgroundColor:"",clearAlpha:0,alpha:1,composite:"source-over",scale:1,flipReverse:!1,flipUpend:!1,filter:"none",isBase:!1,controller:null}),delete be.defs.source,delete be.defs.sourceLoaded,be.stringifyFunction=B,be.processPacketOut=B,be.finalizePacketOut=B,be.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},be.clone=$,be.factoryKill=function(){let t=this.name;Object.entries(o).forEach(([e,i])=>{i.cells.indexOf(t)>=0&&i.removeCell(t),i.base&&i.base.name===t&&i.set({visibility:!1})}),Object.entries(i).forEach(([e,i])=>{if(i.name!==t){let e=i.state;if(e){let i=e.fillStyle,s=e.strokeStyle;i.name&&i.name===t&&(e.fillStyle=e.defs.fillStyle),s.name&&s.name===t&&(e.strokeStyle=e.defs.strokeStyle)}}}),u[t]&&u[t].kill()};let Se=be.getters,Pe=be.setters,ke=be.deltaSetters;be.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},Se.width=function(){return this.currentDimensions[0]||this.element.getAttribute("width")},Pe.width=function(t){this.dimensions[0]=t,this.dirtyDimensions=!0},Se.height=function(){return this.currentDimensions[1]||this.element.getAttribute("height")},Pe.height=function(t){this.dimensions[1]=t,this.dirtyDimensions=!0},Pe.source=function(){},Pe.engine=function(t){},Pe.state=function(t){},Pe.element=function(t){X(t)&&this.installElement(t)},Pe.cleared=function(t){this.cleared=t,this.updateControllerCells()},Pe.compiled=function(t){this.compiled=t,this.updateControllerCells()},Pe.shown=function(t){this.shown=t,this.updateControllerCells()},Pe.compileOrder=function(t){this.compileOrder=t,this.updateControllerCells()},Pe.showOrder=function(t){this.showOrder=t,this.updateControllerCells()},Pe.stashX=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]),this.stashCoordinates[0]=t},Pe.stashY=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]),this.stashCoordinates[1]=t},Pe.stashWidth=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}this.stashDimensions[0]=t},Pe.stashHeight=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}this.stashDimensions[1]=t},ke.stashX=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]);let e=this.stashCoordinates;e[0]=addStrings(e[0],t)},ke.stashY=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]);let e=this.stashCoordinates;e[1]=addStrings(e[1],t)},ke.stashWidth=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}let e=this.stashDimensions;e[0]=addStrings(e[0],t)},ke.stashHeight=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}let e=this.stashDimensions;e[1]=addStrings(e[1],t)},Pe.clearAlpha=function(t){t.toFixed&&(t>1?t=1:t<0&&(t=0),this.clearAlpha=t)},ke.clearAlpha=function(t){t.toFixed&&((t+=this.clearAlpha)>1?t=1:t<0&&(t=0),this.clearAlpha=t)},be.checkSource=function(t,e){this.currentDimensions[0]===t&&this.currentDimensions[1]===e||this.notifySubscribers()},be.getData=function(t,e){return this.checkSource(this.sourceNaturalDimensions[0],this.sourceNaturalDimensions[1]),this.buildStyle(e)},be.updateArtefacts=function(t={}){this.groupBuckets.forEach(e=>{e.artefactBuckets.forEach(e=>{t.dirtyScale&&(e.dirtyScale=!0),t.dirtyDimensions&&(e.dirtyDimensions=!0),t.dirtyLock&&(e.dirtyLock=!0),t.dirtyStart&&(e.dirtyStart=!0),t.dirtyOffset&&(e.dirtyOffset=!0),t.dirtyHandle&&(e.dirtyHandle=!0),t.dirtyRotation&&(e.dirtyRotation=!0),t.dirtyPathObject&&(e.dirtyPathObject=!0)})})},be.cleanDimensionsAdditionalActions=function(){let t=this.element;if(t){let e=this.controller,i=this.currentDimensions,s=this.isBase;if(s&&e&&e.isComponent){let t=this.controller.currentDimensions,e=this.dimensions;e[0]=i[0]=t[0],e[1]=i[1]=t[1]}let[n,r]=i;t.width=n,t.height=r,this.setEngineFromState(this.engine),s&&e&&e.updateBaseHere(),this.groupBuckets&&this.updateArtefacts({dirtyDimensions:!0})}},be.notifySubscriber=function(t){t.sourceNaturalDimensions||(t.sourceNaturalDimensions=[]),t.sourceNaturalWidth=this.currentDimensions[0],t.sourceNaturalHeight=this.currentDimensions[1],t.sourceLoaded=!0,t.dirtyImage=!0,t.dirtyCopyStart=!0,t.dirtyCopyDimensions=!0},be.subscribeAction=function(t={}){this.subscribers.push(t),t.asset=this,t.source=this.element,this.notifySubscriber(t)},be.installElement=function(t){return this.element=t,this.engine=this.element.getContext("2d"),this.state=Gt({engine:this.engine}),this},be.updateControllerCells=function(){this.controller&&(this.controller.dirtyCells=!0)},be.setEngineFromState=function(t){let e=this.state;return e.allKeys.forEach(i=>{"lineDash"===i?(t.lineDash=e.lineDash,t.setLineDash(t.lineDash)):t[i]=e[i]},e),t.textAlign=e.textAlign,t.textBaseline=e.textBaseline,this},be.setToDefaults=function(){let t=this.state.defs,e=this.state,i=this.engine,s=Array.isArray;return Object.entries(t).forEach(([t,n])=>{"lineDash"===t?(s(i.lineDash)?i.lineDash.length=0:i.lineDash=[],s(e.lineDash)?e.lineDash.length=0:e.lineDash=[]):(i[t]=n,e[t]=n)}),i.textAlign=e.textAlign="left",i.textBaseline=e.textBaseline="top",this},be.stylesArray=["Gradient","RadialGradient","Pattern"],be.setEngine=function(t){let e,i,s=this.state,n=t.state.getChanges(t,s),r=this.setEngineActions,o=this.stylesArray;if(Object.keys(n).length)for(i in e=this.engine,n)r[i](n[i],e,o,t,this),s[i]=n[i];return t},be.setEngineActions={fillStyle:function(t,e,i,s,n){if(t.substring){let i=!1;S.indexOf(t)>=0?i=b[t]:h.indexOf(t)>=0&&(i=a[t]),i?(s.state.fillStyle=i,e.fillStyle=i.getData(s,n)):e.fillStyle=t}else e.fillStyle=t.getData(s,n)},filter:function(t,e){e.filter=t},font:function(t,e){e.font=t},globalAlpha:function(t,e){e.globalAlpha=t},globalCompositeOperation:function(t,e){e.globalCompositeOperation=t},lineCap:function(t,e){e.lineCap=t},lineDash:function(t,e){e.lineDash=t,e.setLineDash&&e.setLineDash(t)},lineDashOffset:function(t,e){e.lineDashOffset=t},lineJoin:function(t,e){e.lineJoin=t},lineWidth:function(t,e){e.lineWidth=t},miterLimit:function(t,e){e.miterLimit=t},shadowBlur:function(t,e){e.shadowBlur=t},shadowColor:function(t,e){e.shadowColor=t},shadowOffsetX:function(t,e){e.shadowOffsetX=t},shadowOffsetY:function(t,e){e.shadowOffsetY=t},strokeStyle:function(t,e,i,s,n){if(t.substring){let i=!1;S.indexOf(t)>=0?i=b[t]:h.indexOf(t)>=0&&(i=a[t]),i?(s.state.strokeStyle=i,e.strokeStyle=i.getData(s,n)):e.strokeStyle=t}else e.strokeStyle=t.getData(s,n)}},be.clearShadow=function(){return this.engine.shadowOffsetX=0,this.engine.shadowOffsetY=0,this.engine.shadowBlur=0,this.state.shadowOffsetX=0,this.state.shadowOffsetY=0,this.state.shadowBlur=0,this},be.restoreShadow=function(t){let e=t.state;return this.engine.shadowOffsetX=e.shadowOffsetX,this.engine.shadowOffsetY=e.shadowOffsetY,this.engine.shadowBlur=e.shadowBlur,this.state.shadowOffsetX=e.shadowOffsetX,this.state.shadowOffsetY=e.shadowOffsetY,this.state.shadowBlur=e.shadowBlur,this},be.setToClearShape=function(){return this.engine.fillStyle="rgba(0,0,0,0)",this.engine.strokeStyle="rgba(0,0,0,0)",this.engine.shadowColor="rgba(0,0,0,0)",this.state.fillStyle="rgba(0,0,0,0)",this.state.strokeStyle="rgba(0,0,0,0)",this.state.shadowColor="rgba(0,0,0,0)",this},be.saveEngine=function(){return this.engine.save(),this},be.restoreEngine=function(){return this.engine.restore(),this},be.clear=function(){let t=this;return new Promise(e=>{const{element:i,engine:s,backgroundColor:n,clearAlpha:r,currentDimensions:o}=t,[a,h]=o;if(t.prepareStamp(),s.setTransform(1,0,0,1,0,0),n){let t=s.fillStyle,e=s.globalCompositeOperation,i=s.globalAlpha;s.fillStyle=n,s.globalCompositeOperation="source-over",s.globalAlpha=1,s.fillRect(0,0,a,h),s.fillStyle=t,s.globalCompositeOperation=e,s.globalAlpha=i}else if(r){let t=xe(),{engine:e,element:i}=t;i.width=a,i.height=h;let n=s.getImageData(0,0,a,h);e.putImageData(n,0,0);let o=s.globalAlpha;s.clearRect(0,0,a,h),s.globalAlpha=r,s.drawImage(i,0,0),s.globalAlpha=o}else s.clearRect(0,0,a,h);e(!0)})},be.compile=function(){this.stashOutput;this.sortGroups(),this.prepareStamp(),!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let t=this,e=i=>new Promise((s,n)=>{let r=t.groupBuckets[i];r&&r.stamp?r.stamp().then(t=>{e(i+1).then(t=>{s(!0)}).catch(t=>n(t))}).catch(t=>n(t)):!t.noFilters&&t.filters&&t.filters.length?t.applyFilters().then(e=>t.stashOutputAction()).then(t=>s(!0)).catch(t=>n(t)):t.stashOutputAction().then(t=>s(!0)).catch(t=>n(t))});return e(0)},be.show=function(){var t=this;return new Promise(e=>{let i=t.getHost(),s=!(!i||!i.engine)&&i.engine;if(s){let n=Math.floor,r=i.currentDimensions,o=n(r[0]),a=n(r[1]);o&&a||e(!1);let h,c=t.currentScale,l=t.currentDimensions,u=n(l[0]),d=n(l[1]),f=t.composite,p=t.alpha,m=t.controller,g=t.element;if(s.save(),s.setTransform(1,0,0,1,0,0),s.filter=t.filter,t.isBase){let e,i;switch(t.basePaste||(t.basePaste=[]),h=t.basePaste,t.prepareStamp(),s.globalCompositeOperation="source-over",s.globalAlpha=1,s.clearRect(0,0,o,a),s.globalCompositeOperation=f,s.globalAlpha=p,m?m.fit:"none"){case"contain":e=o/(u||1),i=a/(d||1),e>i?(h[0]=n((o-u*i)/2),h[1]=0,h[2]=n(u*i),h[3]=n(d*i)):(h[0]=0,h[1]=n((a-d*e)/2),h[2]=n(u*e),h[3]=n(d*e));break;case"cover":e=o/(u||1),i=a/(d||1),e0){t.paste||(t.paste=[]),h=t.paste,t.noDeltaUpdates||t.setDelta(t.delta),t.prepareStamp(),s.globalCompositeOperation=f,s.globalAlpha=p;let e=t.currentStampHandlePosition,i=t.currentStampPosition;h[0]=n(-e[0]*c),h[1]=n(-e[1]*c),h[2]=n(u*c),h[3]=n(d*c),t.rotateDestination(s,i[0],i[1])}s.drawImage(g,0,0,u,d,...h),s.restore(),e(!0)}else e(!1)})},be.applyFilters=function(){let t=this;return new Promise((function(e){let i,s,n=t.engine;i=n.getImageData(0,0,t.currentDimensions[0],t.currentDimensions[1]),s=Xt(),Wt(s,{image:i,filters:t.currentFilters}).then(t=>{Yt(s),t?(n.putImageData(t,0,0),e(!0)):e(!1)}).catch(t=>{Yt(s),e(!1)})}))},be.stashOutputAction=function(){let t=this;return this.stashOutput?(this.stashOutput=!1,new Promise((e,i)=>{let[s,n]=t.currentDimensions,r=t.stashCoordinates,o=t.stashDimensions,a=r?r[0]:0,h=r?r[1]:0,c=o?o[0]:s,l=o?o[1]:n;if((c.substring||l.substring||a.substring||h.substring||a||h||c!==s||l!==n)&&(c.substring&&(c=parseFloat(c)/100*s),(isNaN(c)||c<=0)&&(c=1),c>s&&(c=s),l.substring&&(l=parseFloat(l)/100*n),(isNaN(l)||l<=0)&&(l=1),l>n&&(l=n),a.substring&&(a=parseFloat(a)/100*s),(isNaN(a)||a<0)&&(a=0),a+c>s&&(a=s-c),h.substring&&(h=parseFloat(h)/100*n),(isNaN(h)||h<0)&&(h=0),h+l>n&&(h=n-l)),t.engine.save(),t.engine.setTransform(1,0,0,1,0,0),t.stashedImageData=t.engine.getImageData(a,h,c,l),t.engine.restore(),t.stashOutputAsAsset){let e,i;if(t.stashOutputAsAsset=!1,i=xe(),e=i.element,e.width=c,e.height=l,i.engine.putImageData(t.stashedImageData,0,0),t.stashedImage)t.stashedImage.src=e.toDataURL();else{let i=t.stashedImage=document.createElement("img");i.id=t.name+"-image",i.onload=function(){ki.appendChild(i),re("#"+i.id)},i.src=e.toDataURL()}Ce(i)}e(!0)})):Promise.resolve(!1)},be.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=n[this.host]||i[this.host];return t&&(this.currentHost=t),!!t&&this.currentHost}return!1},be.updateBaseHere=function(t,e){if(this.isBase){this.here||(this.here={});let i=this.here,s=this.currentDimensions,n=t.active,r=t.localListener?t.originalWidth:t.w,o=t.localListener?t.originalHeight:t.h;if(s[0]!==r||s[1]!==o){this.basePaste||(this.basePaste=[]);let a,h,c=this.basePaste[0],l=s[0],u=s[1],d=r,f=o,p=t.x,m=t.y,g=l/d||1,y=u/f||1,b=Math.round;switch(i.w=l,i.h=u,e){case"contain":case"cover":c?(a=(d-l/y)/2,i.x=b((p-a)*y),i.y=b(m*y)):(h=(f-u/g)/2,i.x=b(p*g),i.y=b((m-h)*g));break;case"fill":i.x=b(p*g),i.y=b(m*y);break;case"none":default:a=(d-l)/2,h=(f-u)/2,i.x=b(p-a),i.y=b(m-h)}(i.x<0||i.x>l)&&(n=!1),(i.y<0||i.y>u)&&(n=!1),i.active=n}else i.x=t.x,i.y=t.y,i.w=r,i.h=o,i.active=n;t.baseActive=n}},be.prepareStamp=function(){(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&(this.cleanDimensions(),this.dirtyAssetSubscribers=!0),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.dirtyAssetSubscribers&&(this.dirtyAssetSubscribers=!1,this.notifySubscribers())},be.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=this.currentStampHandlePosition,i=this.currentScale,s=this.currentDimensions,n=-e[0]*i,r=-e[1]*i,o=s[0]*i,a=s[1]*i;t.rect(n,r,o,a)}},be.updateHere=function(){this.here||(this.here={});let t=this.here,[e,i]=this.currentDimensions;t.w=e,t.h=i,t.x=-1e4,t.y=-1e4,t.active=!1;let s=this.currentHost;if(s){let e=s.here;if(e&&e.active){let{x:i,y:s,w:n,h:r}=e;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let o=xe(),a=o.engine,[h,c]=this.currentStampPosition;o.rotateDestination(a,h,c,this);let l=a.isPointInPath(this.pathObject,i,s);if(Ce(o),t.active=l,l){let[e,n]=this.currentStampHandlePosition,{flipUpend:r,flipReverse:o,roll:a,scale:l}=this;if(l){let u=(i-h)/l,d=(s-c)/l;if(o&&(u=-u),r&&(d=-d),a){(o&&!r||!o&&r)&&(a=-a);let t=_t(u,d);t.rotate(-a),[u,d]=t,Qt(t)}u+=e,d+=n,t.x=u,t.y=d}}}}},be.getEntityHits=function(){let t=[],e=[],i=[];return this.groupBuckets&&this.groupBuckets.forEach(t=>{t.visibility&&e.push(t.getAllArtefactsAt(this.here))},this),e.length&&(e=e.reduce((t,e)=>t.concat(e),[]),e.forEach(e=>{let s=e.artefact;s.visibility&&i.indexOf(s.name)<0&&(i.push(s.name),t.push(s))})),t},be.rotateDestination=function(t,e,i,s){let n,r,o=s||this,a=o.mimic,h=o.pivot,c=o.currentRotation;if(a&&a.name&&o.useMimicFlip?(n=a.flipReverse?-1:1,r=a.flipUpend?-1:1):(n=o.flipReverse?-1:1,r=o.flipUpend?-1:1),a&&a.name&&o.useMimicRotation?c=a.currentRotation:h&&h.name&&o.addPivotRotation&&(c=h.currentRotation),c){c*=v;let s=Math.cos(c),o=Math.sin(c);t.setTransform(s*n,o*n,-o*r,s*r,e,i)}else t.setTransform(n,0,0,r,e,i);return this};const ve=[];be.poolDefs={element:null,engine:null,state:null,width:300,height:100,alpha:1,composite:"source-over"};const xe=function(){return ve.length||ve.push(Ae({name:"pool_"+z(),isPool:!0})),ve.shift()},Ce=function(t){t&&"Cell"===t.type&&(t.engine.setTransform(1,0,0,1,0,0),ve.push(t.setToDefaults()))},Ae=function(t){return new Cell(t)};k.Cell=Cell;const Group=function(t={}){return this.makeName(t.name),this.register(),this.artefacts=[],this.artefactBuckets=[],this.set(this.defs),this.set(t),this};let we=Group.prototype=Object.create(Object.prototype);we.type="Group",we.lib="group",we.isArtefact=!1,we.isAsset=!1,we=Pt(we),we=ye(we);we.defs=G(we.defs,{artefacts:null,order:0,visibility:!0,regionRadius:0}),we.packetExclusions=Z(we.packetExclusions,["artefactBuckets","batchResort"]),we.postCloneAction=function(t,e){let s;return s=e.host?i[e.host]:this.currentHost?this.currentHost:!!this.host&&i[this.host],s&&(s.addGroups(t.name),t.host||(t.host=s.name)),t},we.kill=function(t=!1){let e=this.name;return Object.entries(i).forEach(([t,i])=>{Array.isArray(i.groups)&&i.groups.indexOf(e)>=0&&(_(i.groups,e),i.batchResort=!0)}),Object.entries(a).forEach(([t,i])=>{Array.isArray(i.groups)&&i.groups.indexOf(e)>=0&&(_(i.groups,e),i.batchResort=!0)}),t&&this.artefactBuckets.forEach(t=>t.kill()),this.deregister()},we.killArtefacts=function(){return this.artefactBuckets.forEach(t=>t.kill()),this};let De=we.getters,Oe=we.setters;De.artefacts=function(){return[].concat(this.artefacts)},Oe.artefacts=function(t){this.artefacts=[],this.addArtefacts(t)},Oe.host=function(t){let e=this.getHost(t);e&&e.addGroups&&(this.host=t,e.addGroups(this.name),this.dirtyHost=!0)},Oe.order=function(t){let e=this.getHost(this.host);this.order=t,e&&e.set({batchResort:!0})},we.getHost=function(t){let e=this.currentHost;return!e||e.substring?i[t]||a[t]||i[e]||a[e]||null:e},we.forceStamp=function(){var t=this;return new Promise(e=>{let i=t.visibility;t.visibility=!0,t.stamp().then(s=>{t.visibility=i,e(s)}).catch(s=>{t.visibility=i,e(s)})})},we.stamp=function(){if(this.dirtyHost||!this.currentHost){this.dirtyHost=!1;let t=this.getHost(this.host);t?this.currentHost=t:this.dirtyHost=!0}let t=this;return new Promise((e,i)=>{if(t.visibility){let s=t.currentHost;if(s){t.sortArtefacts();let n=!!(t.stashOutput||!t.noFilters&&t.filters&&t.filters.length)&&xe();if(n&&n.element){let t=s.currentDimensions;t&&(n.element.width=t[0],n.element.height=t[1]),n.engine.save()}else s.engine&&s.engine.save();t.prepareStamp(n),t.stampAction(n).then(i=>{n?(n.engine.restore(),Ce(n)):s.engine&&(s.engine.restore(),s.setEngineFromState(s.engine)),e(t.name)}).catch(t=>{n?(n.engine.restore(),Ce(n)):s.engine&&(s.engine.restore(),s.setEngineFromState(s.engine)),i(t)})}else e(!1)}else e(!1)})},we.sortArtefacts=function(){if(this.batchResort){this.batchResort=!1;let t=Math.floor,e=[];this.artefacts.forEach(s=>{let n=i[s],r=t(n.order)||0;e[r]||(e[r]=[]),e[r].push(n)}),this.artefactBuckets=e.reduce((t,e)=>t.concat(e),[])}},we.prepareStamp=function(t){let e=this.currentHost;t&&(e=t),this.artefactBuckets.forEach(i=>{"entity"===i.lib&&(i.currentHost&&i.currentHost.name===e.name||(i.currentHost=e,t||(i.dirtyHost=!0))),i.noDeltaUpdates||i.updateByDelta(),i.prepareStamp()})},we.stampAction=function(t){!this.currentHost||this.currentHost.stashOutput;!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let e=this,i=s=>new Promise((n,r)=>{let o=e.artefactBuckets[s];if(o&&o.stamp)o.stamp().then(()=>{i(s+1).then(t=>n(!0)).catch(t=>r(t))}).catch(t=>r(t));else if(t)if(!e.noFilters&&e.filters&&e.filters.length)e.applyFilters(t).then(t=>e.stashAction(t)).then(t=>n(!0)).catch(t=>r(t));else if(e.stashOutput){let i=t.element,s=t.engine,o=!(!e.currentHost||!e.currentHost.engine)&&e.currentHost.engine;if(o){o.save(),o.globalCompositeOperation="source-over",o.globalAlpha=1,o.setTransform(1,0,0,1,0,0),o.drawImage(i,0,0),o.restore();let t=s.getImageData(0,0,i.width,i.height);e.stashAction(t).then(t=>n(!0)).catch(t=>r(t))}else r("Could not find real engine")}else n(!0);else n(!0)});return i(0)},we.applyFilters=function(t){let e=this;return new Promise((i,s)=>{let n=e.currentHost,r=t;n&&r||s("Group.applyFilters - no host");let o=function(){Yt(d),h.save(),h.setTransform(1,0,0,1,0,0),h.drawImage(c,0,0),h.restore()},a=n.element,h=n.engine,c=r.element,l=r.engine;e.isStencil&&(l.save(),l.globalCompositeOperation="source-in",l.globalAlpha=1,l.setTransform(1,0,0,1,0,0),l.drawImage(a,0,0),l.restore()),l.setTransform(1,0,0,1,0,0);let u=l.getImageData(0,0,c.width,c.height),d=Xt();Wt(d,{image:u,filters:e.currentFilters}).then(t=>{if(!t)throw new Error("image issue");l.globalCompositeOperation="source-over",l.globalAlpha=1,l.setTransform(1,0,0,1,0,0),l.putImageData(t,0,0),o(),i(t)}).catch(t=>{o(),s(t)})})},we.stashAction=function(t){if(!t)return Promise.reject("No image data supplied to stashAction");if(this.stashOutput){this.stashOutput=!1;let e=this;return new Promise((i,s)=>{let[n,r,o,a]=e.getCellCoverage(t),h=xe(),c=h.engine,l=h.element;if(l.width=o,l.height=a,c.putImageData(t,-n,-r),e.stashedImageData=c.getImageData(0,0,o,a),e.stashOutputAsAsset)if(e.stashOutputAsAsset=!1,e.stashedImage)e.stashedImage.src=l.toDataURL();else{let t=e.stashedImage=document.createElement("img");t.id=e.name+"-groupimage",t.onload=function(){ki.appendChild(t),re("#"+t.id)},t.src=l.toDataURL()}Ce(h),i(!0)})}return Promise.resolve(!1)},we.getCellCoverage=function(t){let e,i,s=t.width,n=t.height,r=t.data,o=0,a=0,h=s,c=n,l=3;for(i=0;ie&&(h=e),oi&&(c=i),a{t&&(t.substring?Z(this.artefacts,t):t.name&&Z(this.artefacts,t.name))},this),this.batchResort=!0,this},we.removeArtefacts=function(...t){return t.forEach(t=>{t&&(t.substring?_(this.artefacts,t):t.name&&_(this.artefacts,t.name))},this),this.batchResort=!0,this},we.moveArtefactsIntoGroup=function(...t){let e,s;return t.forEach(t=>{t&&(s=t.substring?i[t]:t,s&&s.isArtefact&&(e=s.group?s.group:!!s.host&&u[s.host]),e&&(e.removeArtefacts(t),e.batchResort=!0),Z(this.artefacts,t))},this),this.batchResort=!0,this},we.clearArtefacts=function(){return this.artefacts.length=0,this.artefactBuckets.length=0,this.batchResort=!0,this},we.updateArtefacts=function(t){return this.cascadeAction(t,"setDelta"),this},we.setArtefacts=function(t){return this.cascadeAction(t,"set"),this},we.updateByDelta=function(){return this.cascadeAction(!1,"updateByDelta"),this},we.reverseByDelta=function(){return this.cascadeAction(!1,"reverseByDelta"),this},we.addArtefactClasses=function(t){return this.cascadeAction(t,"addClasses"),this},we.removeArtefactClasses=function(t){return this.cascadeAction(t,"removeClasses"),this},we.cascadeAction=function(t,e){return this.artefacts.forEach(s=>{let n=i[s];n&&n[e]&&n[e](t)}),this},we.setDeltaValues=function(t={}){return this.artefactBuckets.forEach(e=>e.setDeltaValues(t)),this},we.addFiltersToEntitys=function(...t){return this.artefacts.forEach(e=>{let i=c[e];i&&i.addFilters&&i.addFilters(t)}),this},we.removeFiltersFromEntitys=function(...t){return this.artefacts.forEach(e=>{let i=c[e];i&&i.removeFilters&&i.removeFilters(t)}),this},we.clearFiltersFromEntitys=function(){return this.artefacts.forEach(t=>{let e=c[t];e&&e.clearFilters&&e.clearFilters()}),this},we.getArtefactAt=function(t){let e=xe(),i=this.artefactBuckets;this.sortArtefacts();for(let s=i.length-1;s>=0;s--){let n=i[s];if(n){let i=n.checkHit(t,e);if(i)return Ce(e),i}}return Ce(e),!1},we.getAllArtefactsAt=function(t){let e=xe(),i=this.artefactBuckets,s=[],n=[];this.sortArtefacts();for(let r=i.length-1;r>=0;r--){let o=i[r];if(o){let i=o.checkHit(t,e);if(i&&i.artefact){let t=i.artefact;s.indexOf(t.name)<0&&(s.push(t.name),n.push(i))}}}return Ce(e),n},we.getArtefactCollisions=function(t){if(!t||!t.isArtefact||!this.artefactBuckets.length)return[];if(t.substring&&(t=i[t]),!t.collides)return[];let e,s,n,r=this.artefactBuckets,o=[],[a,h]=t.cleanCollisionData();for(s=0,n=r.length;sc&&(o[s]=!1)}let g=xe();for(s=0,n=o.length;s!!t)};const Ee=function(t){return new Group(t)};k.Group=Group;const Vector=function(t,e,i){return this.x=0,this.y=0,this.z=0,Q(t)&&this.set(t,e,i),this};let Te=Vector.prototype=Object.create(Object.prototype);Te.type="Vector",Te.getXYCoordinate=function(){return[this.x,this.y]},Te.getXYZCoordinate=function(){return[this.x,this.y,this.z]},Te.setX=function(t){if(!Q(t))throw new Error(`${this.name} Vector error - setX() arguments error: ${t}`);return this.x=t,this},Te.setY=function(t){if(!Q(t))throw new Error(`${this.name} Vector error - setY() arguments error: ${t}`);return this.y=t,this},Te.setZ=function(t){if(!Q(t))throw new Error(`${this.name} Vector error - setZ() arguments error: ${t}`);return this.z=t,this},Te.setXY=function(t,e){if(!K(t,e))throw new Error(`${this.name} Vector error - setXY() arguments error: ${t}, ${e}`);return this.x=t,this.y=e,this},Te.set=function(t,e,i){return V(t)?this.setFromVector(t):Array.isArray(t)?this.setFromArray(t):K(t,e)?this.setFromArray([t,e,i]):this},Te.setFromArray=function(t){if(!Array.isArray(t))throw new Error(`${this.name} Vector error - setFromArray() arguments error: ${t}`);let[e,i,s]=t;return W(e)&&(this.x=e),W(i)&&(this.y=i),W(s)&&(this.z=s),this},Te.setFromVector=function(t){if(!V(t))throw new Error(`${this.name} Vector error - setFromVector() arguments error: ${JSON.stringify(t)}`);let{x:e,y:i,z:s}=t;return W(e)&&(this.x=e),W(i)&&(this.y=i),W(s)&&(this.z=s),this},Te.zero=function(){return this.x=0,this.y=0,this.z=0,this},Te.vectorAdd=function(t={}){if(Array.isArray(t))return this.vectorAddArray(t);let{x:e,y:i,z:s}=t;return W(e)&&(this.x+=e),W(i)&&(this.y+=i),W(s)&&(this.z+=s),this},Te.vectorAddArray=function(t=[]){let[e,i,s]=t;return W(e)&&(this.x+=e),W(i)&&(this.y+=i),W(s)&&(this.z+=s),this},Te.vectorSubtract=function(t={}){if(Array.isArray(t))return this.vectorSubtractArray(t);let{x:e,y:i,z:s}=t;return W(e)&&(this.x-=e),W(i)&&(this.y-=i),W(s)&&(this.z-=s),this},Te.vectorSubtractArray=function(t){let[e,i,s]=t;return W(e)&&(this.x-=e),W(i)&&(this.y-=i),W(s)&&(this.z-=s),this},Te.scalarMultiply=function(t){if(!W(t))throw new Error(`${this.name} Vector error - scalarMultiply() argument not a number: ${t}`);return this.x*=t,this.y*=t,this.z*=t,this},Te.vectorMultiply=function(t={}){if(Array.isArray(t))return this.vectorMultiplyArray(t);let{x:e,y:i,z:s}=t;return W(e)&&(this.x*=e),W(i)&&(this.y*=i),W(s)&&(this.z*=s),this},Te.vectorMultiplyArray=function(t){let[e,i,s]=t;return W(e)&&(this.x*=e),W(i)&&(this.y*=i),W(s)&&(this.z*=s),this},Te.scalarDivide=function(t){if(!W(t))throw new Error(`${this.name} Vector error - scalarDivide() argument not a number: ${t}`);if(!t)throw new Error(`${this.name} Vector error - scalarDivide() division by zero: ${t}`);return this.x/=t,this.y/=t,this.z/=t,this},Te.getMagnitude=function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},Te.rotate=function(t){if(!W(t))throw new Error(`${this.name} Vector error - rotate() argument not a number: ${t}`);let e=Math.atan2(this.y,this.x);e+=.01745329251*t;let i=this.getMagnitude();return this.x=i*Math.cos(e),this.y=i*Math.sin(e),this},Te.reverse=function(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this},Te.normalize=function(){let t=this.getMagnitude();return t>0&&(this.x/=t,this.y/=t,this.z/=t),this};const Re=[],Fe=function(t,e,i){Re.length||Re.push(new Vector);let s=Re.shift();return s.set(t,e,i),s},He=function(t){t&&"Vector"===t.type&&Re.push(t.zero())},Le=function(t,e,i){return new Vector(t,e,i)};k.Vector=Vector;const Quaternion=function(t={}){return this.name=t.name||"generic",this.n=t.n||1,this.v=Le(),this.set(t),this};let je=Quaternion.prototype=Object.create(Object.prototype);je.type="Quaternion",je.set=function(t={}){if(q(t))return this.setFromQuaternion(t);if(t&&t.type&&"Vector"===t.type)return this.setFromVector(t);if(tt(t.pitch,t.yaw,t.roll))return this.setFromEuler(t);let e,i,s,n,r,o=this.v;return r=!(!Q(t.vector)&&!Q(t.v))&&(t.vector||t.v),n=!(!Q(t.scalar)&&!Q(t.n))&&(t.scalar||t.n||0),e=r?r.x||0:t.x||!1,i=r?r.y||0:t.y||!1,s=r?r.z||0:t.z||!1,this.n=W(n)?n:this.n,o.x=W(e)?e:o.x,o.y=W(i)?i:o.y,o.z=W(s)?s:o.z,this},je.setFromQuaternion=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - setFromQuaternion() bad argument: ${t}`);let e=this.v,i=t.v;return this.n=t.n,e.x=i.x,e.y=i.y,e.z=i.z,this},je.setFromEuler=function(t={}){let e,i,s,n,r,o,a,h,c,l=Math.cos,u=Math.sin,d=this.v;return e=(t.pitch||t.x||0)*v,i=(t.yaw||t.y||0)*v,s=(t.roll||t.z||0)*v,n=l(e/2),r=l(i/2),o=l(s/2),a=u(e/2),h=u(i/2),c=u(s/2),d.x=a*r*o+n*h*c,d.y=n*h*o+a*r*c,d.z=n*r*c-a*h*o,this.n=n*r*o-a*h*c,this},je.zero=function(){let t=this.v;return this.n=1,t.x=0,t.y=0,t.z=0,this},je.getMagnitude=function(){let t=this.v;return Math.sqrt(this.n*this.n+t.x*t.x+t.y*t.y+t.z*t.z)},je.normalize=function(){let t=this.getMagnitude(),e=this.v;if(!t)throw new Error(`${this.name} Quaternion error - normalize() division by zero: ${t}`);return this.n/=t,this.n=this.n>-1e-6&&this.n<1e-6?0:this.n,e.x/=t,e.x=e.x>-1e-6&&e.x<1e-6?0:e.x,e.y/=t,e.y=e.y>-1e-6&&e.y<1e-6?0:e.y,e.z/=t,e.z=e.z>-1e-6&&e.z<1e-6?0:e.z,this},je.quaternionMultiply=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - quaternionMultiply() bad argument: ${t}`);let e=this.v,i=t.v,s=this.n,n=e.x,r=e.y,o=e.z,a=t.n,h=i.x,c=i.y,l=i.z;return this.n=s*a-n*h-r*c-o*l,e.x=s*h+n*a+r*l-o*c,e.y=s*c+r*a+o*h-n*l,e.z=s*l+o*a+n*c-r*h,this},je.getAngle=function(t){let e;return t=!!Q(t)&&t,e=2*Math.acos(this.n),t&&(e*=1/v),e>-1e-6&&e<1e-6?0:e},je.quaternionRotate=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - quaternionRotate() bad argument: ${t}`);let e=$e(t),i=$e(this);return this.setFromQuaternion(e.quaternionMultiply(i)),Me(e),Me(i),this};const Be=[],$e=function(t){Be.length||Be.push(ze({name:"pool"}));let e=Be.shift();return e.set(t),e},Me=function(t){t&&"Quaternion"===t.type&&Be.push(t.zero())},ze=function(t={}){return new Quaternion(t)};function Ie(t={}){(t=pe(t=ue(t=le(t=ce(t=he(t=ae(t))))))).defs=G(t.defs,{domElement:"",pitch:0,yaw:0,offsetZ:0,css:null,classes:"",position:"absolute",checkForResize:!1,trackHere:"",activePadding:5}),t.packetExclusions=Z(t.packetExclusions,["domElement","pathCorners","rotation"]),t.packetFunctions=Z(t.packetFunctions,["onEnter","onLeave","onDown","onUp"]),t.processDOMPacketOut=function(t,e,i){return this.processFactoryPacketOut(t,e,i)},t.processFactoryPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){if(Y(this.domElement)){let e=this.domElement,i=e.cloneNode(!0);i.querySelectorAll('[data-corner-div="sc"]').forEach(t=>i.removeChild(t)),t.outerHTML=i.outerHTML,t.host=e.parentElement.id}return t=this.handlePacketAnchor(t,e)},t.postCloneAction=function(t,e){return this.onEnter&&(t.onEnter=this.onEnter),this.onLeave&&(t.onLeave=this.onLeave),this.onDown&&(t.onDown=this.onDown),this.onUp&&(t.onUp=this.onUp),t};let e=t.setters,s=t.deltaSetters;e.trackHere=function(t){var e;Q(t)&&(t?(Z(Ct,this.name),"local"===t&&(V(e=this)&&(e.localMouseListener&&e.localMouseListener(),e.here||(e.here={}),e.here.originalWidth=e.currentDimensions[0],e.here.originalHeight=e.currentDimensions[1],e.localMouseListener=it("move",(function(t){e.here&&(e.here.x=Math.round(parseFloat(t.offsetX)),e.here.y=Math.round(parseFloat(t.offsetY)))}),e.domElement)))):(_(Ct,this.name),function(t){V(t)&&(t.localMouseListener&&t.localMouseListener(),t.localMouseListener=!1)}(this)),this.trackHere=t)},e.position=function(t){this.position=t,this.dirtyPosition=!0},e.visibility=function(t){this.visibility=t,this.dirtyVisibility=!0},e.offsetZ=function(t){this.offsetZ=t,this.dirtyOffsetZ=!0},s.offsetZ=function(t){this.offsetZ+=t,this.dirtyOffsetZ=!0},e.roll=function(t){this.roll=this.checkRotationAngle(t),this.dirtyRotation=!0},s.roll=function(t){this.roll=this.checkRotationAngle(this.roll+t),this.dirtyRotation=!0},e.pitch=function(t){this.pitch=this.checkRotationAngle(t),this.dirtyRotation=!0},s.pitch=function(t){this.pitch=this.checkRotationAngle(this.pitch+t),this.dirtyRotation=!0},e.yaw=function(t){this.yaw=this.checkRotationAngle(t),this.dirtyRotation=!0},s.yaw=function(t){this.yaw=this.checkRotationAngle(this.yaw+t),this.dirtyRotation=!0},e.css=function(t){this.css=this.css?G(this.css,t):t,this.dirtyCss=!0},e.classes=function(t){this.classes=t,this.dirtyClasses=!0},e.domAttributes=function(t){this.updateDomAttributes(t)},t.checkRotationAngle=function(t){return(t<-180||t>180)&&(t+=t>0?-360:360),t},t.updateDomAttributes=function(t,e){if(this.domElement){let i=this.domElement;t.substring&&Q(e)?e?i.setAttribute(t,e):i.removeAttribute(t):V(t)&&Object.entries(t).forEach(([t,e])=>{e?i.setAttribute(t,e):i.removeAttribute(t)})}return this},t.initializeDomLayout=function(t){let e=t.domElement,s=e.style;if(s.boxSizing="border-box",e&&t.setInitialDimensions){let n,r=e.getBoundingClientRect(),o=(e.style.transform,e.style.transformOrigin,!1);if(t&&t.host&&(o=t.host,o.substring&&i[o]&&(o=i[o])),this.currentDimensions[0]=r.width,this.currentDimensions[1]=r.height,t.width=r.width,t.height=r.height,e.className&&(t.classes=e.className),o&&o.domElement&&(n=o.domElement.getBoundingClientRect(),n&&(t.startX=r.left-n.left,t.startY=r.top-n.top)),"Stack"===this.type){Q(t.perspective)||Q(t.perspectiveZ)||(t.perspectiveZ=Q(s.perspective)&&s.perspective?parseFloat(s.perspective):0);let e=s.perspectiveOrigin;e.length&&(e=e.split(" "),e.length>0&&!Q(t.perspective)&&!Q(t.perspectiveX)&&(t.perspectiveX=e[0]),Q(t.perspective)||Q(t.perspectiveY)||(e.length>1?t.perspectiveY=e[1]:t.perspectiveY=e[0]))}}},t.addClasses=function(t){if(t.substring){let e=this.classes;e+=" "+t,e=e.trim(),e=e.replace(/[\s\uFEFF\xA0]+/g," "),e!==this.classes&&(this.classes=e,this.dirtyClasses=!0)}return this},t.removeClasses=function(t){if(t.substring){let e,i=this.classes;t.split().forEach(t=>{e=new RegExp(" ?"+t+" ?"),i=i.replace(e," "),i=i.trim(),i=i.replace(/[\s\uFEFF\xA0]+/g," ")}),i!==this.classes&&(this.classes=i,this.dirtyClasses=!0)}return this},t.addPathCorners=function(){if(this.domElement&&!this.noUserInteraction){let t=function(){let t=document.createElement("div");return t.style.width=0,t.style.height=0,t.style.position="absolute",t},e=t(),i=t(),s=t(),n=t();e.style.top="0%",e.style.left="0%",e.setAttribute("data-corner-div","sc"),i.style.top="0%",i.style.left="100%",i.setAttribute("data-corner-div","sc"),s.style.top="100%",s.style.left="100%",s.setAttribute("data-corner-div","sc"),n.style.top="100%",n.style.left="0%",n.setAttribute("data-corner-div","sc");let r=this.domElement;r.appendChild(e),r.appendChild(i),r.appendChild(s),r.appendChild(n),this.pathCorners.push(e),this.pathCorners.push(i),this.pathCorners.push(s),this.pathCorners.push(n),this.currentCornersData||(this.currentCornersData=[])}return this},t.checkCornerPositions=function(t){let e=this.pathCorners;if(4===e.length){let i,s=this.getHere(),n=Dt.scrollX-(s.offsetX||0),r=Dt.scrollY-(s.offsetY||0),o=Math.round,a=[];const h=function(t){let e=t[0];e?(a.push(o(e.left+n)),a.push(o(e.top+r))):a.push(0,0)};switch(t){case"topLeft":return i=e[0].getClientRects(),h(i),a;case"topRight":return i=e[1].getClientRects(),h(i),a;case"bottomRight":return i=e[2].getClientRects(),h(i),a;case"bottomLeft":return i=e[3].getClientRects(),h(i),a;default:return e.forEach(t=>{Y(t)&&(i=t.getClientRects(),h(i))}),a}}};const n=["topLeft","topRight","bottomRight","bottomLeft"];return t.getCornerCoordinate=function(t){return n.indexOf(t)>=0?this.checkCornerPositions(t):[].concat(this.currentStampPosition)},t.cleanPathObject=function(){if(this.dirtyPathObject=!1,this.domElement&&!this.noUserInteraction){this.pathCorners.length||this.addPathCorners(),this.currentCornersData||(this.currentCornersData=[]);let t=this.currentCornersData;t.length=0,t.push(...this.checkCornerPositions());let e=this.pathObject=new Path2D;e.moveTo(t[0],t[1]),e.lineTo(t[2],t[3]),e.lineTo(t[4],t[5]),e.lineTo(t[6],t[7]),e.closePath()}},t.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=requestCell(),s=!0);let n,r,o=e.engine,a=this.currentStampPosition;a[0],a[1];return i.some(t=>{if(Array.isArray(t))n=t[0],r=t[1];else{if(!K(t,t.x,t.y))return!1;n=t.x,r=t.y}return!(!n.toFixed||!r.toFixed||isNaN(n)||isNaN(r))&&o.isPointInPath(this.pathObject,n,r)},this)?(s&&releaseCell(e),{x:n,y:r,artefact:this}):(s&&releaseCell(e),!1)},t.cleanRotation=function(){this.dirtyRotation=!1,this.rotation&&q(this.rotation)||(this.rotation=ze()),this.currentRotation&&q(this.rotation)||(this.currentRotation=ze());let t=this.rotation;t.setFromEuler({pitch:this.pitch||0,yaw:this.yaw||0,roll:this.roll||0}),1!==t.getMagnitude()&&t.normalize();let e=$e(),i=this.path,s=this.mimic,n=this.pivot,r=this.lockTo;i&&r.indexOf("path")>=0?e.set(t):s&&this.useMimicRotation&&r.indexOf("mimic")>=0?Q(s.currentRotation)?(e.set(s.currentRotation),this.addOwnRotationToMimic&&e.quaternionRotate(t)):this.dirtyMimicRotation=!0:(e.set(t),n&&this.addPivotRotation&&r.indexOf("pivot")>=0&&(Q(n.currentRotation)?e.quaternionRotate(n.currentRotation):this.dirtyPivotRotation=!0)),this.currentRotation.set(e),Me(e),this.dirtyPositionSubscribers=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicRotation=!0)},t.cleanOffsetZ=function(){this.dirtyOffsetZ=!1},t.cleanContent=function(){this.dirtyContent=!1,this.domElement&&(this.dirtyDimensions=!0)},t.cleanDisplayShape=B,t.cleanDisplayArea=B,t.prepareStamp=function(){(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle||this.dirtyRotation)&&(this.dirtyPathObject=!0),this.dirtyContent&&this.cleanContent(),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyDisplayArea&&this.cleanDisplayArea(),this.dirtyDisplayShape&&this.cleanDisplayShape(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyOffsetZ&&this.cleanOffsetZ(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.pivoted.length&&(this.dirtyStampPositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject()},t.stamp=function(){let t=this;return new Promise((e,i)=>{t.domElement||i(!1);let s,n,r,o,a,[h,c]=t.currentStampPosition,[l,u]=t.currentStampHandlePosition,d=t.currentScale,f=t.currentRotation,p=`${l}px ${u}px 0`,m=`translate(${h-l}px,${c-u}px)`;(t.yaw||t.pitch||t.roll||t.pivot&&t.addPivotRotation||t.mimic&&t.useMimicRotation||t.path&&t.addPathRotation)&&(s=f.v,n=s.x,r=s.y,o=s.z,a=f.getAngle(!1),m+=` rotate3d(${n},${r},${o},${a}rad)`),t.offsetZ&&(m+=` translateZ(${t.offsetZ}px)`),1!==d&&(m+=` scale(${d},${d})`),m!==t.currentTransformString&&(t.currentTransformString=m,t.dirtyTransform=!0),p!==t.currentTransformOriginString&&(t.currentTransformOriginString=p,t.dirtyTransformOrigin=!0),(t.dirtyTransform||t.dirtyPerspective||t.dirtyPosition||t.dirtyDomDimensions||t.dirtyTransformOrigin||t.dirtyVisibility||t.dirtyCss||t.dirtyClasses||t.domShowRequired)&&(yi(t.name),Si(!0)),t.dirtyPositionSubscribers&&t.updatePositionSubscribers(),(t.dirtyMimicRotation||t.dirtyPivotRotation)&&(t.dirtyMimicRotation=!1,t.dirtyPivotRotation=!1,t.dirtyRotation=!0),t.dirtyMimicScale&&(t.dirtyMimicScale=!1,t.dirtyScale=!0),e(!0)})},t.apply=function(){$t(),this.prepareStamp();let t=this;this.stamp().then(()=>{Pi(t.name),t.dirtyPathObject=!0,t.cleanPathObject()}).catch(t=>console.log(t))},t}function Xe(t={}){let e={breakToBanner:3,breakToLandscape:1.5,breakToPortrait:.65,breakToSkyscraper:.35,actionBannerShape:null,actionLandscapeShape:null,actionRectangleShape:null,actionPortraitShape:null,actionSkyscraperShape:null,breakToSmallest:2e4,breakToSmaller:8e4,breakToLarger:18e4,breakToLargest:32e4,actionSmallestArea:null,actionSmallerArea:null,actionRegularArea:null,actionLargerArea:null,actionLargestArea:null};t.defs=G(t.defs,e),G(t,e),t.packetFunctions=Z(t.packetFunctions,["actionBannerShape","actionLandscapeShape","actionRectangleShape","actionPortraitShape","actionSkyscraperShape"]);let i=t.getters,s=t.setters;return i.displayShape=function(){return this.currentDisplayShape},i.displayShapeBreakpoints=function(){return{breakToBanner:this.breakToBanner,breakToLandscape:this.breakToLandscape,breakToPortrait:this.breakToPortrait,breakToSkyscraper:this.breakToSkyscraper,breakToSmallest:this.breakToSmallest,breakToSmaller:this.breakToSmaller,breakToLarger:this.breakToLarger,breakToLargest:this.breakToLargest}},s.displayShapeBreakpoints=function(t={}){for(let[e,i]of Object.entries(t))if(W(i))switch(e){case"breakToBanner":this.breakToBanner=i;break;case"breakToLandscape":this.breakToLandscape=i;break;case"breakToPortrait":this.breakToPortrait=i;break;case"breakToSkyscraper":this.breakToSkyscraper=i;break;case"breakToSmallest":this.breakToSmallest=i;break;case"breakToSmaller":this.breakToSmaller=i;break;case"breakToLarger":this.breakToLarger=i;break;case"breakToLargest":this.breakToLargest=i}this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},t.setDisplayShapeBreakpoints=s.displayShapeBreakpoints,s.breakToBanner=function(t){W(t)&&(this.breakToBanner=t),this.dirtyDisplayShape=!0},s.breakToLandscape=function(t){W(t)&&(this.breakToLandscape=t),this.dirtyDisplayShape=!0},s.breakToPortrait=function(t){W(t)&&(this.breakToPortrait=t),this.dirtyDisplayShape=!0},s.breakToSkyscraper=function(t){W(t)&&(this.breakToSkyscraper=t),this.dirtyDisplayShape=!0},s.breakToSmallest=function(t){W(t)&&(this.breakToSmallest=t),this.dirtyDisplayArea=!0},s.breakToSmaller=function(t){W(t)&&(this.breakToSmaller=t),this.dirtyDisplayArea=!0},s.breakToLarger=function(t){W(t)&&(this.breakToLarger=t),this.dirtyDisplayArea=!0},s.breakToLargest=function(t){W(t)&&(this.breakToLargest=t),this.dirtyDisplayArea=!0},s.actionBannerShape=function(t){N(t)&&(this.actionBannerShape=t),this.dirtyDisplayShape=!0},t.setActionBannerShape=s.actionBannerShape,s.actionLandscapeShape=function(t){N(t)&&(this.actionLandscapeShape=t),this.dirtyDisplayShape=!0},t.setActionLandscapeShape=s.actionLandscapeShape,s.actionRectangleShape=function(t){N(t)&&(this.actionRectangleShape=t),this.dirtyDisplayShape=!0},t.setActionRectangleShape=s.actionRectangleShape,s.actionPortraitShape=function(t){N(t)&&(this.actionPortraitShape=t),this.dirtyDisplayShape=!0},t.setActionPortraitShape=s.actionPortraitShape,s.actionSkyscraperShape=function(t){N(t)&&(this.actionSkyscraperShape=t),this.dirtyDisplayShape=!0},t.setActionSkyscraperShape=s.actionSkyscraperShape,s.actionSmallestArea=function(t){N(t)&&(this.actionSmallestArea=t),this.dirtyDisplayArea=!0},t.setActionSmallestArea=s.actionSmallestArea,s.actionSmallerArea=function(t){N(t)&&(this.actionSmallerArea=t),this.dirtyDisplayArea=!0},t.setActionSmallerArea=s.actionSmallerArea,s.actionRegularArea=function(t){N(t)&&(this.actionRegularArea=t),this.dirtyDisplayArea=!0},t.setActionRegularArea=s.actionRegularArea,s.actionLargerArea=function(t){N(t)&&(this.actionLargerArea=t),this.dirtyDisplayArea=!0},t.setActionLargerArea=s.actionLargerArea,s.actionLargestArea=function(t){N(t)&&(this.actionLargestArea=t),this.dirtyDisplayArea=!0},t.setActionLargestArea=s.actionLargestArea,t.initializeDisplayShapeActions=function(){this.actionBannerShape=B,this.actionLandscapeShape=B,this.actionRectangleShape=B,this.actionPortraitShape=B,this.actionSkyscraperShape=B,this.currentDisplayShape="",this.dirtyDisplayShape=!0,this.actionSmallestArea=B,this.actionSmallerArea=B,this.actionRegularArea=B,this.actionLargerArea=B,this.actionLargestArea=B,this.currentDisplayArea="",this.dirtyDisplayArea=!0},t.cleanDisplayShape=function(){this.dirtyDisplayShape=!1;let[t,e]=this.currentDimensions;if(t>0&&e>0){let i=t/e,s=this.currentDisplayShape,n=this.breakToBanner,r=this.breakToLandscape,o=this.breakToPortrait,a=this.breakToSkyscraper;return i>n?"banner"!==s&&(this.currentDisplayShape="banner",this.actionBannerShape(),!0):i>r?"landscape"!==s&&(this.currentDisplayShape="landscape",this.actionLandscapeShape(),!0):i0&&e>0){let i=t*e,s=this.currentDisplayArea,n=this.breakToLargest,r=this.breakToLarger,o=this.breakToSmaller,a=this.breakToSmallest;return i>n?"largest"!==s&&(this.currentDisplayArea="largest",this.actionLargestArea(),!0):i>r?"larger"!==s&&(this.currentDisplayArea="larger",this.actionLargerArea(),!0):i=0?t:"none"},We.title=function(t){this.title=t,this.dirtyAria=!0},We.label=function(t){this.label=t,this.dirtyAria=!0},We.description=function(t){this.description=t,this.dirtyAria=!0},Ne.backgroundColor=function(){return this.base.backgroundColor},We.backgroundColor=function(t){this.base&&this.base.set({backgroundColor:t})},Ne.alpha=function(){return this.base.alpha},We.alpha=function(t){this.base&&this.base.set({alpha:t})},Ve.alpha=function(t){this.base&&this.base.deltaSet({alpha:t})},Ne.composite=function(){return this.base.composite},We.composite=function(t){this.base&&this.base.set({composite:t})},Ye.setAsCurrentCanvas=function(){return this.base&&li(this),this},Ye.setBase=function(t){return this.base&&(this.base.set(t),this.setBaseHelper()),this},Ye.deltaSetBase=function(t){return this.base&&(this.base.deltaSet(t),this.setBaseHelper()),this},Ye.updateBaseHere=function(){this.base&&this.base.updateBaseHere(this.here,this.fit)},Ye.setBaseHelper=function(){let t={};this.base.dirtyScale&&(t.dirtyScale=!0),this.base.dirtyDimensions&&(t.dirtyDimensions=!0),this.base.dirtyLock&&(t.dirtyLock=!0),this.base.dirtyStart&&(t.dirtyStart=!0),this.base.dirtyOffset&&(t.dirtyOffset=!0),this.base.dirtyHandle&&(t.dirtyHandle=!0),this.base.dirtyRotation&&(t.dirtyRotation=!0),this.cleanCells(),this.base.prepareStamp(),this.updateCells(t)},Ye.updateCells=function(t={}){this.cells.forEach(e=>{let i=a[e];i&&(t.dirtyScale&&(i.dirtyScale=!0),t.dirtyDimensions&&(i.dirtyDimensions=!0),t.dirtyLock&&(i.dirtyLock=!0),t.dirtyStart&&(i.dirtyStart=!0),t.dirtyOffset&&(i.dirtyOffset=!0),t.dirtyHandle&&(i.dirtyHandle=!0),t.dirtyRotation&&(i.dirtyRotation=!0))})},Ye.buildCell=function(t={}){t.host||!1||(t.host=this.base.name);let e=Ae(t);return this.addCell(e),this.cleanCells(),e},Ye.cleanDimensionsAdditionalActions=function(){this.cells&&this.updateCells({dirtyDimensions:!0}),this.dirtyDomDimensions=!0,this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},Ye.addCell=function(t){return(t=t.substring?t:t.name||!1)&&(Z(this.cells,t),a[t].prepareStamp(),this.dirtyCells=!0),t},Ye.removeCell=function(t){return(t=t.substring?t:t.name||!1)&&(_(this.cells,t),this.dirtyCells=!0),this},Ye.killCell=function(t){let e=t.substring?a[t]:t;return e&&e.kill(),this.dirtyCells=!0,this},Ye.clear=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesClear[i];r?r.clear().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):s(!0)});return e(0)},Ye.compile=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesCompile[i];r?r.compile().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):(t.prepareStamp(),t.stamp().then(t=>s(!0)).catch(t=>n(t)))});return e(0)},Ye.show=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesShow[i];r?r.show().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):(t.engine.clearRect(0,0,t.localWidth,t.localHeight),t.base.show().then(t=>{Pi(),this.dirtyAria&&this.cleanAria(),s(!0)}).catch(t=>n(t)))});return e(0)},Ye.render=function(){let t=this;return new Promise(e=>{t.clear().then(()=>t.compile()).then(()=>t.show()).then(()=>e(!0)).catch(()=>e(!1))})},Ye.cleanCells=function(){this.dirtyCells=!1;let t,e=[],i=[],s=[];this.cells.forEach(n=>{let r=a[n];r&&(r.cleared&&e.push(r),r.compiled&&(t=r.compileOrder,i[t]||(i[t]=[]),i[t].push(r)),r.shown&&(t=r.showOrder,s[t]||(s[t]=[]),s[t].push(r)))}),this.cellBatchesClear=[].concat(e),this.cellBatchesCompile=i.reduce((t,e)=>t.concat(e),[]),this.cellBatchesShow=s.reduce((t,e)=>t.concat(e),[])},Ye.cascadeEventAction=function(t){this.currentActiveEntityNames||(this.currentActiveEntityNames=[]);let e=this.currentActiveEntityNames,s=[],n=[],r=[],o=[],h=[],c=[];this.cells.forEach(t=>{let e=a[t];e&&(e.shown||e.isBase)&&s.push(e.getEntityHits())}),s=s.reduce((t,e)=>t.concat(e),[]),s.forEach(t=>{let i=t.name;n.indexOf(i)<0&&(n.push(i),e.indexOf(i)<0?(r.push(t),o.push(i)):(h.push(t),c.push(i)))});let l=r.concat(h),u=function(){e.length&&(o.forEach(t=>_(e,t)),c.forEach(t=>_(e,t)),e.forEach(t=>{let e=i[t];e&&e.onLeave()}))};switch(t){case"down":l.forEach(t=>t.onDown());break;case"up":l.forEach(t=>t.onUp());break;case"enter":r.forEach(t=>t.onEnter());break;case"leave":u();break;case"move":u(),r.forEach(t=>t.onEnter())}return this.currentActiveEntityNames=o.concat(c),this.currentActiveEntityNames},Ye.cleanAria=function(){this.dirtyAria=!1,this.domElement.setAttribute("title",this.title),this.ariaLabelElement.textContent=this.label,this.ariaDescriptionElement.textContent=this.description};const qe=function(t){return new Canvas(t)};k.Canvas=Canvas;const Element=function(t={}){let e=t.domElement;return this.makeName(t.name),this.register(),e&&(t.text?e.textContent=t.text:t.content&&(e.innerHTML=t.content)),this.initializePositions(),this.dimensions[0]=this.dimensions[1]=100,this.pathCorners=[],this.css={},this.here={},this.initializeDomLayout(t),this.set(this.defs),this.set(t),e=this.domElement,e&&(e.id=this.name),this.apply(),this};let Ge=Element.prototype=Object.create(Object.prototype);Ge.type="Element",Ge.lib="element",Ge.isArtefact=!0,Ge.isAsset=!1,Ge=Pt(Ge),Ge=Ie(Ge),Ge.factoryKill=function(){_(Ct,this.name),this.domElement.remove()};let Ue=Ge.setters;Ue.text=function(t){if(Y(this.domElement)){let e=this.domElement,i=e.querySelectorAll('[data-corner-div="sc"]');e.textContent=t,i.forEach(t=>e.appendChild(t)),this.dirtyContent=!0}},Ue.content=function(t){if(this.domElement){let e=this.domElement,i=e.querySelectorAll('[data-corner-div="sc"]');e.innerHTML=t,i.forEach(t=>e.appendChild(t)),this.dirtyContent=!0}},Ge.cleanDimensionsAdditionalActions=function(){this.dirtyDomDimensions=!0},Ge.addCanvas=function(t={}){if(!this.canvas){let e=document.createElement("canvas"),i=this.domElement,s=i.getBoundingClientRect();window.getComputedStyle(i);i.parentNode.insertBefore(e,this.domElement);let n=qe({name:this.name+"-canvas",domElement:e,position:"absolute",width:s.width,height:s.height,mimic:this.name,lockTo:"mimic",useMimicDimensions:!0,useMimicScale:!0,useMimicStart:!0,useMimicHandle:!0,useMimicOffset:!0,useMimicRotation:!0,addOwnDimensionsToMimic:!1,addOwnScaleToMimic:!1,addOwnStartToMimic:!1,addOwnHandleToMimic:!1,addOwnOffsetToMimic:!1,addOwnRotationToMimic:!1});return n.set(t),this.canvas=n,n}};const Ze=function(t){return new Element(t)};k.Element=Element;const Stack=function(t={}){let e,i;return this.makeName(t.name),this.register(),this.initializePositions(),this.initializeCascade(),this.dimensions[0]=300,this.dimensions[1]=150,this.pathCorners=[],this.css={},this.here={},this.perspective={x:"50%",y:"50%",z:0},this.dirtyPerspective=!0,this.initializeDomLayout(t),e=Ee({name:this.name,host:this.name}),this.addGroups(e.name),this.set(this.defs),this.initializeDisplayShapeActions(),this.set(t),i=this.domElement,i&&"root"===i.getAttribute("data-group")&&(Z(ei,this.name),ii()),this};let _e=Stack.prototype=Object.create(Object.prototype);_e.type="Stack",_e.lib="stack",_e.isArtefact=!0,_e.isAsset=!1,_e=Pt(_e),_e=me(_e),_e=Ie(_e),_e=Xe(_e);_e.defs=G(_e.defs,{position:"relative",perspective:null,trackHere:"subscribe",isResponsive:!1,containElementsInHeight:!1}),_e.stringifyFunction=B,_e.processPacketOut=B,_e.finalizePacketOut=B,_e.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},_e.clone=$,_e.factoryKill=function(){let t=this.name;_(ei,t),ii(),_(Ct,t),u[t]&&u[t].kill(),Object.entries(i).forEach(([e,i])=>{i.host===t&&i.kill()}),this.domElement.remove()};let Qe=_e.getters,Ke=_e.setters,Je=_e.deltaSetters;Qe.perspectiveX=function(){return this.perspective.x},Qe.perspectiveY=function(){return this.perspective.y},Qe.perspectiveZ=function(){return this.perspective.z},Ke.perspectiveX=function(t){this.perspective.x=t,this.dirtyPerspective=!0},Ke.perspectiveY=function(t){this.perspective.y=t,this.dirtyPerspective=!0},Ke.perspectiveZ=function(t){this.perspective.z=t,this.dirtyPerspective=!0},Ke.perspective=function(t={}){this.perspective.x=Q(t.x)?t.x:this.perspective.x,this.perspective.y=Q(t.y)?t.y:this.perspective.y,this.perspective.z=Q(t.z)?t.z:this.perspective.z,this.dirtyPerspective=!0},Je.perspectiveX=function(t){this.perspective.x=H(this.perspective.x,t),this.dirtyPerspective=!0},Je.perspectiveY=function(t){this.perspective.y=H(this.perspective.y,t),this.dirtyPerspective=!0},_e.updateArtefacts=function(t={}){this.groupBuckets.forEach(e=>{e.artefactBuckets.forEach(e=>{t.dirtyScale&&(e.dirtyScale=!0),t.dirtyDimensions&&(e.dirtyDimensions=!0),t.dirtyLock&&(e.dirtyLock=!0),t.dirtyStart&&(e.dirtyStart=!0),t.dirtyOffset&&(e.dirtyOffset=!0),t.dirtyHandle&&(e.dirtyHandle=!0),t.dirtyRotation&&(e.dirtyRotation=!0),t.dirtyPathObject&&(e.dirtyPathObject=!0)})})},_e.cleanDimensionsAdditionalActions=function(){this.groupBuckets&&this.updateArtefacts({dirtyDimensions:!0,dirtyPath:!0,dirtyStart:!0,dirtyHandle:!0}),this.dirtyDomDimensions=!0,this.dirtyPath=!0,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},_e.cleanPerspective=function(){this.dirtyPerspective=!1;let t=this.perspective;this.domPerspectiveString=`perspective-origin: ${t.x} ${t.y}; perspective: ${t.z}px;`,this.domShowRequired=!0,this.groupBuckets&&this.updateArtefacts({dirtyHandle:!0,dirtyPathObject:!0})},_e.checkResponsive=function(){this.isResponsive&&this.trackHere&&(this.currentVportWidth||(this.currentVportWidth=Dt.w),this.currentVportHeight||(this.currentVportHeight=Dt.h),this.dirtyHeight&&this.containElementsInHeight&&(console.log("stack height final fixes need to be done"),this.dirtyHeight=!1),this.currentVportWidth!==Dt.w&&(console.log("need to update for resized viewport width"),this.currentVportWidth=Dt.w,this.containElementsInHeight&&(this.dirtyHeight=!0)),this.currentVportHeight!==Dt.h&&(console.log("need to update for resized viewport height"),this.currentVportHeight=Dt.h))},_e.clear=function(){return this.checkResponsive(),Promise.resolve(!0)},_e.compile=function(){let t=this;return new Promise((e,i)=>{t.sortGroups(),t.prepareStamp(),t.stamp().then(()=>{let e=[];return t.groupBuckets.forEach(t=>e.push(t.stamp())),Promise.all(e)}).then(()=>e(!0)).catch(t=>i(!1))})},_e.show=function(){return new Promise(t=>{Pi(),t(!0)})},_e.render=function(){let t=this;return new Promise((e,i)=>{t.compile().then(()=>t.show()).then(()=>e(!0)).catch(t=>i(!1))})},_e.addExistingDomElements=function(t){let e,i,s,n,r;if(Q(t))for(e=t.substring?document.querySelectorAll(t):[].concat(t),n=0,r=e.length;n{ni=!0},si=[],ni=!0;const ri=function(t){let e=t.getAttribute("data-group"),i=t.id||t.getAttribute("name"),s="absolute";e||(t.setAttribute("data-group","root"),e="root",s="relative"),i||(i=z(),t.id=i);let n=ti({name:i,domElement:t,group:e,host:e,position:s,setInitialDimensions:!0});return oi(t,i),n},oi=function(t,e){let i=t.getBoundingClientRect(),s=0;Array.from(t.children).forEach(t=>{if(null!=t.getAttribute("data-stack")||X(t)||"SCRIPT"===t.tagName)t.setAttribute("data-group",e);else{let n=t.getBoundingClientRect(),r=window.getComputedStyle(t),o=parseFloat(r.marginTop)+parseFloat(r.borderTopWidth)+parseFloat(r.paddingTop)+parseFloat(r.paddingBottom)+parseFloat(r.borderBottomWidth)+parseFloat(r.marginBottom);s=s||n.top-i.top;let a={name:t.id||t.getAttribute("name"),domElement:t,group:e,host:e,position:"absolute",width:n.width,height:n.height,startX:n.left-i.left,startY:s,classes:t.className?t.className:""};s+=o+n.height,Ze(a)}})},ai=function(t){let e=t.getAttribute("data-group"),i=t.id||t.getAttribute("name"),s="absolute";return e||(t.setAttribute("data-group","root"),e="root",s=t.style.position),i||(i=z(),t.id=i),qe({name:i,domElement:t,group:e,host:e,position:s,setInitialDimensions:!0})};let hi=null,ci=null;const li=function(t){let e=!1;if(t)if(t.substring){let i=o[t];i&&(hi=i,e=!0)}else"Canvas"===t.type&&(hi=t,e=!0);if(e&&hi.base){let t=u[hi.base.name];t&&(ci=t)}},ui=function(...t){return pi(t),mi("clear")},di=function(...t){return pi(t),mi("compile")},fi=function(...t){return pi(t),mi("show")},pi=function(t){t.length?si=t:ni&&function(){let t=Math.floor;if(ni){ni=!1;let e,s,n=[];ei.forEach(r=>{e=i[r],e&&(s=t(e.order)||0,n[s]||(n[s]=[]),n[s].push(e.name))}),si=n.reduce((t,e)=>t.concat(e),[])}}()},mi=function(t){return new Promise((e,s)=>{let n,r=[];si.forEach(e=>{n=i[e],n&&n[t]&&r.push(n[t]())}),Promise.all(r).then(()=>e(!0)).catch(t=>s(t))})},gi=[],yi=function(t=""){if(!t)throw new Error("core/document addDomShowElement() error - false argument supplied: "+t);if(!t.substring)throw new Error("core/document addDomShowElement() error - argument not a string: "+t);Z(gi,t)};let bi=!1;const Si=function(t=!0){bi=t},Pi=function(t=""){if(bi||t){let e,s,n,r,o,a,h,c,l,u,d,f,p,m,g,y,b,S;for(t?e=[t]:(bi=!1,e=[].concat(gi),gi.length=0),s=0,n=e.length;sconsole.log(t))):(h.width=u+"px",h.height=d?d+"px":"auto")),o.dirtyTransformOrigin&&(o.dirtyTransformOrigin=!1,h.transformOrigin=o.currentTransformOriginString),o.dirtyTransform&&(o.dirtyTransform=!1,h.transform=o.currentTransformString),o.dirtyVisibility&&(o.dirtyVisibility=!1,h.display=o.visibility?"block":"none"),o.dirtyCss)for(o.dirtyCss=!1,m=o.css||{},g=Object.keys(m),f=0,p=g.length;f{let s=Object.assign({},t);s.name=`${s.name}_${i.name}`,s.target=i,e.push(new RenderAnimation(s))}),e}e=t.target.substring?i[t.target]:t.target}else e={clear:ui,compile:di,show:fi};if(!(e&&e.clear&&e.compile&&e.show))return!1;this.makeName(t.name),this.order=Q(t.order)?t.order:this.defs.order,this.onRun=t.onRun||B,this.onHalt=t.onHalt||B,this.onKill=t.onKill||B,this.target=e,this.commence=t.commence||B,this.afterClear=t.afterClear||B,this.afterCompile=t.afterCompile||B,this.afterShow=t.afterShow||B,this.afterCreated=t.afterCreated||B,this.error=t.error||B,this.readyToInitialize=!0;let s=this;return this.fn=function(){return new Promise((t,e)=>{Promise.resolve(s.commence()).then(()=>s.target.clear()).then(()=>Promise.resolve(s.afterClear())).then(()=>s.target.compile()).then(()=>Promise.resolve(s.afterCompile())).then(()=>s.target.show()).then(()=>Promise.resolve(s.afterShow())).then(()=>{s.readyToInitialize&&(s.afterCreated(),s.readyToInitialize=!1),t(!0)}).catch(t=>{s.error(t),e(t)})})},this.register(),t.observer&&(this.observer=et(this,this.target)),t.delay||this.run(),this};let xi=RenderAnimation.prototype=Object.create(Object.prototype);xi.type="RenderAnimation",xi.lib="animation",xi.isArtefact=!1,xi.isAsset=!1,xi=Pt(xi);xi.defs=G(xi.defs,{order:1,onRun:null,onHalt:null,onKill:null,commence:null,afterClear:null,afterCompile:null,afterShow:null,afterCreated:null,error:null,target:null}),xi.stringifyFunction=B,xi.processPacketOut=B,xi.finalizePacketOut=B,xi.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},xi.clone=$,xi.kill=function(){return this.onKill(),_(E,this.name),T(),this.deregister(),!0},xi.run=function(){return this.onRun(),Z(E,this.name),T(),this},xi.isRunning=function(){return E.indexOf(this.name)>=0},xi.halt=function(){return this.onHalt(),_(E,this.name),T(),this};const Ci=function(t){return new RenderAnimation(t)};k.RenderAnimation=RenderAnimation;const UnstackedElement=function(t){let e=t.id||t.name;return this.makeName(e),this.register(),t.setAttribute("data-scrawl-name",this.name),this.domElement=t,this.elementComputedStyles=window.getComputedStyle(t),this.hostStyles={},this.canvasStartX=0,this.canvasStartY=0,this.canvasWidth=0,this.canvasHeight=0,this.canvasZIndex=0,this};let Ai=UnstackedElement.prototype=Object.create(Object.prototype);Ai.type="UnstackedElement",Ai.lib="unstackedelement",Ai.isArtefact=!1,Ai.isAsset=!1,Ai=Pt(Ai);Ai.defs=G(Ai.defs,{canvasOnTop:!1});Ai.getters,Ai.setters,Ai.deltaSetters;Ai.demolish=function(t=!1){return!0},Ai.addCanvas=function(t={}){if(!this.canvas){let e=document.createElement("canvas"),i=this.domElement,s=i.style;"static"===s.position&&(s.position="relative"),i.prepend(e);let n=qe({name:this.name+"-canvas",domElement:e,position:"absolute"});return this.canvas=n,n.set(t),this.updateCanvas(),n}},Ai.includedStyles=["width","height","zIndex","borderBottomLeftRadius","borderBottomRightRadius","borderTopLeftRadius","borderTopRightRadius"],Ai.mimickedStyles=["borderBottomLeftRadius","borderBottomRightRadius","borderTopLeftRadius","borderTopRightRadius"],Ai.checkElementStyleValues=function(){let t={},e=this.domElement,i=this.canvas;if(e&&i&&i.domElement){let s=this.hostStyles,n=this.elementComputedStyles,r=i.domElement,o=this.includedStyles,a=Math.max,{x:h,y:c,width:l,height:u}=e.getBoundingClientRect(),{x:d,y:f}=r.getBoundingClientRect();o.forEach(e=>{switch(e){case"width":let i=a(parseFloat(n.width),l);this.canvasWidth!==i&&(this.canvasWidth=i,this.dirtyDimensions=!0);break;case"height":let r=a(parseFloat(n.height),u);this.canvasHeight!==r&&(this.canvasHeight=r,this.dirtyDimensions=!0);break;case"zIndex":let o="auto"===n.zIndex?0:parseInt(n.zIndex,10);o=this.canvasOnTop?o+1:o-1,this.canvasZIndex!==o&&(this.canvasZIndex=o,this.dirtyZIndex=!0);break;default:let h=s[e],c=n[e];Q(h)&&h===c||(s[e]=c,t[e]=c)}});let p=h-d,m=c-f;(p||m)&&(this.canvasStartX+=p,this.canvasStartY+=m,this.dirtyStart=!0)}return t},Ai.updateCanvas=function(){if(this.canvas&&this.canvas.domElement){let t=this.canvas,e=t.domElement.style,i=this.mimickedStyles,s=this.checkElementStyleValues();for(let[t,n]of Object.entries(s))i.indexOf(t)>=0&&(e[t]=n);if(this.dirtyStart&&(this.dirtyStart=!1,t.set({startX:this.canvasStartX,startY:this.canvasStartY})),this.dirtyDimensions){this.dirtyDimensions=!1;let e=this.canvasWidth,i=this.canvasHeight;t.set({width:e,height:i}),t.dirtyDimensions=!0,t.base.set({width:e,height:i}),t.base.dirtyDimensions=!0,t.cleanDimensions(),t.base.cleanDimensions()}this.dirtyZIndex&&(this.dirtyZIndex=!1,e.zIndex=this.canvasZIndex)}};k.UnstackedElement=UnstackedElement;const wi=function(t,e,s,n){let r=i[t.id];if(!r)return!1;e.isComponent=!0;let o=r.addCanvas(e);s.name=r.name+"-animation",s.target=o;let a=Ci(s),h=et(a,r,n);return{element:r,canvas:o,animation:a,demolish:()=>{h(),a.kill(),o.demolish(),r.demolish(!0)}}},Di=function(t,e,i,s,n){let r,o=t.id;o&&P[o]?r=P[o]:r=new UnstackedElement(t),e.isComponent=!0;let a=!!n&&r.addCanvas(e);i.name=r.name+"-animation",a&&(i.afterClear||(i.afterClear=()=>r.updateCanvas()),i.target=a);let h=Ci(i),c=et(h,r,s);return{element:r,canvas:a,animation:h,demolish:()=>{c(),h.kill(),a&&a.demolish(),r.demolish(!0)}}},Oi=["artefact","group","animation","tween","styles"],Ei=t=>{if(t&&t.substring){let e;return!!Oi.some(i=>(e=A[i][t],e))&&e}return!1};function Ti(t={}){t.defs=G(t.defs,{order:1,ticker:"",targets:null,time:0,action:null,reverseOnCycleEnd:!1,reversed:!1}),t.kill=function(){let t,i=this.ticker;return i===this.name+"_ticker"?(t=e[i],t&&t.kill()):i&&this.removeFromTicker(i),this.deregister(),!0};let i=t.getters,s=t.setters;return i.targets=function(){return[].concat(this.targets)},s.targets=function(t=[]){this.setTargets(t)},s.action=function(t){this.action=t,"function"!=typeof this.action&&(this.action=B)},t.calculateEffectiveTime=function(t){let i,s=J(t,this.time),n=L(s),r=n[1],o=n[0],a=0;return this.effectiveTime=0,"%"===o&&r<=100?this.ticker&&(i=e[this.ticker],i&&(a=i.effectiveDuration,this.effectiveTime=a*(r/100))):this.effectiveTime=r,this},t.addToTicker=function(t){let i;return Q(t)&&(this.ticker&&this.ticker!==t&&this.removeFromTicker(this.ticker),i=e[t],Q(i)&&(this.ticker=t,i.subscribe(this.name),this.calculateEffectiveTime())),this},t.removeFromTicker=function(t){let i;return(t=Q(t)?t:this.ticker)&&(i=e[t],Q(i)&&(this.ticker="",i.unsubscribe(this.name))),this},t.setTargets=function(t){t=[].concat(t);let e=[];return t.forEach(t=>{if(N(t))N(t.set)&&e.push(t);else if(V(t)&&Q(t.name))e.push(t);else{let i=Ei(t);i&&e.push(i)}}),this.targets=e,this},t.addToTargets=function(t){return(t=[].concat(t)).forEach(t=>{"function"==typeof t?"function"==typeof t.set&&this.targets.push(t):(result=Ei(t),result&&this.targets.push(result))},this),this},t.removeFromTargets=function(t){t=[].concat(t);let e=[],i=[].concat(this.targets);return i.forEach(t=>{let i=t.type||"unknown",s=t.name||"unnamed";"unknown"!==i&&"unnamed"!==s&&e.push(`${i}_${s}`)}),t.forEach(t=>{let s;if(s="function"==typeof t?t:Ei(t),s){let t=s.type||"unknown",n=s.name||"unnamed";if("unknown"!==t&&"unnamed"!==n){let s=`${t}_${n}`,r=e.indexOf(s);r>=0&&(i[r]=!1)}}}),this.targets=[],i.forEach(t=>{t&&this.targets.push(t)},this),this},t.checkForTarget=function(t){return!!t.substring&&this.targets.some(e=>e.name===t)},t}const Action=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.calculateEffectiveTime(),Q(t.ticker)&&this.addToTicker(t.ticker),this};let Ri=Action.prototype=Object.create(Object.prototype);Ri.type="Action",Ri.lib="tween",Ri.isArtefact=!1,Ri.isAsset=!1,Ri=Pt(Ri),Ri=Ti(Ri);Ri.defs=G(Ri.defs,{revert:null}),Ri.packetExclusions=Z(Ri.packetExclusions,["targets"]),Ri.packetFunctions=Z(Ri.packetFunctions,["revert","action"]),Ri.finalizePacketOut=function(t,e){return Array.isArray(this.targets)&&(t.targets=this.targets.map(t=>t.name)),t};let Fi=Ri.setters;Fi.revert=function(t){this.revert=t,"function"!=typeof this.revert&&(this.revert=B)},Fi.triggered=function(t){this.triggered!==t&&(t?this.action():this.revert(),this.triggered=t)},Ri.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs,n=!!Q(t.ticker)&&this.ticker;Object.entries(t).forEach(([t,n])=>{"name"!==t&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this),n?(this.ticker=n,this.addToTicker(t.ticker)):Q(t.time)&&this.calculateEffectiveTime()}return this},Ri.getEndTime=function(){return this.effectiveTime},Ri.update=function(t){let e=this.reversed,i=this.effectiveTime,s=this.triggered,n=(this.reverseOnCycleEnd,t.tick),r=t.reverseTick,o=t.willLoop;t.next;return e?r>=i?s||(this.action(),this.triggered=!0):s&&(this.revert(),this.triggered=!1):n>=i?s||(this.action(),this.triggered=!0):s&&(this.revert(),this.triggered=!1),o&&(this.triggered=!this.triggered),!0};function Hi(t={}){(t=ye(t=pe(t=ue(t=le(t=ce(t=he(t=ae(t)))))))).defs=G(t.defs,{method:"fill",pathObject:null,winding:"nonzero",flipReverse:!1,flipUpend:!1,scaleOutline:!0,lockFillStyleToEntity:!1,lockStrokeStyleToEntity:!1,onEnter:null,onLeave:null,onDown:null,onUp:null}),t.packetExclusions=Z(t.packetExclusions,["state"]),t.packetFunctions=Z(t.packetFunctions,["onEnter","onLeave","onDown","onUp"]),t.processEntityPacketOut=function(t,e,i){return this.processFactoryPacketOut(t,e,i)},t.processFactoryPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];return t=G(t,i),t=this.handlePacketAnchor(t,e)},t.postCloneAction=function(t,e){return this.onEnter&&(t.onEnter=this.onEnter),this.onLeave&&(t.onLeave=this.onLeave),this.onDown&&(t.onDown=this.onDown),this.onUp&&(t.onUp=this.onUp),e.sharedState&&(t.state=this.state),e.anchor&&(e.anchor.host=t,Q(e.anchor.focusAction)||(e.anchor.focusAction=this.anchor.focusAction),Q(e.anchor.blurAction)||(e.anchor.blurAction=this.anchor.blurAction),t.buildAnchor(e.anchor),e.anchor.clickAction||(t.anchor.clickAction=this.anchor.clickAction)),t};let e=t.getters,i=t.setters;t.deltaSetters;return e.group=function(){return this.group?this.group.name:""},i.lockStylesToEntity=function(t){this.lockFillStyleToEntity=t,this.lockStrokeStyleToEntity=t},t.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):null)}},t.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=r?r.setters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,h])=>{t&&"name"!==t&&null!=h&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,h):void 0!==n[t]?this[t]=h:void 0!==a[t]&&(r[t]=h))},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e,i,s=this.deltaSetters,n=this.defs,r=this.state,o=r?r.deltaSetters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,h])=>{t&&"name"!==t&&null!=h&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,h):void 0!==n[t]?this[t]=H(this[t],h):void 0!==a[t]&&(r[t]=H(r[t],h)))},this)}return this},t.entityInit=function(t={}){this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.state=Gt(),t.group||(t.group=ci),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.set(t),this.midInitActions(t),this.purge&&this.purgeArtefact(this.purge)},t.midInitActions=B,t.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1,this.dirtyDimensions=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.anchor&&this.dirtyAnchorHold&&(this.dirtyAnchorHold=!1,this.buildAnchor(this.anchor))},t.cleanPathObject=B,t.stamp=function(t=!1,e,i){let s=!(this.noFilters||!this.filters||!this.filters.length);return t?(e&&"Cell"===e.type&&(this.currentHost=e),i&&(this.set(i),this.prepareStamp()),s?this.filteredStamp():this.regularStamp()):this.visibility?this.stashOutput||s?this.filteredStamp():this.regularStamp():Promise.resolve("entity.stamp")},t.regularStamp=function(){let t=this;return new Promise((e,i)=>{t.currentHost&&(t.regularStampSynchronousActions(),e("entity.regularStamp resolving")),i("entity.regularStamp - no currentHost")})},t.filteredStamp=function(){!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let t=this;return new Promise((e,i)=>{let s=function(){if(u&&Yt(u),o.save(),o.setTransform(1,0,0,1,0,0),o.drawImage(c,0,0),t.stashOutput){t.stashOutput=!1;let[e,i,s,n]=t.getCellCoverage(l.getImageData(0,0,c.width,c.height));if(t.stashedImageData=l.getImageData(e,i,s,n),t.stashOutputAsAsset)if(t.stashOutputAsAsset=!1,c.width=s,c.height=n,l.putImageData(t.stashedImageData,0,0),t.stashedImage)t.stashedImage.src=c.toDataURL();else{let e=t.stashedImage=document.createElement("img");e.id=t.name+"-image",e.onload=function(){ki.appendChild(e),re("#"+e.id)},e.src=c.toDataURL()}}Ce(h),o.restore(),t.currentHost=n,t.noCanvasEngineUpdates=m},n=t.currentHost,r=n.element,o=n.engine,a=n.currentDimensions,h=xe(),c=h.element,l=h.engine;t.currentHost=h;let u,d,f=c.width=a[0]||r.width,p=c.height=a[1]||r.height,m=t.noCanvasEngineUpdates;t.noCanvasEngineUpdates=!1,t.regularStampSynchronousActions(),!t.noFilters&&t.filters&&t.filters.length?(t.isStencil&&(l.save(),l.globalCompositeOperation="source-in",l.globalAlpha=1,l.setTransform(1,0,0,1,0,0),l.drawImage(r,0,0),l.restore()),l.setTransform(1,0,0,1,0,0),d=l.getImageData(0,0,f,p),u=Xt(),Wt(u,{image:d,filters:t.currentFilters}).then(t=>{if(!t)throw new Error("image issue");l.globalCompositeOperation="source-over",l.globalAlpha=1,l.setTransform(1,0,0,1,0,0),l.putImageData(t,0,0),s(),e(!0)}).catch(t=>{s(),i(t)})):(s(),e(!0))})},t.getCellCoverage=function(t){let e,i,s=t.width,n=t.height,r=t.data,o=0,a=0,h=s,c=n,l=3;for(i=0;ie&&(h=e),oi&&(c=i),a=1)return.9999;let e=this.unitPositions;if(e&&e.length){let e,i,s,n,r,o,a,h=this.length,c=this.unitProgression,l=this.unitPositions,u=t*h,d=-1;for(let t=0,e=c.length;t=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers()},t.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0},t.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.dirtyDimensions&&(this.cleanSpecies(),this.pathCalculatedOnce=!1),this.calculateLocalPath(this.pathDefinition),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyHandle&&this.cleanHandle(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions();let t=this.currentStampHandlePosition;this.pathObject=new Path2D(`m${-t[0]},${-t[1]}${this.localPath}`)}},t.calculateLocalPath=function(t,e){let i;if(this.pathCalculatedOnce||(i=function(t,e,i,s,n){let r,o,a,h,c=[],l=[],u="",d="",f=[],p=[],m=[],g=t.match(/([A-Za-z][0-9. ,\-]*)/g),y=0,b={},S=0,P=0,k=0,v=0,x=[],C=[],A=[],w=[],D=0,O=0,E=t=>{l.push({c:u.toLowerCase(),p:t||null,x:k,y:v,cx:S,cy:P,rx:D,ry:O}),s||(x.push(S),C.push(P)),k=S,v=P};for(r=0,o=g.length;r0&&l[r-1],{c:s,p:n,x:o,y:a,cx:h,cy:c,rx:u,ry:d}=e;if(n)switch(s){case"h":f[r]=["linear",o,a,n[0]+o,a];break;case"v":f[r]=["linear",o,a,o,n[0]+a];break;case"m":f[r]=["move",o,a];break;case"l":f[r]=["linear",o,a,n[0]+o,n[1]+a];break;case"t":i&&(i.rx||i.ry)?(Bi(t,i.rx-h,i.ry-c),$i(t,180),f[r]=["quadratic",o,a,t.x+h,t.y+c,n[0]+o,n[1]+a]):f[r]=["quadratic",o,a,o,a,n[0]+o,n[1]+a];break;case"q":f[r]=["quadratic",o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a];break;case"s":i&&(i.rx||i.ry)?(Bi(t,i.rx-h,i.ry-c),$i(t,180),f[r]=["bezier",o,a,t.x+h,t.y+c,n[0]+o,n[1]+a,n[2]+o,n[3]+a]):f[r]=["bezier",o,a,o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a];break;case"c":f[r]=["bezier",o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a,n[4]+o,n[5]+a];break;case"a":f[r]=["linear",o,a,n[5]+o,n[6]+a];break;case"z":isNaN(o)&&(o=0),isNaN(a)&&(a=0),f[r]=["close",o,a];break;default:isNaN(o)&&(o=0),isNaN(a)&&(a=0),f[r]=["unknown",o,a]}else f[r]=["no-points-"+s,o,a]}for(b.units=f,r=0,o=f.length;rt+e,0);let e=0;for(r=0,o=p.length;r{let e=i[t];e&&(e.currentPathData=!1,e.dirtyStart=!0,e.addPathHandle&&(e.dirtyHandle=!0),e.addPathOffset&&(e.dirtyOffset=!0),e.addPathRotation&&(e.dirtyRotation=!0),"Polyline"===e.type?e.dirtyPins=!0:"Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||e.dirtyPins.push(this.name))},this)},t.draw=function(t){t.stroke(this.pathObject),this.showBoundingBox&&this.drawBoundingBox(t)},t.fill=function(t){t.fill(this.pathObject,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.drawAndFill=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),t.fill(e,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.fillAndDraw=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),t.fill(e,this.winding),t.stroke(e),this.showBoundingBox&&this.drawBoundingBox(t)},t.drawThenFill=function(t){let e=this.pathObject;t.stroke(e),t.fill(e,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.fillThenDraw=function(t){let e=this.pathObject;t.fill(e,this.winding),t.stroke(e),this.showBoundingBox&&this.drawBoundingBox(t)},t.clear=function(t){let e=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fill(this.pathObject,this.winding),t.globalCompositeOperation=e,this.showBoundingBox&&this.drawBoundingBox(t)},t.drawBoundingBox=function(t){t.save(),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.strokeRect(...this.getBoundingBox()),t.restore()},t.getBoundingBox=function(){let t=Math.floor,e=Math.ceil,i=this.minimumBoundingBoxDimensions,[s,n,r,o]=this.localBox,[a,h]=this.currentStampHandlePosition,[c,l]=this.currentStampPosition;return r"string"!=typeof t?"":t.charAt(0).toUpperCase()+t.slice(1);function Yi(t={}){t.defs=G(t.defs,{end:null,endPivot:"",endPivotCorner:"",addEndPivotHandle:!1,addEndPivotOffset:!1,endPath:"",endPathPosition:0,addEndPathHandle:!1,addEndPathOffset:!0,endParticle:"",endLockTo:"",useStartAsControlPoint:!1}),t.packetExclusions=Z(t.packetExclusions,["controlledLineOffset"]),t.packetExclusionsByRegex=Z(t.packetExclusionsByRegex,[]),t.packetCoordinates=Z(t.packetCoordinates,["end"]),t.packetObjects=Z(t.packetObjects,["endPivot","endPath"]),t.packetFunctions=Z(t.packetFunctions,[]),t.factoryKill=function(){Object.entries(i).forEach(([t,e])=>{e.name!==this.name&&(e.startControlPivot&&e.startControlPivot.name===this.name&&e.set({startControlPivot:!1}),e.controlPivot&&e.controlPivot.name===this.name&&e.set({controlPivot:!1}),e.endControlPivot&&e.endControlPivot.name===this.name&&e.set({endControlPivot:!1}),e.endPivot&&e.endPivot.name===this.name&&e.set({endPivot:!1}),e.startControlPath&&e.startControlPath.name===this.name&&e.set({startControlPath:!1}),e.controlPath&&e.controlPath.name===this.name&&e.set({controlPath:!1}),e.endControlPath&&e.endControlPath.name===this.name&&e.set({endControlPath:!1}),e.endPath&&e.endPath.name===this.name&&e.set({endPath:!1}))})};t.getters;let e=t.setters,s=t.deltaSetters;return e.useStartAsControlPoint=function(t){if(this.useStartAsControlPoint=t,!t){let t=this.controlledLineOffset;t[0]=0,t[1]=0}this.updateDirty()},e.endPivot=function(t){this.setControlHelper(t,"endPivot","end"),this.updateDirty(),this.dirtyEnd=!0},e.endParticle=function(t){this.setControlHelper(t,"endParticle","end"),this.updateDirty(),this.dirtyEnd=!0},e.endPath=function(t){this.setControlHelper(t,"endPath","end"),this.updateDirty(),this.dirtyEnd=!0},e.endPathPosition=function(t){this.endPathPosition=t,this.dirtyEnd=!0,this.currentEndPathData=!1},s.endPathPosition=function(t){this.endPathPosition+=t,this.dirtyEnd=!0,this.currentEndPathData=!1},e.endX=function(t){null!=t&&(this.end[0]=t,this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1)},e.endY=function(t){null!=t&&(this.end[1]=t,this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1)},e.end=function(t,e){this.setCoordinateHelper("end",t,e),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.endX=function(t){let e=this.end;e[0]=H(e[0],t),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.endY=function(t){let e=this.end;e[1]=H(e[1],t),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.end=function(t,e){this.setDeltaCoordinateHelper("end",t,e),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},e.endLockTo=function(t){this.endLockTo=t,this.updateDirty(),this.dirtyEndLock=!0,this.currentEndPathData=!1},t.curveInit=function(t){this.end=Kt(),this.currentEnd=Kt(),this.endLockTo="coord",this.dirtyEnd=!0,this.dirtyPins=[],this.controlledLineOffset=Kt()},t.setControlHelper=function(t,e,s){if(I(t)&&!t)this[e]=null,"startControl"===s?this.dirtyStartControlLock=!0:"control"===s?this.dirtyControlLock=!0:"endControl"===s?this.dirtyEndControlLock=!0:this.dirtyEndLock=!0;else if(t){let n=this[e],r=t.substring?i[t]:t;e.indexOf("Pivot")>0?r&&r.isArtefact&&(n&&n.isArtefact&&_(n.pivoted,this.name),Z(r.pivoted,this.name),this[e]=r):e.indexOf("Path")>0?r&&r.isArtefact&&(n&&n.isArtefact&&_(n.pathed,this.name),Z(r.pathed,this.name),this[e]=r):e.indexOf("Particle")>0&&(r=t.substring?d[t]:t,r||(this.updateDirty(),"startControl"===s?this.dirtyStartControl=!0:"control"===s?this.dirtyControl=!0:"endControl"===s?this.dirtyEndControl=!0:this.dirtyEnd=!0,this[e]=t))}},t.buildPathPositionObject=function(t,e){if(t){let i,s,[n,...r]=t;switch(n){case"linear":i=this.positionPointOnPath(this.getLinearXY(e,...r)),s=this.getLinearAngle(e,...r);break;case"quadratic":i=this.positionPointOnPath(this.getQuadraticXY(e,...r)),s=this.getQuadraticAngle(e,...r);break;case"bezier":i=this.positionPointOnPath(this.getBezierXY(e,...r)),s=this.getBezierAngle(e,...r)}let o=0;this.flipReverse&&o++,this.flipUpend&&o++,1===o&&(s=-s),s+=this.roll;this.currentStampPosition;let a=this.controlledLineOffset;this.localBox;return i.x+=a[0],i.y+=a[1],i.angle=s,i}return!1},t.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1),this.dirtyPins.length&&this.preparePinsForStamp(),this.dirtyLock&&this.cleanLock(),this.dirtyStartControlLock&&this.cleanControlLock("startControl"),this.dirtyEndControlLock&&this.cleanControlLock("endControl"),this.dirtyControlLock&&this.cleanControlLock("control"),this.dirtyEndLock&&this.cleanControlLock("end"),(this.dirtyScale||this.dirtySpecies||this.dirtyDimensions||this.dirtyStart||this.dirtyStartControl||this.dirtyEndControl||this.dirtyControl||this.dirtyEnd||this.dirtyHandle)&&(this.dirtyPathObject=!0,this.useStartAsControlPoint&&this.dirtyStart&&(this.dirtySpecies=!0,this.pathCalculatedOnce=!1),(this.dirtyScale||this.dirtySpecies||this.dirtyStartControl||this.dirtyEndControl||this.dirtyControl||this.dirtyEnd)&&(this.pathCalculatedOnce=!1)),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.useStartAsControlPoint&&(this.dirtySpecies=!0,this.dirtyPathObject=!0,this.pathCalculatedOnce=!1)),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),(this.dirtyStartControl||"particle"===this.startControlLockTo)&&this.cleanControl("startControl"),(this.dirtyEndControl||"particle"===this.endControlLockTo)&&this.cleanControl("endControl"),(this.dirtyControl||"particle"===this.controlLockTo)&&this.cleanControl("control"),(this.dirtyEnd||"particle"===this.endLockTo)&&this.cleanControl("end"),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&(this.updatePositionSubscribers(),this.updateControlPathSubscribers())},t.cleanControlLock=function(t){let e=Xi(t);this[`dirty${e}Lock`]=!1,this["dirty"+e]=!0},t.cleanControl=function(t){let e=Xi(t);this["dirty"+e]=!1;let s,n,r=t+"Path",o=t+"Particle",a=this[t+"Pivot"],h=this[r],c=this[o];a&&a.substring&&(s=i[a],s&&(a=s)),h&&h.substring&&(s=i[h],s&&(h=s)),c&&c.substring&&(s=d[c],s&&(c=s));let l,u,f,p,m,g,y,b=this[t+"LockTo"],S=this[t],P=this["current"+e];switch(("pivot"!==b||a&&!a.substring)&&("path"!==b||h&&!h.substring)&&("particle"!==b||c&&!c.substring)||(b="coord"),b){case"pivot":this.pivotCorner&&a.getCornerCoordinate?[l,u]=a.getCornerCoordinate(this[t+"PivotCorner"]):[l,u]=a.currentStampPosition,this.addPivotOffset||([f,p]=a.currentOffset,l-=f,u-=p);break;case"path":n=this.getControlPathData(h,t,e),l=n.x,u=n.y,this.addPathOffset||(l-=h.currentOffset[0],u-=h.currentOffset[1]);break;case"particle":l=c.position.x,u=c.position.y,this.pathCalculatedOnce=!1;break;case"mouse":m=this.getHere(),l=m.x||0,u=m.y||0;break;default:l=u=0,g=this.getHost(),g&&(y=g.currentDimensions,y&&(this.cleanPosition(P,S,y),[l,u]=P))}P[0]=l,P[1]=u,this.dirtySpecies=!0,this.dirtyPathObject=!0,this.dirtyPositionSubscribers=!0},t.getControlPathData=function(t,e,i){let s=this[`current${i}PathData`];if(s)return s;let n=this[e+"PathPosition"],r=n,o=t.getPathPositionData(n);if(n<0&&(n+=1),n>1&&(n%=1),n=parseFloat(n.toFixed(6)),n!==r&&(this[e+"PathPosition"]=n),o)return this[`current${i}PathData`]=o,o;{let t=this.getHost();if(t){let s=t.currentDimensions;if(s){let t=this["current"+i];return this.cleanPosition(t,this[e],s),{x:t[0],y:t[1]}}}return{x:0,y:0}}},t.updateControlPathSubscribers=function(){[].concat(this.endSubscriber,this.endControlSubscriber,this.controlSubscriber,this.startControlSubscriber).forEach(t=>{let e=i[t];e&&("Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||("Quadratic"===e.type?(e.dirtyControl=!0,e.currentControlPathData=!1):"Bezier"===e.type&&(e.dirtyStartControl=!0,e.dirtyEndControl=!0,e.currentStartControlPathData=!1,e.currentEndControlPathData=!1),e.currentEndPathData=!1,e.dirtyEnd=!0),e.currentPathData=!1,e.dirtyStart=!0)})},t}const Bezier=function(t={}){return this.startControl=Kt(),this.endControl=Kt(),this.currentStartControl=Kt(),this.currentEndControl=Kt(),this.startControlLockTo="coord",this.endControlLockTo="coord",this.curveInit(t),this.shapeInit(t),this.dirtyStartControl=!0,this.dirtyEndControl=!0,this};let Ni=Bezier.prototype=Object.create(Object.prototype);Ni.type="Bezier",Ni.lib="entity",Ni.isArtefact=!0,Ni.isAsset=!1,Ni=Pt(Ni),Ni=Ii(Ni),Ni=Yi(Ni);Ni.defs=G(Ni.defs,{startControl:null,startControlPivot:"",startControlPivotCorner:"",addStartControlPivotHandle:!1,addStartControlPivotOffset:!1,startControlPath:"",startControlPathPosition:0,addStartControlPathHandle:!1,addStartControlPathOffset:!0,startControlParticle:"",endControl:null,endControlPivot:"",endControlPivotCorner:"",addEndControlPivotHandle:!1,addEndControlPivotOffset:!1,endControlPath:"",endControlPathPosition:0,addEndControlPathHandle:!1,addEndControlPathOffset:!0,endControlParticle:"",startControlLockTo:"",endControlLockTo:""}),Ni.packetExclusions=Z(Ni.packetExclusions,[]),Ni.packetExclusionsByRegex=Z(Ni.packetExclusionsByRegex,[]),Ni.packetCoordinates=Z(Ni.packetCoordinates,["startControl","endControl"]),Ni.packetObjects=Z(Ni.packetObjects,["startControlPivot","startControlPath","endControlPivot","endControlPath"]),Ni.packetFunctions=Z(Ni.packetFunctions,[]);Ni.getters;let Wi=Ni.setters,Vi=Ni.deltaSetters;Wi.endControlPivot=function(t){this.setControlHelper(t,"endControlPivot","endControl"),this.updateDirty(),this.dirtyEndControl=!0},Wi.endControlParticle=function(t){this.setControlHelper(t,"endControlParticle","endControl"),this.updateDirty(),this.dirtyEndControl=!0},Wi.endControlPath=function(t){this.setControlHelper(t,"endControlPath","endControl"),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Wi.endControlPathPosition=function(t){this.endControlPathPosition=t,this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Vi.endControlPathPosition=function(t){this.endControlPathPosition+=t,this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Wi.startControlPivot=function(t){this.setControlHelper(t,"startControlPivot","startControl"),this.updateDirty(),this.dirtyStartControl=!0},Wi.startControlParticle=function(t){this.setControlHelper(t,"startControlParticle","startControl"),this.updateDirty(),this.dirtyStartControl=!0},Wi.startControlPath=function(t){this.setControlHelper(t,"startControlPath","startControl"),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Wi.startControlPathPosition=function(t){this.startControlPathPosition=t,this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Vi.startControlPathPosition=function(t){this.startControlPathPosition+=t,this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Wi.startControlX=function(t){null!=t&&(this.startControl[0]=t,this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1)},Wi.startControlY=function(t){null!=t&&(this.startControl[1]=t,this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1)},Wi.startControl=function(t,e){this.setCoordinateHelper("startControl",t,e),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Vi.startControlX=function(t){let e=this.startControl;e[0]=H(e[0],t),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Vi.startControlY=function(t){let e=this.startControl;e[1]=H(e[1],t),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Vi.startControl=function(t,e){this.setDeltaCoordinateHelper("startControl",t,e),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},Wi.endControlX=function(t){null!=t&&(this.endControl[0]=t,this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1)},Wi.endControlY=function(t){null!=t&&(this.endControl[1]=t,this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1)},Wi.endControl=function(t,e){this.setCoordinateHelper("endControl",t,e),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Vi.endControlX=function(t){let e=this.endControl;e[0]=H(e[0],t),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Vi.endControlY=function(t){let e=this.endControl;e[1]=H(e[1],t),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Vi.endControl=function(t,e){this.setDeltaCoordinateHelper("endControl",t,e),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},Wi.startControlLockTo=function(t){this.startControlLockTo=t,this.updateDirty(),this.dirtyStartControlLock=!0},Wi.endControlLockTo=function(t){this.endControlLockTo=t,this.updateDirty(),this.dirtyEndControlLock=!0,this.currentEndControlPathData=!1},Ni.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeBezierPath(),this.pathDefinition=t},Ni.makeBezierPath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentStartControl,[n,r]=this.currentEndControl,[o,a]=this.currentEnd;return`m0,0c${(i-t).toFixed(2)},${(s-e).toFixed(2)} ${(n-t).toFixed(2)},${(r-e).toFixed(2)} ${(o-t).toFixed(2)},${(a-e).toFixed(2)}`},Ni.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyStartControl=!0,this.dirtyEndControl=!0,this.dirtyEnd=!0},Ni.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath,i=this.startControlPivot,s=this.startControlPath,n=this.endControlPivot,r=this.endControlPath;this.dirtyPins.forEach(o=>{(i&&i.name===o||s&&s.name===o)&&(this.dirtyStartControl=!0,this.startControlLockTo.includes("path")&&(this.currentStartControlPathData=!1)),(n&&n.name===o||r&&r.name===o)&&(this.dirtyEndControl=!0,this.endControlLockTo.includes("path")&&(this.currentEndControlPathData=!1)),(t&&t.name===o||e&&e.name===o)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};k.Bezier=Bezier;const Cog=function(t={}){return this.shapeInit(t),this};let qi=Cog.prototype=Object.create(Object.prototype);qi.type="Cog",qi.lib="entity",qi.isArtefact=!0,qi.isAsset=!1,qi=Pt(qi),qi=Ii(qi);qi.defs=G(qi.defs,{outerRadius:0,innerRadius:0,outerControlsDistance:0,innerControlsDistance:0,outerControlsOffset:0,innerControlsOffset:0,points:0,twist:0,curve:"bezier"});let Gi=qi.setters,Ui=qi.deltaSetters;Gi.outerRadius=function(t){this.outerRadius=t,this.updateDirty()},Ui.outerRadius=function(t){this.outerRadius+=t,this.updateDirty()},Gi.innerRadius=function(t){this.innerRadius=t,this.updateDirty()},Ui.innerRadius=function(t){this.innerRadius+=t,this.updateDirty()},Gi.outerControlsDistance=function(t){this.outerControlsDistance=t,this.updateDirty()},Ui.outerControlsDistance=function(t){this.outerControlsDistance+=t,this.updateDirty()},Gi.innerControlsDistance=function(t){this.innerControlsDistance=t,this.updateDirty()},Ui.innerControlsDistance=function(t){this.innerControlsDistance+=t,this.updateDirty()},Gi.outerControlsOffset=function(t){this.outerControlsOffset=t,this.updateDirty()},Ui.outerControlsOffset=function(t){this.outerControlsOffset+=t,this.updateDirty()},Gi.innerControlsOffset=function(t){this.innerControlsOffset=t,this.updateDirty()},Ui.innerControlsOffset=function(t){this.innerControlsOffset+=t,this.updateDirty()},Gi.points=function(t){this.points=t,this.updateDirty()},Ui.points=function(t){this.points+=t,this.updateDirty()},Gi.twist=function(t){this.twist=t,this.updateDirty()},Ui.twist=function(t){this.twist+=t,this.updateDirty()},Gi.curve=function(t){t&&["line","quadratic","bezier"].indexOf(t)>=0?(this.curve=t,this.updateDirty()):(this.curve="bezier",this.updateDirty())},qi.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeCogPath(),this.pathDefinition=t},qi.makeCogPath=function(){let t,e,i,s,n,r,o,{points:a,twist:h,outerRadius:c,innerRadius:l,outerControlsDistance:u,innerControlsDistance:d,outerControlsOffset:f,innerControlsOffset:p,curve:m}=this,g=360/a,y=[],b="";if(c.substring||l.substring||u.substring||d.substring||f.substring||p.substring){let t=this.getHost();if(t){let[e,i]=t.currentDimensions;c=c.substring?parseFloat(c)/100*e:c,l=l.substring?parseFloat(l)/100*e:l,u=u.substring?parseFloat(u)/100*e:u,d=d.substring?parseFloat(d)/100*e:d,f=f.substring?parseFloat(f)/100*e:f,p=p.substring?parseFloat(p)/100*e:p}}let S=Fe({x:0,y:-c}),P=Fe({x:0,y:-l}),k=Fe({x:u+f,y:-c}),v=Fe({x:-d+p,y:-l}),x=Fe({x:d+p,y:-l}),C=Fe({x:-u+f,y:-c});if(v.rotate(-g/2),v.rotate(h),P.rotate(-g/2),P.rotate(h),x.rotate(-g/2),x.rotate(h),t=S.x,e=S.y,y.push(t),"bezier"==m)for(o=0;o{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;V(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),V(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),this.deregister(),this},Zi.get=function(t){if(Q(t)){if("random"===t)return this.generateRandomColor(),this.get();if(t.toFixed)return this.getRangeColor(t);{let e=this.getters[t];if(e)return e.call(this);{let e=this.defs[t];if(void 0!==e){let i=this[t];return void 0!==i?i:e}return undef}}}{let{r:t,g:e,b:i,a:s}=this;return this.opaque?`rgb(${t}, ${e}, ${i})`:`rgba(${t}, ${e}, ${i}, ${s})`}},Zi.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs;Object.entries(t).forEach(([t,n])=>{"name"!==t&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this)}return t.random?this.generateRandomColor(t):this.checkValues(),this};let _i=Zi.setters;_i.color=function(t){this.convert(t)},Zi.setColor=function(t){return this.convert(t),this},_i.minimumColor=function(t){let{r:e,g:i,b:s,a:n}=this;this.convert(t),this.rMin=this.r,this.gMin=this.g,this.bMin=this.b,this.aMin=this.a,this.r=e,this.g=i,this.b=s,this.a=n},Zi.setMinimumColor=function(t){let{r:e,g:i,b:s,a:n}=this;return this.convert(t),this.rMin=this.r,this.gMin=this.g,this.bMin=this.b,this.aMin=this.a,this.r=e,this.g=i,this.b=s,this.a=n,this},_i.maximumColor=function(t){let{r:e,g:i,b:s,a:n}=this;this.convert(t),this.rMax=this.r,this.gMax=this.g,this.bMax=this.b,this.aMax=this.a,this.r=e,this.g=i,this.b=s,this.a=n},Zi.setMaximumColor=function(t){let{r:e,g:i,b:s,a:n}=this;return this.convert(t),this.rMax=this.r,this.gMax=this.g,this.bMax=this.b,this.aMax=this.a,this.r=e,this.g=i,this.b=s,this.a=n,this},Zi.getData=function(){return this.autoUpdate&&this.update(),this.checkValues(),this.get()},Zi.generateRandomColor=function(t={}){let e=Math.round,i=Math.random,s=this.rMax=J(t.rMax,this.rMax,255),n=this.gMax=J(t.gMax,this.gMax,255),r=this.bMax=J(t.bMax,this.bMax,255),o=this.rMin=J(t.rMin,this.rMin,0),a=this.gMin=J(t.gMin,this.gMin,0),h=this.bMin=J(t.bMin,this.bMin,0);return this.r=t.r||e(i()*(s-o)+o),this.g=t.g||e(i()*(n-a)+a),this.b=t.b||e(i()*(r-h)+h),this.opaque||(aMax=this.aMax=J(t.aMax,this.aMax,1),aMin=this.aMin=J(t.aMin,this.aMin,0),this.a=t.a||i()*(aMax-aMin)+aMin),this.checkValues(),this},Zi.getRangeColor=function(t){if(Q(t)&&t.toFixed){let e=Math.floor,{rMin:i,gMin:s,bMin:n,aMin:r,rMax:o,gMax:a,bMax:h,aMax:c}=this;t>1?t=1:t<0&&(t=0),isNaN(t)&&(t=1);let l=e(i+(o-i)*t),u=e(s+(a-s)*t),d=e(n+(h-n)*t),f=e(r+(c-r)*t);return this.opaque?`rgb(${l}, ${u}, ${d})`:`rgba(${l}, ${u}, ${d}, ${f})`}{let{r:t,g:e,b:i,a:s}=this;return this.opaque?`rgb(${t}, ${e}, ${i})`:`rgba(${t}, ${e}, ${i}, ${s})`}},Zi.checkValues=function(){let t=Math.floor,e=t(this.r)||0,i=t(this.g)||0,s=t(this.b)||0,n=this.a;return this.r=e>255?255:e<0?0:e,this.g=i>255?255:i<0?0:i,this.b=s>255?255:s<0?0:s,this.a=n>1?1:n<0?0:n,this},Zi.updateArray=["r","g","b","a"],Zi.update=function(){Q(this.rCurrent)||(this.rCurrent=this.r),Q(this.gCurrent)||(this.gCurrent=this.g),Q(this.bCurrent)||(this.bCurrent=this.b),Q(this.aCurrent)||(this.aCurrent=this.a);let t=this.updateArray;return(this.rShift||this.gShift||this.bShift||this.aShift)&&t.forEach(t=>{let e=this[t+"Shift"];if(e){this[t+"Current"]+=e;let i=this[t],s=this[t+"Min"],n=this[t+"Max"],r=this[t+"Bounce"],o=this[t+"Current"],a=Math.floor(o+e);(a>n||a(n+s)/2?n:s,e=0)),this[t]=a,this[t+"Shift"]=e}},this),this},Zi.updateByDelta=Zi.update,Zi.convert=function(t){let e,i,s,n,r,o=Math.round;return(t=t.substring?t:"").length&&(t.toLowerCase(),e=0,i=0,s=0,n=1,"#"===t[0]?4==t.length?(e=parseInt(t[1]+t[1],16),i=parseInt(t[2]+t[2],16),s=parseInt(t[3]+t[3],16)):5==t.length?(e=parseInt(t[1]+t[1],16),i=parseInt(t[2]+t[2],16),s=parseInt(t[3]+t[3],16),n=parseInt(t[4]+t[4],16)/255):7==t.length?(e=parseInt(t[1]+t[2],16),i=parseInt(t[3]+t[4],16),s=parseInt(t[5]+t[6],16)):9==t.length&&(e=parseInt(t[1]+t[2],16),i=parseInt(t[3]+t[4],16),s=parseInt(t[5]+t[6],16),n=parseInt(t[7]+t[8],16)/255):/rgb\(/.test(t)?(r=t.match(/([0-9.]+\b)/g),/%/.test(t)?(e=o(r[0]/100*255),i=o(r[1]/100*255),s=o(r[2]/100*255)):(e=o(r[0]),i=o(r[1]),s=o(r[2]))):/rgba\(/.test(t)?(r=t.match(/([0-9.]+\b)/g),/%/.test(t)?(e=o(r[0]/100*255),i=o(r[1]/100*255),s=o(r[2]/100*255),n=r[3]/100):(e=o(r[0]),i=o(r[1]),s=o(r[2]),n=r[3])):/hsl\(/.test(t)||/hsla\(/.test(t)||"transparent"===t?(e=0,i=0,s=0,n=0):(r=this.colorLibrary[t],r&&(e=parseInt(r[0]+r[1],16),i=parseInt(r[2]+r[3],16),s=parseInt(r[4]+r[5],16),n=1)),this.r=e,this.g=i,this.b=s,this.a=n,this.checkValues()),this},Zi.colorLibrary={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffe4c4",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"778899",lightslategrey:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};const Qi=function(t){return new Color(t)};k.Color=Color;const ParticleHistory=function(t){let e=[];return Object.setPrototypeOf(e,ParticleHistory.prototype),t&&e.set(t),e};let Ki=ParticleHistory.prototype=Object.create(Array.prototype);Ki.constructor=ParticleHistory,Ki.type="ParticleHistory";const Ji=[],ts=function(t){t&&"ParticleHistory"===t.type&&(t.length=0,Ji.push(t),Ji.length>100&&(Ji.length=0))};k.ParticleHistory=ParticleHistory;const Particle=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.initializePositions(),this.set(t),this};let es=Particle.prototype=Object.create(Object.prototype);es.type="Particle",es.lib="particle",es.isArtefact=!1,es.isAsset=!1,es=Pt(es);es.defs=G(es.defs,{position:null,velocity:null,load:null,history:null,historyLength:1,engine:"euler",forces:null,mass:1,fill:"#000000",stroke:"#000000"}),es.packetExclusions=Z(es.packetExclusions,[]),es.packetExclusionsByRegex=Z(es.packetExclusionsByRegex,["^(local|dirty|current)"]),es.packetCoordinates=Z(es.packetCoordinates,[]),es.packetObjects=Z(es.packetObjects,["position","velocity","acceleration"]),es.packetFunctions=Z(es.packetFunctions,[]),es.factoryKill=function(){this.history.forEach(t=>ts(t));let t=[];m.forEach(e=>{let i=p[e];(i.particleFrom&&i.particleFrom.name===this.name||i.particleTo&&i.particleTo.name===this.name)&&t.push[i]}),t.forEach(t=>t.kill())};let is=es.getters,ss=es.setters,ns=es.deltaSetters;is.positionX=function(){return this.position.x},is.positionY=function(){return this.position.y},is.positionZ=function(){return this.position.z},is.position=function(){let t=this.position;return[t.x,t.y,t.z]},ss.positionX=function(t){this.position.x=t},ss.positionY=function(t){this.position.y=t},ss.positionZ=function(t){this.position.z=t},ss.position=function(t){this.position.set(t)},ns.positionX=function(t){this.position.x+=t},ns.positionY=function(t){this.position.y+=t},ns.positionZ=function(t){this.position.z+=t},ns.position=B,is.velocityX=function(){return this.velocity.x},is.velocityY=function(){return this.velocity.y},is.velocityZ=function(){return this.velocity.z},is.velocity=function(){let t=this.velocity;return[t.x,t.y,t.z]},ss.velocityX=function(t){this.velocity.x=t},ss.velocityY=function(t){this.velocity.y=t},ss.velocityZ=function(t){this.velocity.z=t},ss.velocity=function(t,e,i){this.velocity.set(t,e,i)},ns.velocityX=function(t){this.velocity.x+=t},ns.velocityY=function(t){this.velocity.y+=t},ns.velocityZ=function(t){this.velocity.z+=t},ns.velocity=B,ss.forces=function(t){t&&(Array.isArray(t)?(this.forces.length=0,this.forces=this.forces.concat(t)):this.forces.push(t))},ss.load=B,ss.history=B,ns.load=B,es.initializePositions=function(){this.initialPosition=Le(),this.position=Le(),this.velocity=Le(),this.load=Le(),this.forces=[],this.history=[],this.isRunning=!1},es.applyForces=function(t,e){this.load.zero(),this.isBeingDragged||this.forces.forEach(i=>{let s=f[i];s&&s.action&&s.action(this,t,e)})},es.update=function(t,e){this.isBeingDragged?this.position.setFromVector(this.isBeingDragged).vectorAdd(this.dragOffset):cs[this.engine].call(this,t*e.tickMultiplier)},es.manageHistory=function(t,e){let{history:i,remainingTime:s,position:n,historyLength:r,hasLifetime:o,distanceLimit:a,initialPosition:h,killBeyondCanvas:c}=this,l=!0,u=0;if(o)if(u=s-t,u<=0){let t=i.pop();ts(t),l=!1,i.length||(this.isRunning=!1)}else this.remainingTime=u;let d=i[i.length-1];if(d){let[t,i,s,n]=d;if(c){let t=e.element.width,i=e.element.height;(s<0||n<0||s>t||n>i)&&(l=!1,this.isRunning=!1)}if(a){let t=Fe(h);t.vectorSubtractArray([s,n,i]),t.getMagnitude()>a&&(l=!1,this.isRunning=!1),He(t)}}if(l){let{x:t,y:e,z:s}=n,o=(Ji.length||Ji.push(new ParticleHistory),Ji.shift());if(o.push(u,s,t,e),i.unshift(o),i.length>r){i.splice(r).forEach(t=>ts(t))}}},es.run=function(t,e,i){this.hasLifetime=!1,t&&(this.remainingTime=t,this.hasLifetime=!0),this.distanceLimit=0,e&&(this.initialPosition.set(this.position),this.distanceLimit=e),this.killBeyondCanvas=i,this.isRunning=!0};const rs=function(t){return new Particle(t)};k.Particle=Particle;const os=[],as=function(t){os.length||os.push(new Particle);let e=os.shift();return e.set(t),e},hs=function(t){if(t&&"Particle"===t.type&&(t.history.forEach(t=>ts(t)),t.history.length=0,t.set(t.defs),os.push(t),os.length>50)){let t=[].concat(os);os.length=0,t.forEach(t=>t.kill())}},cs={euler:function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=Fe(),o=Fe(i);r.setFromVector(s).scalarDivide(n),o.vectorAdd(r.scalarMultiply(t)),i.setFromVector(o),e.vectorAdd(o.scalarMultiply(t)),He(r)},"improved-euler":function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=Fe(),o=Fe(),a=Fe(),h=Fe(i);r.setFromVector(s).scalarDivide(n).scalarMultiply(t),o.setFromVector(s).vectorAdd(r).scalarDivide(n).scalarMultiply(t),a.setFromVector(r).vectorAdd(o).scalarDivide(2),h.vectorAdd(a),i.setFromVector(h),e.vectorAdd(h.scalarMultiply(t)),He(r)},"runge-kutta":function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=Fe(),o=Fe(),a=Fe(),h=Fe(),c=Fe(),l=Fe(i);r.setFromVector(s).scalarDivide(n).scalarMultiply(t).scalarDivide(2),o.setFromVector(s).vectorAdd(r).scalarDivide(n).scalarMultiply(t).scalarDivide(2),a.setFromVector(s).vectorAdd(o).scalarDivide(n).scalarMultiply(t).scalarDivide(2),h.setFromVector(s).vectorAdd(a).scalarDivide(n).scalarMultiply(t).scalarDivide(2),o.scalarMultiply(2),a.scalarMultiply(2),c.setFromVector(r).vectorAdd(o).vectorAdd(a).vectorAdd(h).scalarDivide(6),l.vectorAdd(c),i.setFromVector(l),e.vectorAdd(l.scalarMultiply(t)),He(r)}},Emitter=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.fillColorFactory=Qi({name:this.name+"-fillColorFactory"}),this.strokeColorFactory=Qi({name:this.name+"-strokeColorFactory"}),this.range=Le(),this.rangeFrom=Le(),this.preAction=B,this.stampAction=B,this.postAction=B,this.particleStore=[],this.deadParticles=[],this.liveParticles=[],t.group||(t.group=ci),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let ls=Emitter.prototype=Object.create(Object.prototype);ls.type="Emitter",ls.lib="entity",ls.isArtefact=!0,ls.isAsset=!1,ls=Pt(ls),ls=Hi(ls);ls.defs=G(ls.defs,{world:null,artefact:null,range:null,rangeFrom:null,generationRate:0,particleCount:0,generateAlongPath:!1,generateInArea:!1,generateFromExistingParticles:!1,generateFromExistingParticleHistories:!1,limitDirectionToAngleMultiples:0,generationChoke:15,killAfterTime:0,killAfterTimeVariation:0,killRadius:0,killRadiusVariation:0,killBeyondCanvas:!1,historyLength:1,forces:null,mass:1,massVariation:0,engine:"euler",hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000",resetAfterBlur:3}),ls.packetExclusions=Z(ls.packetExclusions,["forces","particleStore","deadParticles","liveParticles","fillColorFactory","strokeColorFactory"]),ls.packetExclusionsByRegex=Z(ls.packetExclusionsByRegex,[]),ls.packetCoordinates=Z(ls.packetCoordinates,[]),ls.packetObjects=Z(ls.packetObjects,["world","artefact","generateInArea","generateAlongPath"]),ls.packetFunctions=Z(ls.packetFunctions,["preAction","stampAction","postAction"]),ls.finalizePacketOut=function(t,e){let i=e.forces||this.forces||!1;if(i){let e=[];i.forEach(t=>{t.substring?e.push(t):V(t)&&t.name&&e.push(t.name)}),t.forces=e}let s=[];return this.particleStore.forEach(t=>s.push(t.saveAsPacket())),t.particleStore=s,t},ls.postCloneAction=function(t,e){return t},ls.factoryKill=function(t,e){this.isRunning=!1,t&&this.artefact.kill(),e&&this.world.kill(),this.fillColorFactory.kill(),this.strokeColorFactory.kill(),this.deadParticles.forEach(t=>t.kill()),this.liveParticles.forEach(t=>t.kill()),this.particleStore.forEach(t=>t.kill())};ls.getters;let us=ls.setters,ds=ls.deltaSetters;us.rangeX=function(t){this.range.x=t},us.rangeY=function(t){this.range.y=t},us.rangeZ=function(t){this.range.z=t},us.range=function(t){this.range.set(t)},us.rangeFromX=function(t){this.rangeFrom.x=t},us.rangeFromY=function(t){this.rangeFrom.y=t},us.rangeFromZ=function(t){this.rangeFrom.z=t},us.rangeFrom=function(t){this.rangeFrom.set(t)},us.preAction=function(t){N(t)&&(this.preAction=t)},us.stampAction=function(t){N(t)&&(this.stampAction=t)},us.postAction=function(t){N(t)&&(this.postAction=t)},us.world=function(t){let e;t.substring?e=g[t]:V(t)&&"World"===t.type&&(e=t),e&&(this.world=e)},us.artefact=function(t){let e;t.substring?e=i[t]:V(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},us.generateAlongPath=function(t){let e;t.substring?e=i[t]:V(t)&&t.isArtefact&&(e=t),e&&e.useAsPath?this.generateAlongPath=e:this.generateAlongPath=!1},us.generateInArea=function(t){let e;t.substring?e=i[t]:V(t)&&t.isArtefact&&(e=t),this.generateInArea=e||!1},us.fillColor=function(t){this.fillColorFactory.set({color:t})},us.fillMinimumColor=function(t){this.fillColorFactory.set({minimumColor:t})},us.fillMaximumColor=function(t){this.fillColorFactory.set({maximumColor:t})},us.strokeColor=function(t){this.strokeColorFactory.set({color:t})},us.strokeMinimumColor=function(t){this.strokeColorFactory.set({minimumColor:t})},us.strokeMaximumColor=function(t){this.strokeColorFactory.set({maximumColor:t})},us.hitRadius=function(t){t.toFixed&&(this.hitRadius=t,this.width=this.height=2*t)},ds.hitRadius=function(t){t.toFixed&&(this.hitRadius+=t,this.width=this.height=2*this.hitRadius)},us.width=function(t){t.toFixed&&(this.hitRadius=t/2,this.width=this.height=t)},ds.width=function(t){t.toFixed&&(this.hitRadius=t/2,this.width=this.height=t)},us.height=us.width,ds.height=ds.width,ls.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1,this.dirtyDimensions=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions();let t=Date.now(),{particleStore:e,deadParticles:i,liveParticles:s,particleCount:n,generationRate:r,generatorChoke:o,resetAfterBlur:a}=this;o||(this.generatorChoke=o=t),e.forEach(t=>{t.isRunning?s.push(t):i.push(t)}),e.length=0,i.forEach(t=>hs(t)),i.length=0,e.push(...s),s.length=0;let h=t-o;if(h/1e3>a&&(h=0,this.generatorChoke=t),h>0&&r){let i=Math.floor(r/1e3*h);if(n){let t=n-e.length;t<=0?i=0:tO.isPointInPath(T,...t,M);w.rotateDestination(O,o,a,D);t:for(n=0;n1?([h,g,...y]=a[Math.floor(Math.random()*a.length)],y?D.setFromArray(y):D.setFromVector(o.position)):D.setFromVector(o.position)):D.setFromArray(b),r=as(),r.set({positionX:D.x,positionY:D.y,positionZ:D.z,historyLength:c,engine:l,forces:u,mass:i(d,f),fill:p.get("random"),stroke:m.get("random")}),T?(D.zero(),e=Math.floor(360/T),D.x=s(j,F),D.rotate(Math.floor(Math.random()*e)*T),r.set({velocityX:D.x,velocityY:D.y,velocityZ:s($,L)})):r.set({velocityX:s(j,F),velocityY:s(B,H),velocityZ:s($,L)}),He(D),r.velocity.rotate(A);let t=Math.abs(i(P,k)),n=Math.abs(i(v,x));r.run(t,n,C),S.push(r)}}else if(O){let e,o,a=S.length,h=Fe();for(n=0;na&&(i.forEach(t=>hs(t)),i.length=0,f=.016),i.forEach(e=>e.applyForces(t,d)),i.forEach(e=>e.update(f,t)),s.call(this,d),i.forEach(t=>{t.manageHistory(f,d),n.call(this,e,t,d)}),r.call(this,d),h){let t=d.engine;t.save(),t.lineWidth=1,t.strokeStyle=l,t.setTransform(1,0,0,1,0,0),t.beginPath(),t.arc(u[0],u[1],c,0,2*Math.PI),t.stroke(),t.restore()}this.lastUpdated=p},ls.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;let i,s,n=Array.isArray(t)?t:[t],r=this.currentStampPosition,o=!1;if(n.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!K(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=Fe(r).vectorSubtract(t);return e.getMagnitude(){let{mass:s,load:n}=t,r=Fe();r.setFromVector(e.gravity).scalarMultiply(s),n.vectorAdd(r),He(r)}});const Palette=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.colors=t.colors||{"0 ":[0,0,0,1],"999 ":[255,255,255,1]},this.stops=Array(1e3),this.set(t),this.dirtyPalette=!0,this};let ms=Palette.prototype=Object.create(Object.prototype);ms.type="Palette",ms.lib="palette",ms.isArtefact=!1,ms.isAsset=!1,ms=Pt(ms);ms.defs=G(ms.defs,{colors:null,stops:null,cyclic:!1}),ms.packetExclusions=Z(ms.packetExclusions,["stops"]);ms.getters;let gs=ms.setters;gs.colors=function(t){if(V(t)){let e=this.factory;Object.entries(t).forEach(([i,s])=>{s.substring&&(e.convert(s),t[i]=[e.r,e.g,e.b,e.a])}),this.colors=t,this.dirtyPalette=!0}},gs.stops=B,ms.recalculateHold=[],ms.recalculate=function(){let t,e,i,s,n,r,o,a,h,c,l,u,d,f,p,m=this.colors,g=this.stops,y=this.makeColorString,b=this.recalculateHold;for(t=Object.keys(m),t=t.map(t=>parseInt(t,10)),t.sort((t,e)=>t-e),g.fill("rgba(0,0,0,0)"),this.dirtyPalette=!1,e=0,i=t.length-1;e999&&(r-=1e3);else{if(r=t[0],r>0)for(l=g[r],e=0,i=r;et=(t=ti?i:t;return e=o(r(t[0]),0,255),i=o(r(t[1]),0,255),s=o(r(t[2]),0,255),n=o(t[3],0,1),`rgba(${e},${i},${s},${n})`},ms.updateColor=function(t,e){let i=this.factory;K(t,e)&&(t=t.substring?parseInt(t,10):Math.floor(t))>=0&&t<=999&&(i.convert(e),t+=" ",this.colors[t]=[i.r,i.g,i.b,i.a],this.dirtyPalette=!0)},ms.removeColor=function(t){Q(t)&&(t=t.substring?parseInt(t,10):Math.floor(t))>=0&&t<=999&&(t+=" ",delete this.colors[t],this.dirtyPalette=!0)},ms.addStopsToGradient=function(t,e,i,s){let n,r,o,a,h,c,l=this.stops,u=Object.keys(this.colors);if(t){if(u=u.map(t=>parseInt(t,10)),u.sort((t,e)=>t-e),K(e,i)||(e=0,i=999),e===i)return l[e]||"rgba(0,0,0,0)";if(ee&&h0&&r<1&&t.addColorStop(r,l[h]));else if(s)for(t.addColorStop(0,l[e]),t.addColorStop(1,l[i]),c=999-e,n=c+i,o=0,a=u.length;oe)r=(h-e)/n;else{if(!(h0&&r<1&&t.addColorStop(r,l[h])}else for(t.addColorStop(0,l[e]),t.addColorStop(1,l[i]),n=e-i,o=0,a=u.length;oi&&(r=1-(h-i)/n,r>0&&r<1&&t.addColorStop(r,l[h]));return t}return"rgba(0,0,0,0)"},ms.factory=Qi({name:"palette-factory-color-calculator",opaque:!1});function ys(t={}){t.defs=G(t.defs,{start:null,end:null,palette:null,paletteStart:0,paletteEnd:999,cyclePalette:!1}),t.finalizePacketOut=function(t,e){return e.colors?t.colors=e.colors:this.palette&&this.palette.colors?t.colors=this.palette.colors:t.colors={"0 ":[0,0,0,1],"999 ":[255,255,255,1]},t},t.kill=function(){let t=this.name;return this.palette&&this.palette.kill&&this.palette.kill(),Object.entries(c).forEach(([e,i])=>{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;V(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),V(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),this.deregister(),this};let e=t.getters,i=t.setters,s=t.deltaSetters;return e.startX=function(){return this.currentStart[0]},e.startY=function(){return this.currentStart[1]},i.startX=function(t){null!=t&&(this.start[0]=t,this.dirtyStart=!0)},i.startY=function(t){null!=t&&(this.start[1]=t,this.dirtyStart=!0)},i.start=function(t,e){this.setCoordinateHelper("start",t,e),this.dirtyStart=!0},s.startX=function(t){let e=this.start;e[0]=H(e[0],t),this.dirtyStart=!0},s.startY=function(t){let e=this.start;e[1]=H(e[1],t),this.dirtyStart=!0},s.start=function(t,e){this.setDeltaCoordinateHelper("start",t,e),this.dirtyStart=!0},e.endX=function(){return this.currentEnd[0]},e.endY=function(){return this.currentEnd[1]},i.endX=function(t){null!=t&&(this.end[0]=t,this.dirtyEnd=!0)},i.endY=function(t){null!=t&&(this.end[1]=t,this.dirtyEnd=!0)},i.end=function(t,e){this.setCoordinateHelper("end",t,e),this.dirtyEnd=!0},s.endX=function(t){let e=this.end;e[0]=H(e[0],t),this.dirtyEnd=!0},s.endY=function(t){let e=this.end;e[1]=H(e[1],t),this.dirtyEnd=!0},s.end=function(t,e){this.setDeltaCoordinateHelper("end",t,e),this.dirtyEnd=!0},i.palette=function(t={}){"Palette"===t.type&&(this.palette=t)},i.paletteStart=function(t){t.toFixed&&(this.paletteStart=t,(t<0||t>999)&&(this.paletteStart=t>500?999:0))},s.paletteStart=function(t){let e;t.toFixed&&(e=this.paletteStart+t,(e<0||e>999)&&(e=this.cyclePalette?e>500?e-1e3:e+1e3:t>500?999:0),this.paletteStart=e)},i.paletteEnd=function(t){t.toFixed&&(this.paletteEnd=t,(t<0||t>999)&&(this.paletteEnd=t>500?999:0))},s.paletteEnd=function(t){let e;t.toFixed&&(e=this.paletteEnd+t,(e<0||e>999)&&(e=this.cyclePalette?e>500?e-1e3:e+1e3:t>500?999:0),this.paletteEnd=e)},i.colors=function(t){let e=this.palette;e&&e.colors&&e.set({colors:t})},i.delta=function(t={}){t&&(this.delta=U(this.delta,t))},t.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.palette;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},t.set=function(t={}){if(Object.keys(t).length){let e=this.setters,i=this.defs,s=this.palette,n=s?s.setters:{},r=s?s.defs:{};Object.entries(t).forEach(([t,o])=>{if(t&&"name"!==t&&null!=o){let a=e[t],h=!1;a||(a=n[t],h=!0),a?a.call(h?this.palette:this,o):void 0!==i[t]?this[t]=o:void 0!==r[t]&&(s[t]=o)}},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e=this.deltaSetters,i=this.defs,s=this.palette,n=s?s.deltaSetters:{},r=s?s.defs:{};Object.entries(t).forEach(([t,o])=>{if(t&&"name"!==t&&null!=o){let a=e[t],h=!1;a||(a=n[t],h=!0),a?a.call(h?this.palette:this,o):void 0!==i[t]?this[t]=H(this[t],o):void 0!==r[t]&&(s[t]=H(this[t],o))}},this)}return this},t.setCoordinateHelper=function(t,e,i){let s=this[t];Array.isArray(e)?(s[0]=e[0],s[1]=e[1]):(s[0]=e,s[1]=i)},t.setDeltaCoordinateHelper=function(t,e,i){let s=this[t],n=s[0],r=s[1];Array.isArray(e)?(s[0]=H(n,e[0]),s[1]=H(r,e[1])):(s[0]=H(n,e),s[1]=H(r,i))},t.updateByDelta=function(){return this.setDelta(this.delta),this},t.stylesInit=function(t={}){this.makeName(t.name),this.register(),this.gradientArgs=[],this.start=Kt(),this.end=Kt(),this.currentStart=Kt(),this.currentEnd=Kt(),this.palette=function(t){return new Palette(t)}({name:this.name+"_palette"}),this.delta={},this.set(this.defs),this.set(t)},t.getData=function(t,e){return this.palette&&this.palette.dirtyPalette&&this.palette.recalculate(),this.cleanStyle(t,e),this.finalizeCoordinates(t),this.buildStyle(e)},t.cleanStyle=function(t={},e={}){let i,s,n,r;t.lockFillStyleToEntity||t.lockStrokeStyleToEntity?(i=t.currentDimensions,r=t.currentScale,s=i[0]*r,n=i[1]*r):(i=e.currentDimensions,s=i[0],n=i[1]),this.cleanPosition(this.currentStart,this.start,[s,n]),this.cleanPosition(this.currentEnd,this.end,[s,n]),this.cleanRadius(s)},t.cleanPosition=function(t,e,i){let s,n;for(let r=0;r<2;r++)s=e[r],n=i[r],s.toFixed?t[r]=s:t[r]="left"===s||"top"===s?0:"right"===s||"bottom"===s?n:"center"===s?n/2:parseFloat(s)/100*n},t.finalizeCoordinates=function(t={}){this.currentStart,this.currentEnd;let e,i,s=t.currentStampPosition,n=t.currentStampHandlePosition,r=t.currentScale;t.lockFillStyleToEntity||t.lockStrokeStyleToEntity?(e=-n[0]*r||0,i=-n[1]*r||0):(e=-s[0]||0,i=-s[1]||0),this.updateGradientArgs(e,i)},t.cleanRadius=B,t.buildStyle=function(t){return"rgba(0,0,0,0)"},t.addStopsToGradient=function(t,e,i,s){return this.palette?this.palette.addStopsToGradient(t,e,i,s):t},t.updateColor=function(t,e){return this.palette&&this.palette.updateColor(t,e),this},t.removeColor=function(t){return this.palette&&this.palette.removeColor(t),this},t}k.Palette=Palette;const Gradient=function(t={}){return this.stylesInit(t),this};let bs=Gradient.prototype=Object.create(Object.prototype);bs.type="Gradient",bs.lib="styles",bs.isArtefact=!1,bs.isAsset=!1,bs=Pt(bs),bs=ys(bs),bs.packetObjects=Z(bs.packetObjects,["palette"]),bs.buildStyle=function(t={}){if(t){let e=t.engine;if(e){let t=e.createLinearGradient(...this.gradientArgs);return this.addStopsToGradient(t,this.paletteStart,this.paletteEnd,this.cyclePalette)}}return"rgba(0,0,0,0)"},bs.updateGradientArgs=function(t,e){let i=this.gradientArgs,s=this.currentStart,n=this.currentEnd,r=s[0]+t,o=s[1]+e,a=n[0]+t,h=n[1]+e;r===a&&o===h&&a++,i.length=0,i.push(r,o,a,h)};k.Gradient=Gradient;const Grid=function(t={}){return this.tileFill=[],this.tileSources=[],this.entityInit(t),t.tileSources||(this.tileSources=[].concat([{type:"color",source:"#000000"},{type:"color",source:"#ffffff"}])),t.tileFill?Array.isArray(t.tileFill)&&this.tileFill.length===t.tileFill.length&&(this.tileFill=t.tileFill):(this.tileFill.length=this.columns*this.rows,this.tileFill.fill(0)),this.tilePaths=[],this.tileRealCoordinates=[],this.tileVirtualCoordinates=[],t.dimensions||(t.width||(this.currentDimensions[0]=this.dimensions[0]=20),t.height||(this.currentDimensions[1]=this.dimensions[1]=20)),this};let Ss=Grid.prototype=Object.create(Object.prototype);Ss.type="Grid",Ss.lib="entity",Ss.isArtefact=!0,Ss.isAsset=!1,Ss=Pt(Ss),Ss=Hi(Ss);Ss.defs=G(Ss.defs,{columns:2,rows:2,columnGutterWidth:1,rowGutterWidth:1,tileSources:null,tileFill:null,gutterColor:"#808080"}),Ss.packetExclusions=Z(Ss.packetExclusions,["tileSources"]),Ss.finalizePacketOut=function(t,e){let i=t.tileSources=[];this.tileSources.forEach(t=>{i.push({type:t.type,source:V(t.source)?t.source.name:t.source})}),V(t.gutterColor)&&(t.gutterColor=t.gutterColor.name);let s=JSON.parse(this.state.saveAsPacket(e))[3];return t=G(t,s),t=this.handlePacketAnchor(t,e)};Ss.getters;let Ps=Ss.setters,ks=Ss.deltaSetters;Ps.columns=function(t){if(W(t)&&(Number.isInteger(t)||(t=parseInt(t,10)),t!==this.columns)){let e,i,s,n=this.tileFill,r=this.columns,o=[];for(this.columns=t,e=0,i=this.rows;e{W(t)&&(i[t]=e)}))},Ss.setTileSourceTo=function(t,e){W(t)&&V(e)&&e.type&&e.source&&(this.tileSources[t]=e)},Ss.removeTileSource=function(t){W(t)&&t&&(this.tileSources[t]=null,this.tileFill=this.tileFill.map(e=>e===t?0:e))},Ss.getTileSource=function(t,e){if(W(t))return W(e)?this.tileFill[t*this.rows+e]:this.tileFill[t]},Ss.getTilesUsingSource=function(t){let e=[];return W(t)&&this.tileFill.forEach((i,s)=>i==t&&e.push(s)),e},Ss.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=new Path2D,i=new Path2D,s=this.currentStampHandlePosition,n=this.currentScale,r=this.currentDimensions,o=-s[0]*n,a=-s[1]*n,h=r[0]*n,c=r[1]*n;t.rect(o,a,h,c);let l,u,d,f,p=this.columns,m=this.rows,g=h/p,y=c/m,b=this.tilePaths,S=this.tileRealCoordinates,P=this.tileVirtualCoordinates;for(e.moveTo(o,a),e.lineTo(o+h,a),l=1;l<=m;l++){let t=a+l*y;e.moveTo(o,t),e.lineTo(o+h,t)}for(this.rowLines=e,i.moveTo(o,a),i.lineTo(o,a+c),u=1;u<=p;u++){let t=o+u*g;i.moveTo(t,a),i.lineTo(t,a+c)}for(this.columnLines=i,b.length=0,S.length=0,P.length=0,l=0;l{if(r&&r.type)switch(r.type){case"color":t.fillStyle=r.source;break;case"cellGradient":this.lockFillStyleToEntity=!1,t.fillStyle=r.source.getData(this,this.currentHost);break;case"gridGradient":this.lockFillStyleToEntity=!0,t.fillStyle=r.source.getData(this,this.currentHost)}let y=o.map(t=>t===g);if(y.length)switch(r.type){case"gridPicture":e=r.source.substring?c[r.source]:r.source,e.simpleStamp&&(n.width=m[0]*p,n.height=m[1]*p,s.globalCompositeOperation="source-over",s.fillStyle="#000000",y.forEach((t,e)=>{t&&s.fillRect(l[e][0],l[e][1],d,f)}),s.globalCompositeOperation="source-in",e.simpleStamp(i,{startX:0,startY:0,width:m[0]*p,height:m[1]*p,method:"fill"}),t.drawImage(n,h[0][0],h[0][1]));break;case"tilePicture":e=r.source.substring?c[r.source]:r.source,e.simpleStamp&&(n.width=d,n.height=f,s.globalCompositeOperation="source-over",e.simpleStamp(i,{startX:0,startY:0,width:d,height:f,method:"fill"}),y.forEach((e,i)=>e&&t.drawImage(n,h[i][0],h[i][1])));break;default:y.forEach((e,i)=>e&&t.fill(a[i],u))}});let g,y=this.gutterColor,b=this.rowGutterWidth,S=this.columnGutterWidth;if(Q(y)){switch(y.substring?g={type:"color",source:this.gutterColor}:V(y)?g=y:W(y)&&V(r[y])&&(g=r[y]),g.type){case"cellGradient":this.lockFillStyleToEntity=!1,t.strokeStyle=g.source.getData(this,this.currentHost);break;case"gridGradient":this.lockFillStyleToEntity=!0,t.strokeStyle=g.source.getData(this,this.currentHost);break;case"color":t.strokeStyle=g.source}switch(g.type){case"gridPicture":case"tilePicture":if((b||S)&&(e=g.source.substring?c[g.source]:g.source,e.simpleStamp)){let r=this.currentStampHandlePosition,o=this.currentScale,a=r[0]*o,c=r[1]*o;n.width=m[0]*o,n.height=m[1]*o,s.globalCompositeOperation="source-over",s.strokeStyle="#000000",s.translate(a,c),b&&(s.lineWidth=b,s.stroke(this.rowLines)),S&&(s.lineWidth=S,s.stroke(this.columnLines)),s.globalCompositeOperation="source-in",e.simpleStamp(i,{startX:0,startY:0,width:m[0]*o,height:m[1]*o,method:"fill"}),t.drawImage(n,h[0][0],h[0][1]),s.translate(0,0)}break;default:b&&(t.lineWidth=b,t.stroke(this.rowLines)),S&&(t.lineWidth=S,t.stroke(this.columnLines))}}Ce(i),t.restore()},Ss.fill=function(t){this.performFill(t)},Ss.drawAndFill=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),this.performFill(t)},Ss.fillAndDraw=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),this.performFill(t),t.stroke(e)},Ss.drawThenFill=function(t){let e=this.pathObject;t.stroke(e),this.performFill(t)},Ss.fillThenDraw=function(t){let e=this.pathObject;this.performFill(t),t.stroke(e)},Ss.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=xe(),s=!0);let n,r,o,a=e.engine,h=this.currentStampPosition,c=h[0],l=h[1],u=new Set,d=this.tilePaths;const f=t=>{let e,i;if(Array.isArray(t))e=t[0],i=t[1];else{if(!K(t,t.x,t.y))return[!1];e=t.x,i=t.y}return!e.toFixed||!i.toFixed||isNaN(e)||isNaN(i)?[!1]:[!0,e,i]};return e.rotateDestination(a,c,l,this),i.some(t=>([n,r,o]=f(t),!!n&&a.isPointInPath(this.pathObject,r,o,this.winding)),this)?(i.forEach(t=>{[n,r,o]=f(t),n&&d.some((t,e)=>!!a.isPointInPath(t,r,o,this.winding)&&(u.add(e),!0))}),s&&Ce(e),{x:r,y:o,tiles:[...u],artefact:this}):(s&&Ce(e),!1)};k.Grid=Grid;const Line=function(t={}){return this.curveInit(t),this.shapeInit(t),this};let vs=Line.prototype=Object.create(Object.prototype);vs.type="Line",vs.lib="entity",vs.isArtefact=!0,vs.isAsset=!1,vs=Pt(vs),vs=Ii(vs),vs=Yi(vs),vs.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeLinePath(),this.pathDefinition=t},vs.makeLinePath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentEnd;return`m0,0l${(i-t).toFixed(2)},${(s-e).toFixed(2)}`},vs.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyEnd=!0},vs.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath;this.dirtyPins.forEach(i=>{(t&&t.name===i||e&&e.name===i)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};k.Line=Line;const Loom=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.state=Gt(),t.group||(t.group=ci),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.delta={},this.set(t),this.fromPathData=[],this.toPathData=[],this.watchFromPath=null,this.watchIndex=-1,this.engineInstructions=[],this.engineDeltaLengths=[],this};let xs=Loom.prototype=Object.create(Object.prototype);xs.type="Loom",xs.lib="entity",xs.isArtefact=!0,xs.isAsset=!1,xs=Pt(xs),xs=pe(xs);xs.defs=G(xs.defs,{fromPath:null,toPath:null,fromPathStart:0,fromPathEnd:1,toPathStart:0,toPathEnd:1,synchronizePathCursors:!0,loopPathCursors:!0,constantPathSpeed:!0,isHorizontalCopy:!0,showBoundingBox:!1,boundingBoxColor:"#000000",source:null,sourceIsVideoOrSprite:!1,interferenceLoops:2,interferenceFactor:1.03,visibility:!0,order:0,delta:null,host:null,group:null,anchor:null,noCanvasEngineUpdates:!1,noDeltaUpdates:!1,onEnter:null,onLeave:null,onDown:null,onUp:null,noUserInteraction:!1,method:"fill"}),xs.packetExclusions=Z(xs.packetExclusions,["pathObject","state"]),xs.packetExclusionsByRegex=Z(xs.packetExclusionsByRegex,["^(local|dirty|current)","Subscriber$"]),xs.packetCoordinates=Z(xs.packetCoordinates,[]),xs.packetObjects=Z(xs.packetObjects,["group","fromPath","toPath","source"]),xs.packetFunctions=Z(xs.packetFunctions,["onEnter","onLeave","onDown","onUp"]),xs.processPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},xs.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];return t=G(t,i),t=this.handlePacketAnchor(t,e)},xs.handlePacketAnchor=function(t,e){if(this.anchor){let i=JSON.parse(this.anchor.saveAsPacket(e))[3];t.anchor=i}return t},xs.clone=$;let Cs=xs.getters,As=xs.setters,ws=xs.deltaSetters;xs.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},xs.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=r?r.setters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,h])=>{t&&"name"!==t&&null!=h&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,h):void 0!==n[t]?this[t]=h:void 0!==a[t]&&(r[t]=h))},this)}return this},xs.setDelta=function(t={}){if(Object.keys(t).length){let e,i,s=this.deltaSetters,n=this.defs,r=this.state,o=r?r.deltaSetters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,h])=>{t&&"name"!==t&&null!=h&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,h):void 0!==n[t]?this[t]=addStrings(this[t],h):void 0!==a[t]&&(r[t]=addStrings(r[t],h)))},this)}return this},As.host=function(t){if(t){let e=i[t];e&&e.here?this.host=e.name:this.host=t}else this.host=""},Cs.group=function(){return this.group?this.group.name:""},As.group=function(t){let e;t&&(this.group&&"Group"===this.group.type&&this.group.removeArtefacts(this.name),t.substring?(e=group[t],this.group=e||t):this.group=t),this.group&&"Group"===this.group.type&&this.group.addArtefacts(this.name)},xs.getHere=function(){return currentCorePosition},As.delta=function(t={}){t&&(this.delta=U(this.delta,t))},As.fromPath=function(t){if(t){let e=this.fromPath,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&removeItem(e.pathed,n),Z(s.pathed,n),this.fromPath=s,this.dirtyStart=!0)}},As.toPath=function(t){if(t){let e=this.toPath,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&removeItem(e.pathed,n),Z(s.pathed,n),this.toPath=s,this.dirtyStart=!0)}},As.source=function(t){if((t=t.substring?i[t]:t)&&"Picture"===t.type){let e=this.source;e&&"Picture"===e.type&&e.imageUnsubscribe(this.name),this.source=t,t.imageSubscribe(this.name),this.dirtyInput=!0}},As.isHorizontalCopy=function(t){this.isHorizontalCopy=!!t,this.dirtyPathData=!0},As.synchronizePathCursors=function(t){this.synchronizePathCursors=!!t,t&&(this.toPathStart=this.fromPathStart,this.toPathEnd=this.fromPathEnd),this.dirtyPathData=!0},As.loopPathCursors=function(t){if(this.loopPathCursors=!!t,t){let t,e=Math.floor;t=this.fromPathStart,(t<0||t>1)&&(this.fromPathStart=t-e(t)),t=this.fromPathEnd,(t<0||t>1)&&(this.fromPathEnd=t-e(t)),t=this.toPathStart,(t<0||t>1)&&(this.toPathStart=t-e(t)),t=this.toPathEnd,(t<0||t>1)&&(this.toPathEnd=t-e(t))}this.dirtyOutput=!0},As.fromPathStart=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.fromPathStart=t,this.synchronizePathCursors&&(this.toPathStart=t),this.dirtyPathData=!0},ws.fromPathStart=function(t){let e=this.fromPathStart+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.fromPathStart=e,this.synchronizePathCursors&&(this.toPathStart=e),this.dirtyPathData=!0},As.fromPathEnd=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.fromPathEnd=t,this.synchronizePathCursors&&(this.toPathEnd=t),this.dirtyPathData=!0},ws.fromPathEnd=function(t){let e=this.fromPathEnd+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.fromPathEnd=e,this.synchronizePathCursors&&(this.toPathEnd=e),this.dirtyPathData=!0},As.toPathStart=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.toPathStart=t,this.synchronizePathCursors&&(this.fromPathStart=t),this.dirtyPathData=!0},ws.toPathStart=function(t){let e=this.toPathStart+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.toPathStart=e,this.synchronizePathCursors&&(this.fromPathStart=e),this.dirtyPathData=!0},As.toPathEnd=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.toPathEnd=t,this.synchronizePathCursors&&(this.fromPathEnd=t),this.dirtyPathData=!0},ws.toPathEnd=function(t){let e=this.toPathEnd+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.toPathEnd=e,this.synchronizePathCursors&&(this.fromPathEnd=e),this.dirtyPathData=!0},xs.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=i[this.host];if(t)return this.currentHost=t,this.dirtyHost=!0,this.currentHost}return currentCorePosition},xs.updateByDelta=function(){return this.setDelta(this.delta),this},xs.reverseByDelta=function(){let t={};return Object.entries(this.delta).forEach(([e,i])=>{i=i.substring?-parseFloat(i)+"%":-i,t[e]=i}),this.setDelta(t),this},xs.setDeltaValues=function(t={}){let e,i,s=this.delta;return Object.entries(t).forEach(([t,n])=>{if(xt(s[t]))switch(i=n,e=s[t],i){case"reverse":e.toFixed&&(s[t]=-e);break;case"zero":e.toFixed&&(s[t]=0)}}),this},xs.midInitActions=B,xs.cleanCollisionData=function(){return[0,[]]},xs.getSensors=function(){return[]},xs.prepareStamp=function(){let t=this.fromPath,e=this.toPath,[i,s,n,r]=this.getBoundingBox();if(!this.dirtyPathData){let{x:i,y:s}=t.getPathPositionData(0),{x:n,y:r}=t.getPathPositionData(1),{x:o,y:a}=e.getPathPositionData(0),{x:h,y:c}=e.getPathPositionData(1),l=[i,s,n,r,o,a,h,c];this.pathTests&&!this.pathTests.some((t,e)=>t!==l[e])||(this.pathTests=l,this.dirtyPathData=!0)}if(this.dirtyPathData||!this.fromPathData.length){this.dirtyPathData=!1,this.watchIndex=-1,this.engineInstructions.length=0,this.engineDeltaLengths.length=0;let n=Math.ceil,r=Math.max,o=Math.min,a=this.fromPathData;a.length=0;let h=this.toPathData;if(h.length=0,t&&e){let c,l,u,d,f=n(t.length),p=n(e.length);c=this.setSourceDimension(r(f,p));let m,g,y,b=this.fromPathStart,S=this.fromPathEnd,P=this.toPathStart,k=this.toPathEnd,v=this.constantPathSpeed;m=b{t.cleanInput().catch(i=>{console.log(t.name+" - cleanInput Error: source has a zero dimension"),e(!1)}).then(e=>(t.sourceImageData=e,t.cleanOutput())).then(e=>(t.output=e,t.regularStamp())).then(t=>{e(!0)}).catch(t=>{i(t)})}):i?new Promise((e,i)=>{t.cleanOutput().then(e=>(t.output=e,t.regularStamp())).then(t=>{e(!0)}).catch(t=>{i(t)})}):this.regularStamp()}return Promise.resolve(!1)},xs.cleanInput=function(){let t=this;return new Promise((e,i)=>{t.dirtyInput=!1,t.setSourceDimension();let s=t.sourceDimension;s||(t.dirtyInput=!0,i());let n=xe(),r=n.engine,o=n.element;o.width=s,o.height=s,r.setTransform(1,0,0,1,0,0),t.source.stamp(!0,n,{startX:0,startY:0,handleX:0,handleY:0,offsetX:0,offsetY:0,roll:0,scale:1,width:s,height:s,method:"fill"}).then(t=>{let i=r.getImageData(0,0,s,s);Ce(n),e(i)}).catch(t=>{Ce(n),i(t)})})},xs.cleanOutput=function(){let t=this;return new Promise((e,i)=>{t.dirtyOutput=!1,t.setSourceDimension();let s=t.sourceDimension,n=t.sourceImageData;if(s&&n){let i,r,o,a,h,c,l,u,d,f,p,m=Math.hypot,g=Math.floor,y=Math.ceil,b=Math.atan2,S=Math.cos,P=Math.sin,k=t.fromPathData,v=t.toPathData,x=k.length,C=t.fromPathStart*x,A=t.fromPathSteps||1,w=t.toPathStart*x,D=t.toPathSteps||1,O=.5*Math.PI,E=O-1.5708,T=t.isHorizontalCopy,R=t.loopPathCursors,F=t.watchFromPath,H=t.watchIndex,L=t.engineInstructions,j=t.engineDeltaLengths,[B,$,M,z]=t.getBoundingBox(),I=xe(),X=I.engine,Y=I.element;Y.width=s,Y.height=s,X.setTransform(1,0,0,1,0,0),X.putImageData(n,0,0);let N=xe(),W=N.engine,V=N.element;if(V.width=M,V.height=z,W.globalAlpha=t.state.globalAlpha,W.setTransform(1,0,0,1,0,0),!L.length){for(let t=0;t=0&&w>=0?([i,r]=k[g(C)],[o,a]=v[g(w)],h=o-i,c=a-r,l=m(h,c),T?(u=-b(h,c)+O,d=S(u),f=P(u),L.push([d,f,-f,d,i,r]),j.push(l)):(u=-b(h,c)+E,d=S(u),f=P(u),L.push([d,f,-f,d,i,r,l]),j.push(l))):(L.push(!1),j.push(!1)),C+=A,w+=D,R&&(C>=x&&(C-=x),w>=x&&(w-=x));H<0&&(H=0),t.watchIndex=H}if(T)for(let t=0;t=s&&(H=0);else for(let t=0;t=s&&(H=0);let q=t.interferenceFactor,G=t.interferenceLoops,U=y(M*q),Z=y(z*q);Y.width=U,Y.height=Z,W.setTransform(1,0,0,1,0,0),X.setTransform(1,0,0,1,0,0);for(let t=0;t{t.currentHost&&(t.regularStampSynchronousActions(),e(!0)),i(new Error(t.name+" has no current host"))})},xs.regularStampSynchronousActions=function(){let t=this.currentHost;if(t){let e=t.engine;this.noCanvasEngineUpdates||t.setEngine(this),this[this.method](e)}},xs.getBoundingBox=function(){let t=this.fromPath,e=this.toPath;if(t&&e){if(this.dirtyStart)if(t.getBoundingBox&&e.getBoundingBox){this.dirtyStart=!1;let[i,s,n,r,o,a]=t.getBoundingBox(),[h,c,l,u,d,f]=e.getBoundingBox();(isNaN(i)||isNaN(s)||isNaN(n)||isNaN(r)||isNaN(o)||isNaN(a)||isNaN(h)||isNaN(c)||isNaN(l)||isNaN(u)||isNaN(d)||isNaN(f))&&(this.dirtyStart=!0),i==h&&s==c&&n==l&&r==u&&o==d&&a==f&&(this.dirtyStart=!0),i+=o,s+=a,h+=d,c+=f;let p=Math.min(i,h),m=Math.max(i+n,h+l),g=Math.min(s,c),y=Math.max(s+r,c+u);this.boundingBox=[p,g,m-p,y-g],this.dirtyPathData=!0}else this.boundingBox=[0,0,0,0]}else this.boundingBox=[0,0,0,0];return this.boundingBox},xs.fill=function(t){this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.draw=function(t){this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.drawAndFill=function(t){this.doStroke(t),this.currentHost.clearShadow(),this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.fillAndDraw=function(t){this.doFill(t),this.currentHost.clearShadow(),this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.drawThenFill=function(t){this.doStroke(t),this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.fillThenDraw=function(t){this.doFill(t),this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},xs.clear=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element,s=t.globalCompositeOperation;if(e&&i){let i=xe(),n=i.engine,r=i.element,[o,a,h,c]=this.getBoundingBox();r.width=h,r.height=c,n.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.globalCompositeOperation="destination-out",t.drawImage(r,0,0,h,c,o,a,h,c),t.globalCompositeOperation=s,Ce(i),this.showBoundingBox&&this.drawBoundingBox(t)}},xs.none=B,xs.doStroke=function(t){let e=this.fromPath,i=this.toPath;if(e&&e.getBoundingBox&&i&&i.getBoundingBox){let s=this.currentHost;if(s){let n=e.currentStampPosition,r=e.getPathPositionData(1),o=i.currentStampPosition,a=i.getPathPositionData(1);s.rotateDestination(t,n[0],n[1],e),t.stroke(e.pathObject),s.rotateDestination(t,o[0],o[1],e),t.stroke(i.pathObject),t.setTransform(1,0,0,1,0,0),t.beginPath(),t.moveTo(r.x,r.y),t.lineTo(a.x,a.y),t.moveTo(...o),t.lineTo(...n),t.closePath(),t.stroke()}}},xs.doFill=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element;if(e&&i){let i=xe(),s=i.engine,n=i.element,[r,o,a,h]=this.getBoundingBox();n.width=a,n.height=h,s.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.drawImage(n,0,0,a,h,r,o,a,h),Ce(i)}},xs.drawBoundingBox=function(t){this.dirtyStart&&this.getBoundingBox(),t.save();let e=t.getTransform();t.setTransform(1,0,0,1,0,0),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.strokeRect(...this.boundingBox),t.restore(),t.setTransform(e)},xs.checkHit=function(t=[]){if(this.noUserInteraction)return!1;let e,i,s,n,r,o=Array.isArray(t)?t:[t],a=!(!this.output||!this.output.data)&&this.output.data;if(a){let[t,h,c,l]=this.getBoundingBox();if(o.some(o=>{if(Array.isArray(o))e=o[0],i=o[1];else{if(!K(o,o.x,o.y))return!1;e=o.x,i=o.y}return!(!e.toFixed||!i.toFixed||isNaN(e)||isNaN(i))&&(s=e-t,n=i-h,!(s<0||s>c||n<0||n>l)&&(r=4*(n*c+s)+3,!!a&&a[r]>0))},this))return{x:e,y:i,artefact:this}}return!1};k.Loom=Loom;const Spring=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.action||(this.action=B),this};let Ds=Spring.prototype=Object.create(Object.prototype);Ds.type="Spring",Ds.lib="spring",Ds.isArtefact=!1,Ds.isAsset=!1,Ds=Pt(Ds);Ds.defs=G(Ds.defs,{particleFrom:null,particleFromIsStatic:!1,particleTo:null,particleToIsStatic:!1,springConstant:50,damperConstant:10,restLength:1}),Ds.packetObjects=Z(Ds.packetObjects,["particleFrom","particleTo"]),Ds.kill=function(){return this.deregister(),!0};let Os=Ds.setters;Os.particleFrom=function(t){t.substring&&(t=d[t]),t&&"Particle"===t.type&&(this.particleFrom=t)},Os.particleTo=function(t){t.substring&&(t=d[t]),t&&"Particle"===t.type&&(this.particleTo=t)},Ds.applySpring=function(){let{particleFrom:t,particleTo:e,particleFromIsStatic:i,particleToIsStatic:s,springConstant:n,damperConstant:r,restLength:o}=this;if(t&&e){let{position:a,velocity:h,load:c}=t,{position:l,velocity:u,load:d}=e,f=Fe(u).vectorSubtract(h),p=Fe(l).vectorSubtract(a),m=Fe(p).normalize(),g=Fe(m);m.scalarMultiply(n*(p.getMagnitude()-o)),f.vectorMultiply(g).scalarMultiply(r).vectorMultiply(g);let y=Fe(m).vectorAdd(f);i||c.vectorAdd(y),s||d.vectorSubtract(y),He(f)}};const Es=function(t){return new Spring(t)};k.Spring=Spring;const Net=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.generate=B,this.postGenerate=B,this.stampAction=B,this.particleStore=[],this.springs=[],t.group||(t.group=ci),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let Ts=Net.prototype=Object.create(Object.prototype);Ts.type="Net",Ts.lib="entity",Ts.isArtefact=!0,Ts.isAsset=!1,Ts=Pt(Ts),Ts=Hi(Ts);Ts.defs=G(Ts.defs,{world:null,artefact:null,historyLength:1,forces:null,mass:1,engine:"euler",springConstant:50,damperConstant:10,restLength:1,showSprings:!1,showSpringsColor:"#000000",rows:0,columns:0,rowDistance:0,columnDistance:0,shapeTemplate:null,precision:20,joinTemplateEnds:!1,particlesAreDraggable:!1,hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000",resetAfterBlur:3}),Ts.packetExclusions=Z(Ts.packetExclusions,["forces","springs","particleStore"]),Ts.packetExclusionsByRegex=Z(Ts.packetExclusionsByRegex,[]),Ts.packetCoordinates=Z(Ts.packetCoordinates,[]),Ts.packetObjects=Z(Ts.packetObjects,["world","artefact","shapeTemplate"]),Ts.packetFunctions=Z(Ts.packetFunctions,["generate","postGenerate","stampAction"]),Ts.finalizePacketOut=function(t,e){let i=e.forces||this.forces||!1;if(i){let e=[];i.forEach(t=>{t.substring?e.push(t):V(t)&&t.name&&e.push(t.name)}),t.forces=e}let s=[];return this.particleStore.forEach(t=>s.push(t.saveAsPacket())),t.particleStore=s,t},Ts.postCloneAction=function(t,e){return t},Ts.factoryKill=function(t,e){this.isRunning=!1,t&&(this.artefact.kill(),this.shapeTemplate&&this.shapeTemplate.kill()),e&&this.world.kill(),this.purgeParticlesFromLibrary()},Ts.purgeParticlesFromLibrary=function(){let{particleStore:t,springs:e}=this;s.forEach(t=>{let e=i[t];e&&(e.particle&&!e.particle.substring&&e.particle.name&&(e.particle=e.particle.name),"Polyline"===e.type&&e.useParticlesAsPins&&e.pins.forEach((t,i)=>{V(t)&&"Particle"===t.type&&(e.pins[i]=t.name,e.dirtyPins=!0)}))}),t.forEach(t=>t.kill()),t.length=0,e.forEach(t=>t.kill()),e.length=0};Ts.getters;let Rs=Ts.setters;Ts.deltaSetters;Rs.generate=function(t){N(t)?this.generate=t:t.substring&&Fs[t]&&(this.generate=Fs[t])},Rs.postGenerate=function(t){N(t)&&(this.postGenerate=t)},Rs.stampAction=function(t){N(t)&&(this.stampAction=t)},Rs.world=function(t){let e;t.substring?e=g[t]:V(t)&&"World"===t.type&&(e=t),e&&(this.world=e)},Rs.artefact=function(t){let e;t.substring?e=i[t]:V(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},Rs.shapeTemplate=function(t){let e;t.substring?e=c[t]:V(t)&&t.isArtefact&&Q(t.species)&&(e=t),e&&(this.shapeTemplate=e)},Ts.regularStampSynchronousActions=function(){let{world:t,artefact:e,particleStore:i,springs:s,generate:n,postGenerate:r,stampAction:o,lastUpdated:a,resetAfterBlur:h,showSprings:c,showSpringsColor:l,showHitRadius:u,hitRadius:d,hitRadiusColor:f}=this,p=1,m="source-over";this.state&&(p=this.state.globalAlpha,m=this.state.globalCompositeOperation);let g=this.currentHost,y=.016,b=Date.now();if(a&&(y=(b-a)/1e3),y>h&&(this.purgeParticlesFromLibrary(),y=.016),i.length||(n.call(this,g),r.call(this)),i.forEach(e=>e.applyForces(t,g)),s.forEach(t=>t.applySpring()),i.forEach(e=>e.update(y,t)),c){let t=g.engine;t.save(),t.globalAlpha=p,t.globalCompositeOperation=m,t.strokeStyle=l,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.shadowColor="rgba(0,0,0,0)",t.lineWidth=1,t.setTransform(1,0,0,1,0,0),t.beginPath(),s.forEach(e=>{let{particleFrom:i,particleTo:s}=e;t.moveTo(i.position.x,i.position.y),t.lineTo(s.position.x,s.position.y)}),t.stroke(),t.restore()}if(i.forEach(t=>{t.manageHistory(y,g),o.call(this,e,t,g)}),u){let t=g.engine;t.save(),t.globalAlpha=p,t.globalCompositeOperation=m,t.lineWidth=1,t.strokeStyle=f,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.shadowColor="rgba(0,0,0,0)",t.setTransform(1,0,0,1,0,0),t.beginPath(),i.forEach(e=>{t.moveTo(e.position.x,e.position.y),t.arc(e.position.x,e.position.y,d,0,2*Math.PI)}),t.stroke(),t.restore()}this.lastUpdated=b},Ts.restart=function(){return this.purgeParticlesFromLibrary(),this.lastUpdated=Date.now(),this},Ts.checkHit=function(t=[],e){if(this.lastHitParticle=null,!this.particlesAreDraggable)return!1;if(this.noUserInteraction)return!1;let i,s,n,r,o,a=Array.isArray(t)?t:[t],h=this.particleStore,c=!1;if(a.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!K(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=Fe();for(n=0,r=h.length;n0&&c>0){let f,p,S,P,k,[v,x]=this.currentStampPosition,[C,A]=t.currentDimensions,w=l.substring?parseFloat(l)/100*A:l,D=u.substring?parseFloat(u)/100*A:u;for(P=0;P0&&c>0){let f,p,S,P,k,[v,x]=this.currentStampPosition,[C,A]=t.currentDimensions,w=l.substring?parseFloat(l)/100*A:l,D=u.substring?parseFloat(u)/100*A:u;for(P=0;P0;k--)E=d[`${m}-${k}-${P}`],T=d[`${m}-${k-1}-${P+1}`],O(E,T,`${m}-${k}-${P}~${m}-${k-1}-${P+1}`)}},"weak-shape":function(t){let{particleStore:e,artefact:i,historyLength:s,engine:n,forces:r,springs:o,mass:a,showSprings:h,showSpringsColor:c,name:l,springConstant:u,damperConstant:f,restLength:p,shapeTemplate:m,precision:g,joinTemplateEnds:y}=this;const b=function(t,e,i){let s,n,r;s=Fe(t.position).vectorSubtract(e.position),n=s.getMagnitude(),r=Es({name:i,particleFrom:t,particleTo:e,springConstant:u,damperConstant:f,restLength:n*p}),o.push(r),He(s)};let S,P,k,v;if(m&&g){for(S=0;S=0){for(S=0;S=0||t.type;let[C,A]=e.get("position");x=rs({name:f+"-hub",positionX:C,positionY:A,positionZ:0,velocityX:0,velocityY:0,velocityZ:0,historyLength:r,engine:o,forces:a,mass:c,fill:n.get("fillStyle"),stroke:n.get("strokeStyle")}),x.run(0,0,!1),s.forEach((t,e)=>b(t,x,`${f}-${e}-hub`)),s.push(x)}}};k.Net=Net;const Oval=function(t={}){return this.shapeInit(t),this};let Hs=Oval.prototype=Object.create(Object.prototype);Hs.type="Oval",Hs.lib="entity",Hs.isArtefact=!0,Hs.isAsset=!1,Hs=Pt(Hs),Hs=Ii(Hs);Hs.defs=G(Hs.defs,{radiusX:5,radiusY:5,intersectX:.5,intersectY:.5,offshootA:.55,offshootB:0});let Ls=Hs.setters,js=Hs.deltaSetters;Ls.radius=function(t){this.setRectHelper(t,["radiusX","radiusY"])},Ls.radiusX=function(t){this.setRectHelper(t,["radiusX"])},Ls.radiusY=function(t){this.setRectHelper(t,["radiusY"])},js.radius=function(t){this.deltaRectHelper(t,["radiusX","radiusY"])},js.radiusX=function(t){this.deltaRectHelper(t,["radiusX"])},js.radiusY=function(t){this.deltaRectHelper(t,["radiusY"])},Ls.offshootA=function(t){this.offshootA=t,this.updateDirty()},Ls.offshootB=function(t){this.offshootB=t,this.updateDirty()},js.offshootA=function(t){t.toFixed&&(this.offshootA+=t,this.updateDirty())},js.offshootB=function(t){t.toFixed&&(this.offshootB+=t,this.updateDirty())},Ls.intersectA=function(t){this.intersectA=t,this.updateDirty()},Ls.intersectB=function(t){this.intersectB=t,this.updateDirty()},js.intersectA=function(t){t.toFixed&&(this.intersectA+=t,this.updateDirty())},js.intersectB=function(t){t.toFixed&&(this.intersectB+=t,this.updateDirty())},Hs.setRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=t},this)},Hs.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=addStrings(this[e],t)},this)},Hs.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeOvalPath(),this.pathDefinition=t},Hs.makeOvalPath=function(){let t,e,i=parseFloat(this.offshootA.toFixed(6)),s=parseFloat(this.offshootB.toFixed(6)),n=this.radiusX,r=this.radiusY;if(n.substring||r.substring){let i=this.getHost();if(i){let[s,o]=i.currentDimensions;t=2*(n.substring?parseFloat(n)/100*s:n),e=2*(r.substring?parseFloat(r)/100*o:r)}}else t=2*n,e=2*r;let o=parseFloat((t*this.intersectX).toFixed(2)),a=parseFloat((t-o).toFixed(2)),h=parseFloat((e*this.intersectY).toFixed(2)),c=parseFloat((e-h).toFixed(2)),l="m0,0";return l+=`c${a*i},${h*s} ${a-a*s},${h-h*i}, ${a},${h} `,l+=`${-a*s},${c*i} ${a*i-a},${c-c*s} ${-a},${c} `,l+=`${-o*i},${-c*s} ${o*s-o},${c*i-c} ${-o},${-c} `,l+=`${o*s},${-h*i} ${o-o*i},${h*s-h} ${o},${-h}z`,l},Hs.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};k.Oval=Oval;const VideoAsset=function(t={}){return this.assetConstructor(t)};let Bs=VideoAsset.prototype=Object.create(Object.prototype);Bs.type="Video",Bs.lib="asset",Bs.isArtefact=!1,Bs.isAsset=!0,Bs=Pt(Bs),Bs=Jt(Bs),Bs.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},Bs.stringifyFunction=B,Bs.processPacketOut=B,Bs.finalizePacketOut=B,Bs.clone=$;Bs.getters;let $s=Bs.setters;Bs.deltaSetters;$s.source=function(t={}){t&&("VIDEO"===t.tagName.toUpperCase()&&(this.source=t,this.sourceNaturalWidth=t.videoWidth||0,this.sourceNaturalHeight=t.videoHeight||0,this.sourceLoaded=t.readyState>2),this.sourceLoaded&&this.notifySubscribers())},Bs.checkSource=function(t,e){let i=this.source;i&&i.readyState>2?(this.sourceLoaded=!0,this.sourceNaturalWidth===i.videoWidth&&this.sourceNaturalHeight===i.videoHeight&&this.sourceNaturalWidth===t&&this.sourceNaturalHeight===e||(this.sourceNaturalWidth=i.videoWidth,this.sourceNaturalHeight=i.videoHeight,this.notifySubscribers())):this.sourceLoaded=!1},Bs.addTextTrack=function(t,e,i){let s=this.source;s&&s.addTextTrack&&s.addTextTrack(t,e,i)},Bs.captureStream=function(){let t=this.source;return!(!t||!t.captureStream)&&t.captureStream()},Bs.canPlayType=function(t){let e=this.source;return e?e.canPlayType(t):"maybe"},Bs.fastSeek=function(t){let e=this.source;e&&e.fastSeek&&e.fastSeek(t)},Bs.load=function(){let t=this.source;t&&t.load()},Bs.pause=function(){let t=this.source;t&&t.pause()},Bs.play=function(){let t=this.source;return t?t.play().catch(t=>console.log(t.code,t.name,t.message)):Promise.reject("Source not defined")},Bs.setMediaKeys=function(t){let e=this.source;return e?e.setMediaKeys?e.setMediaKeys(t):Promise.reject("setMediaKeys not supported"):Promise.reject("Source not defined")},Bs.setSinkId=function(){let t=this.source;return t?t.setSinkId?t.setSinkId():Promise.reject("setSinkId not supported"):Promise.reject("Source not defined")};const Ms=["video_audioTracks","video_autoPlay","video_buffered","video_controller","video_controls","video_controlsList","video_crossOrigin","video_currentSrc","video_currentTime","video_defaultMuted","video_defaultPlaybackRate","video_disableRemotePlayback","video_duration","video_ended","video_error","video_loop","video_mediaGroup","video_mediaKeys","video_muted","video_networkState","video_paused","video_playbackRate","video_readyState","video_seekable","video_seeking","video_sinkId","video_src","video_srcObject","video_textTracks","video_videoTracks","video_volume"],zs=["video_autoPlay","video_controller","video_controls","video_crossOrigin","video_currentTime","video_defaultMuted","video_defaultPlaybackRate","video_disableRemotePlayback","video_loop","video_mediaGroup","video_muted","video_playbackRate","video_src","video_srcObject","video_volume"],Is=function(...t){let e=/.*\/(.*?)\./,i="";if(t.length){let s,n,r,o,a,h,c=!1,l=t[0];if(l.substring){let i=e.exec(l);s=i&&i[1]?i[1]:"",a=[...t],n="",r=!1,o=null,h="auto",c=!0}else l&&l.src&&(s=l.name||"",a=[...l.src],n=l.className||"",r=l.visibility||!1,o=document.querySelector(o),h=l.preload||"auto",c=!0);let u=Xs({name:s});if(c){let t=document.createElement("video");t.name=s,t.className=n,t.style.display=r?"block":"none",t.crossOrigin="anonymous",t.preload=h,a.forEach(e=>{let i=document.createElement("source");i.src=e,t.appendChild(i)}),t.onload=()=>{u.set({source:t}),o&&o.appendChild(t)},u.set({source:t}),i=s}}return i},Xs=function(t){return new VideoAsset(t)};k.VideoAsset=VideoAsset;const SpriteAsset=function(t={}){return this.assetConstructor(t),this};let Ys=SpriteAsset.prototype=Object.create(Object.prototype);Ys.type="Sprite",Ys.lib="asset",Ys.isArtefact=!1,Ys.isAsset=!0,Ys=Pt(Ys),Ys=Jt(Ys);Ys.defs=G(Ys.defs,{manifest:null}),Ys.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},Ys.stringifyFunction=B,Ys.processPacketOut=B,Ys.finalizePacketOut=B,Ys.clone=$;Ys.getters;let Ns=Ys.setters;Ys.deltaSetters;Ns.source=function(t=[]){if(t&&t[0]){this.sourceHold||(this.sourceHold={});let e=this.sourceHold;t.forEach(t=>{let i=t.id||t.name;i&&(e[i]=t)}),this.source=t[0],this.sourceNaturalWidth=t[0].naturalWidth,this.sourceNaturalHeight=t[0].naturalHeight,this.sourceLoaded=t[0].complete}},Ys.checkSource=B;const Ws=function(...t){let e=/.*\/(.*?)\./,i=/\.(jpeg|jpg|png|gif|webp|svg|JPEG|JPG|PNG|GIF|WEBP|SVG)/,s=[];return t.forEach(t=>{let n,r,o,a,h,c=!1,l=!1;if(t.substring){let s=e.exec(t);n=s&&s[1]?s[1]:"",r=[t],o="",a=!1,h=t.replace(i,".json"),l=!0}else V(t)&&t.imageSrc&&t.manifestSrc?(n=t.name||"",r=Array.isArray(t.imageSrc)?t.imageSrc:[t.imageSrc],h=t.manifestSrc,o=t.className||"",a=t.visibility||!1,c=document.querySelector(t.parent),l=!0):s.push(!1);if(l){let t=Vs({name:n});V(h)?t.manifest=h:fetch(h).then(t=>{if(200!==t.status)throw new Error("Failed to load manifest");return t.json()}).then(e=>t.manifest=e).catch(t=>console.log(t.message));let l=[];r.forEach(t=>{let s,r,h=document.createElement("img");i.test(t)&&(r=e.exec(t),s=r&&r[1]?r[1]:""),h.name=s||n,h.className=o,h.crossorigin="anonymous",h.style.display=a?"block":"none",c&&c.appendChild(h),h.src=t,l.push(h)}),t.set({source:l}),s.push(n)}else s.push(!1)}),s},Vs=function(t){return new SpriteAsset(t)};function qs(t={}){t.defs=G(t.defs,{asset:null,removeAssetOnKill:!1,spriteIsRunning:!1,spriteLastFrameChange:0,spriteCurrentFrame:0,spriteTrack:"default",spriteForward:!0,spriteFrameDuration:100,spriteWillLoop:!0});let e=t.setters;return e.asset=function(t){let e=this.asset,i=t&&t.name?t.name:t;e&&!e.substring&&e.unsubscribe(this),this.asset=i,this.dirtyAsset=!0},e.imageSource=function(t){let e=ne(t);if(e){let t=n[e[0]];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},e.videoSource=function(t){let e=Is(t);if(e){let t=n[e];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},e.spriteSource=function(t){let e=Ws(t);if(e){let t=n[e];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},t.cleanAsset=function(){let t=this.asset;if(t&&t.substring){let e=n[t];e&&(this.dirtyAsset=!1,e.subscribe(this))}},t.videoAction=function(t,...e){let i=this.asset;if(i&&"Video"===i.type)return i[t](...e)},t.videoPromiseAction=function(t,...e){let i=this.asset;return i&&"Video"===i.type?i[t](...e):Promise.reject("Asset not a video")},t.videoAddTextTrack=function(t,e,i){return this.videoAction("addTextTrack",t,e,i)},t.videoCaptureStream=function(){return this.videoAction("captureStream")},t.videoCanPlayType=function(t){return this.videoAction("canPlayType",t)},t.videoFastSeek=function(t){return this.videoAction("fastSeek",t)},t.videoLoad=function(){return this.videoAction("load")},t.videoPause=function(){return this.videoAction("pause")},t.videoPlay=function(){return this.videoPromiseAction("play")},t.videoSetMediaKeys=function(t){return this.videoPromiseAction("setMediaKeys",t)},t.videoSetSinkId=function(){return this.videoPromiseAction("setSinkId")},t.checkSpriteFrame=function(){let t=this.asset;if(t&&"Sprite"===t.type){let e=this.copyArray;if(this.spriteIsRunning){let i=this.spriteLastFrameChange,s=this.spriteFrameDuration,n=Date.now();if(n>i+s){let i=t.manifest;if(i){let s=i[this.spriteTrack],r=s.length,o=this.spriteCurrentFrame,a=this.spriteWillLoop;o=this.spriteForward?o+1:o-1,o<0&&(o=a?r-1:0),o>=r&&(o=a?0:r-1);let[h,c,l,u,d]=s[o];if(e.length=0,e.push(c,l,u,d),this.dirtyCopyStart=!1,this.dirtyCopyDimensions=!1,h!==(this.source.id||this.source.name)){let e=t.sourceHold[h];e&&(this.source=e)}this.spriteCurrentFrame=o,this.spriteLastFrameChange=n}}}else{let[i,s,n,r,o]=t.manifest[this.spriteTrack][this.spriteCurrentFrame],[a,h,c,l]=e;a===s&&h===n&&c===r&&l===o||(e.length=0,e.push(s,n,r,o),this.dirtyCopyStart=!1,this.dirtyCopyDimensions=!1)}}},t.playSprite=function(t,e,i,s,n){Q(t)&&(this.spriteFrameDuration=t),Q(e)&&(this.spriteWillLoop=e),Q(i)&&(this.spriteTrack=i),Q(s)&&(this.spriteForward=s),Q(n)&&(this.spriteCurrentFrame=n),this.spriteLastFrameChange=Date.now(),this.spriteIsRunning=!0},t.haltSprite=function(t,e,i,s,n){Q(t)&&(this.spriteFrameDuration=t),Q(e)&&(this.spriteWillLoop=e),Q(i)&&(this.spriteTrack=i),Q(s)&&(this.spriteForward=s),Q(n)&&(this.spriteCurrentFrame=n),this.spriteIsRunning=!1},t}k.SpriteAsset=SpriteAsset;const Pattern=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this};let Gs=Pattern.prototype=Object.create(Object.prototype);Gs.type="Pattern",Gs.lib="styles",Gs.isArtefact=!1,Gs.isAsset=!1,Gs=Pt(Gs),Gs=ge(Gs),Gs=qs(Gs);Gs.defs=G(Gs.defs,{}),Gs.packetObjects=Z(Gs.packetObjects,["asset"]),Gs.finalizePacketOut=function(t,e){if(Array.isArray(e.patternMatrix))t.patternMatrix=e.patternMatrix;else{let e=this.patternMatrix;e&&(t.patternMatrix=[e.a,e.b,e.c,e.d,e.e,e.f])}return t},Gs.kill=function(){let{name:t,asset:e,removeAssetOnKill:i}=this;return V(e)&&e.unsubscribe(this),Object.entries(c).forEach(([e,i])=>{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;V(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),V(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),i&&(i.substring?e.kill(!0):e.kill()),this.deregister(),this},Gs.get=function(t){let e=this.source;if(0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!e){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t];return void 0!==i?(e=this[t],void 0!==e?e:i):undef}}return Ms.indexOf(t)>=0||ie.indexOf(t)>=0?e[t.substring(6)]:void 0},Gs.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs,n=this.source;Object.entries(t).forEach(([t,r])=>{0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!n?t&&"name"!==t&&null!=r&&(e=i[t],e?e.call(this,r):void 0!==s[t]&&(this[t]=r)):(zs.indexOf(t)>=0||se.indexOf(t)>=0)&&(n[t.substring(6)]=r)},this)}return this},Gs.getData=function(t,e){return this.dirtyAsset&&this.cleanAsset(),this.asset.checkSource(this.sourceNaturalWidth,this.sourceNaturalHeight),this.buildStyle(e)};k.Pattern=Pattern;const FontAttributes=function(t={}){return this.makeName(t.name),this.set(this.defs),this.set(t),this};let Us=FontAttributes.prototype=Object.create(Object.prototype);Us.type="FontAttributes",Us.lib="fontattribute",Us=Pt(Us);Us.defs=G(Us.defs,{style:"normal",variant:"normal",weight:"normal",stretch:"normal",sizeValue:12,sizeMetric:"px",family:"sans-serif"});let Zs=Us.getters,_s=Us.setters;Us.deltaSetters;Zs.size=function(){return this.sizeValue?`${this.sizeValue}${this.sizeMetric}`:this.sizeMetric},_s.size=function(t){if(Q(t)){let e,i=0,s="medium";t.indexOf("xx-small")>=0?s="xx-small":t.indexOf("x-small")>=0?s="x-small":t.indexOf("smaller")>=0?s="smaller":t.indexOf("small")>=0?s="small":t.indexOf("xx-large")>=0?s="xx-large":t.indexOf("x-large")>=0?s="x-large":t.indexOf("larger")>=0?s="larger":t.indexOf("large")>=0?s="large":t.indexOf("medium")>=0?s="medium":(i=12,s="px"),/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(t)?(e=t.match(/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i),i="."!==e[1]?parseFloat(e[1]):12,s=e[2]):/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(t)&&(e=t.match(/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i),i="."!==e[1]?parseFloat(e[1]):12,s=e[2]),this.sizeValue=i,this.sizeMetric=s}},_s.font=function(t){Q(t)&&(_s.style.call(this,t),_s.variant.call(this,t),_s.weight.call(this,t),_s.stretch.call(this,t),_s.size.call(this,t),_s.family.call(this,t))},_s.style=function(t){let e="normal";Q(t)&&(e=t.indexOf("italic")>=0?"italic":e,e=t.indexOf("oblique")>=0?"oblique":e),this.style=e},_s.variant=function(t){let e="normal";e=t.indexOf("small-caps")>=0?"small-caps":e,this.variant=e},_s.weight=function(t){let e="normal";Q(t)&&(t.toFixed?e=t:(e=t.indexOf("bold")>=0?"bold":e,e=t.indexOf("lighter")>=0?"lighter":e,e=t.indexOf("bolder")>=0?"bolder":e,e=t.indexOf(" 100 ")>=0?"100":e,e=t.indexOf(" 200 ")>=0?"200":e,e=t.indexOf(" 300 ")>=0?"300":e,e=t.indexOf(" 400 ")>=0?"400":e,e=t.indexOf(" 500 ")>=0?"500":e,e=t.indexOf(" 600 ")>=0?"600":e,e=t.indexOf(" 700 ")>=0?"700":e,e=t.indexOf(" 800 ")>=0?"800":e,e=t.indexOf(" 900 ")>=0?"900":e,e=/^\d00$/.test(t)?t:e)),this.weight=e},_s.stretch=function(t){let e="normal";Q(t)&&(e=t.indexOf("semi-condensed")>=0?"semi-condensed":e,e=t.indexOf("condensed")>=0?"condensed":e,e=t.indexOf("extra-condensed")>=0?"extra-condensed":e,e=t.indexOf("ultra-condensed")>=0?"ultra-condensed":e,e=t.indexOf("semi-condensed")>=0?"semi-condensed":e,e=t.indexOf("condensed")>=0?"condensed":e,e=t.indexOf("extra-condensed")>=0?"extra-condensed":e,e=t.indexOf("ultra-condensed")>=0?"ultra-condensed":e),this.stretch=e},_s.family=function(t){if(Q(t)){let e=t.match(/( xx-small| x-small| small| medium| large| x-large| xx-large| smaller| larger|\d%|\dem|\dch|\dex|\drem|\dvh|\dvw|\dvmin|\dvmax|\dpx|\dcm|\dmm|\din|\dpc|\dpt) (.*)$/i);this.family=e&&e[2]?e[2]:t}},Us.buildFont=function(t=1){let e="";"normal"!==this.style&&(e+=this.style+" "),"normal"!==this.variant&&(e+=this.variant+" "),"normal"!==this.weight&&(e+=this.weight+" "),"normal"!==this.stretch&&(e+=this.stretch+" "),this.sizeValue?e+=`${this.sizeValue*t}${this.sizeMetric} `:e+=this.sizeMetric+" ",e+=""+this.family;let i=xe();return i.engine.font=e,e=i.engine.font,Ce(i),e},Us.update=function(t=1,e){return e&&this.set(e),this.buildFont(t)};k.FontAttributes=FontAttributes;const Qs=document.createElement("div");Qs.style.padding=0,Qs.style.border=0,Qs.style.margin=0,Qs.style.height="auto",Qs.style.lineHeight=1,Qs.style.boxSizing="border-box",Qs.innerHTML="|/}ÁÅþ§¶¿∑ƒ⌈⌊qwertyd0123456789QWERTY",Qs.setAttribute("aria-hidden","true"),ki.appendChild(Qs);const Ks=document.createElement("textarea"),Js=(t,e)=>(t=parseFloat(t),W(t)||(t=0),W(e)||(e=0),parseFloat(t.toFixed(e))),tn=(t,e)=>(t=parseFloat(t),W(t)||(t=0),W(e)||(e=0),Math.abs(parseFloat(t.toFixed(e)))),Phrase=function(t={}){return this.fontAttributes=function(t){return new FontAttributes(t)}(t),delete t.font,delete t.style,delete t.variant,delete t.weight,delete t.stretch,delete t.size,delete t.sizeValue,delete t.sizeMetric,delete t.family,this.entityInit(t),this.sectionStyles=[],this.sectionClasses={DEFAULTS:{defaults:!0},BOLD:{weight:"bold"},ITALIC:{style:"italic"},"SMALL-CAPS":{variant:"small-caps"},HIGHLIGHT:{highlight:!0},UNDERLINE:{underline:!0},OVERLINE:{overline:!0},"/BOLD":{weight:"normal"},"/ITALIC":{style:"normal"},"/SMALL-CAPS":{variant:"normal"},"/HIGHLIGHT":{highlight:!1},"/UNDERLINE":{underline:!1},"/OVERLINE":{overline:!1}},this.dirtyDimensions=!0,this.dirtyText=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this};let en=Phrase.prototype=Object.create(Object.prototype);en.type="Phrase",en.lib="entity",en.isArtefact=!0,en.isAsset=!1,en=Pt(en),en=Hi(en);en.defs=G(en.defs,{text:"",width:"auto",exposeText:!0,lineHeight:1.15,letterSpacing:0,justify:"left",sectionClassMarker:"§",sectionClasses:null,overlinePosition:-.1,overlineStyle:"rgb(250,0,0)",underlinePosition:.6,underlineStyle:"rgb(250,0,0)",highlightStyle:"rgba(250,218,94,0.4)",boundingBoxColor:"rgba(0,0,0,0.5)",showBoundingBox:!1,textPath:"",textPathPosition:0,textPathLoop:!0,addTextPathRoll:!0,textPathDirection:"ltr",treatWordAsGlyph:!1}),en.packetExclusions=Z(en.packetExclusions,["textPositions","textLines","textLineWidths","textLineWords","textGlyphs","textGlyphWidths","fontAttributes"]),en.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];t=G(t,i);let s=JSON.parse(this.fontAttributes.saveAsPacket(e))[3];return delete s.name,t=G(t,s),t=this.handlePacketAnchor(t,e)},en.factoryKill=function(){this.exposedTextHold&&this.exposedTextHold.remove()};let sn=en.getters,nn=en.setters,rn=en.deltaSetters;nn.handleX=function(t){null!=t&&(this.handle[0]=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0)},nn.handleY=function(t){null!=t&&(this.handle[1]=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0)},nn.handle=function(t,e){this.setCoordinateHelper("handle",t,e),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},rn.handleX=function(t){let e=this.handle;e[0]=addStrings(e[0],t),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},rn.handleY=function(t){let e=this.handle;e[1]=addStrings(e[1],t),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},rn.handle=function(t,e){this.setDeltaCoordinateHelper("handle",t,e),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},sn.text=function(){return this.currentText||this.text||""},nn.text=function(t){var e;this.text=(e=t).substring?e:e.toString,this.dirtyText=!0,this.dirtyPathObject=!0,this.dirtyDimensions=!0},en.permittedJustifications=["left","right","center","full"],nn.justify=function(t){this.permittedJustifications.indexOf(t)>=0&&(this.justify=t),this.dirtyText=!0,this.dirtyPathObject=!0},nn.width=function(t){this.dimensions[0]=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyPathObject=!0,this.dirtyText=!0},rn.width=function(t){let e=this.dimensions;e[0]=addStrings(e[0],t),this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyPathObject=!0,this.dirtyText=!0},nn.scale=function(t){this.scale=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this.dirtyScale=!0},rn.scale=function(t){this.scale+=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this.dirtyScale=!0},nn.lineHeight=function(t){this.lineHeight=tn(t,3),this.lineHeight<.5&&(this.lineHeight=.5),this.dirtyPathObject=!0,this.dirtyText=!0},rn.lineHeight=function(t){this.lineHeight+=Js(t,3),this.lineHeight<.5&&(this.lineHeight=.5),this.dirtyPathObject=!0,this.dirtyText=!0},nn.letterSpacing=function(t){this.letterSpacing=tn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},rn.letterSpacing=function(t){this.letterSpacing+=Js(t,3),this.letterSpacing<0&&(this.letterSpacing=0),this.dirtyPathObject=!0,this.dirtyText=!0},nn.overlinePosition=function(t){this.overlinePosition=Js(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},rn.overlinePosition=function(t){this.overlinePosition+=Js(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},nn.underlinePosition=function(t){this.underlinePosition=Js(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},rn.underlinePosition=function(t){this.underlinePosition+=Js(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},nn.textPath=function(t){this.textPath=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},nn.textPathPosition=function(t){this.textPathLoop?(t<0&&(t=Math.abs(t)),t>1&&(t%=1),this.textPathPosition=parseFloat(t.toFixed(6))):this.textPathPosition=t},rn.textPathPosition=function(t){let e=this.textPathPosition+t;this.textPathLoop?(e<0&&(e+=1),e>1&&(e%=1),this.textPathPosition=parseFloat(e.toFixed(6))):this.textPathPosition=e},sn.font=function(){return this.fontAttributes.get("font")},nn.font=function(t){this.fontAttributes.set({font:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.style=function(){return this.fontAttributes.get("style")},nn.style=function(t){this.fontAttributes.set({style:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.variant=function(){return this.fontAttributes.get("variant")},nn.variant=function(t){this.fontAttributes.set({variant:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.weight=function(){return this.fontAttributes.get("weight")},nn.weight=function(t){this.fontAttributes.set({weight:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.stretch=function(){return this.fontAttributes.get("stretch")},nn.stretch=function(t){this.fontAttributes.set({stretch:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.size=function(){return this.fontAttributes.get("size")},nn.size=function(t){this.fontAttributes.set({size:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.sizeValue=function(){return this.fontAttributes.get("sizeValue")},nn.sizeValue=function(t){this.fontAttributes.set({sizeValue:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},rn.sizeValue=function(t){this.fontAttributes.deltaSet({sizeValue:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.sizeMetric=function(){return this.fontAttributes.get("sizeMetric")},nn.sizeMetric=function(t){this.fontAttributes.set({sizeMetric:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},sn.family=function(){return this.fontAttributes.get("family")},nn.family=function(t){this.fontAttributes.set({family:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},en.cleanDimensionsAdditionalActions=function(){if("auto"===this.dimensions[0]){this.buildText();let t=xe(),e=t.engine;e.font=this.fontAttributes.buildFont(),this.currentDimensions[0]=Math.ceil(e.measureText(this.currentText).width),Ce(t)}this.textLines?this.currentDimensions[1]=Math.ceil(this.textHeight*this.textLines.length*this.lineHeight/this.scale):this.dirtyDimensions=!0},en.setSectionStyles=function(t){let e,i,s,n=new RegExp(this.sectionClassMarker),r=t.split(n),o=this.sectionStyles,a=this.sectionClasses,h="";return o.length=0,r.forEach(t=>{e=a[t],e?(i=h.length,s=o[i],s?Object.assign(s,e):o[i]=Object.assign({},e)):Q(t)&&(h+=t)}),h},en.addSectionClass=function(t,e){return K(t,e)&&t.substring&&V(e)&&(this.sectionClasses[t]=e),this.dirtyText=!0,this.dirtyPathObject=!0,this},en.removeSectionClass=function(t){return delete this.sectionClasses[t],this.dirtyText=!0,this.dirtyPathObject=!0,this},en.getTextPath=function(){let t=this.textPath;return t&&t.substring&&(t=this.textPath=i[this.textPath],"Shape"===t.type&&t.useAsPath?t.pathed.push(this.name):t=this.path=!1),t},en.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.dirtyFont&&this.fontAttributes&&(this.dirtyFont=!1,this.fontAttributes.buildFont(this.scale),this.dirtyText=!0,this.dirtyMimicDimensions=!0,this.dirtyPositionSubscribers=!0),this.dirtyText&&this.buildText(),this.dirtyHandle&&this.cleanHandle();let t=this.pathObject=new Path2D,e=this.currentHandle,i=this.currentDimensions,s=this.currentScale,n=-e[0]*s,r=-e[1]*s,o=i[0]*s,a=i[1]*s;this.boxStartValues=[n,r],t.rect(n,r,o,a)}},en.buildText=function(){this.dirtyText=!1;let t=this.convertTextEntityCharacters(this.text);if(t=this.setSectionStyles(t),this.currentText=t,isNaN(this.currentDimensions[0]))this.dirtyText=!0;else if(this.calculateTextPositions(t),this.exposeText){if(!this.exposedTextHold){let t=document.createElement("div");t.id=this.name+"-text-hold",t.setAttribute("aria-live","polite"),this.exposedTextHold=t,this.exposedTextHoldAttached=!1}this.exposedTextHold.textContent=t,this.exposedTextHoldAttached||this.currentHost&&this.currentHost.controller&&this.currentHost.controller.textHold&&(this.currentHost.controller.textHold.appendChild(this.exposedTextHold),this.exposedTextHoldAttached=!0)}},en.convertTextEntityCharacters=function(t){let e=t.trim();return e=e.replace(/[\s\uFEFF\xA0]+/g," "),Ks.innerHTML=e,Ks.value},en.calculateTextPositions=function(t){const e=function(t){if(!H)return F.dirtyPathObject=!0,F.dirtyText=!0,"black";if(t.substring){let e=!1;if(S.indexOf(t)>=0?e=b[t]:h.indexOf(t)>=0&&(e=a[t]),e)return e}return t};let i,s,n,r,o,c,l,u,d,f,p,m,g,y,P,k,v,x,C,A,w,D,O,E,T=xe(),R=T.engine,F=this,H=!(!this.group||!this.group.getHost)&&this.group.getHost(),L=[],j=[],B=[],$=[],M=[],z=[],I=[],X=this.getTextPath(),Y=this.fontAttributes,N=Y.clone({}),W=this.sectionStyles,V=this.state,q={},G=[],U=this.currentScale,Z=this.currentDimensions,_=Z[0]*U,K=this.treatWordAsGlyph,J=this.lineHeight,tt=this.justify,et=Y.update(U),it=e(V.fillStyle),st=e(V.strokeStyle),nt=this.letterSpacing*U,rt=et,ot=it,at=st,ht=nt,ct=(!!this.highlightStyle&&e(this.highlightStyle),!1),lt=(!!this.underlineStyle&&e(this.underlineStyle),this.underlinePosition,!1),ut=(!!this.overlineStyle&&e(this.overlineStyle),this.overlinePosition,!1),dt=0;for(i=K?t.split(" "):t.split(""),G.push(rt),f=0,p=i.length;f{Qs.style.font=t,r=Qs.clientHeight,q[t]=r}),dt=Math.max(...Object.values(q)),w=A=o=c=0,f=0,p=M.length;fv&&!n[0]&&(z[f]-=P-v));for(f=0,p=M.length;f=_&&ot+e,0),B.push(P),A-=P,o=c+1),f+1===p&&(A===w?(y=t,j.push(y),$.push(K?y.split(" ").length-1:y.split(" ").length),B.push(w)):(y=i.slice(o).join(""),j.push(y),P=K?y.split(" ").length-1:y.split(" ").length,$.push(P),P=L.slice(o).reduce((t,e)=>t+e,0),B.push(P))),Q(k[3])&&(ct=k[3]),Q(k[4])&&(lt=k[4]),Q(k[5])&&(ut=k[5]),k[3]=ct,k[4]=lt,k[5]=ut;if(U<=0&&(U=1),Z[1]=Math.ceil(dt*j.length*J/U),this.cleanHandle(),this.dirtyHandle=!1,D=this.currentHandle,O=-D[0]*U,E=-D[1]*U,!X)if("full"===tt)for(l=0,u=E,f=0,p=B.length;f1?(_-B[f])/($[f]-1):0,m=0,g=j[f].length;m1||n<0)&&(n=n>.5?n-1:n+1),t[10]=n<=1&&n>=0&&r.getPathPositionData(n,f),t[9]=s,c?l+=s/o:l-=s/o,d&&(l>1||l<0)&&(l=l>.5?l-1:l+1)}},en.preStamper=function(t,e,i,s){const n=function(e){return e.getData?e.getData(i,t):e};let[r,o,a,h,c,l,...u]=s;if(r&&(e.font=r),h||c||l){let t=i.highlightStyle,s=i.textHeight,r=i.underlineStyle,o=i.underlinePosition,a=i.overlineStyle,d=i.overlinePosition;e.save(),h&&(e.fillStyle=n(t),e.fillRect(u[1],u[2],u[3],s)),c&&(e.strokeStyle=n(r),e.strokeRect(u[1],u[2]+s*o,u[3],1)),l&&(e.strokeStyle=n(a),e.strokeRect(u[1],u[2]+s*d,u[3],1)),e.restore()}return o&&(e.strokeStyle=n(o)),a&&(e.fillStyle=n(a)),u},en.stamper={draw:function(t,e,i){t.strokeText(...i)},fill:function(t,e,i){t.fillText(...i)},drawAndFill:function(t,e,i){t.strokeText(...i),e.currentHost.clearShadow(),t.fillText(...i),e.currentHost.restoreShadow(e)},fillAndDraw:function(t,e,i){t.strokeText(...i),e.currentHost.clearShadow(),t.fillText(...i),t.strokeText(...i),e.currentHost.restoreShadow(e)},drawThenFill:function(t,e,i){t.strokeText(...i),t.fillText(...i)},fillThenDraw:function(t,e,i){t.fillText(...i),t.strokeText(...i)},clear:function(t,e,i){let s=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fillText(...i),t.globalCompositeOperation=s}},en.drawBoundingBox=function(t){t.save(),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.stroke(this.pathObject),t.restore()},en.performRotation=function(t){let e=this.currentHost;if(e){let[i,s]=this.currentStampPosition;e.rotateDestination(t,i,s,this)}};k.Phrase=Phrase;const Picture=function(t={}){return this.copyStart=Kt(),this.currentCopyStart=Kt(),this.copyDimensions=Kt(),this.currentCopyDimensions=Kt(),this.copyArray=[],this.pasteArray=[],this.entityInit(t),t.copyStart||(t.copyStartX||(this.copyStart[0]=0),t.copyStartY||(this.copyStart[1]=0)),t.copyDimensions||(t.copyWidth||(this.copyDimensions[0]=1),t.copyHeight||(this.copyDimensions[1]=1)),this.imageSubscribers=[],this.dirtyCopyStart=!0,this.dirtyCopyDimensions=!0,this.dirtyImage=!0,this};let on=Picture.prototype=Object.create(Object.prototype);on.type="Picture",on.lib="entity",on.isArtefact=!0,on.isAsset=!1,on=Pt(on),on=Hi(on),on=qs(on);on.defs=G(on.defs,{copyStart:null,copyDimensions:null,checkHitIgnoreTransparency:!1}),on.packetCoordinates=Z(on.packetCoordinates,["copyStart","copyDimensions"]),on.packetObjects=Z(on.packetObjects,["asset"]),on.factoryKill=function(t=!1){let{asset:e,removeAssetOnKill:i}=this;V(e)&&e.unsubscribe(this),i&&(i.substring?e.kill(!0):e.kill())};let an=on.getters,hn=on.setters,cn=on.deltaSetters;an.copyStartX=function(){return this.currentCopyStart[0]},an.copyStartY=function(){return this.currentCopyStart[1]},hn.copyStartX=function(t){null!=t&&(this.copyStart[0]=t,this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0)},hn.copyStartY=function(t){null!=t&&(this.copyStart[1]=t,this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0)},hn.copyStart=function(t,e){this.setCoordinateHelper("copyStart",t,e),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},cn.copyStartX=function(t){let e=this.copyStart;e[0]=H(e[0],t),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},cn.copyStartY=function(t){let e=this.copyStart;e[1]=H(e[1],t),this.dirtyCopyStart=!0},cn.copyStart=function(t,e){this.setDeltaCoordinateHelper("copyStart",t,e),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},an.copyWidth=function(){return this.currentCopyDimensions[0]},an.copyHeight=function(){return this.currentCopyDimensions[1]},hn.copyWidth=function(t){null!=t&&(this.copyDimensions[0]=t,this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0)},hn.copyHeight=function(t){null!=t&&(this.copyDimensions[1]=t,this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0)},hn.copyDimensions=function(t,e){this.setCoordinateHelper("copyDimensions",t,e),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},cn.copyWidth=function(t){let e=this.copyDimensions;e[0]=H(e[0],t),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},cn.copyHeight=function(t){let e=this.copyDimensions;e[1]=H(e[1],t),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},cn.copyDimensions=function(t,e){this.setDeltaCoordinateHelper("copyDimensions",t,e),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},hn.checkHitIgnoreTransparency=function(t){this.checkHitIgnoreTransparency=t,t&&(this.stashOutput=!0)},on.get=function(t){let e=this.source;if(0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!e){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}}return Ms.indexOf(t)>=0||ie.indexOf(t)>=0?e[t.substring(6)]:void 0},on.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=this.source,a=r?r.setters:{},h=r?r.defs:{};Object.entries(t).forEach(([t,c])=>{0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!o?t&&"name"!==t&&null!=c&&(e=s[t],i=!1,e||(e=a[t],i=!0),e?e.call(i?this.state:this,c):void 0!==n[t]?this[t]=c:void 0!==h[t]&&(r[t]=c)):(zs.indexOf(t)>=0||se.indexOf(t)>=0)&&(o[t.substring(6)]=c)},this)}return this},on.updateImageSubscribers=function(){this.dirtyImageSubscribers=!1,this.imageSubscribers.length&&this.imageSubscribers.forEach(t=>{let e=i[t];e&&(e.dirtyInput=!0)})},on.imageSubscribe=function(t){t&&t.substring&&Z(this.imageSubscribers,t)},on.imageUnsubscribe=function(t){t&&t.substring&&_(this.imageSubscribers,t)},on.cleanImage=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(K(t,e)){this.dirtyImage=!1;let i=this.currentCopyStart,s=i[0],n=i[1],r=this.currentCopyDimensions,o=r[0],a=r[1];s+o>t&&(i[0]=t-o),n+a>e&&(i[1]=e-a);let h=this.copyArray;h.length=0,h.push(i[0],i[1],o,a)}},on.cleanCopyStart=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(K(t,e)){this.dirtyCopyStart=!1,this.cleanPosition(this.currentCopyStart,this.copyStart,[t,e]);let i=this.currentCopyStart,s=i[0],n=i[1];(s<0||s>t)&&(i[0]=s<0?0:t-1),(n<0||n>e)&&(i[1]=n<0?0:e-1),this.dirtyImage=!0}},on.cleanCopyDimensions=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(K(t,e)){this.dirtyCopyDimensions=!1;let i=this.copyDimensions,s=this.currentCopyDimensions,n=i[0],r=i[1];n.substring?s[0]=parseFloat(n)/100*t:s[0]=n,r.substring?s[1]=parseFloat(r)/100*e:s[1]=r;let o=s[0],a=s[1];(o<=0||o>t)&&(s[0]=o<=0?1:t),(a<=0||a>e)&&(s[1]=a<=0?1:e),this.dirtyImage=!0}},on.prepareStamp=function(){this.dirtyAsset&&this.cleanAsset(),this.asset&&("Sprite"===this.asset.type?this.checkSpriteFrame(this):this.asset.checkSource?this.asset.checkSource(this.sourceNaturalWidth,this.sourceNaturalHeight):this.dirtyAsset=!0),(this.dirtyDimensions||this.dirtyHandle||this.dirtyScale)&&(this.dirtyPaste=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyCopyStart&&this.cleanCopyStart(),this.dirtyCopyDimensions&&this.cleanCopyDimensions(),this.dirtyImage&&this.cleanImage(),this.dirtyPaste&&this.preparePasteObject(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.dirtyImageSubscribers&&this.updateImageSubscribers()},on.preparePasteObject=function(){this.dirtyPaste=!1;let t=this.currentStampHandlePosition,e=this.currentDimensions,i=this.currentScale,s=-t[0]*i,n=-t[1]*i,r=e[0]*i,o=e[1]*i,a=this.pasteArray;a.length=0,a.push(s,n,r,o),this.dirtyPathObject=!0},on.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.pasteArray||this.preparePasteObject(),(this.pathObject=new Path2D).rect(...this.pasteArray)}},on.draw=function(t){t.stroke(this.pathObject)},on.fill=function(t){this.source&&t.drawImage(this.source,...this.copyArray,...this.pasteArray)},on.drawAndFill=function(t){t.stroke(this.pathObject),this.source&&(this.currentHost.clearShadow(),t.drawImage(this.source,...this.copyArray,...this.pasteArray))},on.fillAndDraw=function(t){t.stroke(this.pathObject),this.source&&(this.currentHost.clearShadow(),t.drawImage(this.source,...this.copyArray,...this.pasteArray)),t.stroke(this.pathObject)},on.drawThenFill=function(t){t.stroke(this.pathObject),this.source&&t.drawImage(this.source,...this.copyArray,...this.pasteArray)},on.fillThenDraw=function(t){this.source&&t.drawImage(this.source,...this.copyArray,...this.pasteArray),t.stroke(this.pathObject)},on.checkHitReturn=function(t,e,i){if(this.checkHitIgnoreTransparency&&i&&i.engine){let[s,n,r,o]=this.copyArray,[a,h,c,l]=this.pasteArray,[u,d]=this.currentStampPosition,f=4*((e-d)*c+(t-u))+3;return!!i.engine.getImageData(s,n,r,o).data[f]&&{x:t,y:e,artefact:this}}return{x:t,y:e,artefact:this}};k.Picture=Picture;const Polygon=function(t={}){return this.shapeInit(t),this};let ln=Polygon.prototype=Object.create(Object.prototype);ln.type="Polygon",ln.lib="entity",ln.isArtefact=!0,ln.isAsset=!1,ln=Pt(ln),ln=Ii(ln);ln.defs=G(ln.defs,{sides:0,radius:0});let un=ln.setters,dn=ln.deltaSetters;un.sides=function(t){this.sides=t,this.updateDirty()},dn.sides=function(t){this.sides+=t,this.updateDirty()},un.radius=function(t){this.radius=t,this.updateDirty()},dn.radius=function(t){this.radius+=t,this.updateDirty()},ln.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makePolygonPath(),this.pathDefinition=t},ln.makePolygonPath=function(){let t,e,i,s=this.radius,n=this.sides,r=360/n,o="",a=[],h=0,c=Fe({x:0,y:-s});for(let t=0;t{if("pins"===e){let e=[];t.pins.forEach(t=>{V(t)?e.push(t.name):Array.isArray(t)?e.push([].concat(t)):e.push(t)}),t.pins=e}}),t};let pn=fn.getters,mn=fn.setters,gn=fn.deltaSetters;pn.pins=function(t){return Q(t)?this.getPinAt(t):this.currentPins.concat()},mn.pins=function(t){if(Q(t)){let e=this.pins;if(Array.isArray(t))e.forEach((t,e)=>this.removePinAt(e)),e.length=0,e.push(...t),this.updateDirty();else if(V(t)&&Q(t.index)){let i=e[t.index];Array.isArray(i)&&(Q(t.x)&&(i[0]=t.x),Q(t.y)&&(i[1]=t.y),this.updateDirty())}}},gn.pins=function(t){if(Q(t)){let e=this.pins;if(V(t)&&Q(t.index)){let i=e[t.index];Array.isArray(i)&&(Q(t.x)&&(i[0]=addStrings(i[0],t.x)),Q(t.y)&&(i[1]=addStrings(i[1],t.y)),this.updateDirty())}}},mn.tension=function(t){t.toFixed&&(this.tension=t,this.updateDirty())},gn.tension=function(t){t.toFixed&&(this.tension+=t,this.updateDirty())},mn.closed=function(t){this.closed=t,this.updateDirty()},mn.mapToPins=function(t){this.mapToPins=t,this.updateDirty()},mn.flipUpend=function(t){this.flipUpend=t,this.updateDirty()},mn.flipReverse=function(t){this.flipReverse=t,this.updateDirty()},mn.useAsPath=function(t){this.useAsPath=t,this.updateDirty()},mn.pivot=function(t){if(I(t)&&!t)this.pivot=null,"pivot"===this.lockTo[0]&&(this.lockTo[0]="start"),"pivot"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.pivot,s=t.substring?i[t]:t,n=this.name;s&&s.name&&(e&&e.name!==s.name&&_(e.pivoted,n),Z(s.pivoted,n),this.pivot=s,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}this.updateDirty()},fn.updateDirty=function(){this.dirtySpecies=!0,this.dirtyPathObject=!0,this.dirtyPins=!0},fn.getPinAt=function(t){let e=Math.floor(t);if(this.useAsPath){let t=this.getPathPositionData(this.unitPartials[e]);return[t.x,t.y]}{let t,i,s=this.currentPins,n=s[e],[r,o,a,h]=this.localBox,[c,l]=n,[u,d]=s[0],[f,p]=this.localOffset,[m,g]=this.currentStampPosition;return this.mapToPins?(t=c-u+r,i=l-u+o):(t=c-f,i=l-p),[m+t,g+i]}},fn.updatePinAt=function(t,e){if(K(t,e)){e=Math.floor(e);let i=this.pins;if(e=0){let s=i[e];V(s)&&s.pivoted&&_(s.pivoted,this.name),i[e]=t,this.updateDirty()}}},fn.removePinAt=function(t){t=Math.floor(t);let e=this.pins;if(t=0){let i=e[t];V(i)&&i.pivoted&&_(i.pivoted,this.name),e[t]=null,this.updateDirty()}},fn.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1),this.useParticlesAsPins&&(this.dirtyPins=!0),(this.dirtyPins||this.dirtyLock)&&(this.dirtySpecies=!0),(this.dirtyScale||this.dirtySpecies||this.dirtyDimensions||this.dirtyStart||this.dirtyHandle)&&(this.dirtyPathObject=!0,(this.dirtyScale||this.dirtySpecies)&&(this.pathCalculatedOnce=!1)),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&(this.cleanPathObject(),this.updatePathSubscribers()),this.dirtyPositionSubscribers&&this.updatePositionSubscribers()},fn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makePolylinePath(),this.pathDefinition=t},fn.getPathParts=function(t,e,i,s,n,r,o){let a=Math.sqrt,h=Math.pow,c=a(h(i-t,2)+h(s-e,2)),l=a(h(n-i,2)+h(r-s,2)),u=o*c/(c+l),d=o*l/(c+l);return[i-u*(n-t),s-u*(r-e),i,s,i+d*(n-t),s+d*(r-e)]},fn.buildLine=function(t,e,i){let s="m0,0l";for(let n=2;n2&&(t=i[r],e=i[r+1],n=0);return s},fn.cleanCoordinate=function(t,e){return t.toFixed?t:"left"===t||"top"===t?0:"right"===t||"bottom"===t?e:"center"===t?e/2:parseFloat(t)/100*e},fn.cleanPinsArray=function(){this.dirtyPins=!1;let t=this.pins,e=this.currentPins;if(e.length=0,this.useParticlesAsPins)t.forEach((i,s)=>{let n;i&&i.substring?(n=d[i],n&&(t[s]=n)):n=i;let r=!(!n||!n.position)&&n.position;r&&e.push([r.x,r.y])}),e.length||(this.dirtyPins=!0);else{let s,n,r,o=1,a=1,h=this.getHost(),c=this.cleanCoordinate;h&&(r=h.currentDimensions,r&&([o,a]=r)),t.forEach((r,h)=>{let l;if(r&&r.substring?(l=i[r],t[h]=l):l=r,l)if(Array.isArray(l))[s,n]=l,e.push([c(s,o),c(n,a)]);else if(V(l)&&l.currentStart){let t=this.name;l.pivoted.indexOf(t)<0&&Z(l.pivoted,t),e.push([...l.currentStampPosition])}})}if(e.length){let t=e[0][0],i=e[0][1];e.forEach(e=>{e[0]{let e=i[t];e&&(e.dirtyStart=!0)})};k.Polyline=Polyline;const Quadratic=function(t={}){return this.control=Kt(),this.currentControl=Kt(),this.controlLockTo="coord",this.curveInit(t),this.shapeInit(t),this.dirtyControl=!0,this};let yn=Quadratic.prototype=Object.create(Object.prototype);yn.type="Quadratic",yn.lib="entity",yn.isArtefact=!0,yn.isAsset=!1,yn=Pt(yn),yn=Ii(yn),yn=Yi(yn);yn.defs=G(yn.defs,{control:null,controlPivot:"",controlPivotCorner:"",addControlPivotHandle:!1,addControlPivotOffset:!1,controlPath:"",controlPathPosition:0,addControlPathHandle:!1,addControlPathOffset:!0,controlParticle:"",controlLockTo:""}),yn.packetExclusions=Z(yn.packetExclusions,[]),yn.packetExclusionsByRegex=Z(yn.packetExclusionsByRegex,[]),yn.packetCoordinates=Z(yn.packetCoordinates,["control"]),yn.packetObjects=Z(yn.packetObjects,["controlPivot","controlPath"]),yn.packetFunctions=Z(yn.packetFunctions,[]);yn.getters;let bn=yn.setters,Sn=yn.deltaSetters;bn.controlPivot=function(t){this.setControlHelper(t,"controlPivot","control"),this.updateDirty(),this.dirtyControl=!0},bn.controlParticle=function(t){this.setControlHelper(t,"controlParticle","control"),this.updateDirty(),this.dirtyControl=!0},bn.controlPath=function(t){this.setControlHelper(t,"controlPath","control"),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},bn.controlPathPosition=function(t){this.controlPathPosition=t,this.dirtyControl=!0,this.currentControlPathData=!1},Sn.controlPathPosition=function(t){this.controlPathPosition+=t,this.dirtyControl=!0,this.currentControlPathData=!1},bn.controlX=function(t){null!=t&&(this.control[0]=t,this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1)},bn.controlY=function(t){null!=t&&(this.control[1]=t,this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1)},bn.control=function(t,e){this.setCoordinateHelper("control",t,e),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},Sn.controlX=function(t){let e=this.control;e[0]=H(e[0],t),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},Sn.controlY=function(t){let e=this.control;e[1]=H(e[1],t),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},Sn.control=function(t,e){this.setDeltaCoordinateHelper("control",t,e),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},bn.controlLockTo=function(t){this.controlLockTo=t,this.updateDirty(),this.dirtyControlLock=!0,this.currentControlPathData=!1},yn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeQuadraticPath(),this.pathDefinition=t},yn.makeQuadraticPath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentControl,[n,r]=this.currentEnd;return`m0,0q${(i-t).toFixed(2)},${(s-e).toFixed(2)} ${(n-t).toFixed(2)},${(r-e).toFixed(2)}`},yn.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyControl=!0,this.dirtyEnd=!0},yn.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath,i=this.controlPivot,s=this.controlPath;this.dirtyPins.forEach(n=>{(i&&i.name===n||s&&s.name===n)&&(this.dirtyControl=!0,this.controlLockTo.includes("path")&&(this.currentControlPathData=!1)),(t&&t.name===n||e&&e.name===n)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};k.Quadratic=Quadratic;const RadialGradient=function(t={}){return this.stylesInit(t),this};let Pn=RadialGradient.prototype=Object.create(Object.prototype);Pn.type="RadialGradient",Pn.lib="styles",Pn.isArtefact=!1,Pn.isAsset=!1,Pn=Pt(Pn),Pn=ys(Pn);Pn.defs=G(Pn.defs,{startRadius:0,endRadius:0}),Pn.packetObjects=Z(Pn.packetObjects,["palette"]);let kn=Pn.getters,vn=Pn.setters,xn=Pn.deltaSetters;kn.startRadius=function(t){return this.currentStartRadius},kn.endRadius=function(t){return this.currentEndRadius},vn.startRadius=function(t){this.startRadius=t,this.dirtyStyle=!0},vn.endRadius=function(t){this.endRadius=t,this.dirtyStyle=!0},xn.startRadius=function(t){this.startRadius=H(this.startRadius,t),this.dirtyStyle=!0},xn.endRadius=function(t){this.endRadius=H(this.endRadius,t),this.dirtyStyle=!0},Pn.cleanRadius=function(t){const e=(t,e)=>{if(W(t))return t;switch(t){case"top":case"left":return 0;case"bottom":case"right":return e;case"center":return e/2;default:return t=parseFloat(t),W(t)?t/100*e:0}};this.currentStartRadius=t?e(this.startRadius,t):this.defs.startRadius,this.currentEndRadius=t?e(this.endRadius,t):this.defs.endRadius},Pn.buildStyle=function(t={}){if(t){let e=t.engine;if(e){let t=e.createRadialGradient(...this.gradientArgs);return this.addStopsToGradient(t,this.paletteStart,this.paletteEnd,this.cyclePalette)}}return"rgba(0,0,0,0)"},Pn.updateGradientArgs=function(t,e){let i=this.gradientArgs,s=this.currentStart,n=this.currentEnd,r=this.currentStartRadius,o=this.currentEndRadius,a=s[0]+t,h=s[1]+e,c=n[0]+t,l=n[1]+e;a===c&&h===l&&r===o&&o++,i.length=0,i.push(a,h,r,c,l,o)};k.RadialGradient=RadialGradient;const Rectangle=function(t={}){return this.shapeInit(t),this.currentRectangleWidth=1,this.currentRectangleHeight=1,this};let Cn=Rectangle.prototype=Object.create(Object.prototype);Cn.type="Rectangle",Cn.lib="entity",Cn.isArtefact=!0,Cn.isAsset=!1,Cn=Pt(Cn),Cn=Ii(Cn);Cn.defs=G(Cn.defs,{rectangleWidth:10,rectangleHeight:10,radiusTLX:0,radiusTLY:0,radiusTRX:0,radiusTRY:0,radiusBRX:0,radiusBRY:0,radiusBLX:0,radiusBLY:0,offshootA:.55,offshootB:0});let An=Cn.setters,wn=Cn.deltaSetters;An.radius=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX","radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},An.radiusX=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX"])},An.radiusY=function(t){this.setRectHelper(t,["radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},An.radiusT=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY","radiusTRX","radiusTRY"])},An.radiusB=function(t){this.setRectHelper(t,["radiusBRX","radiusBRY","radiusBLX","radiusBLY"])},An.radiusL=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY","radiusBLX","radiusBLY"])},An.radiusR=function(t){this.setRectHelper(t,["radiusTRX","radiusTRY","radiusBRX","radiusBRY"])},An.radiusTX=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX"])},An.radiusBX=function(t){this.setRectHelper(t,["radiusBRX","radiusBLX"])},An.radiusLX=function(t){this.setRectHelper(t,["radiusTLX","radiusBLX"])},An.radiusRX=function(t){this.setRectHelper(t,["radiusTRX","radiusBRX"])},An.radiusTY=function(t){this.setRectHelper(t,["radiusTLY","radiusTRY"])},An.radiusBY=function(t){this.setRectHelper(t,["radiusBRY","radiusBLY"])},An.radiusLY=function(t){this.setRectHelper(t,["radiusTLY","radiusBLY"])},An.radiusRY=function(t){this.setRectHelper(t,["radiusTRY","radiusBRY"])},An.radiusTL=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY"])},An.radiusTR=function(t){this.setRectHelper(t,["radiusTRX","radiusTRY"])},An.radiusBL=function(t){this.setRectHelper(t,["radiusBLX","radiusBLY"])},An.radiusBR=function(t){this.setRectHelper(t,["radiusBRX","radiusBRY"])},An.radiusTLX=function(t){this.setRectHelper(t,["radiusTLX"])},An.radiusTLY=function(t){this.setRectHelper(t,["radiusTLY"])},An.radiusTRX=function(t){this.setRectHelper(t,["radiusTRX"])},An.radiusTRY=function(t){this.setRectHelper(t,["radiusTRY"])},An.radiusBRX=function(t){this.setRectHelper(t,["radiusBRX"])},An.radiusBRY=function(t){this.setRectHelper(t,["radiusBRY"])},An.radiusBLX=function(t){this.setRectHelper(t,["radiusBLX"])},An.radiusBLY=function(t){this.setRectHelper(t,["radiusBLY"])},wn.radius=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX","radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},wn.radiusX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX"])},wn.radiusY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},wn.radiusT=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY","radiusTRX","radiusTRY"])},wn.radiusB=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBRY","radiusBLX","radiusBLY"])},wn.radiusL=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY","radiusBLX","radiusBLY"])},wn.radiusR=function(t){this.deltaRectHelper(t,["radiusTRX","radiusTRY","radiusBRX","radiusBRY"])},wn.radiusTX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX"])},wn.radiusBX=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBLX"])},wn.radiusLX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusBLX"])},wn.radiusRX=function(t){this.deltaRectHelper(t,["radiusTRX","radiusBRX"])},wn.radiusTY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusTRY"])},wn.radiusBY=function(t){this.deltaRectHelper(t,["radiusBRY","radiusBLY"])},wn.radiusLY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusBLY"])},wn.radiusRY=function(t){this.deltaRectHelper(t,["radiusTRY","radiusBRY"])},wn.radiusTL=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY"])},wn.radiusTR=function(t){this.deltaRectHelper(t,["radiusTRX","radiusTRY"])},wn.radiusBL=function(t){this.deltaRectHelper(t,["radiusBLX","radiusBLY"])},wn.radiusBR=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBRY"])},wn.radiusTLX=function(t){this.deltaRectHelper(t,["radiusTLX"])},wn.radiusTLY=function(t){this.deltaRectHelper(t,["radiusTLY"])},wn.radiusTRX=function(t){this.deltaRectHelper(t,["radiusTRX"])},wn.radiusTRY=function(t){this.deltaRectHelper(t,["radiusTRY"])},wn.radiusBRX=function(t){this.deltaRectHelper(t,["radiusBRX"])},wn.radiusBRY=function(t){this.deltaRectHelper(t,["radiusBRY"])},wn.radiusBLX=function(t){this.deltaRectHelper(t,["radiusBLX"])},wn.radiusBLY=function(t){this.deltaRectHelper(t,["radiusBLY"])},An.offshootA=function(t){this.offshootA=t,this.updateDirty()},An.offshootB=function(t){this.offshootB=t,this.updateDirty()},wn.offshootA=function(t){t.toFixed&&(this.offshootA+=t,this.updateDirty())},wn.offshootB=function(t){t.toFixed&&(this.offshootB+=t,this.updateDirty())},An.rectangleWidth=function(t){null!=t&&(this.rectangleWidth=t,this.dirtyDimensions=!0)},An.rectangleHeight=function(t){null!=t&&(this.rectangleHeight=t,this.dirtyDimensions=!0)},wn.rectangleWidth=function(t){this.rectangleWidth=H(this.rectangleWidth,t),this.dirtyDimensions=!0},wn.rectangleHeight=function(t){this.rectangleHeight=H(this.rectangleHeight,t),this.dirtyDimensions=!0},Cn.setRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=t},this)},Cn.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=H(this[e],t)},this)},Cn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeRectanglePath(),this.pathDefinition=t},Cn.cleanDimensions=function(){let t=this.getHost();if(t){let e=t.currentDimensions?t.currentDimensions:[t.w,t.h],i=this.rectangleWidth,s=this.rectangleHeight,n=this.currentRectangleWidth||1,r=this.currentRectangleHeight||1;i.substring&&(i=parseFloat(i)/100*e[0]),s.substring&&(s=parseFloat(s)/100*e[1]);let o,a=this.mimic;a&&a.name&&this.useMimicDimensions&&(o=a.currentDimensions),o?(this.currentRectangleWidth=this.addOwnDimensionsToMimic?o[0]+i:o[0],this.currentRectangleHeight=this.addOwnDimensionsToMimic?o[1]+s:o[1]):(this.currentRectangleWidth=i,this.currentRectangleHeight=s),this.currentDimensions[0]=this.currentRectangleWidth,this.currentDimensions[1]=this.currentRectangleHeight,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0,n===this.currentRectangleWidth&&r===this.currentRectangleHeight||(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicDimensions=!0)}else this.dirtyDimensions=!0},Cn.makeRectanglePath=function(){this.dirtyDimensions&&this.cleanDimensions();let t=this.currentRectangleWidth,e=this.currentRectangleHeight,i=this.offshootA,s=this.offshootB,n=this.radiusTLX,r=this.radiusTLY,o=this.radiusTRX,a=this.radiusTRY,h=this.radiusBRX,c=this.radiusBRY,l=this.radiusBLX,u=this.radiusBLY;(n.substring||r.substring||o.substring||a.substring||h.substring||c.substring||l.substring||u.substring)&&(n=n.substring?parseFloat(n)/100*t:n,r=r.substring?parseFloat(r)/100*e:r,o=o.substring?parseFloat(o)/100*t:o,a=a.substring?parseFloat(a)/100*e:a,h=h.substring?parseFloat(h)/100*t:h,c=c.substring?parseFloat(c)/100*e:c,l=l.substring?parseFloat(l)/100*t:l,u=u.substring?parseFloat(u)/100*e:u);let d="m0,0";return t-n-o!=0&&(d+="h"+(t-n-o)),o+a!==0&&(d+=`c${o*i},${a*s} ${o-o*s},${a-a*i}, ${o},${a}`),e-a-c!=0&&(d+="v"+(e-a-c)),h+c!==0&&(d+=`c${-h*s},${c*i} ${h*i-h},${c-c*s} ${-h},${c}`),-t+l+h!==0&&(d+="h"+(-t+l+h)),l+u!==0&&(d+=`c${-l*i},${-u*s} ${l*s-l},${u*i-u} ${-l},${-u}`),-e+r+u!==0&&(d+="v"+(-e+r+u)),n+r!==0&&(d+=`c${n*s},${-r*i} ${n-n*i},${r*s-r} ${n},${-r}`),d+="z",d},Cn.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};k.Rectangle=Rectangle;const Shape=function(t={}){return this.shapeInit(t),this};let Dn=Shape.prototype=Object.create(Object.prototype);Dn.type="Shape",Dn.lib="entity",Dn.isArtefact=!0,Dn.isAsset=!1,Dn=Pt(Dn),Dn=Ii(Dn);Dn.defs=G(Dn.defs,{}),Dn.cleanSpecies=function(){this.dirtySpecies=!1},Dn.cleanStampHandlePositionsAdditionalActions=function(){let t=this.localBox,e=this.currentStampHandlePosition;e[0]+=t[0],e[1]+=t[1]};k.Shape=Shape;const Spiral=function(t={}){return this.shapeInit(t),this};let On=Spiral.prototype=Object.create(Object.prototype);On.type="Spiral",On.lib="entity",On.isArtefact=!0,On.isAsset=!1,On=Pt(On),On=Ii(On);On.defs=G(On.defs,{loops:1,loopIncrement:1,drawFromLoop:0});let En=On.setters,Tn=On.deltaSetters;En.loops=function(t){this.loops=t,this.updateDirty()},Tn.loops=function(t){this.loops+=t,this.updateDirty()},En.loopIncrement=function(t){this.loopIncrement=t,this.updateDirty()},Tn.loopIncrement=function(t){this.loopIncrement+=t,this.updateDirty()},En.drawFromLoop=function(t){this.drawFromLoop=Math.floor(t),this.updateDirty()},Tn.drawFromLoop=function(t){this.drawFromLoop=Math.floor(this.drawFromLoop+t),this.updateDirty()},On.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeSpiralPath(),this.pathDefinition=t},On.firstTurn=[[.043,0,.082,-.035,.088,-.088],[.007,-.057,-.024,-.121,-.088,-.162],[-.07,-.045,-.169,-.054,-.265,-.015],[-.106,.043,-.194,.138,-.235,.265],[-.044,.139,-.026,.3,.058,.442],[.091,.153,.25,.267,.442,.308],[.206,.044,.431,-.001,.619,-.131],[.2,-.139,.34,-.361,.381,-.619]],On.subsequentTurns=[[0,-.27,-.11,-.52,-.29,-.71],[-.19,-.19,-.44,-.29,-.71,-.29],[-.27,0,-.52,.11,-.71,.29],[-.19,.19,-.29,.44,-.29,.71],[0,.27,.11,.52,.29,.71],[.19,.19,.44,.29,.71,.29],[.27,0,.52,-.11,.71,-.29],[.19,-.19,.29,-.44,.29,-.71]],On.makeSpiralPath=function(){let t,e,i,s,n,r,o,a,h,c,l,u,d=Math.floor(this.loops),f=this.loopIncrement,p=Math.floor(this.drawFromLoop),m=this.firstTurn,g=this.subsequentTurns,y=[];for(let o=0;o=p&&(b+=`c${t},${e} ${i},${s} ${n},${r}`),[o,a,h,c,l,u]=g[d],y[d]=[t+o*f,e+a*f,i+h*f,s+c*f,n+l*f,r+u*f];return b},On.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox,n=this.scale;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t/n},${-e/n}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};k.Spiral=Spiral;const Star=function(t={}){return this.shapeInit(t),this};let Rn=Star.prototype=Object.create(Object.prototype);Rn.type="Star",Rn.lib="entity",Rn.isArtefact=!0,Rn.isAsset=!1,Rn=Pt(Rn),Rn=Ii(Rn);Rn.defs=G(Rn.defs,{radius1:0,radius2:0,points:0,twist:0});let Fn=Rn.setters,Hn=Rn.deltaSetters;Fn.radius1=function(t){this.radius1=t,this.updateDirty()},Hn.radius1=function(t){this.radius1+=t,this.updateDirty()},Fn.radius2=function(t){this.radius2=t,this.updateDirty()},Hn.radius2=function(t){this.radius2+=t,this.updateDirty()},Fn.points=function(t){this.points=t,this.updateDirty()},Hn.points=function(t){this.points+=t,this.updateDirty()},Fn.twist=function(t){this.twist=t,this.updateDirty()},Hn.twist=function(t){this.twist+=t,this.updateDirty()},Rn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeStarPath(),this.pathDefinition=t},Rn.makeStarPath=function(){let t,e,i,s,n,r,o,a=this.points,h=this.twist,c=this.radius1,l=this.radius2,u=360/a,d=[],f="";if(c.substring||l.substring){let t=this.getHost();if(t){let[e,i]=t.currentDimensions;c=c.substring?parseFloat(c)/100*e:c,l=l.substring?parseFloat(l)/100*e:l}}let p=Fe({x:0,y:-c}),m=Fe({x:0,y:-l});for(t=p.x,e=p.y,d.push(t),m.rotate(-u/2),m.rotate(h),o=0;o{this[e]=t},this)},Ln.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=addStrings(this[e],t)},this)},Ln.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeTetragonPath(),this.pathDefinition=t},Ln.makeTetragonPath=function(){let t,e,i=this.radiusX,s=this.radiusY;if(i.substring||s.substring){let n=this.getHost();if(n){let[r,o]=n.currentDimensions;t=2*(i.substring?parseFloat(i)/100*r:i),e=2*(s.substring?parseFloat(s)/100*o:s)}}else t=2*i,e=2*s;let n=parseFloat((t*this.intersectX).toFixed(2)),r=parseFloat((t-n).toFixed(2)),o=parseFloat((e*this.intersectY).toFixed(2)),a=parseFloat((e-o).toFixed(2)),h="m0,0";return h+=`l${r},${o} ${-r},${a} ${-n},${-a} ${n},${-o}z`,h},Ln.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};k.Tetragon=Tetragon;const Ticker=function(t={}){return this.makeName(t.name),this.register(),this.subscribers=[],this.subscriberObjects=[],this.set(this.defs),this.set(t),this.cycleCount=0,this.active=!1,this.effectiveDuration=0,this.startTime=0,this.currentTime=0,this.tick=0,this.lastEvent=0,t.subscribers&&this.subscribe(t.subscribers),this.setEffectiveDuration(),this};let $n=Ticker.prototype=Object.create(Object.prototype);$n.type="Ticker",$n.lib="animationtickers",$n.isArtefact=!1,$n.isAsset=!1,$n=Pt($n);$n.defs=G($n.defs,{order:1,duration:0,subscribers:null,killOnComplete:!1,cycles:1,eventChoke:0,onRun:null,onHalt:null,onReverse:null,onResume:null,onSeekTo:null,onSeekFor:null,onComplete:null,onReset:null}),$n.packetExclusions=Z($n.packetExclusions,["subscribers"]),$n.packetFunctions=Z($n.packetFunctions,["onRun","onHalt","onReverse","onResume","onSeekTo","onSeekFor","onComplete","onReset"]),$n.kill=function(){return this.active&&this.halt(),_(In,this.name),Xn=!0,this.deregister(),!0},$n.killTweens=function(t=!1){let e,i,s;for(e=0,i=this.subscribers.length;e{t=y[i],t&&e.push(t)})},$n.getSubscriberObjects=function(){return this.subscribers.length&&!this.subscriberObjects.length&&this.repopulateSubscriberObjects(),this.subscriberObjects},$n.sortSubscribers=function(){let t=this.subscribers;if(t.length>1){let e=[].concat(t),i=Math.floor,s=[];e.forEach(t=>{let e=i(t.effectiveTime)||0;s[e]||(s[e]=[]),s[e].push(t)}),this.subscribers=s.reduce((t,e)=>t.concat(e),[])}this.repopulateSubscriberObjects()},$n.updateSubscribers=function(t,e){e=!!Q(e)&&e;let i,s,n=this.getSubscriberObjects();if(e)for(i=n.length-1;i>=0;i--)n[i].set(t);else for(i=0,s=n.length;it.reversed=!t.reversed),this},$n.makeTickerUpdateEvent=function(){return new CustomEvent("tickerupdate",{detail:{name:this.name,type:"Ticker",tick:this.tick,reverseTick:this.effectiveDuration-this.tick},bubbles:!0,cancelable:!0})},$n.recalculateEffectiveDuration=function(){let t,e=this.getSubscriberObjects(),i=0;return this.duration?this.setEffectiveDuration():(e.forEach(e=>{t=e.getEndTime(),t>i&&(i=t)}),this.effectiveDuration=i),this},$n.setEffectiveDuration=function(){let t;return this.duration&&(t=L(this.duration),"%"===t[0]?(this.duration=0,this.recalculateEffectiveDuration()):this.effectiveDuration=t[1]),this},$n.fn=function(t){let e=Nn();t=!!Q(t)&&t;let i,s,n,r,o,a,h,c,l=this.active,u=this.startTime,d=this.cycles,f=this.cycleCount,p=this.effectiveDuration,m=this.eventChoke;if(l&&u&&(!d||f=p?(c=this.tick=0,this.startTime=this.currentTime,e.tick=p,e.reverseTick=0,e.willLoop=!0,d&&(f++,this.cycleCount=f)):(e.tick=c,e.reverseTick=p-c),e.next=!0):c>=p?(e.tick=p,e.reverseTick=0,l=this.active=!1,d&&(f++,this.cycleCount=f)):(e.tick=c,e.reverseTick=p-c,e.next=!0),n=this.getSubscriberObjects(),t)for(i=n.length-1;i>=0;i--)n[i].update(e);else for(i=0,s=n.length;i=d&&this.killTweens(!0)}Wn(e)},$n.run=function(){return this.active||(this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!1}),this.active=!0,Z(In,this.name),Xn=!0,"function"==typeof this.onRun&&this.onRun()),this},$n.isRunning=function(){return this.active},$n.reset=function(){return this.active&&this.halt(),this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!1}),this.active=!0,this.fn(!0),this.active=!1,"function"==typeof this.onReset&&this.onReset(),this},$n.complete=function(){return this.active&&this.halt(),this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!0}),this.active=!0,this.fn(),this.active=!1,"function"==typeof this.onComplete&&this.onComplete(),this},$n.reverse=function(t=!1){let e;return t=J(t,!1),this.active&&this.halt(),e=this.currentTime-this.startTime,this.startTime=this.currentTime-(this.effectiveDuration-e),this.changeSubscriberDirection(),this.active=!0,this.fn(),this.active=!1,"function"==typeof this.onReverse&&this.onReverse(),t&&this.resume(),this},$n.halt=function(){return this.active=!1,Z(In,this.name),Xn=!0,"function"==typeof this.onHalt&&this.onHalt(),this},$n.resume=function(){let t,e,i;return this.active||(t=Date.now(),e=this.currentTime,i=this.startTime,this.startTime=t-(e-i),this.currentTime=t,this.active=!0,Z(In,this.name),Xn=!0,"function"==typeof this.onResume&&this.onResume()),this},$n.seekTo=function(t,e=!1){let i=!1;return t=J(t,0),this.active&&this.halt(),this.cycles&&this.cycleCount>=this.cycles&&(this.cycleCount=this.cycles-1),t=this.cycles&&(this.cycleCount=this.cycles-1),this.startTime-=t,t<0&&(i=!0),this.active=!0,this.fn(i),this.active=!1,"function"==typeof this.onSeekFor&&this.onSeekFor(),e&&this.resume(),this};let In=[],Xn=!0;vt({name:"coreTickersAnimation",order:0,fn:function(){return new Promise(t=>{if(Xn){Xn=!1;let t=[].concat(In),i=Math.floor,s=[];t.forEach(t=>{let n=e[t];if(Q(n)){let t=i(n.order)||0;s[t]||(s[t]=[]),s[t].push(n.name)}}),In=s.reduce((t,e)=>t.concat(e),[])}In.forEach(t=>{let i=e[t];i&&i.fn&&i.fn()}),t(!0)})}});const Yn=[],Nn=function(){return Yn.length||Yn.push({tick:0,reverseTick:0,willLoop:!1,next:!1}),Yn.shift()},Wn=function(t){t&&(t.tick=0,t.reverseTick=0,t.willLoop=!1,t.next=!1,Yn.push(t))},Vn=function(t){return new Ticker(t)};k.Ticker=Ticker;const Tracer=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.stampAction=B,this.trace=rs(t),t.group||(t.group=ci),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let qn=Tracer.prototype=Object.create(Object.prototype);qn.type="Tracer",qn.lib="entity",qn.isArtefact=!0,qn.isAsset=!1,qn=Pt(qn),qn=Hi(qn);qn.defs=G(qn.defs,{artefact:null,historyLength:1,hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000"}),qn.packetExclusions=Z(qn.packetExclusions,[]),qn.packetExclusionsByRegex=Z(qn.packetExclusionsByRegex,[]),qn.packetCoordinates=Z(qn.packetCoordinates,[]),qn.packetObjects=Z(qn.packetObjects,["artefact","particle"]),qn.packetFunctions=Z(qn.packetFunctions,["stampAction"]),qn.finalizePacketOut=function(t,e){return t},qn.postCloneAction=function(t,e){return t.trace=rs({name:t.name,historyLength:e.historyLength||this.historyLength||1}),t},qn.factoryKill=function(t){t&&this.artefact.kill(),this.trace.kill()};qn.getters;let Gn=qn.setters;qn.deltaSetters;Gn.stampAction=function(t){N(t)&&(this.stampAction=t)},Gn.artefact=function(t){let e;t.substring?e=i[t]:V(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},qn.regularStampSynchronousActions=function(){let{artefact:t,trace:e,stampAction:i,showHitRadius:s,hitRadius:n,hitRadiusColor:r,currentStampPosition:o}=this,a=this.currentHost;if(e.set({position:o}),e.manageHistory(0,a),i.call(this,t,e,a),s){let t=a.engine;t.save(),t.lineWidth=1,t.strokeStyle=r,t.setTransform(1,0,0,1,0,0),t.beginPath(),t.arc(o[0],o[1],n,0,2*Math.PI),t.stroke(),t.restore()}},qn.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;let i,s,n=Array.isArray(t)?t:[t],r=this.currentStampPosition,o=!1;if(n.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!K(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=Fe(r).vectorSubtract(t);return e.getMagnitude()t.name)),Array.isArray(this.definitions)&&(t.definitions=this.definitions.map(t=>{let e={};if(e.attribute=t.attribute,e.start=t.start,e.end=t.end,t.engine&&t.engine.substring)e.engine=t.engine.substring;else if(Q(t.engine)&&null!==t.engine){let i=this.stringifyFunction(t.engine);i&&(e.engine=i,e.engineIsFunction=!0)}return e})),t},Un.postCloneAction=function(t,i){if(i.useNewTicker){let s=e[this.ticker];Q(i.cycles)?t.cycles=i.cycles:t.cycles=s?s.cycles:1;let n=e[t.ticker];n.cycles=t.cycles,Q(i.duration)&&(t.duration=i.duration,t.calculateEffectiveDuration(),n&&n.recalculateEffectiveDuration())}return Array.isArray(t.definitions)&&t.definitions.forEach((t,e)=>{t.engineIsFunction&&(t.engine=this.definitions[e].engine)}),t};let Zn=Un.getters,_n=Un.setters;Zn.definitions=function(){return[].concat(this.definitions)},_n.definitions=function(t){this.definitions=[].concat(t),this.setDefinitionsValues()},_n.commenceAction=function(t){this.commenceAction=t,"function"!=typeof this.commenceAction&&(this.commenceAction=B)},_n.completeAction=function(t){this.completeAction=t,"function"!=typeof this.completeAction&&(this.completeAction=B)},Un.set=function(t={}){if(Object.keys(t).length){let e,i,s,n,r=this.setters,o=Object.keys(t),a=this.defs,h=!!Q(t.ticker)&&this.ticker;for(i=0,s=o.length;ie?r=1:ni&&(r=1)),a?r&&r==this.status||(this.status=r,this.doSimpleUpdate(t),t.next||(this.status=h?-1:1)):r!=this.status&&(this.status=r,this.doSimpleUpdate(t),t.next||(this.status=h?-1:1)),t.willLoop&&(this.reverseOnCycleEnd?this.reversed=!h:this.status=-1)},Un.doSimpleUpdate=function(t={}){let e,i,s,n,r,o,a,h,c,l,u,d,f,p,m=this.effectiveTime,g=this.engineActions,y=this.effectiveDuration,b=this.status,S=this.definitions,P=this.targets,k=this.action,v=this.setObj||{},x=Math.round;for(e=this.reversed?t.reverseTick-m:t.tick-m,o=y&&!b?e/y:b>0?1:0,i=0,s=S.length;i(t=parseFloat(t),W(t)||(t=0),W(e)||(e=0),parseFloat(t.toFixed(e))),Wheel=function(t={}){return tt(t.dimensions,t.width,t.height,t.radius)||(t.radius=5),this.entityInit(t),this};let Kn=Wheel.prototype=Object.create(Object.prototype);Kn.type="Wheel",Kn.lib="entity",Kn.isArtefact=!0,Kn.isAsset=!1,Kn=Pt(Kn),Kn=Hi(Kn);Kn.defs=G(Kn.defs,{radius:5,startAngle:0,endAngle:360,clockwise:!0,includeCenter:!1,closed:!0});Kn.getters;let Jn=Kn.setters,tr=Kn.deltaSetters;Jn.width=function(t){if(null!=t){let e=this.dimensions;e[0]=e[1]=t,this.dimensionsHelper()}},tr.width=function(t){let e=this.dimensions;e[0]=e[1]=addStrings(e[0],t),this.dimensionsHelper()},Jn.height=function(t){if(null!=t){let e=this.dimensions;e[0]=e[1]=t,this.dimensionsHelper()}},tr.height=function(t){let e=this.dimensions;e[0]=e[1]=addStrings(e[0],t),this.dimensionsHelper()},Jn.dimensions=function(t,e){this.setCoordinateHelper("dimensions",t,e),this.dimensionsHelper()},tr.dimensions=function(t,e){this.setDeltaCoordinateHelper("dimensions",t,e),this.dimensionsHelper()},Jn.radius=function(t){this.radius=t,this.radiusHelper()},tr.radius=function(t){this.radius=addStrings(this.radius,t),this.radiusHelper()},Jn.startAngle=function(t){this.startAngle=Qn(t,4),this.dirtyPathObject=!0},tr.startAngle=function(t){this.startAngle+=Qn(t,4),this.dirtyPathObject=!0},Jn.endAngle=function(t){this.endAngle=Qn(t,4),this.dirtyPathObject=!0},tr.endAngle=function(t){this.endAngle+=Qn(t,4),this.dirtyPathObject=!0},Jn.closed=function(t){Q(t)&&(this.closed=!!t,this.dirtyPathObject=!0)},Jn.includeCenter=function(t){Q(t)&&(this.includeCenter=!!t,this.dirtyPathObject=!0)},Jn.clockwise=function(t){Q(t)&&(this.clockwise=!!t,this.dirtyPathObject=!0)},Kn.dimensionsHelper=function(){let t=this.dimensions[0];t.substring?this.radius=parseFloat(t)/2+"%":this.radius=t/2,this.dirtyDimensions=!0},Kn.radiusHelper=function(){let t=this.radius,e=this.dimensions;t.substring?e[0]=e[1]=2*parseFloat(t)+"%":e[0]=e[1]=2*t,this.dirtyDimensions=!0},Kn.cleanDimensionsAdditionalActions=function(){let t=this.radius,e=this.currentDimensions,i=t.substring?parseFloat(t)/100*e[0]:t;e[0]!==2*i?(e[1]=e[0],this.currentRadius=e[0]/2):this.currentRadius=i},Kn.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=this.currentStampHandlePosition,i=this.currentScale,s=this.currentRadius*i,n=s-e[0]*i,r=s-e[1]*i,o=this.startAngle*v,a=this.endAngle*v;t.arc(n,r,s,o,a,!this.clockwise),this.includeCenter?(t.lineTo(n,r),t.closePath()):this.closed&&t.closePath()}};k.Wheel=Wheel;const World=function(t={}){this.makeName(t.name),this.register(),this.set(this.defs);let e=t.keytypes||{};return e.gravity||(e.gravity="Vector"),t.gravity||(t.gravity=[0,9.81,0]),t.userAttributes&&t.userAttributes.forEach(t=>{this.addAttribute(t),t.type&&(e[t.key]=t.type)}),this.initializeAttributes(e),this.set(t),this};let er=World.prototype=Object.create(Object.prototype);er.type="World",er.lib="world",er.isArtefact=!1,er.isAsset=!1,er=Pt(er);er.defs=G(er.defs,{gravity:null,tickMultiplier:1,keytypes:null}),er.kill=function(){return this.deregister(),!0};let ir=er.getters,sr=er.setters,nr=er.deltaSetters;sr.gravityX=function(t){this.gravity&&Q(t)&&this.gravity.setX(t)},sr.gravityY=function(t){this.gravity&&Q(t)&&this.gravity.setY(t)},sr.gravityZ=function(t){this.gravity&&Q(t)&&this.gravity.setZ(t)},sr.gravity=function(t){this.gravity&&Q(t)&&this.gravity.set(t)},er.addAttribute=function(t={}){let{key:e,defaultValue:i,setter:s,deltaSetter:n,getter:r}=t;return e&&e.substring&&(this.defs[e]=Q(i)?i:null,this[e]=Q(i)?i:null,N(s)&&(sr[e]=s),N(n)&&(nr[e]=n),N(r)&&(ir[e]=r)),this},er.removeAttribute=function(t){return t&&t.substring&&(delete this.defs[t],delete this[t],delete ir[t],delete sr[t],delete nr[t]),this},er.initializeAttributes=function(t){for(let[e,i]of Object.entries(t))switch(i){case"Quaternion":this[e]=ze();break;case"Vector":this[e]=Le();break;case"Coordinate":this[e]=Kt()}};k.World=World;window.scrawlEnvironmentTouchSupported=!!("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch),window.scrawlEnvironmentOffscreenCanvasSupported="OffscreenCanvas"in window,document.querySelectorAll("[data-stack]").forEach(t=>ri(t)),function(){let t;document.querySelectorAll("canvas").forEach((e,i)=>{t=ai(e),i||li(t)})}(),F(),$t(),Et(),wt=!0,jt();var rr={library:A,startCoreAnimationLoop:F,stopCoreAnimationLoop:function(){w=!1},currentCorePosition:Dt,startCoreListeners:jt,stopCoreListeners:function(){At=!1,wt=!1,Lt.halt(),Bt("removeEventListener")},observeAndUpdate:function(t={}){if(!K(t.event,t.origin,t.updates))return!1;let e=t.target.substring&&t.targetLibrarySection?A[t.targetLibrarySection][t.target]:t.target;if(!e)return!1;let i=t.event,s=t.origin,n=t.useNativeListener?at:it,r=t.useNativeListener?ht:st,o=B;t.preventDefault&&(o=t=>{t.preventDefault(),t.returnValue=!1});let a=function(i){o(i);let s=!(!i||!i.target)&&i.target.id;if(s){let n=t.updates[s];if(n){let t,s=n[0],r=n[1],o=i.target.value,a=!0;switch(r){case"float":t=parseFloat(o);break;case"int":t=parseInt(o,10);break;case"round":t=Math.round(o);break;case"roundDown":t=Math.floor(o);break;case"roundUp":t=Math.ceil(o);break;case"raw":t=o;break;case"string":t=""+o;break;case"boolean":t=!!Q(o)&&(o.substring?"true"===o.toLowerCase()||"false"!==o.toLowerCase()&&!!parseFloat(o):!!o);break;default:r.substring?t=`${parseFloat(o)}${r}`:a=!1}a&&("Group"===e.type?e.setArtefacts({[s]:t}):e.set({[s]:t}))}}};return n(i,a,s),function(){r(i,a,s)}},makeDragZone:function(t={}){let{zone:e,coordinateSource:i,collisionGroup:s,startOn:n,endOn:r,updateOnStart:o,updateOnEnd:a,exposeCurrentArtefact:h}=t;if(!e)return new Error("dragZone constructor - no zone supplied");if(e.substring&&(e=artefact[e]),!e||["Canvas","Stack"].indexOf(e.type)<0)return new Error("dragZone constructor - zone object is not a Stack or Canvas wrapper");let c=e.domElement;if(!c)return new Error("dragZone constructor - zone does not contain a target DOM element");if(s?s.substring&&(s=u[s]):s="Canvas"===e.type?u[e.base.name]:u[e.name],!s||"Group"!==s.type)return new Error("dragZone constructor - unable to recover collisionGroup group");if(i?i.here?i=i.here:K(i.x,i.y)||(i=!1):i="Canvas"===e.type?e.base.here:e.here,!i)return new Error("dragZone constructor - unable to discover a usable coordinateSource object");Array.isArray(n)||(n=["down"]),Array.isArray(r)||(r=["up"]);let l=!1;V(o)&&(o=function(){l.artefact.set(t.updateOnStart)}),N(o)||(o=B),V(a)&&(a=function(){l.artefact.set(t.updateOnEnd)}),N(a)||(a=B),I(h)||(h=!1);const d=function(t){t.cancelable&&(t.preventDefault(),t.returnValue=!1)},f=function(t){d(t);let e=t.type;"touchstart"!==e&&"touchcancel"!==e||Rt(t),l=s.getArtefactAt(i),l&&(l.artefact.pickupArtefact(i),o())};let p=function(t){d(t),l&&(l.artefact.dropArtefact(),a(),l=!1)};const m=function(){st(n,f,c),st(r,p,c)};return it(n,f,c),it(r,p,c),h?function(t=!1){if(!t)return l;m()}:m},makeComponent:function(t){let e=!!Y(t.domElement)&&t.domElement,s=V(t.animationHooks)?t.animationHooks:{},n=V(t.canvasSpecs)?t.canvasSpecs:{},r=V(t.observerSpecs)?t.observerSpecs:{},o=!I(t.includeCanvas)||t.includeCanvas;return e&&e.id&&i[e.id]?wi(e,n,s,r):Di(e,n,s,r,o)},addStack:function(t={}){let e,s,n,r,o,a,h="absolute";return e=t.element&&t.element.substring?document.querySelector(t.element):Y(t.element)?t.element:document.createElement("div"),t.host&&t.host.substring?(s=document.querySelector(t.host),s||(s=document.body)):s=Y(t.host)?t.host:Q(e.parentElement)?e.parentElement:document.body,Q(t.width)&&(e.style.width=t.width.toFixed?t.width+"px":t.width),Q(t.height)&&(e.style.height=t.height.toFixed?t.height+"px":t.height),a=t.name||e.id||e.getAttribute("name")||"",a||(a=z()),e.id=a,e.setAttribute("data-stack","data-stack"),s&&null!=s.getAttribute("data-stack")?(n=i[s.id],o=n?n.name:"root"):o="root",e.setAttribute("data-group",o),"root"===o&&(h="relative"),e.parentElement&&s.id===e.parentElement.id||s.appendChild(e),r=ti({name:a,domElement:e,group:o,host:o,position:h,setInitialDimensions:!0}),oi(e,a),Array.from(e.childNodes).forEach(t=>{t.id&&ei.indexOf(t.id)>=0&&_(ei,t.id)}),delete t.name,delete t.element,delete t.host,delete t.width,delete t.height,r.set(t),ni=!0,r},getStack:function(t){let e,i=document.querySelector(t);return i&&(e=ri(i)),e},addCanvas:function(t={}){let e=document.createElement("canvas"),s=t.name?t.name:z(),n=t.host,r="root",o=t.width||300,a=t.height||150,h="relative";if(n.substring){let t=i[n];!t&&n?(n=document.querySelector(n),n&&(r=n.id)):n=t}n?"Stack"===n.type?(r=n.name,h="absolute",n=n.domElement):Y(n)||(n=document.body):n=document.body,e.id=s,e.setAttribute("data-group",r),e.width=o,e.height=a,e.style.position=h,n.appendChild(e);let c=qe({name:s,domElement:e,group:r,host:r,position:h,setInitialDimensions:!1,setAsCurrentCanvas:!Q(t.setAsCurrentCanvas)||t.setAsCurrentCanvas,trackHere:Q(t.trackHere)?t.trackHere:"subscribe"});return delete t.group,delete t.host,delete t.name,delete t.element,delete t.position,delete t.setInitialDimensions,delete t.setAsCurrentCanvas,delete t.trackHere,c.set(t),c},getCanvas:function(t){let e=document.querySelector(t),i=!1;return e&&(i=ai(e)),li(i),i},setCurrentCanvas:li,clear:ui,compile:di,show:fi,render:function(...t){return pi(t),mi("render")},addListener:it,removeListener:st,addNativeListener:at,removeNativeListener:ht,makeAnimationObserver:et,reducedMotionActions:pt,setReduceMotionAction:t=>dt=t,setNoPreferenceMotionAction:t=>ft=t,colorSchemeActions:St,setColorSchemeDarkAction:t=>yt=t,setColorSchemeLightAction:t=>bt=t,makeAction:function(t){return new Action(t)},makeAnimation:vt,makeBezier:function(t={}){return t.species="bezier",new Bezier(t)},makeBlock:function(t){return new Block(t)},makeCog:function(t={}){return t.species="cog",new Cog(t)},makeColor:Qi,requestCoordinate:_t,releaseCoordinate:Qt,makeEmitter:function(t){return new Emitter(t)},makeFilter:function(t){return new Filter(t)},makeForce:ps,makeGradient:function(t){return new Gradient(t)},makeGrid:function(t){return new Grid(t)},makeGroup:Ee,importImage:ne,importDomImage:re,createImageFromCell:function(t,e=!1){let i=t.substring?a[t]||o[t]:t;"Canvas"===i.type&&(i=i.base),"Cell"===i.type&&(i.stashOutput=!0,e&&(i.stashOutputAsAsset=!0))},createImageFromGroup:function(t,e=!1){let i;t&&!t.substring?"Group"===t.type?i=t:"Cell"===t.type?i=u[t.name]:"Canvas"===t.type&&(i=u[t.base.name]):t&&t.substring&&(i=u[t]),i&&(i.stashOutput=!0,e&&(i.stashOutputAsAsset=!0))},createImageFromEntity:function(t,e=!1){let s=t.substring?i[t]:t;s.isArtefact&&(s.stashOutput=!0,e&&(s.stashOutputAsAsset=!0))},makeLine:function(t={}){return t.species="line",new Line(t)},makeLoom:function(t){return new Loom(t)},makeNet:function(t){return new Net(t)},makeOval:function(t={}){return t.species="oval",new Oval(t)},makePattern:function(t){return new Pattern(t)},makePhrase:function(t){return new Phrase(t)},makePicture:function(t){return new Picture(t)},makePolygon:function(t={}){return t.species="polygon",new Polygon(t)},makePolyline:function(t={}){return t.species="polyline",new Polyline(t)},makeQuadratic:function(t={}){return t.species="quadratic",new Quadratic(t)},requestQuaternion:$e,releaseQuaternion:Me,makeRadialGradient:function(t){return new RadialGradient(t)},makeRectangle:function(t={}){return t.species="rectangle",new Rectangle(t)},makeRender:Ci,makeShape:function(t){return new Shape(t)},makeSpiral:function(t={}){return t.species="spiral",new Spiral(t)},makeSpring:Es,importSprite:Ws,makeStar:function(t={}){return t.species="star",new Star(t)},makeTetragon:function(t={}){return t.species="tetragon",new Tetragon(t)},makeTicker:Vn,makeTracer:function(t){return new Tracer(t)},makeTween:function(t){return new Tween(t)},requestVector:Fe,releaseVector:He,importDomVideo:function(t){let e=/.*\/(.*?)\./;document.querySelectorAll(t).forEach(t=>{let i;if("VIDEO"===t.tagName.toUpperCase()){if(t.id||t.name)i=t.id||t.name;else{let s=e.exec(t.src);i=s&&s[1]?s[1]:""}let s=Xs({name:i,source:t});t.readyState<=2&&(t.oncanplay=()=>{s.set({source:t})})}})},importVideo:Is,importMediaStream:function(t={}){let e={};e.audio=!Q(t.audio)||t.audio,e.video={};let i=e.video.width={};t.minWidth&&(i.min=t.minWidth),t.maxWidth&&(i.max=t.maxWidth),i.ideal=t.width?t.width:1280;let s=e.video.height={};t.minHeight&&(s.min=t.minHeight),t.maxHeight&&(s.max=t.maxHeight),s.ideal=t.height?t.height:720,t.facing&&(e.video.facingMode=t.facing);let n=t.name||z(),r=document.createElement("video"),o=Xs({name:n,source:r});return new Promise((t,i)=>{navigator&&navigator.mediaDevices?navigator.mediaDevices.getUserMedia(e).then(e=>{let i,s=e.getVideoTracks();Array.isArray(s)&&s[0]&&(i=s[0].getConstraints()),r.id=o.name,i&&(r.width=i.width,r.height=i.height),r.srcObject=e,r.onloadedmetadata=function(t){r.play()},t(o)}).catch(e=>{console.log(e.message),t(o)}):i("Navigator.mediaDevices object not found")})},makeWheel:function(t){return new Wheel(t)},makeWorld:function(t){return new World(t)}};export default rr; +const t={},e={},i={},s=[],n={},r=[],o={},a={},l=[],h={},c={},u={},d={},f={},p={},m=[],g={},y={},b={},k=[],S={},O={},P=Math.PI/180,v=new Set(["all","background","backgroundAttachment","backgroundBlendMode","backgroundClip","backgroundColor","backgroundOrigin","backgroundPosition","backgroundRepeat","border","borderBottom","borderBottomColor","borderBottomStyle","borderBottomWidth","borderCollapse","borderColor","borderLeft","borderLeftColor","borderLeftStyle","borderLeftWidth","borderRight","borderRightColor","borderRightStyle","borderRightWidth","borderSpacing","borderStyle","borderTop","borderTopColor","borderTopStyle","borderTopWidth","borderWidth","clear","color","columns","content","counterIncrement","counterReset","cursor","direction","display","emptyCells","float","font","fontFamily","fontSize","fontSizeAdjust","fontStretch","fontStyle","fontSynthesis","fontVariant","fontVariantAlternates","fontVariantCaps","fontVariantEastAsian","fontVariantLigatures","fontVariantNumeric","fontVariantPosition","fontWeight","grid","gridArea","gridAutoColumns","gridAutoFlow","gridAutoPosition","gridAutoRows","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","gridTemplate","gridTemplateAreas","gridTemplateRows","gridTemplateColumns","imageResolution","imeMode","inherit","inlineSize","isolation","letterSpacing","lineBreak","lineHeight","listStyle","listStyleImage","listStylePosition","listStyleType","margin","marginBlockStart","marginBlockEnd","marginInlineStart","marginInlineEnd","marginBottom","marginLeft","marginRight","marginTop","marks","mask","maskType","maxWidth","maxHeight","maxBlockSize","maxInlineSize","maxZoom","minWidth","minHeight","minBlockSize","minInlineSize","minZoom","mixBlendMode","objectFit","objectPosition","offsetBlockStart","offsetBlockEnd","offsetInlineStart","offsetInlineEnd","orphans","overflow","overflowWrap","overflowX","overflowY","pad","padding","paddingBlockStart","paddingBlockEnd","paddingInlineStart","paddingInlineEnd","paddingBottom","paddingLeft","paddingRight","paddingTop","pageBreakAfter","pageBreakBefore","pageBreakInside","pointerEvents","position","prefix","quotes","rubyAlign","rubyMerge","rubyPosition","scrollBehavior","scrollSnapCoordinate","scrollSnapDestination","scrollSnapPointsX","scrollSnapPointsY","scrollSnapType","scrollSnapTypeX","scrollSnapTypeY","shapeImageThreshold","shapeMargin","shapeOutside","tableLayout","textAlign","textDecoration","textIndent","textOrientation","textOverflow","textRendering","textShadow","textTransform","textUnderlinePosition","unicodeRange","unset","verticalAlign","widows","willChange","wordBreak","wordSpacing","wordWrap","zIndex"]),w=new Set(["alignContent","alignItems","alignSelf","animation","animationDelay","animationDirection","animationDuration","animationFillMode","animationIterationCount","animationName","animationPlayState","animationTimingFunction","backfaceVisibility","backgroundImage","backgroundSize","borderBottomLeftRadius","borderBottomRightRadius","borderImage","borderImageOutset","borderImageRepeat","borderImageSlice","borderImageSource","borderImageWidth","borderRadius","borderTopLeftRadius","borderTopRightRadius","boxDecorationBreak","boxShadow","boxSizing","columnCount","columnFill","columnGap","columnRule","columnRuleColor","columnRuleStyle","columnRuleWidth","columnSpan","columnWidth","filter","flex","flexBasis","flexDirection","flexFlow","flexGrow","flexShrink","flexWrap","fontFeatureSettings","fontKerning","fontLanguageOverride","hyphens","imageRendering","imageOrientation","initial","justifyContent","linearGradient","opacity","order","orientation","outline","outlineColor","outlineOffset","outlineStyle","outlineWidth","resize","tabSize","textAlignLast","textCombineUpright","textDecorationColor","textDecorationLine","textDecorationStyle","touchAction","transformStyle","transition","transitionDelay","transitionDuration","transitionProperty","transitionTimingFunction","unicodeBidi","whiteSpace","writingMode"]);var x=Object.freeze({__proto__:null,version:"8.4.0",anchor:{},anchornames:[],animation:t,animationnames:[],asset:n,assetnames:r,animationtickers:e,animationtickersnames:[],artefact:i,artefactnames:s,canvas:o,canvasnames:[],cell:a,cellnames:l,element:{},elementnames:[],entity:h,entitynames:[],filter:c,filternames:[],fontattribute:{},fontattributenames:[],force:f,forcenames:[],group:u,groupnames:[],palette:{},palettenames:[],particle:d,particlenames:[],spring:p,springnames:m,stack:{},stacknames:[],styles:b,stylesnames:k,tween:y,tweennames:[],unstackedelement:S,unstackedelementnames:[],world:g,worldnames:[],constructors:O,radian:P,css:v,xcss:w});let A=!1,C=!0,D=[],R=[];const E=function(){C=!0},F=function(){let e=[];C&&function(){if(C){C=!1;let e,i,s=Math.floor,n=[];R.forEach(r=>{e=t[r],e&&(i=s(e.order)||0,n[i]||(n[i]=[]),n[i].push(e))}),D=n.reduce((t,e)=>t.concat(e),[])}}(),D.forEach(t=>{t&&t.fn&&e.push(t.fn())}),Promise.all(e).then(()=>{A&&window.requestAnimationFrame(()=>F())}).catch(t=>console.log("animationLoop error: ",t))},T=function(){A=!0,F()},H=(t,e)=>{if(!J(e))throw new Error(`core/utilities addStrings() error - no delta argument supplied ${t}, ${e}`);if(null!=e){let i=!(!t.substring&&!e.substring);return V(t)?t+=V(e)?e:parseFloat(e):t=parseFloat(t)+(V(e)?e:parseFloat(e)),i?t+"%":t}return t},M=t=>{let e,i,s;if(!J(t))throw new Error("core/utilities convertTime() error - no argument supplied");if(V(t))return["ms",t];if(!t.substring)throw new Error("core/utilities convertTime() error - invalid argument: "+t);if(e=t.match(/^\d+\.?\d*(\D*)/),i=e[1].toLowerCase?e[1].toLowerCase():"ms",s=parseFloat(t),!V(s))throw new Error("core/base error - convertTime() argument converts to NaN: "+t);switch(i){case"s":s*=1e3;break;case"%":break;default:i="ms"}return[i,s]},I=t=>t.toFixed?0==t?t:isNaN(t)?0:t<-1e-6||t>1e-6?t:0:t,B=()=>{},L=function(t){return t},$=function(){return this},j=()=>Promise.resolve(!0),N=()=>performance.now().toString(36)+Math.random().toString(36).substr(2),X=function(t,e,i){return e+t*(i-e)},Y=t=>"boolean"==typeof t,z=t=>"[object HTMLCanvasElement]"===Object.prototype.toString.call(t),G=t=>!!(t&&t.querySelector&&t.dispatchEvent),W=t=>"function"==typeof t,V=t=>!(void 0===t||!t.toFixed||Number.isNaN(t)),U=t=>"[object Object]"===Object.prototype.toString.call(t),q=t=>!(!t||!t.type||"Quaternion"!==t.type),_=(t,e)=>{if(!U(t)||!U(e))throw new Error(`core/utilities mergeOver() error - insufficient arguments supplied ${t}, ${e}`);for(let i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},Z=(t,e)=>{if(!U(t)||!U(e))throw new Error(`core/utilities mergeDiscard() error - insufficient arguments supplied ${t}, ${e}`);return Object.entries(e).forEach(([i,s])=>{null===s?delete t[i]:t[i]=e[i]}),t},Q=(t,e)=>{if(!tt(t,e))throw new Error(`core/utilities pushUnique() error - insufficient arguments supplied ${t}, ${e}`);if(!Array.isArray(t))throw new Error("core/utilities pushUnique() error - argument not an array "+t);return Array.isArray(e)?e.forEach(e=>Q(t,e)):t.indexOf(e)<0&&t.push(e),t},K=(t,e)=>{if(!tt(t,e))throw new Error(`core/utilities removeItem() error - insufficient arguments supplied ${t}, ${e}`);if(!Array.isArray(t))throw new Error("core/utilities removeItem() error - argument not an array "+t);let i=t.indexOf(e);return i>=0&&t.splice(i,1),t},J=t=>void 0!==t,tt=(...t)=>t.every(t=>void 0!==t),et=(...t)=>t.find(t=>void 0!==t),it=(...t)=>!!t.find(t=>void 0!==t);var st=function(){var t=4022871197;return function(e){if(e){e=e.toString();for(var i=0;i>>0,t=(s*=t)>>>0,t+=4294967296*(s-=t)}return 2.3283064365386963e-10*(t>>>0)}t=4022871197}},nt=function(t){return function(){var e,i,s=48,n=1,r=s,o=new Array(s),a=0,l=new st;for(e=0;e=s&&(r=0);var t=1768863*o[r]+2.3283064365386963e-10*n;return o[r]=t-(n=0|t)},c=function(t){return Math.floor(t*(h()+11102230246251565e-32*(2097152*h()|0)))};c.string=function(t){var e,i="";for(e=0;e0){var o=i.indexOf(this);~o?i.splice(o+1):i.push(this),~o?s.splice(o,1/0,n):s.push(n),~i.indexOf(r)&&(r=e.call(this,n,r))}else i.push(r);return null==t?r:t.call(this,n,r)}}(i,n),s)),c.initState(),c.hashString(t)},c.addEntropy=function(){var t=[];for(e=0;e{e.forEach(e=>{e.isIntersecting?!t.isRunning()&&t.run():e.isIntersecting||t.isRunning()&&t.halt()})},i);return e&&e.domElement&&s.observe(e.domElement),function(){s.disconnect()}}return B},Lt=function(t,e,i){if(!W(e))throw new Error(`core/document addListener() error - no function supplied: ${t}, ${i}`);return jt(t,e,i,"removeEventListener"),jt(t,e,i,"addEventListener"),function(){$t(t,e,i)}},$t=function(t,e,i){if(!W(e))throw new Error(`core/document removeListener() error - no function supplied: ${t}, ${i}`);jt(t,e,i,"removeEventListener")},jt=function(t,e,i,s){let n,r=[].concat(t);n=i.substring?document.body.querySelectorAll(i):Array.isArray(i)?i:[i],navigator.pointerEnabled||navigator.msPointerEnabled?Xt(r,e,n,s):Nt(r,e,n,s)},Nt=function(t,e,i,s){t.forEach(t=>{i.forEach(i=>{if(!(G(i)||i.document||i.characterSet))throw new Error(`core/document actionMouseListener() error - bad target: ${t}, ${i}`);switch(t){case"move":i[s]("mousemove",e,!1),i[s]("touchmove",e,!1),i[s]("touchfollow",e,!1);break;case"up":i[s]("mouseup",e,!1),i[s]("touchend",e,!1);break;case"down":i[s]("mousedown",e,!1),i[s]("touchstart",e,!1);break;case"leave":i[s]("mouseleave",e,!1),i[s]("touchleave",e,!1);break;case"enter":i[s]("mouseenter",e,!1),i[s]("touchenter",e,!1)}})})},Xt=function(t,e,i,s){t.forEach(t=>{i.forEach(i=>{if(!(G(i)||i.document||i.characterSet))throw new Error(`core/document actionPointerListener() error - bad target: ${t}, ${i}`);switch(t){case"move":i[s]("pointermove",e,!1);break;case"up":i[s]("pointerup",e,!1);break;case"down":i[s]("pointerdown",e,!1);break;case"leave":i[s]("pointerleave",e,!1);break;case"enter":i[s]("pointerenter",e,!1)}})})},Yt=function(t,e,i){if(!W(e))throw new Error(`core/document addNativeListener() error - no function supplied: ${t}, ${i}`);return Gt(t,e,i,"removeEventListener"),Gt(t,e,i,"addEventListener"),function(){zt(t,e,i)}},zt=function(t,e,i){if(!W(e))throw new Error(`core/document removeNativeListener() error - no function supplied: ${t}, ${i}`);Gt(t,e,i,"removeEventListener")},Gt=function(t,e,i,s){let n,r=[].concat(t);n=i.substring?document.body.querySelectorAll(i):Array.isArray(i)?i:[i],r.forEach(t=>{n.forEach(i=>{if(!(G(i)||i.document||i.characterSet))throw new Error(`core/document actionNativeListener() error - bad target: ${t}, ${i}`);i[s](t,e,!1)})})},Wt=window.matchMedia("(prefers-reduced-motion: reduce)");let Vt=Wt.matches,Ut=B,qt=B;const _t=()=>{Vt?Ut():qt()};Wt.addEventListener("change",()=>{Vt=Wt.matches,_t()});const Zt=window.matchMedia("(prefers-color-scheme: dark)");let Qt=Zt.matches,Kt=B,Jt=B;const te=()=>{Qt?Kt():Jt()};Zt.addEventListener("change",()=>{Qt=Zt.matches,te()});function ee(t={}){t.defs={},t.getters={},t.setters={},t.deltaSetters={},t.get=function(t){if(J(t)){let e=this.getters[t];if(e)return e.call(this);{let e=this.defs[t];if(void 0!==e){let i=this[t];return void 0!==i?i:e}}}return null},t.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e,i=this.deltaSetters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=H(this[t],n)))},this)}return this};return t.defs=_(t.defs,{name:""}),t.packetExclusions=[],t.packetExclusionsByRegex=[],t.packetCoordinates=[],t.packetObjects=[],t.packetFunctions=[],t.saveAsPacket=function(t={}){Y(t)&&t&&(t={includeDefaults:!0});let e=this.defs,i=Object.keys(e),s=this.packetExclusions,n=this.packetExclusionsByRegex,r=this.packetCoordinates,o=this.packetObjects,a=this.packetFunctions,l=t.includeDefaults||!1,h={};return l&&!Array.isArray(l)?l=Object.keys(e):l||(l=[]),Object.entries(this).forEach(([t,e])=>{let c,u=!0;if(i.indexOf(t)<0&&(u=!1),u&&s.indexOf(t)>=0&&(u=!1),u&&(c=n.some(e=>new RegExp(e).test(t)),c&&(u=!1)),u)if(a.indexOf(t)>=0){if(J(e)&&null!==e){let i=this.stringifyFunction(e);i&&i.length&&(h[t]=i)}}else o.indexOf(t)>=0&&this[t]&&this[t].name?h[t]=this[t].name:r.indexOf(t)>=0?(l.indexOf(t)>=0||e[0]||e[1])&&(h[t]=e):(c=this.processPacketOut(t,e,l),c&&(h[t]=e))},this),h=this.finalizePacketOut(h,t),JSON.stringify([this.name,this.type,this.lib,h])},t.stringifyFunction=function(t){let e=t.toString().match(/\(([\s\S]*?)\)[\s\S]*?\{([\s\S]*)\}/),i=e[1],s=e[2];return!!tt(i,s)&&`${i}~~~${s}`},t.processPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){return t},t.importPacket=function(t){let e=this;const i=function(t){return new Promise((i,s)=>{let n;t.substring||s(new Error("Packet url supplied for import is not a string")),"["===t[0]?(n=e.actionPacket(t),n&&n.lib?i(n):s(n)):t.indexOf('"name":')>=0?s(new Error("Bad packet supplied for import")):fetch(t).then(t=>{if(!t.ok)throw new Error(`Packet import from server failed - ${t.status}: ${t.statusText} - ${t.url}`);return t.text()}).then(t=>{if(n=e.actionPacket(t),!n||!n.lib)throw n;i(n)}).catch(t=>s(t))})};if(Array.isArray(t)){let e=[];return t.forEach(t=>e.push(i(t))),new Promise((t,i)=>{Promise.all(e).then(e=>t(e)).catch(t=>i(t))})}if(t.substring)return i(t);Promise.reject(new Error("Argument supplied for packet import is not a string or array of strings"))},t.actionPacketExclusions=["Image","Sprite","Video","Canvas","Stack"],t.actionPacket=function(t){try{if(t&&t.substring){if("["===t[0]){let e,i,s,n;try{[e,i,s,n]=JSON.parse(t)}catch(t){throw new Error("Failed to process packet due to JSON parsing error - "+t.message)}if(tt(e,i,s,n)){if(this.actionPacketExclusions.indexOf(i)>=0)throw new Error("Failed to process packet - Stacks, Canvases and visual assets are excluded from the packet system");let t=x[s][e];if(t)t.set(n);else{if(n.outerHTML&&n.host){let t=document.querySelector("#"+n.host);if(t){let i=document.createElement("div");i.innerHTML=n.outerHTML;let s=i.firstElementChild;s&&(s.id=e,t.appendChild(s),n.domElement=s)}}if(t=new O[i](n),!t)throw new Error("Failed to create Scrawl-canvas object from supplied packet")}if(t.packetFunctions.forEach(e=>this.actionPacketFunctions(t,e)),n.anchor&&t.anchor&&t.anchor.packetFunctions.forEach(e=>{t.anchor[e]=n.anchor[e],this.actionPacketFunctions(t.anchor,e),t.anchor.build()}),n.glyphStyles&&t.glyphStyles&&n.glyphStyles.forEach((e,i)=>{U(e)&&t.setGlyphStyles(e,i)}),t)return t;throw new Error("Failed to process supplied packet")}throw new Error("Failed to process packet - JSON string holds incomplete data")}throw new Error("Failed to process packet - JSON string does not represent an array")}throw new Error("Failed to process packet - not a JSON string")}catch(t){return console.log(t),t}},t.actionPacketFunctions=function(t,e){let i=t[e];if(J(i)&&null!==i&&i.substring)if("~~~"===i)t[e]=B;else{let s,n,r;[s,n]=i.split("~~~"),s=s.split(","),s=s.map(t=>t.trim()),n.indexOf("[native code]")<0?(r=new Function(...s,n),t[e]=r.bind(t)):t[e]=B}},t.clone=function(t={}){let e,i,s=this.name;this.name=t.name||"",t.useNewTicker?(i=this.ticker,this.ticker=null,e=this.saveAsPacket(),this.ticker=i):e=this.saveAsPacket(),this.name=s;let n=this.actionPacket(e);return this.packetFunctions.forEach(t=>{this[t]&&(n[t]=this[t])}),n=this.postCloneAction(n,t),n.set(t),n},t.postCloneAction=function(t,e){return t},t.kill=function(){return this.deregister()},t.makeName=function(t){return t&&t.substring&&x[this.lib+"names"].indexOf(t)<0?this.name=t:this.name=N(),this},t.register=function(){if(!J(this.name))throw new Error("core/base error - register() name not set: "+this);let t=x[this.lib+"names"],e=x[this.lib];return this.isArtefact&&(Q(s,this.name),i[this.name]=this),this.isAsset&&(Q(r,this.name),n[this.name]=this),Q(t,this.name),e[this.name]=this,this},t.deregister=function(){if(!J(this.name))throw new Error("core/base error - deregister() name not set: "+this);let t=x[this.lib+"names"],e=x[this.lib];return this.isArtefact&&(K(s,this.name),delete i[this.name]),this.isAsset&&(K(r,this.name),delete n[this.name]),K(t,this.name),delete e[this.name],this},t}const Animation=function(t={}){return this.makeName(t.name),this.order=J(t.order)?t.order:this.defs.order,this.fn=t.fn||j,this.onRun=t.onRun||B,this.onHalt=t.onHalt||B,this.onKill=t.onKill||B,this.register(),t.delay||this.run(),this};let ie=Animation.prototype=Object.create(Object.prototype);ie.type="Animation",ie.lib="animation",ie.isArtefact=!1,ie.isAsset=!1,ie=ee(ie);ie.defs=_(ie.defs,{order:1,fn:null,onRun:null,onHalt:null,onKill:null}),ie.stringifyFunction=B,ie.processPacketOut=B,ie.finalizePacketOut=B,ie.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},ie.clone=$,ie.run=function(){return this.onRun(),Q(R,this.name),E(),this},ie.isRunning=function(){return R.indexOf(this.name)>=0},ie.halt=function(){return this.onHalt(),K(R,this.name),E(),this},ie.kill=function(){return this.onKill(),K(R,this.name),E(),this.deregister(),!0};const se=function(t){return new Animation(t)};O.Animation=Animation;const ne=[];let re=!1,oe=!1,ae=!1;const le={x:0,y:0,scrollX:0,scrollY:0,w:0,h:0,type:"mouse"},he=function(t){let e=document.documentElement.clientWidth,i=document.documentElement.clientHeight;le.w===e&&le.h===i||(le.w=e,le.h=i,oe=!0,ae=!0)},ce=function(t){let e=window.pageXOffset,i=window.pageYOffset;le.scrollX===e&&le.scrollY===i||(le.x+=e-le.scrollX,le.y+=i-le.scrollY,le.scrollX=e,le.scrollY=i,oe=!0)},ue=function(t){let e=Math.round(t.pageX),i=Math.round(t.pageY);le.x===e&&le.y===i||(le.type=navigator.pointerEnabled?"pointer":"mouse",le.x=e,le.y=i,oe=!0)},de=function(t){if(("touchstart"===t.type||"touchmove"===t.type)&&t.touches&&t.touches[0]){let e=t.touches[0],i=Math.round(e.pageX),s=Math.round(e.pageY);le.x===i&&le.y===s||(le.type="touch",le.x=i,le.y=s,fe())}},fe=function(){ne.forEach(t=>pe(t))},pe=function(t){let e=i[t];if(!J(e))throw new Error("core/userInteraction updateUiSubscribedElement() error - artefact does not exist: "+t);let s=e.domElement;if(!G(s))throw new Error("core/userInteraction updateUiSubscribedElement() error - DOM element missing: "+t);let n=s.getBoundingClientRect(),r=Math.round(n.left+window.pageXOffset),o=Math.round(n.top+window.pageYOffset);e.here||(e.here={});let a=e.here;if(a.w=Math.round(n.width),a.h=Math.round(n.height),a.type=le.type,e.localMouseListener?(a.localListener=!0,a.active=!1,a.normX=!!a.originalWidth&&a.x/a.originalWidth,a.normY=!!a.originalHeight&&a.y/a.originalHeight,a.offsetX=r,a.offsetY=o,a.x>e.activePadding&&a.x0+e.activePadding&&a.y1||a.normY<0||a.normY>1)&&(a.active=!1)),"Canvas"===e.type&&e.updateBaseHere(a,e.fit),e.checkForResize&&!e.dirtyDimensions&&!e.dirtyDomDimensions){let[t,i]=e.currentDimensions;if("Canvas"===e.type){e.computedStyles||(e.computedStyles=window.getComputedStyle(e.domElement));let s=e.computedStyles,n=Math.floor(a.w-parseFloat(s.borderLeftWidth)-parseFloat(s.borderRightWidth)-parseFloat(s.paddingLeft)-parseFloat(s.paddingRight)),r=Math.floor(a.h-parseFloat(s.borderTopWidth)-parseFloat(s.borderBottomWidth)-parseFloat(s.paddingTop)-parseFloat(s.paddingBottom));t===n&&i===r||e.set({dimensions:[n,r]})}else t===a.w&&i===a.h||e.set({dimensions:[a.w,a.h]})}},me=se({name:"coreListenersTracker",order:0,delay:!0,fn:function(){return new Promise(t=>{ne.length||t(!1),re&&oe&&(oe=!1,fe()),ae&&(ae=!1,function(){for(const[t,e]of Object.entries(h))"Phrase"===e.type&&(e.dirtyDimensions=!0,e.dirtyFont=!0)}()),t(!0)})}}),ge=function(){ye("removeEventListener"),ye("addEventListener"),re=!0,oe=!0,me.run()},ye=function(t){navigator.pointerEnabled||navigator.msPointerEnabled?(window[t]("pointermove",ue,!1),window[t]("pointerup",ue,!1),window[t]("pointerdown",ue,!1),window[t]("pointerleave",ue,!1),window[t]("pointerenter",ue,!1)):(window[t]("mousemove",ue,!1),window[t]("mouseup",ue,!1),window[t]("mousedown",ue,!1),window[t]("mouseleave",ue,!1),window[t]("mouseenter",ue,!1),window[t]("touchmove",de,!1),window[t]("touchstart",de,!1),window[t]("touchend",de,!1),window[t]("touchcancel",de,!1)),window[t]("scroll",ce,!1),window[t]("resize",he,!1)},be=function(){he(),oe=!0},ke=URL.createObjectURL(new Blob(['let packet,packetFiltersArray,source,work,cache,actions,workstore={},workstoreLastAccessed={};const createResultObject=function(e){return{r:new Uint8ClampedArray(e),g:new Uint8ClampedArray(e),b:new Uint8ClampedArray(e),a:new Uint8ClampedArray(e)}},unknit=function(e){let t=e.data,l=Math.floor(t.length/4);source=createResultObject(l),e.channels=source;let n=source.r,r=source.g,o=source.b,s=source.a,u=(work=createResultObject(l)).r,a=work.g,c=work.b,i=work.a,f=0;for(let e=0,l=t.length;e{workstoreLastAccessed[e]actions.push(...e.actions)),actions.length&&(unknit(cache.source),actions.forEach(e=>theBigActionsObject[e.action]&&theBigActionsObject[e.action](e)),knit()),postMessage(packet)},onerror=function(e){console.log("error"+e.message),postMessage(packet)};const buildImageGrid=function(e){if(e||(e=cache.source),e&&e.width&&e.height){let t=`grid-${e.width}-${e.height}`;if(workstore[t])return workstoreLastAccessed[t]=Date.now(),workstore[t];let l=[],n=0;for(let t=0,r=e.height;t=s&&(e=s-l-1),t+(n=n.toFixed&&!isNaN(n)?n:1)>=a&&(t=a-n-1),e<1&&(e=1),t<1&&(t=1),e+l>=s&&(l=s-e-1),t+n>=a&&(n=a-t-1);let c=e+l,i=t+n;(r=r.toFixed&&!isNaN(r)?r:0)<0&&(r=0),r>=c&&(r=c-1),(o=o.toFixed&&!isNaN(o)?o:0)<0&&(o=0),o>=i&&(o=i-1);let f=`alphatileset-${s}-${a}-${e}-${t}-${l}-${n}-${r}-${o}`;if(workstore[f])return workstoreLastAccessed[f]=Date.now(),workstore[f];let h,g,p,d,b,k,w,R=[];for(d=o-i,b=a;d=0&&k=0&&t=0&&k=0&&t=0&&k=0&&t=0&&k=0&&t=o&&(e=o-1),(t=t.toFixed&&!isNaN(t)?t:1)<1&&(t=1),t>=s&&(t=s-1),(l=l.toFixed&&!isNaN(l)?l:0)<0&&(l=0),l>=e&&(l=e-1),(n=n.toFixed&&!isNaN(n)?n:0)<0&&(n=0),n>=t&&(n=t-1);let u=`imagetileset-${o}-${s}-${e}-${t}-${l}-${n}`;if(workstore[u])return workstoreLastAccessed[u]=Date.now(),workstore[u];let a=[];for(let r=n-t,u=s;r=0&&y=0&&t=0&&o=0&&n=e&&(l=e-1),null==n||n<0?n=0:n>=t&&(n=t-1);let s=o.width,u=o.height,a=`matrix-${s}-${u}-${e}-${t}-${l}-${n}`;if(workstore[a])return workstoreLastAccessed[a]=Date.now(),workstore[a];let c,i,f,h,g,p,d=o.data.length,b=[],k=[];for(f=-n,h=t-n;f=d&&(l-=d),t.push(l)}k.push(t)}return workstore[a]=k,workstoreLastAccessed[a]=Date.now(),k},checkChannelLevelsParameters=function(e){const t=function(e,t=!1){if(e.toFixed)return e<0?[[0,255,0]]:e>255?[[0,255,255]]:isNaN(e)?t?[[0,255,255]]:[[0,255,0]]:[[0,255,e]];if(e.substring&&(e=e.split(",")),Array.isArray(e)){if(!e.length)return e;if(Array.isArray(e[0]))return e;if((e=e.map(e=>parseInt(e,10))).sort((e,t)=>e-t),1==e.length)return[[0,255,e[0]]];let t,l,n=[];for(let r=0,o=e.length;r0){antiRatio=1-l;for(let e=0,t=n.length;e1&&(s-=1),u<0&&(u+=1),u>1&&(u-=1),a<0&&(a+=1),a>1&&(a-=1),[255*o(s,n,r),255*o(u,n,r),255*o(a,n,r)]},theBigActionsObject={"alpha-to-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,excludeRed:a,excludeGreen:c,excludeBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!0),null==c&&(c=!0),null==i&&(i=!0);const{r:h,g:g,b:p,a:d}=t,{r:b,g:k,b:w,a:R}=l;for(let e=0;e{for(let l=0,n=e.length;lo?255:0:i[e],d[e]=s?f[e]>s?255:0:f[e],b[e]=u?h[e]>u?255:0:h[e],k[e]=a?g[e]>a?255:0:g[e];c?processResults(l,work,1-r):processResults(work,l,r)},blend:function(e){let[t,l,n]=getInputAndOutputChannels(e),{opacity:r,blend:o,offsetX:s,offsetY:u,lineOut:a}=(l.r.length,e);null==r&&(r=1),null==o&&(o=""),null==s&&(s=0),null==u&&(u=0);const{r:c,g:i,b:f,a:h}=t,{r:g,g:p,b:d,a:b}=l,{r:k,g:w,b:R,a:O}=n;let[A,y,I,m,M,x]=getInputAndOutputDimensions(e);const B=function(e,t,l){g[t]=l.r[e],p[t]=l.g[e],d[t]=l.b[e],b[t]=l.a[e]},G=function(e,t){let l=e-s,n=t-u,r=-1;return l>=0&&l=0&&n255*(e+t*(1-e));switch(o){case"color-burn":const e=(e,t)=>1==t?255:0==e?0:255*(1-Math.min(1,(1-t)/e));for(let l=0;l0==t?0:1==e?255:255*Math.min(1,t/(1-e));for(let e=0;ee255*Math.abs(e-t);for(let e=0;e255*(e+t-2*t*e);for(let e=0;ee<=.5?e*t*255:255*(t+(e-t*e));for(let e=0;ee>t?e:t;for(let e=0;e255*(e+t);for(let e=0;ee*t*255;for(let e=0;ee>=.5?e*t*255:255*(t+(e-t*e));for(let e=0;e255*(t+(e-t*e));for(let e=0;e{let l=t<=.25?((16*t-12)*t+4)*t:Math.sqrt(t);return e<=.5?255*(t-(1-2*e)*t*(1-t)):255*(t+(2*e-1)*(l-t))};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,u,f)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,i,f)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,i,a)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,u,f)};for(let e=0;et*e+n*l*(1-t);for(let e=0;e=e&&l<=a&&n>=s&&n<=c&&r>=u&&r<=f){t=!0;break}}h[e]=u[e],g[e]=a[e],p[e]=c[e],d[e]=t?0:f[e]}s?processResults(l,work,1-r):processResults(work,l,r)},"clamp-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lowRed:o,lowGreen:s,lowBlue:u,highRed:a,highGreen:c,highBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255),null==c&&(c=255),null==i&&(i=255);const h=a-o,g=c-s,p=i-u,{r:d,g:b,b:k,a:w}=t,{r:R,g:O,b:A,a:y}=l;for(let e=0;eg?255:(n-h)/p*255},{r:b,g:k,b:w,a:R}=t,{r:O,g:A,b:y,a:I}=l;for(let e=0;e=0&&l=0&&nt*e*n+n*l*(1-t);for(let t=0;t=0){let t=h[n]/255,l=O[r]/255;g[n]=e(c[n],t,k[r],l),p[n]=e(i[n],t,w[r],l),d[n]=e(f[n],t,R[r],l),b[n]=255*(t*l+l*(1-t))}}break;case"source-in":const r=(e,t,l)=>t*e*l;for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=r(c[l],e,t),p[l]=r(i[l],e,t),d[l]=r(f[l],e,t),b[l]=e*t*255}}break;case"source-out":const s=(e,t,l)=>t*e*(1-l);for(let e=0;e=0&&B(r,l,n)}break;case"destination-atop":const u=(e,t,l,n)=>t*e*(1-n)+n*l*t;for(let e=0;et*e*(1-n)+n*l;for(let e=0;et*e*l;for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=I(k[n],t,e),p[l]=I(w[n],t,e),d[l]=I(R[n],t,e),b[l]=e*t*255}}break;case"destination-out":const m=(e,t,l)=>l*e*(1-t);for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=m(k[n],e,t),p[l]=m(w[n],e,t),d[l]=m(R[n],e,t),b[l]=t*(1-e)*255}}break;case"clear":break;case"xor":const M=(e,t,l,n)=>t*e*(1-n)+n*l*(1-t);for(let e=0;et*e+n*l*(1-t);for(let e=0;e=0&&l=0&&n=0){let c,i=Math.floor(l+(127-o[r])/127*u),h=Math.floor(e+(127-s[r])/127*a);f?c=i<0||i>=M||h<0||h>=x?-1:h*M+i:(i<0&&(i=0),i>=M&&(i=M-1),h<0&&(h=0),h>=x&&(h=x-1),c=h*M+i),$(c,n,t)}else $(n,n,t)}h?processResults(l,work,1-r):processResults(work,l,r)},emboss:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,strength:o,angle:s,tolerance:u,keepOnlyChangedAreas:a,postProcessResults:c,lineOut:i}=e;for(null==r&&(r=1),null==o&&(o=1),null==s&&(s=0),null==u&&(u=0),null==a&&(a=!1),null==c&&(c=!1),o=Math.abs(o);s<0;)s+=360;s%=360;let f=Math.floor(s/45),h=s%45/45*o,g=new Array(9);(g=g.fill(0,0,9))[4]=1,0==f?(g[5]=o-h,g[8]=h,g[3]=-g[5],g[0]=-g[8]):1==f?(g[8]=o-h,g[7]=h,g[0]=-g[8],g[1]=-g[7]):2==f?(g[7]=o-h,g[6]=h,g[1]=-g[7],g[2]=-g[6]):3==f?(g[6]=o-h,g[3]=h,g[2]=-g[6],g[5]=-g[3]):4==f?(g[3]=o-h,g[0]=h,g[5]=-g[3],g[8]=-g[0]):5==f?(g[0]=o-h,g[1]=h,g[8]=-g[0],g[7]=-g[1]):6==f?(g[1]=o-h,g[2]=h,g[7]=-g[1],g[6]=-g[2]):(g[2]=o-h,g[5]=h,g[6]=-g[2],g[3]=-g[5]);const{r:p,g:d,b:b,a:k}=t,{r:w,g:R,b:O,a:A}=l;grid=buildMatrixGrid(3,3,1,1);const y=function(e,t){let l=0;for(let n=0,r=t.length;n=p[e]-u&&w[e]<=p[e]+u&&R[e]>=d[e]-u&&R[e]<=d[e]+u&&O[e]>=b[e]-u&&O[e]<=b[e]+u&&(a?A[e]=0:(w[e]=127,R[e]=127,O[e]=127)));i?processResults(l,work,1-r):processResults(work,l,r)},flood:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,alpha:a,lineOut:c}=(Math.floor,e);null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255);const{r:i,g:f,b:h,a:g}=l;i.fill(o,0,n-1),f.fill(s,0,n-1),h.fill(u,0,n-1),g.fill(a,0,n-1),c?processResults(l,work,1-r):processResults(work,l,r)},grayscale:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lineOut:o}=e;null==r&&(r=1);const{r:s,g:u,b:a,a:c}=t,{r:i,g:f,b:h,a:g}=l;for(let e=0;e=n&&e<=r)return o}};let[l,n]=getInputAndOutputChannels(e),r=l.r.length,{opacity:o,red:s,green:u,blue:a,alpha:c,lineOut:i}=e;null==o&&(o=1),null==s&&(s=[0]),null==u&&(u=[0]),null==a&&(a=[0]),null==c&&(c=[255]);const{r:f,g:h,b:g,a:p}=l,{r:d,g:b,b:k,a:w}=n;for(let e=0;e=0&&y=0&&I=0&&y=0&&I=0&&m=0&&M=0&&x=0&&B=0&&G=0&&Ct+e[l],0);n=Math.floor(n/l.length);for(let e=0,r=l.length;e{i?t(b,O,e):l(b,O,e),f?t(k,A,e):l(k,A,e),h?t(w,y,e):l(w,y,e),g?t(R,I,e):l(R,I,e)}),p?processResults(r,work,1-o):processResults(work,r,o)},"process-image":function(e){const{assetData:t,lineOut:l}=e;if(l&&l.substring&&l.length&&t&&t.width&&t.height&&t.data){let e=t.data,n=e.length,r=createResultObject(n/4),o=r.r,s=r.g,u=r.b,a=r.a,c=0;for(let t=0;t{let s=i.filters;s&&s.indexOf(t)>=0&&K(s,t)}),Object.entries(u).forEach(([e,i])=>{let s=i.filters;s&&s.indexOf(t)>=0&&K(s,t)}),Object.entries(a).forEach(([e,i])=>{let s=i.filters;s&&s.indexOf(t)>=0&&K(s,t)}),this.deregister(),this};Se.setters,Se.deltaSetters;Se.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this)}return this.method&&Oe[this.method]&&Oe[this.method](this),this},Se.setDelta=function(t={}){if(Object.keys(t).length){let e,i=this.deltaSetters,s=this.defs;Object.entries(t).forEach(([t,n])=>{t&&"name"!==t&&null!=n&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=addStrings(this[t],n)))},this)}return this.method&&Oe[this.method]&&Oe[this.method](this),this};const Oe={alphaToChannels:function(t){t.actions=[{action:"alpha-to-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue,excludeRed:null==t.excludeRed||t.excludeRed,excludeGreen:null==t.excludeGreen||t.excludeGreen,excludeBlue:null==t.excludeBlue||t.excludeBlue}]},areaAlpha:function(t){t.actions=[{action:"area-alpha",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,tileWidth:null!=t.tileWidth?t.tileWidth:1,tileHeight:null!=t.tileHeight?t.tileHeight:1,offsetX:null!=t.offsetX?t.offsetX:0,offsetY:null!=t.offsetY?t.offsetY:0,gutterWidth:null!=t.gutterWidth?t.gutterWidth:1,gutterHeight:null!=t.gutterHeight?t.gutterHeight:1,areaAlphaLevels:null!=t.areaAlphaLevels?t.areaAlphaLevels:[255,0,0,0]}]},binary:function(t){t.actions=[{action:"binary",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:0,green:null!=t.green?t.green:0,blue:null!=t.blue?t.blue:0,alpha:null!=t.alpha?t.alpha:0}]},blend:function(t){t.actions=[{action:"blend",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",lineMix:null!=t.lineMix?t.lineMix:"",blend:null!=t.blend?t.blend:"normal",offsetX:null!=t.offsetX?t.offsetX:0,offsetY:null!=t.offsetY?t.offsetY:0,opacity:null!=t.opacity?t.opacity:1}]},blue:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,excludeRed:!0,excludeGreen:!0}]},blur:function(t){t.actions=[{action:"blur",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue,includeAlpha:null!=t.includeAlpha&&t.includeAlpha,processHorizontal:null==t.processHorizontal||t.processHorizontal,processVertical:null==t.processVertical||t.processVertical,radius:null!=t.radius?t.radius:1,passes:null!=t.passes?t.passes:1,step:null!=t.step?t.step:1}]},brightness:function(t){let e=null!=t.level?t.level:1;t.actions=[{action:"modulate-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:e,green:e,blue:e}]},channelLevels:function(t){t.actions=[{action:"lock-channels-to-levels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:[0],green:null!=t.green?t.green:[0],blue:null!=t.blue?t.blue:[0],alpha:null!=t.alpha?t.alpha:[255]}]},channels:function(t){t.actions=[{action:"modulate-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:1,green:null!=t.green?t.green:1,blue:null!=t.blue?t.blue:1,alpha:null!=t.alpha?t.alpha:1}]},channelstep:function(t){t.actions=[{action:"step-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:1,green:null!=t.green?t.green:1,blue:null!=t.blue?t.blue:1}]},channelsToAlpha:function(t){t.actions=[{action:"channels-to-alpha",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue}]},chroma:function(t){t.actions=[{action:"chroma",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,ranges:null!=t.ranges?t.ranges:[]}]},chromakey:function(t){t.actions=[{action:"colors-to-alpha",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:0,green:null!=t.green?t.green:255,blue:null!=t.blue?t.blue:0,transparentAt:null!=t.transparentAt?t.transparentAt:0,opaqueAt:null!=t.opaqueAt?t.opaqueAt:1}]},clampChannels:function(t){t.actions=[{action:"clamp-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,lowRed:null!=t.lowRed?t.lowRed:0,lowGreen:null!=t.lowGreen?t.lowGreen:0,lowBlue:null!=t.lowBlue?t.lowBlue:0,highRed:null!=t.highRed?t.highRed:255,highGreen:null!=t.highGreen?t.highGreen:255,highBlue:null!=t.highBlue?t.highBlue:255}]},compose:function(t){t.actions=[{action:"compose",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",lineMix:null!=t.lineMix?t.lineMix:"",compose:null!=t.compose?t.compose:"source-over",offsetX:null!=t.offsetX?t.offsetX:0,offsetY:null!=t.offsetY?t.offsetY:0,opacity:null!=t.opacity?t.opacity:1}]},cyan:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeGreen:!0,includeBlue:!0,excludeRed:!0}]},displace:function(t){t.actions=[{action:"displace",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",lineMix:null!=t.lineMix?t.lineMix:"",opacity:null!=t.opacity?t.opacity:1,channelX:null!=t.channelX?t.channelX:"red",channelY:null!=t.channelY?t.channelY:"green",offsetX:null!=t.offsetX?t.offsetX:0,offsetY:null!=t.offsetY?t.offsetY:0,scaleX:null!=t.scaleX?t.scaleX:1,scaleY:null!=t.scaleY?t.scaleY:1,transparentEdges:null!=t.transparentEdges&&t.transparentEdges}]},edgeDetect:function(t){t.actions=[{action:"matrix",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,width:3,height:3,offsetX:1,offsetY:1,includeRed:!0,includeGreen:!0,includeBlue:!0,includeAlpha:!1,weights:[0,1,0,1,-4,1,0,1,0]}]},emboss:function(t){const e=[];t.useNaturalGrayscale?e.push({action:"grayscale",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:"emboss-work"}):e.push({action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:"emboss-work",includeRed:!0,includeGreen:!0,includeBlue:!0}),t.clamp&&e.push({action:"clamp-channels",lineIn:"emboss-work",lineOut:"emboss-work",lowRed:0+t.clamp,lowGreen:0+t.clamp,lowBlue:0+t.clamp,highRed:255-t.clamp,highGreen:255-t.clamp,highBlue:255-t.clamp}),t.smoothing&&e.push({action:"blur",lineIn:"emboss-work",lineOut:"emboss-work",radius:t.smoothing,passes:2}),e.push({action:"emboss",lineIn:"emboss-work",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,angle:null!=t.angle?t.angle:0,strength:null!=t.strength?t.strength:1,tolerance:null!=t.tolerance?t.tolerance:0,keepOnlyChangedAreas:null!=t.keepOnlyChangedAreas&&t.keepOnlyChangedAreas,postProcessResults:null==t.postProcessResults||t.postProcessResults}),t.actions=e},flood:function(t){t.actions=[{action:"flood",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:null!=t.red?t.red:0,green:null!=t.green?t.green:0,blue:null!=t.blue?t.blue:0,alpha:null!=t.alpha?t.alpha:255}]},gray:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:!0,includeGreen:!0,includeBlue:!0}]},grayscale:function(t){t.actions=[{action:"grayscale",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1}]},green:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,excludeRed:!0,excludeBlue:!0}]},image:function(t){t.actions=[{action:"process-image",lineOut:null!=t.lineOut?t.lineOut:"",asset:null!=t.asset?t.asset:"",width:null!=t.width?t.width:1,height:null!=t.height?t.height:1,copyWidth:null!=t.copyWidth?t.copyWidth:1,copyHeight:null!=t.copyHeight?t.copyHeight:1,copyX:null!=t.copyX?t.copyX:0,copyY:null!=t.copyY?t.copyY:0}]},invert:function(t){t.actions=[{action:"invert-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:!0,includeGreen:!0,includeBlue:!0}]},magenta:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:!0,includeBlue:!0,excludeGreen:!0}]},matrix:function(t){t.actions=[{action:"matrix",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,width:3,height:3,offsetX:1,offsetY:1,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue,includeAlpha:null!=t.includeAlpha&&t.includeAlpha,weights:null!=t.weights?t.weights:[0,0,0,0,1,0,0,0,0]}]},matrix5:function(t){t.actions=[{action:"matrix",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,width:5,height:5,offsetX:2,offsetY:2,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue,includeAlpha:null!=t.includeAlpha&&t.includeAlpha,weights:null!=t.weights?t.weights:[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0]}]},notblue:function(t){t.actions=[{action:"set-channel-to-level",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeBlue:!0,level:0}]},notgreen:function(t){t.actions=[{action:"set-channel-to-level",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeGreen:!0,level:0}]},notred:function(t){t.actions=[{action:"set-channel-to-level",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:!0,level:0}]},offset:function(t){t.actions=[{action:"offset",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,offsetRedX:null!=t.offsetX?t.offsetX:0,offsetRedY:null!=t.offsetY?t.offsetY:0,offsetGreenX:null!=t.offsetX?t.offsetX:0,offsetGreenY:null!=t.offsetY?t.offsetY:0,offsetBlueX:null!=t.offsetX?t.offsetX:0,offsetBlueY:null!=t.offsetY?t.offsetY:0,offsetAlphaX:null!=t.offsetX?t.offsetX:0,offsetAlphaY:null!=t.offsetY?t.offsetY:0}]},offsetChannels:function(t){t.actions=[{action:"offset",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,offsetRedX:null!=t.offsetRedX?t.offsetRedX:0,offsetRedY:null!=t.offsetRedY?t.offsetRedY:0,offsetGreenX:null!=t.offsetGreenX?t.offsetGreenX:0,offsetGreenY:null!=t.offsetGreenY?t.offsetGreenY:0,offsetBlueX:null!=t.offsetBlueX?t.offsetBlueX:0,offsetBlueY:null!=t.offsetBlueY?t.offsetBlueY:0,offsetAlphaX:null!=t.offsetAlphaX?t.offsetAlphaX:0,offsetAlphaY:null!=t.offsetAlphaY?t.offsetAlphaY:0}]},pixelate:function(t){t.actions=[{action:"pixelate",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,tileWidth:null!=t.tileWidth?t.tileWidth:1,tileHeight:null!=t.tileHeight?t.tileHeight:1,offsetX:null!=t.offsetX?t.offsetX:0,offsetY:null!=t.offsetY?t.offsetY:0,includeRed:null==t.includeRed||t.includeRed,includeGreen:null==t.includeGreen||t.includeGreen,includeBlue:null==t.includeBlue||t.includeBlue,includeAlpha:null!=t.includeAlpha&&t.includeAlpha}]},red:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,excludeGreen:!0,excludeBlue:!0}]},saturation:function(t){let e=null!=t.level?t.level:1;t.actions=[{action:"modulate-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,red:e,green:e,blue:e,saturation:!0}]},sepia:function(t){t.actions=[{action:"tint-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,redInRed:.393,redInGreen:.349,redInBlue:.272,greenInRed:.769,greenInGreen:.686,greenInBlue:.534,blueInRed:.189,blueInGreen:.168,blueInBlue:.131}]},sharpen:function(t){t.actions=[{action:"matrix",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,width:3,height:3,offsetX:1,offsetY:1,includeRed:!0,includeGreen:!0,includeBlue:!0,includeAlpha:!1,weights:[0,-1,0,-1,5,-1,0,-1,0]}]},threshold:function(t){let e=null!=t.lowRed?t.lowRed:0,i=null!=t.lowGreen?t.lowGreen:0,s=null!=t.lowBlue?t.lowBlue:0,n=null!=t.highRed?t.highRed:255,r=null!=t.highGreen?t.highGreen:255,o=null!=t.highBlue?t.highBlue:255,a=null!=t.low?t.low:[e,i,s],l=null!=t.high?t.high:[n,r,o];t.actions=[{action:"threshold",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,level:null!=t.level?t.level:128,low:a,high:l}]},tint:function(t){t.actions=[{action:"tint-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,redInRed:null!=t.redInRed?t.redInRed:1,redInGreen:null!=t.redInGreen?t.redInGreen:0,redInBlue:null!=t.redInBlue?t.redInBlue:0,greenInRed:null!=t.greenInRed?t.greenInRed:0,greenInGreen:null!=t.greenInGreen?t.greenInGreen:1,greenInBlue:null!=t.greenInBlue?t.greenInBlue:0,blueInRed:null!=t.blueInRed?t.blueInRed:0,blueInGreen:null!=t.blueInGreen?t.blueInGreen:0,blueInBlue:null!=t.blueInBlue?t.blueInBlue:1}]},userDefined:function(t){t.actions=[{action:"user-defined-legacy",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1}]},yellow:function(t){t.actions=[{action:"average-channels",lineIn:null!=t.lineIn?t.lineIn:"",lineOut:null!=t.lineOut?t.lineOut:"",opacity:null!=t.opacity?t.opacity:1,includeRed:!0,includeGreen:!0,excludeBlue:!0}]}},Pe=[],ve=function(){return Pe.length||Pe.push(new Worker(ke)),Pe.shift()},we=function(t){Pe.push(t)},xe=function(t,e){return new Promise((i,s)=>{t.onmessage=t=>{t&&t.data&&t.data.image?i(t.data.image):i(!1)},t.onerror=t=>{console.log("error",t.lineno,t.message),i(!1)},t.postMessage(e)})};O.Filter=Filter;const State=function(t={}){return this.set(this.defs),this.lineDash=[],this.set(t),this};let Ae=State.prototype=Object.create(Object.prototype);Ae.type="State",Ae=ee(Ae),Ae.defs={fillStyle:"rgba(0,0,0,1)",strokeStyle:"rgba(0,0,0,1)",globalAlpha:1,globalCompositeOperation:"source-over",lineWidth:1,lineCap:"butt",lineJoin:"miter",lineDash:null,lineDashOffset:0,miterLimit:10,shadowOffsetX:0,shadowOffsetY:0,shadowBlur:0,shadowColor:"rgba(0,0,0,0)",font:"12px sans-serif",textAlign:"left",textBaseline:"top",filter:"none"},Ae.processPacketOut=function(t,e,i){let s=!0;switch(t){case"lineDash":e.length||(s=i.indexOf("lineDash")>=0);break;default:i.indexOf(t)<0&&e===this.defs[t]&&(s=!1)}return s},Ae.finalizePacketOut=function(t,e){let i=t.fillStyle,s=t.strokeStyle;return i&&!i.substring&&(t.fillStyle=i.name),s&&!s.substring&&(t.strokeStyle=s.name),t},Ae.set=function(t={}){if(Object.keys(t).length){let e,i,s,n=Object.keys(t),r=this.defs;for(i=0,s=n.length;i0&&(this[0]/=t,this[1]/=t),this};const Ee=[],Fe=function(t,e){Ee.length||Ee.push(new Coordinate);let i=Ee.shift();return i.set(t,e),i},Te=function(t){t&&"Coordinate"===t.type&&Ee.push(t.zero())},He=function(t,e){return new Coordinate(t,e)};function Me(t={}){t.defs=_(t.defs,{sourceLoaded:!1,source:null,subscribers:null}),t.packetExclusions=Q(t.packetExclusions,["sourceLoaded","source","subscribers"]),t.finalizePacketOut=function(t,e){return this.subscribers&&this.subscribers.length&&(t.subscribers=this.subscribers.map(t=>t.name)),t},t.kill=function(t=!1){return t&&this.source&&this.source.remove(),this.deregister()};t.getters;let e=t.setters;t.deltaSetters;return e.source=function(t={}){t&&this.sourceLoaded&&this.notifySubscribers()},e.subscribers=B,t.assetConstructor=function(t={}){return this.makeName(t.name),this.register(),this.subscribers=[],this.set(this.defs),this.set(t),t.subscribe&&this.subscribers.push(t.subscribe),this},t.subscribe=function(t={}){if(t&&t.name){let e=t.name;this.subscribers.every(t=>t.name!==e)&&this.subscribeAction(t)}},t.subscribeAction=function(t={}){this.subscribers.push(t),t.asset=this,t.source=this.source,this.notifySubscriber(t)},t.unsubscribe=function(t={}){if(t.name){let e=t.name,i=this.subscribers.findIndex(t=>t.name===e);i>=0&&(t.source=null,t.asset=null,t.sourceNaturalHeight=0,t.sourceNaturalWidth=0,t.sourceLoaded=!1,this.subscribers.splice(i,1))}},t.notifySubscribers=function(){this.subscribers.forEach(t=>this.notifySubscriber(t),this)},t.notifySubscriber=function(t){t.sourceNaturalWidth=this.sourceNaturalWidth,t.sourceNaturalHeight=this.sourceNaturalHeight,t.sourceLoaded=this.sourceLoaded,t.source=this.source,t.dirtyImage=!0,t.dirtyCopyStart=!0,t.dirtyCopyDimensions=!0,t.dirtyImageSubscribers=!0},t}O.Coordinate=Coordinate;const ImageAsset=function(t={}){return this.assetConstructor(t)};let Ie=ImageAsset.prototype=Object.create(Object.prototype);Ie.type="Image",Ie.lib="asset",Ie.isArtefact=!1,Ie.isAsset=!0,Ie=ee(Ie),Ie=Me(Ie);Ie.defs=_(Ie.defs,{intrinsicDimensions:null}),Ie.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},Ie.stringifyFunction=B,Ie.processPacketOut=B,Ie.finalizePacketOut=B,Ie.clone=$;Ie.getters;let Be=Ie.setters;Ie.deltaSetters;Be.source=function(t={}){t&&(["IMG","PICTURE"].indexOf(t.tagName.toUpperCase())>=0&&(this.source=t,this.sourceNaturalWidth=t.naturalWidth,this.sourceNaturalHeight=t.naturalHeight,this.sourceLoaded=t.complete),this.sourceLoaded&&this.notifySubscribers())},Be.currentSrc=function(t){this.currentSrc=t,this.currentFile=this.currentSrc.split("/").pop()},Ie.checkSource=function(t,e){let i=this.source,s="element";if(this.sourceLoaded){let n=this.intrinsicDimensions[this.currentFile];switch(this.currentSrc!==i.currentSrc?(this.set({currentSrc:i.currentSrc}),n=this.intrinsicDimensions[this.currentFile],s=n?"intrinsic":"zero"):n&&(s="intrinsic"),s){case"zero":this.sourceNaturalWidth=0,this.sourceNaturalHeight=0,this.notifySubscribers();break;case"intrinsic":this.sourceNaturalWidth===n[0]&&this.sourceNaturalHeight===n[1]||(this.sourceNaturalWidth=n[0],this.sourceNaturalHeight=n[1],this.notifySubscribers());break;default:this.sourceNaturalWidth===i.naturalWidth&&this.sourceNaturalHeight===i.naturalHeight&&this.sourceNaturalWidth===t&&this.sourceNaturalHeight===e||(this.sourceNaturalWidth=i.naturalWidth,this.sourceNaturalHeight=i.naturalHeight,this.notifySubscribers())}}};const Le=[],$e=[],je=function(...t){let e=/.*\/(.*?)\./,i=[];return t.forEach(t=>{let s,n,r,o,a=!1,l=!1;if(t.substring){let i=e.exec(t);s=i&&i[1]?i[1]:"",n=t,r="",o=!1,l=!0}else(t=!!U(t)&&t)&&t.src&&(s=t.name||"",n=t.src,r=t.className||"",o=t.visibility||!1,t.parent&&(a=document.querySelector(t.parent)),l=!0);if(l){let t=Xe({name:s,intrinsicDimensions:{}}),e=document.createElement("img");e.name=s,e.className=r,e.crossorigin="anonymous",e.style.display=o?"block":"none",a&&a.appendChild(e),e.onload=()=>{t.set({source:e})},e.src=n,t.set({source:e}),i.push(s)}else i.push(!1)}),i},Ne=function(t){let e=/.*\/(.*?)\./;document.querySelectorAll(t).forEach(t=>{let i;if(["IMG","PICTURE"].indexOf(t.tagName.toUpperCase())>=0){if(t.id||t.name)i=t.id||t.name;else{let s=e.exec(t.src);i=s&&s[1]?s[1]:""}let s=t.dataset.dimensions||{};s.substring&&(s=JSON.parse(s));let n=Xe({name:i,source:t,intrinsicDimensions:s,currentSrc:t.currentSrc});t.onload=()=>{n.set({source:t})}}})},Xe=function(t){return new ImageAsset(t)};function Ye(t={}){t.defs=_(t.defs,{group:null,visibility:!0,order:0,start:null,handle:null,offset:null,dimensions:null,pivoted:null,mimicked:null,particle:null,lockTo:null,bringToFrontOnDrag:!0,scale:1,roll:0,noUserInteraction:!1,noPositionDependencies:!1,noCanvasEngineUpdates:!1,noFilters:!1,noPathUpdates:!1,purge:null}),t.packetExclusions=Q(t.packetExclusions,["pathObject","mimicked","pivoted"]),t.packetExclusionsByRegex=Q(t.packetExclusionsByRegex,["^(local|dirty|current)","Subscriber$"]),t.packetCoordinates=Q(t.packetCoordinates,["start","handle","offset"]),t.packetObjects=Q(t.packetObjects,["group"]),t.packetFunctions=Q(t.packetFunctions,[]),t.processPacketOut=function(t,e,i){let s=!0;switch(t){case"lockTo":"start"===e[0]&&"start"===e[1]&&(s=i.indexOf("lockTo")>=0);break;default:"entity"===this.lib?s=this.processEntityPacketOut(t,e,i):this.isArtefact&&(s=this.processDOMPacketOut(t,e,i))}return s},t.handlePacketAnchor=function(t,e){if(this.anchor){let i=JSON.parse(this.anchor.saveAsPacket(e))[3];t.anchor=i}return t},t.kill=function(t=!1,e=!1){let s=this.name;return Object.entries(u).forEach(([t,e])=>{e.artefacts.indexOf(s)>=0&&e.removeArtefacts(s)}),this.anchor&&this.demolishAnchor(),Object.entries(i).forEach(([t,e])=>{e.name!==s&&(e.pivot&&e.pivot.name===s&&e.set({pivot:!1}),e.mimic&&e.mimic.name===s&&e.set({mimic:!1}),e.path&&e.path.name===s&&e.set({path:!1}),e.generateAlongPath&&e.generateAlongPath.name===s&&e.set({generateAlongPath:!1}),e.generateInArea&&e.generateInArea.name===s&&e.set({generateInArea:!1}),e.artefact&&e.artefact.name===s&&e.set({artefact:!1}),Array.isArray(e.pins)&&e.pins.forEach((t,i)=>{U(t)&&t.name===s&&e.removePinAt(i)}))}),Object.entries(y).forEach(([t,e])=>{e.checkForTarget(s)&&e.removeFromTargets(this)}),this.factoryKill(t,e),this.deregister(),this},t.factoryKill=B;let e=t.getters,s=t.setters,n=t.deltaSetters;return e.positionX=function(){return this.currentStampPosition[0]},e.positionY=function(){return this.currentStampPosition[1]},e.position=function(){return[].concat(this.currentStampPosition)},e.startX=function(){return this.currentStart[0]},e.startY=function(){return this.currentStart[1]},e.start=function(){return[].concat(this.currentStart)},s.startX=function(t){null!=t&&(this.start[0]=t,this.dirtyStart=!0)},s.startY=function(t){null!=t&&(this.start[1]=t,this.dirtyStart=!0)},s.start=function(t,e){this.setCoordinateHelper("start",t,e),this.dirtyStart=!0},n.startX=function(t){let e=this.start;e[0]=H(e[0],t),this.dirtyStart=!0},n.startY=function(t){let e=this.start;e[1]=H(e[1],t),this.dirtyStart=!0},n.start=function(t,e){this.setDeltaCoordinateHelper("start",t,e),this.dirtyStart=!0},e.handleX=function(){return this.currentHandle[0]},e.handleY=function(){return this.currentHandle[1]},e.handle=function(){return[].concat(this.currentHandle)},s.handleX=function(t){null!=t&&(this.handle[0]=t,this.dirtyHandle=!0)},s.handleY=function(t){null!=t&&(this.handle[1]=t,this.dirtyHandle=!0)},s.handle=function(t,e){this.setCoordinateHelper("handle",t,e),this.dirtyHandle=!0},n.handleX=function(t){let e=this.handle;e[0]=H(e[0],t),this.dirtyHandle=!0},n.handleY=function(t){let e=this.handle;e[1]=H(e[1],t),this.dirtyHandle=!0},n.handle=function(t,e){this.setDeltaCoordinateHelper("handle",t,e),this.dirtyHandle=!0},e.offsetX=function(){return this.currentOffset[0]},e.offsetY=function(){return this.currentOffset[1]},e.offset=function(){return[].concat(this.currentOffset)},s.offsetX=function(t){null!=t&&(this.offset[0]=t,this.dirtyOffset=!0)},s.offsetY=function(t){null!=t&&(this.offset[1]=t,this.dirtyOffset=!0)},s.offset=function(t,e){this.setCoordinateHelper("offset",t,e),this.dirtyOffset=!0},n.offsetX=function(t){let e=this.offset;e[0]=H(e[0],t),this.dirtyOffset=!0},n.offsetY=function(t){let e=this.offset;e[1]=H(e[1],t),this.dirtyOffset=!0},n.offset=function(t,e){this.setDeltaCoordinateHelper("offset",t,e),this.dirtyOffset=!0},e.width=function(){return this.currentDimensions[0]},e.height=function(){return this.currentDimensions[1]},e.dimensions=function(){return[].concat(this.currentDimensions)},s.width=function(t){null!=t&&(this.dimensions[0]=t,this.dirtyDimensions=!0)},s.height=function(t){null!=t&&(this.dimensions[1]=t,this.dirtyDimensions=!0)},s.dimensions=function(t,e){this.setCoordinateHelper("dimensions",t,e),this.dirtyDimensions=!0},n.width=function(t){let e=this.dimensions;e[0]=H(e[0],t),this.dirtyDimensions=!0},n.height=function(t){let e=this.dimensions;e[1]=H(e[1],t),this.dirtyDimensions=!0},n.dimensions=function(t,e){this.setDeltaCoordinateHelper("dimensions",t,e),this.dirtyDimensions=!0},s.particle=function(t){Y(t)&&!t?(this.particle=null,"particle"===this.lockTo[0]&&(this.lockTo[0]="start"),"particle"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0):(this.particle=t,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)},s.lockTo=function(t){Array.isArray(t)?(this.lockTo[0]=t[0],this.lockTo[1]=t[1]):(this.lockTo[0]=t,this.lockTo[1]=t),this.dirtyLock=!0,this.dirtyStampPositions=!0},s.lockXTo=function(t){this.lockTo[0]=t,this.dirtyLock=!0,this.dirtyStampPositions=!0},s.lockYTo=function(t){this.lockTo[1]=t,this.dirtyLock=!0,this.dirtyStampPositions=!0},e.roll=function(){return this.currentRotation},s.roll=function(t){this.roll=t,this.dirtyRotation=!0},n.roll=function(t){this.roll+=t,this.dirtyRotation=!0},e.scale=function(){return this.currentScale},s.scale=function(t){this.scale=t,this.dirtyScale=!0},n.scale=function(t){this.scale+=t,this.dirtyScale=!0},s.host=function(t){if(t){let e=i[t];e&&e.here?this.host=e.name:this.host=t}else this.host="";this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyStart=!0,this.dirtyOffset=!0},s.group=function(t){let e;t&&(this.group&&"Group"===this.group.type&&this.group.removeArtefacts(this.name),t.substring?(e=u[t],this.group=e||t):this.group=t),this.group&&"Group"===this.group.type&&this.group.addArtefacts(this.name)},t.purgeArtefact=function(t){return t.substring&&(t="all"===t?["pivot","mimic","path","filter"]:[t]),Array.isArray(t)&&t.forEach(t=>function(t,e){switch(e){case"pivot":delete t.pivot,delete t.pivotCorner,delete t.pivotPin,delete t.addPivotHandle,delete t.addPivotOffset,delete t.addPivotRotation;break;case"mimic":delete t.mimic,delete t.useMimicDimensions,delete t.useMimicScale,delete t.useMimicStart,delete t.useMimicHandle,delete t.useMimicOffset,delete t.useMimicRotation,delete t.useMimicFlip,delete t.addOwnDimensionsToMimic,delete t.addOwnScaleToMimic,delete t.addOwnStartToMimic,delete t.addOwnHandleToMimic,delete t.addOwnOffsetToMimic,delete t.addOwnRotationToMimic;break;case"path":delete t.path,delete t.pathPosition,delete t.addPathHandle,delete t.addPathOffset,delete t.addPathRotation,delete t.constantPathSpeed;break;case"filter":delete t.filter,delete t.filters,delete t.isStencil}}(this,t)),this},t.initializePositions=function(){this.dimensions=He(),this.start=He(),this.handle=He(),this.offset=He(),this.currentDimensions=He(),this.currentStart=He(),this.currentHandle=He(),this.currentOffset=He(),this.currentDragOffset=He(),this.currentDragCache=He(),this.currentStartCache=He(),this.currentStampPosition=He(),this.currentStampHandlePosition=He(),this.delta={},this.lockTo=["start","start"],this.pivoted=[],this.mimicked=[],this.dirtyScale=!0,this.dirtyDimensions=!0,this.dirtyLock=!0,this.dirtyStart=!0,this.dirtyOffset=!0,this.dirtyHandle=!0,this.dirtyRotation=!0,this.isBeingDragged=!1,this.initializeDomPositions()},t.initializeDomPositions=B,t.setCoordinateHelper=function(t,e,i){let s=this[t];Array.isArray(e)?(s[0]=e[0],s[1]=e[1]):U(e)?it(e.x,e.y)?(s[0]=et(e.x,s[0]),s[1]=et(e.y,s[1])):(s[0]=et(e.width,e.w,s[0]),s[1]=et(e.height,e.h,s[1])):(s[0]=e,s[1]=i)},t.setDeltaCoordinateHelper=function(t,e,i){let s=this[t],n=s[0],r=s[1];Array.isArray(e)?(s[0]=H(n,e[0]),s[1]=H(r,e[1])):U(e)?it(e.x,e.y)?(s[0]=H(n,et(e.x,0)),s[1]=H(r,et(e.y,0))):(s[0]=H(n,et(e.width,e.w,0)),s[1]=H(r,et(e.height,e.h,0))):(s[0]=H(n,e),s[1]=H(r,i))},t.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=i[this.host];if(t)return this.currentHost=t,this.dirtyHost=!0,this.currentHost}return le},t.getHere=function(){let t=this.getHost();if(t){if(t.here&&Object.keys(t.here))return t.here;if(t.currentDimensions){let e=t.currentDimensions;if(e)return{w:e[0],h:e[1]}}}return le},t.cleanPosition=function(t,e,i){let s,n;for(let r=0;r<2;r++)s=e[r],n=i[r],s.toFixed?t[r]=s:t[r]="left"===s||"top"===s?0:"right"===s||"bottom"===s?n:"center"===s?n/2:parseFloat(s)/100*n},t.cleanScale=function(){this.dirtyScale=!1;let t,e=this.scale,i=this.mimic,s=this.currentScale;i&&this.useMimicScale?i.currentScale?(t=i.currentScale,this.addOwnScaleToMimic&&(t+=e)):(t=e,this.dirtyMimicScale=!0):t=e,this.currentScale=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,s!==this.currentScale&&(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicScale=!0)},t.cleanDimensions=function(){this.dirtyDimensions=!1;let t=this.getHost(),e=this.dimensions,i=this.currentDimensions;if(t){let s=t.currentDimensions?t.currentDimensions:[t.w,t.h],[n,r]=e,o=i[0],a=i[1];n.substring&&(n=parseFloat(n)/100*s[0]),r.substring&&(r="auto"===r?0:parseFloat(r)/100*s[1]);let l,h=this.mimic;h&&h.name&&this.useMimicDimensions&&(l=h.currentDimensions),l?(i[0]=this.addOwnDimensionsToMimic?l[0]+n:l[0],i[1]=this.addOwnDimensionsToMimic?l[1]+r:l[1]):(i[0]=n,i[1]=r),this.cleanDimensionsAdditionalActions(),this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0,o===i[0]&&a===i[1]||(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicDimensions=!0)}else this.dirtyDimensions=!0},t.cleanDimensionsAdditionalActions=B,t.cleanLock=function(){this.dirtyLock=!1,this.dirtyStart=!0,this.dirtyHandle=!0},t.cleanStart=function(){let t,e,i=this.getHost();i&&(this.dirtyStart=!1,tt(i.w,i.h)?(t=i.w,e=i.h):i.currentDimensions?[t,e]=i.currentDimensions:this.dirtyStart=!0),this.dirtyStart||(this.cleanPosition(this.currentStart,this.start,[t,e]),this.dirtyStampPositions=!0)},t.cleanOffset=function(){let t,e,i=this.getHost();i&&(this.dirtyOffset=!1,tt(i.w,i.h)?(t=i.w,e=i.h):i.currentDimensions?[t,e]=i.currentDimensions:this.dirtyOffset=!0),this.dirtyStart||(this.cleanPosition(this.currentOffset,this.offset,[t,e]),this.dirtyStampPositions=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicOffset=!0))},t.cleanHandle=function(){this.dirtyHandle=!1;let t=this.currentHandle;this.cleanPosition(t,this.handle,this.currentDimensions),this.dirtyStampHandlePositions=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicHandle=!0)},t.cleanRotation=function(){this.dirtyRotation=!1;let t,e=this.roll,i=this.currentRotation,s=this.path,n=this.mimic,r=this.pivot,o=this.lockTo;if(s&&o.indexOf("path")>=0){if(t=e,this.addPathRotation){let e=this.getPathData();e&&(t+=e.angle)}}else n&&this.useMimicRotation&&o.indexOf("mimic")>=0?J(n.currentRotation)?(t=n.currentRotation,this.addOwnRotationToMimic&&(t+=e)):this.dirtyMimicRotation=!0:(t=e,r&&this.addPivotRotation&&o.indexOf("pivot")>=0&&(J(r.currentRotation)?t+=r.currentRotation:this.dirtyPivotRotation=!0));this.currentRotation=t,t!==i&&(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicRotation=!0)},t.cleanStampPositions=function(){this.dirtyStampPositions=!1;let{currentStampPosition:t,currentStart:e,currentOffset:i,currentStartCache:s,currentDragOffset:n}=this,[r,o]=t;if(this.noPositionDependencies)t[0]=e[0],t[1]=e[1];else{let{isBeingDragged:r,lockTo:o,pivot:a,pivotCorner:l,pivotPin:h,addPivotOffset:c,path:u,addPathOffset:f,mimic:p,useMimicStart:m,useMimicOffset:g,addOwnStartToMimic:y,addOwnOffsetToMimic:b,particle:k}=this;const S={start:function(t){t.setFromArray(e).add(i)},path:function(t){w?(t.setFromVector(w),f||t.subtract(u.currentOffset)):t.setFromArray(e).add(i)},pivot:function(t){l&&a.getCornerCoordinate?t.setFromArray(a.getCornerCoordinate(l)):"Polyline"==a.type?t.setFromArray(a.getPinAt(h)):t.setFromArray(a.currentStampPosition),c||t.subtract(a.currentOffset),t.add(i)},mimic:function(t){m||g?(t.setFromArray(p.currentStampPosition),m&&y&&t.add(e),g&&b&&t.add(i),m||t.subtract(p.currentStart).add(e),g||t.subtract(p.currentOffset).add(i)):t.setFromArray(e).add(i)},particle:function(t){k.substring&&(k=d[k]),k?t.setFromVector(k.position):t.setFromArray(e).add(i)},mouse:function(t){t.setFromVector(v),r&&(s.setFromArray(t),t.add(n)),t.add(i)}};let O,P,v,w,x=Fe(),A=!1;if(x.length=0,r)x.push("mouse","mouse"),A=!0,this.getCornerCoordinate&&this.cleanPathObject();else for(O=0;O<2;O++)P=o[O],("pivot"!==P||a)&&("path"!==P||u)&&("mimic"!==P||p)&&("particle"!==P||d)||(P="start"),"mouse"===P&&(A=!0),x.push(P);A&&(v=this.getHere()),x.indexOf("path")>=0&&(w=this.getPathData());let[C,D]=x,R=Fe(),E=Fe();S[C](R),C==D?E.setFromArray(R):S[D](E),t[0]=R[0],t[1]=E[1],Te(x)}r===t[0]&&o===t[1]||(this.dirtyPositionSubscribers=!0)},t.cleanStampHandlePositions=function(){this.dirtyStampHandlePositions=!1;let t=this.currentStampHandlePosition,e=this.currentHandle,i=t[0],s=t[1];if(this.noPositionDependencies)t[0]=e[0],t[1]=e[1];else{let i,s,n,r=this.lockTo,o=this.pivot,a=this.path,l=this.mimic;for(s=0;s<2;s++){switch(i=r[s],"pivot"!==i||o||(i="start"),"path"!==i||a||(i="start"),"mimic"!==i||l||(i="start"),n=e[s],i){case"pivot":this.addPivotHandle&&(n+=o.currentHandle[s]);break;case"path":this.addPathHandle&&(n+=a.currentHandle[s]);break;case"mimic":this.useMimicHandle&&(n=l.currentHandle[s],this.addOwnHandleToMimic&&(n+=e[s]))}t[s]=n}}this.cleanStampHandlePositionsAdditionalActions(),i===t[0]&&s===t[1]||(this.dirtyPositionSubscribers=!0)},t.cleanStampHandlePositionsAdditionalActions=B,t.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=ni(),s=!0);let n,r,o=e.engine,a=this.currentStampPosition,l=a[0],h=a[1];if(i.some(t=>{if(Array.isArray(t))n=t[0],r=t[1];else{if(!tt(t,t.x,t.y))return!1;n=t.x,r=t.y}return!(!n.toFixed||!r.toFixed||isNaN(n)||isNaN(r))&&(e.rotateDestination(o,l,h,this),o.isPointInPath(this.pathObject,n,r,this.winding))},this)){let t=this.checkHitReturn(n,r,e);return s&&ri(e),t}return s&&ri(e),!1},t.checkHitReturn=function(t,e,i){return{x:t,y:e,artefact:this}},t.pickupArtefact=function(t={}){let{x:e,y:i}=t;return tt(e,i)&&(this.isBeingDragged=!0,this.currentDragCache.set(this.currentDragOffset),"start"===this.lockTo[0]?this.currentDragOffset[0]=this.currentStart[0]-e:"pivot"===this.lockTo[0]&&this.pivot?this.currentDragOffset[0]=this.pivot.get("startX")-e:"mimic"===this.lockTo[0]&&this.mimic&&(this.currentDragOffset[0]=this.mimic.get("startX")-e),"start"===this.lockTo[1]?this.currentDragOffset[1]=this.currentStart[1]-i:"pivot"===this.lockTo[1]&&this.pivot?this.currentDragOffset[1]=this.pivot.get("startY")-i:"mimic"===this.lockTo[1]&&this.mimic&&(this.currentDragOffset[1]=this.mimic.get("startY")-i),this.bringToFrontOnDrag&&(this.order+=9999,this.group.batchResort=!0),J(this.dirtyPathObject)&&(this.dirtyPathObject=!0)),this},t.dropArtefact=function(){return this.start.set(this.currentStartCache).add(this.currentDragOffset),this.dirtyStart=!0,this.currentDragOffset.set(this.currentDragCache),this.bringToFrontOnDrag&&(this.order-=9999,this.order<0&&(this.order=0),this.group.batchResort=!0),J(this.dirtyPathObject)&&(this.dirtyPathObject=!0),this.isBeingDragged=!1,this},t.updatePositionSubscribers=function(){this.dirtyPositionSubscribers=!1,this.pivoted&&this.pivoted.length&&this.updatePivotSubscribers(),this.mimicked&&this.mimicked.length&&this.updateMimicSubscribers(),this.pathed&&this.pathed.length&&this.updatePathSubscribers()},t.updatePivotSubscribers=B,t.updateMimicSubscribers=B,t.updatePathSubscribers=B,t.updateImageSubscribers=B,t}function ze(t={}){let e={delta:null,noDeltaUpdates:!1};t.defs=_(t.defs,e),_(t,e);let i=t.setters;t.deltaSetters;return i.delta=function(t={}){t&&(this.delta=Z(this.delta,t))},t.updateByDelta=function(){return"kaliedoscope-clock-background"==this.name&&console.log(this.name,"updateByDelta"),this.setDelta(this.delta),this},t.reverseByDelta=function(){let t={};return Object.entries(this.delta).forEach(([e,i])=>{i=i.substring?-parseFloat(i)+"%":-i,t[e]=i}),this.setDelta(t),this},t.setDeltaValues=function(t={}){let e,i,s=this.delta;return Object.entries(t).forEach(([t,n])=>{if(J(s[t]))switch(i=n,e=s[t],i){case"reverse":e.toFixed&&(s[t]=-e);break;case"zero":e.toFixed&&(s[t]=0)}}),this},t}function Ge(t={}){let e={pivot:"",pivotCorner:"",pivotPin:0,addPivotHandle:!1,addPivotOffset:!0,addPivotRotation:!1};t.defs=_(t.defs,e),_(t,e),t.packetObjects=Q(t.packetObjects,["pivot"]);t.getters;let s=t.setters;t.deltaSetters;return s.pivot=function(t){if(Y(t)&&!t)this.pivot=null,"pivot"===this.lockTo[0]&&(this.lockTo[0]="start"),"pivot"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.pivot,s=this.name,r=t.substring?i[t]:t;r||(r=n[t],r&&"Cell"!==r.type&&(r=!1)),r&&r.name&&(e&&e.name!==r.name&&K(e.pivoted,s),Q(r.pivoted,s),this.pivot=r,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},t.pivotCorners=["topLeft","topRight","bottomRight","bottomLeft"],s.pivotCorner=function(t){this.pivotCorners.indexOf(t)>=0&&(this.pivotCorner=t)},s.addPivotHandle=function(t){this.addPivotHandle=t,this.dirtyHandle=!0},s.addPivotOffset=function(t){this.addPivotOffset=t,this.dirtyOffset=!0},s.addPivotRotation=function(t){this.addPivotRotation=t,this.dirtyRotation=!0},t.updatePivotSubscribers=function(){this.pivoted.forEach(t=>{let e=i[t];e||(e=n[t],e&&"Cell"===e.type||(e=!1)),e&&(e.dirtyStart=!0,e.addPivotHandle&&(e.dirtyHandle=!0),e.addPivotOffset&&(e.dirtyOffset=!0),e.addPivotRotation&&(e.dirtyRotation=!0),"Polyline"===e.type?e.dirtyPins=!0:"Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||e.dirtyPins.push(this.name))},this)},t}function We(t={}){let e={mimic:"",useMimicDimensions:!1,useMimicScale:!1,useMimicStart:!1,useMimicHandle:!1,useMimicOffset:!1,useMimicRotation:!1,useMimicFlip:!1,addOwnDimensionsToMimic:!1,addOwnScaleToMimic:!1,addOwnStartToMimic:!1,addOwnHandleToMimic:!1,addOwnOffsetToMimic:!1,addOwnRotationToMimic:!1};t.defs=_(t.defs,e),_(t,e),t.packetObjects=Q(t.packetObjects,["mimic"]);t.getters;let s=t.setters;t.deltaSetters;return s.mimic=function(t){if(Y(t)&&!t)this.mimic=null,"mimic"===this.lockTo[0]&&(this.lockTo[0]="start"),"mimic"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.mimic,s=this.name,r=t.substring?i[t]:t;r||(r=n[t],r&&"Cell"!==r.type&&(r=!1)),r&&r.name&&(e&&e.name!==r.name&&removeItem(e.mimicked,s),Q(r.mimicked,s),this.mimic=r,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},s.useMimicDimensions=function(t){this.useMimicDimensions=t,this.dirtyDimensions=!0},s.useMimicScale=function(t){this.useMimicScale=t,this.dirtyScale=!0},s.useMimicStart=function(t){this.useMimicStart=t,this.dirtyStart=!0},s.useMimicHandle=function(t){this.useMimicHandle=t,this.dirtyHandle=!0},s.useMimicOffset=function(t){this.useMimicOffset=t,this.dirtyOffset=!0},s.useMimicRotation=function(t){this.useMimicRotation=t,this.dirtyRotation=!0},s.addOwnDimensionsToMimic=function(t){this.addOwnDimensionsToMimic=t,this.dirtyDimensions=!0},s.addOwnScaleToMimic=function(t){this.addOwnScaleToMimic=t,this.dirtyScale=!0},s.addOwnStartToMimic=function(t){this.addOwnStartToMimic=t,this.dirtyStart=!0},s.addOwnHandleToMimic=function(t){this.addOwnHandleToMimic=t,this.dirtyHandle=!0},s.addOwnOffsetToMimic=function(t){this.addOwnOffsetToMimic=t,this.dirtyOffset=!0},s.addOwnRotationToMimic=function(t){this.addOwnRotationToMimic=t,this.dirtyRotation=!0},t.updateMimicSubscribers=function(){let t=this.dirtyMimicHandle,e=this.dirtyMimicOffset,s=this.dirtyMimicRotation,r=this.dirtyMimicScale,o=this.dirtyMimicDimensions;this.mimicked.forEach(a=>{let l=i[a];l||(l=n[a],l&&"Cell"===l.type||(l=!1)),l&&(l.useMimicStart&&(l.dirtyStart=!0),t&&l.useMimicHandle&&(l.dirtyHandle=!0),e&&l.useMimicOffset&&(l.dirtyOffset=!0),s&&l.useMimicRotation&&(l.dirtyRotation=!0),r&&l.useMimicScale&&(l.dirtyScale=!0),o&&l.useMimicDimensions&&(l.dirtyDimensions=!0))}),this.dirtyMimicHandle=!1,this.dirtyMimicOffset=!1,this.dirtyMimicRotation=!1,this.dirtyMimicScale=!1,this.dirtyMimicDimensions=!1},t}function Ve(t={}){let e={path:"",pathPosition:0,addPathHandle:!1,addPathOffset:!0,addPathRotation:!1,constantPathSpeed:!1};t.defs=_(t.defs,e),_(t,e),t.packetObjects=Q(t.packetObjects,["path"]);let s=t.setters,n=t.deltaSetters;return s.path=function(t){if(Y(t)&&!t)this.path=null,"path"===this.lockTo[0]&&(this.lockTo[0]="start"),"path"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.path,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&K(e.pathed,n),Q(s.pathed,n),this.path=s,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}},s.pathPosition=function(t){t<0&&(t=Math.abs(t)),t>1&&(t%=1),this.pathPosition=parseFloat(t.toFixed(6)),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0,this.currentPathData=!1},n.pathPosition=function(t){let e=this.pathPosition+t;e<0&&(e+=1),e>1&&(e%=1),this.pathPosition=parseFloat(e.toFixed(6)),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0,this.currentPathData=!1},s.addPathHandle=function(t){this.addPathHandle=t,this.dirtyHandle=!0},s.addPathOffset=function(t){this.addPathOffset=t,this.dirtyOffset=!0},s.addPathRotation=function(t){this.addPathRotation=t,this.dirtyRotation=!0},t.getPathData=function(){if(this.currentPathData)return this.currentPathData;let t,e=this.pathPosition,i=this.path;return!!i&&(t=i.getPathPositionData(e,this.constantPathSpeed),this.addPathRotation&&(this.dirtyRotation=!0),this.currentPathData=t,t)},t}O.ImageAsset=ImageAsset;const Anchor=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.build(),this};let Ue=Anchor.prototype=Object.create(Object.prototype);Ue.type="Anchor",Ue.lib="anchor",Ue.isArtefact=!1,Ue.isAsset=!1,Ue=ee(Ue);Ue.defs=_(Ue.defs,{host:null,description:"",download:"",href:"",hreflang:"",ping:"",referrerpolicy:"",rel:"noreferrer",target:"_blank",anchorType:"",clickAction:null,focusAction:!1,blurAction:!1}),Ue.packetExclusions=Q(Ue.packetExclusions,["domElement"]),Ue.packetObjects=Q(Ue.packetExclusions,["host"]),Ue.packetFunctions=Q(Ue.packetFunctions,["clickAction"]),Ue.demolish=function(){this.domElement&&this.hold&&this.hold.removeChild(this.domElement),this.deregister()};let qe=Ue.setters;qe.host=function(t){let e=t.substring?i[t]:t;e&&e.name&&(this.host=e)},qe.hold=function(t){G(t)&&(this.domElement&&this.hold&&this.hold.removeChild(this.domElement),this.hold=t,this.domElement&&this.hold.appendChild(this.domElement))},qe.download=function(t){this.download=t,this.domElement&&this.update("download")},qe.href=function(t){this.href=t,this.domElement&&this.update("href")},qe.hreflang=function(t){this.hreflang=t,this.domElement&&this.update("hreflang")},qe.ping=function(t){this.ping=t,this.domElement&&this.update("ping")},qe.referrerpolicy=function(t){this.referrerpolicy=t,this.domElement&&this.update("referrerpolicy")},qe.rel=function(t){this.rel=t,this.domElement&&this.update("rel")},qe.target=function(t){this.target=t,this.domElement&&this.update("target")},qe.anchorType=function(t){this.anchorType=t,this.domElement&&this.update("type")},qe.description=function(t){this.description=t,this.domElement&&(this.domElement.textContent=t)},qe.clickAction=function(t){W(t)&&(this.clickAction=t,this.domElement&&this.domElement.setAttribute("onclick",t()))},Ue.build=function(){this.domElement&&this.hold&&this.hold.removeChild(this.domElement);let t=document.createElement("a");t.id=this.name,this.download&&t.setAttribute("download",this.download),this.href&&t.setAttribute("href",this.href),this.hreflang&&t.setAttribute("hreflang",this.hreflang),this.ping&&t.setAttribute("ping",this.ping),this.referrerpolicy&&t.setAttribute("referrerpolicy",this.referrerpolicy),this.rel&&t.setAttribute("rel",this.rel),this.target&&t.setAttribute("target",this.target),this.anchorType&&t.setAttribute("type",this.anchorType),this.clickAction&&W(this.clickAction)&&t.setAttribute("onclick",this.clickAction()),this.description&&(t.textContent=this.description),this.focusAction&&t.addEventListener("focus",t=>this.host.onEnter(),!1),this.blurAction&&t.addEventListener("blur",t=>this.host.onLeave(),!1),this.domElement=t,this.hold&&this.hold.appendChild(t)},Ue.update=function(t){this.domElement&&this.domElement.setAttribute(t,this[t])},Ue.click=function(){if(this.hasBeenRecentlyClicked)return!1;{let t=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0});this.hasBeenRecentlyClicked=!0;let e=this;return setTimeout(()=>e.hasBeenRecentlyClicked=!1,200),this.domElement.dispatchEvent(t)}};function _e(t={}){t.defs=_(t.defs,{anchor:null}),t.demolishAnchor=function(){this.anchor&&this.anchor.demolish()};let e=t.getters,i=t.setters;t.deltaSetters;return e.anchorDescription=function(){return this.anchor?this.anchor.get("description"):""},i.anchorDescription=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.description(t)},e.anchorType=function(){return this.anchor?this.anchor.get("type"):""},i.anchorType=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.anchorType(t)},e.anchorTarget=function(){return this.anchor?this.anchor.get("target"):""},i.anchorTarget=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.target(t)},e.anchorRel=function(){return this.anchor?this.anchor.get("rel"):""},i.anchorRel=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.rel(t)},e.anchorReferrerPolicy=function(){return this.anchor?this.anchor.get("referrerpolicy"):""},i.anchorReferrerPolicy=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.referrerpolicy(t)},e.anchorPing=function(){return this.anchor?this.anchor.get("ping"):""},i.anchorPing=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.ping(t)},e.anchorHreflang=function(){return this.anchor?this.anchor.get("hreflang"):""},i.anchorHreflang=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.hreflang(t)},e.anchorHref=function(){return this.anchor?this.anchor.get("href"):""},i.anchorHref=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.href(t)},e.anchorDownload=function(){return this.anchor?this.anchor.get("download"):""},i.anchorDownload=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.download(t)},i.anchorFocusAction=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.focusAction(t)},i.anchorBlurAction=function(t){this.anchor||this.buildAnchor(),this.anchor&&this.anchor.setters.blurAction(t)},i.anchor=function(t={}){this.anchor?this.anchor.set(t):this.buildAnchor(t)},t.buildAnchor=function(t={}){this.anchor&&this.anchor.demolish(),t.name||(t.name=this.name+"-anchor"),t.description||(t.description=`Anchor link for ${this.name} ${this.type}`),t.host=this,t.hold=this.getAnchorHold(),this.anchor=function(t){return new Anchor(t)}(t)},t.getAnchorHold=function(){let t=this.currentHost;if(t){if("Canvas"===t.type)return t.navigation;if("Cell"===t.type){let e=t.currentHost?t.currentHost:o[t.host];if(e&&"Canvas"===e.type)return e.navigation}}return this.dirtyAnchorHold=!0,ss},t.rebuildAnchor=function(){this.anchor&&this.anchor.build()},t.clickAnchor=function(){this.anchor&&this.anchor.click()},t}function Ze(t={}){t.defs=_(t.defs,{groups:null,groupBuckets:null,batchResort:!0});let e=t.getters,i=t.setters;return e.groups=function(){return[].concat(this.groups)},i.groups=function(t){this.groups=[],this.addGroups(t)},t.sortGroups=function(t=!1){if(this.batchResort){this.batchResort=!1;let t,e,i=Math.floor,s=this.groups,n=[];s.forEach(s=>{t=u[s],e=t?i(t.order):0,n[e]||(n[e]=[]),n[e].push(t)}),this.groupBuckets=n.reduce((t,e)=>t.concat(e),[])}},t.initializeCascade=function(){this.groups=[],this.groupBuckets=[]},t.addGroups=function(...t){return t.forEach(t=>{t&&t.substring?Q(this.groups,t):u[t]&&Q(this.groups,t.name)},this),this.batchResort=!0,this},t.removeGroups=function(...t){return t.forEach(t=>{t&&t.substring?K(this.groups,t):u[t]&&K(this.groups,t.name)},this),this.batchResort=!0,this},t.cascadeAction=function(t,e){let i;return console.log("cascadeAction"),this.groups.forEach(s=>{i=u[s],i&&i[e](t)},this),this},t.updateArtefacts=function(t){return this.cascadeAction(t,"updateArtefacts"),this},t.setArtefacts=function(t){return this.cascadeAction(t,"setArtefacts"),this},t.addArtefactClasses=function(t){return this.cascadeAction(t,"addArtefactClasses"),this},t.removeArtefactClasses=function(t){return this.cascadeAction(t,"removeArtefactClasses"),this},t.updateByDelta=function(){return this.cascadeAction(!1,"updateByDelta"),this},t.reverseByDelta=function(){return this.cascadeAction(!1,"reverseByDelta"),this},t.getArtefactAt=function(t){if(t=et(t,this.here,!1)){let e,i;for(let s=this.groups.length-1;s>=0;s--)if(e=u[this.groups[s]],e&&(i=e.getArtefactAt(t),i))return i}return!1},t.getAllArtefactsAt=function(t){if(t=et(t,this.here,!1)){let e,i,s=[];for(let n=this.groups.length-1;n>=0;n--)e=u[this.groups[n]],e&&(i=e.getAllArtefactsAt(t),i&&(s=s.concat(i)));return s}return[]},t}function Qe(t={}){t.defs=_(t.defs,{repeat:"repeat",patternMatrix:null});let e=t.setters;return t.repeatValues=["repeat","repeat-x","repeat-y","no-repeat"],e.repeat=function(t){this.repeatValues.indexOf(t)>=0?this.repeat=t:this.repeat=this.defs.repeat},t.matrixNumberPosCheck=["a","b","c","d","e","f"],t.updateMatrixNumber=function(t,e){this.patternMatrix||(this.patternMatrix=new DOMMatrix),t=t.substring?parseFloat(t):t;let i=this.matrixNumberPosCheck.indexOf(e);V(t)&&i>=0&&(this.patternMatrix[e]=t)},e.matrixA=function(t){this.updateMatrixNumber(t,"a")},e.matrixB=function(t){this.updateMatrixNumber(t,"b")},e.matrixC=function(t){this.updateMatrixNumber(t,"c")},e.matrixD=function(t){this.updateMatrixNumber(t,"d")},e.matrixE=function(t){this.updateMatrixNumber(t,"e")},e.matrixF=function(t){this.updateMatrixNumber(t,"f")},e.patternMatrix=function(t){if(Array.isArray(t)){let e=this.updateMatrixNumber;e(t[0],"a"),e(t[1],"b"),e(t[2],"c"),e(t[3],"d"),e(t[4],"e"),e(t[5],"f")}},t.buildStyle=function(t={}){if(t){t.substring&&(t=a[t]);let e=this.source,i=this.sourceLoaded,s=this.repeat,n=t.engine;if("Cell"!==this.type&&"Noise"!==this.type||(e=this.element,i=!0),n&&i){let t=n.createPattern(e,s);return t.setTransform(this.patternMatrix),t}}return"rgba(0,0,0,0)"},t}function Ke(t={}){return t.defs=_(t.defs,{filters:null,isStencil:!1}),t.setters.filters=function(t){Array.isArray(this.filters)||(this.filters=[]),t&&(Array.isArray(t)?(this.filters=t,this.dirtyFilters=!0,this.dirtyImageSubscribers=!0):t.substring&&(Q(this.filters,t),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0))},t.cleanFilters=function(){this.dirtyFilters=!1,this.filters||(this.filters=[]);let t,e,i=this.filters,s=Math.floor,n=[];i.forEach(i=>{t=c[i],t&&(e=s(t.order)||0,n[e]||(n[e]=[]),n[e].push(t))}),this.currentFilters=n.reduce((t,e)=>t.concat(e),[])},t.addFilters=function(...t){return Array.isArray(this.filters)||(this.filters=[]),t.forEach(t=>{t&&"Filter"===t.type&&(t=t.name),Q(this.filters,t)},this),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t.removeFilters=function(...t){return Array.isArray(this.filters)||(this.filters=[]),t.forEach(t=>{t&&"Filter"===t.type&&(t=t.name),K(this.filters,t)},this),this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t.clearFilters=function(){return Array.isArray(this.filters)||(this.filters=[]),this.filters.length=0,this.dirtyFilters=!0,this.dirtyImageSubscribers=!0,this},t.preprocessFilters=function(t){t.forEach(t=>{t.actions.forEach(t=>{if("process-image"==t.action){let e=!0,i=n[t.asset];if(i){"Noise"===i.type&&i.checkSource();let s=i.sourceNaturalWidth||i.sourceNaturalDimensions[0]||i.currentDimensions[0],n=i.sourceNaturalHeight||i.sourceNaturalDimensions[1]||i.currentDimensions[1];if(s&&n){e=!1;let r=t.copyX||0,o=t.copyY||0,a=t.copyWidth||1,l=t.copyHeight||1,h=t.width||1,c=t.height||1;r.substring&&(r=parseFloat(r)/100*s),o.substring&&(o=parseFloat(o)/100*n),a.substring&&(a=parseFloat(a)/100*s),l.substring&&(l=parseFloat(l)/100*n),r=Math.abs(r),o=Math.abs(o),a=Math.abs(a),l=Math.abs(l),r>s&&(r=s-2,a=1),o>n&&(o=n-2,l=1),a>s&&(a=s-1,r=0),l>n&&(l=n-1,o=0),r+a>s&&(r=s-a-1),o+l>n&&(o=n-l-1);let u=ni(),d=u.engine,f=u.element;f.width=h,f.height=c,d.setTransform(1,0,0,1,0,0),d.globalCompositeOperation="source-over",d.globalAlpha=1;let p=i.source||i.element;d.drawImage(p,r,o,a,l,0,0,h,c),t.assetData=d.getImageData(0,0,h,c),ri(u)}}e&&(t.assetData={width:1,height:1,data:[0,0,0,0]})}})})},t}O.Anchor=Anchor;const Cell=function(t={}){if(this.makeName(t.name),t.isPool||this.register(),this.initializePositions(),this.initializeCascade(),!z(t.element)){let e=document.createElement("canvas");e.id=this.name,e.width=300,e.height=150,t.element=e}return this.installElement(t.element),t.isPool?this.set(this.poolDefs):this.set(this.defs),this.set(t),this.state.setStateFromEngine(this.engine),t.isPool||ci({name:this.name,host:this.name}),this.subscribers=[],this.sourceNaturalDimensions=He(),this.sourceLoaded=!0,this.here={},this};let Je=Cell.prototype=Object.create(Object.prototype);Je.type="Cell",Je.lib="cell",Je.isArtefact=!1,Je.isAsset=!0,Je=ee(Je),Je=Me(Je),Je=Ye(Je),Je=ze(Je),Je=Ge(Je),Je=We(Je),Je=Ve(Je),Je=_e(Je),Je=Ze(Je),Je=Qe(Je),Je=Ke(Je);Je.defs=_(Je.defs,{cleared:!0,compiled:!0,shown:!0,compileOrder:0,showOrder:0,backgroundColor:"",clearAlpha:0,alpha:1,composite:"source-over",scale:1,flipReverse:!1,flipUpend:!1,filter:"none",isBase:!1,controller:null}),delete Je.defs.source,delete Je.defs.sourceLoaded,Je.stringifyFunction=B,Je.processPacketOut=B,Je.finalizePacketOut=B,Je.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},Je.clone=$,Je.factoryKill=function(){let t=this.name;Object.entries(o).forEach(([e,i])=>{i.cells.indexOf(t)>=0&&i.removeCell(t),i.base&&i.base.name===t&&i.set({visibility:!1})}),Object.entries(i).forEach(([e,i])=>{if(i.name!==t){let e=i.state;if(e){let i=e.fillStyle,s=e.strokeStyle;i.name&&i.name===t&&(e.fillStyle=e.defs.fillStyle),s.name&&s.name===t&&(e.strokeStyle=e.defs.strokeStyle)}}}),u[t]&&u[t].kill()};let ti=Je.getters,ei=Je.setters,ii=Je.deltaSetters;Je.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},ti.width=function(){return this.currentDimensions[0]||this.element.getAttribute("width")},ei.width=function(t){this.dimensions[0]=t,this.dirtyDimensions=!0},ti.height=function(){return this.currentDimensions[1]||this.element.getAttribute("height")},ei.height=function(t){this.dimensions[1]=t,this.dirtyDimensions=!0},ei.source=function(){},ei.engine=function(t){},ei.state=function(t){},ei.element=function(t){z(t)&&this.installElement(t)},ei.cleared=function(t){this.cleared=t,this.updateControllerCells()},ei.compiled=function(t){this.compiled=t,this.updateControllerCells()},ei.shown=function(t){this.shown=t,this.updateControllerCells()},ei.compileOrder=function(t){this.compileOrder=t,this.updateControllerCells()},ei.showOrder=function(t){this.showOrder=t,this.updateControllerCells()},ei.stashX=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]),this.stashCoordinates[0]=t},ei.stashY=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]),this.stashCoordinates[1]=t},ei.stashWidth=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}this.stashDimensions[0]=t},ei.stashHeight=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}this.stashDimensions[1]=t},ii.stashX=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]);let e=this.stashCoordinates;e[0]=addStrings(e[0],t)},ii.stashY=function(t){this.stashCoordinates||(this.stashCoordinates=[0,0]);let e=this.stashCoordinates;e[1]=addStrings(e[1],t)},ii.stashWidth=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}let e=this.stashDimensions;e[0]=addStrings(e[0],t)},ii.stashHeight=function(t){if(!this.stashDimensions){let t=this.currentDimensions;this.stashDimensions=[t[0],t[1]]}let e=this.stashDimensions;e[1]=addStrings(e[1],t)},ei.clearAlpha=function(t){t.toFixed&&(t>1?t=1:t<0&&(t=0),this.clearAlpha=t)},ii.clearAlpha=function(t){t.toFixed&&((t+=this.clearAlpha)>1?t=1:t<0&&(t=0),this.clearAlpha=t)},Je.checkSource=function(t,e){this.currentDimensions[0]===t&&this.currentDimensions[1]===e||this.notifySubscribers()},Je.getData=function(t,e){return this.checkSource(this.sourceNaturalDimensions[0],this.sourceNaturalDimensions[1]),this.buildStyle(e)},Je.updateArtefacts=function(t={}){this.groupBuckets.forEach(e=>{e.artefactBuckets.forEach(e=>{t.dirtyScale&&(e.dirtyScale=!0),t.dirtyDimensions&&(e.dirtyDimensions=!0),t.dirtyLock&&(e.dirtyLock=!0),t.dirtyStart&&(e.dirtyStart=!0),t.dirtyOffset&&(e.dirtyOffset=!0),t.dirtyHandle&&(e.dirtyHandle=!0),t.dirtyRotation&&(e.dirtyRotation=!0),t.dirtyPathObject&&(e.dirtyPathObject=!0)})})},Je.cleanDimensionsAdditionalActions=function(){let t=this.element;if(t){let e=this.controller,i=this.currentDimensions,s=this.isBase;if(s&&e&&e.isComponent){let t=this.controller.currentDimensions,e=this.dimensions;e[0]=i[0]=t[0],e[1]=i[1]=t[1]}let[n,r]=i;t.width=n,t.height=r,this.setEngineFromState(this.engine),s&&e&&e.updateBaseHere(),this.groupBuckets&&this.updateArtefacts({dirtyDimensions:!0})}},Je.notifySubscriber=function(t){t.sourceNaturalDimensions||(t.sourceNaturalDimensions=[]),t.sourceNaturalWidth=this.currentDimensions[0],t.sourceNaturalHeight=this.currentDimensions[1],t.sourceLoaded=!0,t.dirtyImage=!0,t.dirtyCopyStart=!0,t.dirtyCopyDimensions=!0},Je.subscribeAction=function(t={}){this.subscribers.push(t),t.asset=this,t.source=this.element,this.notifySubscriber(t)},Je.installElement=function(t){return this.element=t,this.engine=this.element.getContext("2d"),this.state=De({engine:this.engine}),this},Je.updateControllerCells=function(){this.controller&&(this.controller.dirtyCells=!0)},Je.setEngineFromState=function(t){let e=this.state;return e.allKeys.forEach(i=>{"lineDash"===i?(t.lineDash=e.lineDash,t.setLineDash(t.lineDash)):t[i]=e[i]},e),t.textAlign=e.textAlign,t.textBaseline=e.textBaseline,this},Je.setToDefaults=function(){let t=this.state.defs,e=this.state,i=this.engine,s=Array.isArray;return Object.entries(t).forEach(([t,n])=>{"lineDash"===t?(s(i.lineDash)?i.lineDash.length=0:i.lineDash=[],s(e.lineDash)?e.lineDash.length=0:e.lineDash=[]):(i[t]=n,e[t]=n)}),i.textAlign=e.textAlign="left",i.textBaseline=e.textBaseline="top",this},Je.stylesArray=["Gradient","RadialGradient","Pattern"],Je.setEngine=function(t){let e,i,s=this.state,n=t.state.getChanges(t,s),r=this.setEngineActions,o=this.stylesArray;if(Object.keys(n).length)for(i in e=this.engine,n)r[i](n[i],e,o,t,this),s[i]=n[i];return t},Je.setEngineActions={fillStyle:function(t,e,i,s,n){if(t.substring){let i=!1;k.indexOf(t)>=0?i=b[t]:l.indexOf(t)>=0&&(i=a[t]),i?(s.state.fillStyle=i,e.fillStyle=i.getData(s,n)):e.fillStyle=t}else e.fillStyle=t.getData(s,n)},filter:function(t,e){e.filter=t},font:function(t,e){e.font=t},globalAlpha:function(t,e){e.globalAlpha=t},globalCompositeOperation:function(t,e){e.globalCompositeOperation=t},lineCap:function(t,e){e.lineCap=t},lineDash:function(t,e){e.lineDash=t,e.setLineDash&&e.setLineDash(t)},lineDashOffset:function(t,e){e.lineDashOffset=t},lineJoin:function(t,e){e.lineJoin=t},lineWidth:function(t,e){e.lineWidth=t},miterLimit:function(t,e){e.miterLimit=t},shadowBlur:function(t,e){e.shadowBlur=t},shadowColor:function(t,e){e.shadowColor=t},shadowOffsetX:function(t,e){e.shadowOffsetX=t},shadowOffsetY:function(t,e){e.shadowOffsetY=t},strokeStyle:function(t,e,i,s,n){if(t.substring){let i=!1;k.indexOf(t)>=0?i=b[t]:l.indexOf(t)>=0&&(i=a[t]),i?(s.state.strokeStyle=i,e.strokeStyle=i.getData(s,n)):e.strokeStyle=t}else e.strokeStyle=t.getData(s,n)}},Je.clearShadow=function(){return this.engine.shadowOffsetX=0,this.engine.shadowOffsetY=0,this.engine.shadowBlur=0,this.state.shadowOffsetX=0,this.state.shadowOffsetY=0,this.state.shadowBlur=0,this},Je.restoreShadow=function(t){let e=t.state;return this.engine.shadowOffsetX=e.shadowOffsetX,this.engine.shadowOffsetY=e.shadowOffsetY,this.engine.shadowBlur=e.shadowBlur,this.state.shadowOffsetX=e.shadowOffsetX,this.state.shadowOffsetY=e.shadowOffsetY,this.state.shadowBlur=e.shadowBlur,this},Je.setToClearShape=function(){return this.engine.fillStyle="rgba(0,0,0,0)",this.engine.strokeStyle="rgba(0,0,0,0)",this.engine.shadowColor="rgba(0,0,0,0)",this.state.fillStyle="rgba(0,0,0,0)",this.state.strokeStyle="rgba(0,0,0,0)",this.state.shadowColor="rgba(0,0,0,0)",this},Je.saveEngine=function(){return this.engine.save(),this},Je.restoreEngine=function(){return this.engine.restore(),this},Je.getComputedFontSizes=function(){let t=this.getHost();if(t&&t.domElement){let e=window.getComputedStyle(t.domElement),i=window.getComputedStyle(document.documentElement);return[parseFloat(e.fontSize),parseFloat(i.fontSize),window.innerWidth,window.innerHeight]}return!1},Je.clear=function(){let t=this;return new Promise(e=>{const{element:i,engine:s,backgroundColor:n,clearAlpha:r,currentDimensions:o}=t,[a,l]=o;if(t.prepareStamp(),s.setTransform(1,0,0,1,0,0),n){let t=s.fillStyle,e=s.globalCompositeOperation,i=s.globalAlpha;s.fillStyle=n,s.globalCompositeOperation="source-over",s.globalAlpha=1,s.fillRect(0,0,a,l),s.fillStyle=t,s.globalCompositeOperation=e,s.globalAlpha=i}else if(r){let t=ni(),{engine:e,element:i}=t;i.width=a,i.height=l;let n=s.getImageData(0,0,a,l);e.putImageData(n,0,0);let o=s.globalAlpha;s.clearRect(0,0,a,l),s.globalAlpha=r,s.drawImage(i,0,0),s.globalAlpha=o}else s.clearRect(0,0,a,l);e(!0)})},Je.compile=function(){this.stashOutput;this.sortGroups(),this.prepareStamp(),!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let t=this,e=i=>new Promise((s,n)=>{let r=t.groupBuckets[i];r&&r.stamp?r.stamp().then(t=>{e(i+1).then(t=>{s(!0)}).catch(t=>n(t))}).catch(t=>n(t)):!t.noFilters&&t.filters&&t.filters.length?t.applyFilters().then(e=>t.stashOutputAction()).then(t=>s(!0)).catch(t=>n(t)):t.stashOutputAction().then(t=>s(!0)).catch(t=>n(t))});return e(0)},Je.show=function(){var t=this;return new Promise(e=>{let i=t.getHost(),s=!(!i||!i.engine)&&i.engine;if(s){let n=Math.floor,r=i.currentDimensions,o=n(r[0]),a=n(r[1]);o&&a||e(!1);let l,h=t.currentScale,c=t.currentDimensions,u=n(c[0]),d=n(c[1]),f=t.composite,p=t.alpha,m=t.controller,g=t.element;if(s.save(),s.setTransform(1,0,0,1,0,0),s.filter=t.filter,t.isBase){let e,i;switch(t.basePaste||(t.basePaste=[]),l=t.basePaste,t.prepareStamp(),s.globalCompositeOperation="source-over",s.globalAlpha=1,s.clearRect(0,0,o,a),s.globalCompositeOperation=f,s.globalAlpha=p,m?m.fit:"none"){case"contain":e=o/(u||1),i=a/(d||1),e>i?(l[0]=n((o-u*i)/2),l[1]=0,l[2]=n(u*i),l[3]=n(d*i)):(l[0]=0,l[1]=n((a-d*e)/2),l[2]=n(u*e),l[3]=n(d*e));break;case"cover":e=o/(u||1),i=a/(d||1),e0){t.paste||(t.paste=[]),l=t.paste,t.noDeltaUpdates||t.setDelta(t.delta),t.prepareStamp(),s.globalCompositeOperation=f,s.globalAlpha=p;let e=t.currentStampHandlePosition,i=t.currentStampPosition;l[0]=n(-e[0]*h),l[1]=n(-e[1]*h),l[2]=n(u*h),l[3]=n(d*h),t.rotateDestination(s,i[0],i[1])}s.drawImage(g,0,0,u,d,...l),s.restore(),e(!0)}else e(!1)})},Je.applyFilters=function(){let t=this;return new Promise((function(e){let i,s,n=t.engine;i=n.getImageData(0,0,t.currentDimensions[0],t.currentDimensions[1]),s=ve(),t.preprocessFilters(t.currentFilters),xe(s,{image:i,filters:t.currentFilters}).then(t=>{we(s),t?(n.putImageData(t,0,0),e(!0)):e(!1)}).catch(t=>{we(s),e(!1)})}))},Je.stashOutputAction=function(){let t=this;return this.stashOutput?(this.stashOutput=!1,new Promise((e,i)=>{let[s,n]=t.currentDimensions,r=t.stashCoordinates,o=t.stashDimensions,a=r?r[0]:0,l=r?r[1]:0,h=o?o[0]:s,c=o?o[1]:n;if((h.substring||c.substring||a.substring||l.substring||a||l||h!==s||c!==n)&&(h.substring&&(h=parseFloat(h)/100*s),(isNaN(h)||h<=0)&&(h=1),h>s&&(h=s),c.substring&&(c=parseFloat(c)/100*n),(isNaN(c)||c<=0)&&(c=1),c>n&&(c=n),a.substring&&(a=parseFloat(a)/100*s),(isNaN(a)||a<0)&&(a=0),a+h>s&&(a=s-h),l.substring&&(l=parseFloat(l)/100*n),(isNaN(l)||l<0)&&(l=0),l+c>n&&(l=n-c)),t.engine.save(),t.engine.setTransform(1,0,0,1,0,0),t.stashedImageData=t.engine.getImageData(a,l,h,c),t.engine.restore(),t.stashOutputAsAsset){let e,i;if(t.stashOutputAsAsset=!1,i=ni(),e=i.element,e.width=h,e.height=c,i.engine.putImageData(t.stashedImageData,0,0),t.stashedImage)t.stashedImage.src=e.toDataURL();else{let i=t.stashedImage=document.createElement("img");i.id=t.name+"-image",i.onload=function(){is.appendChild(i),Ne("#"+i.id)},i.src=e.toDataURL()}ri(i)}e(!0)})):Promise.resolve(!1)},Je.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=n[this.host]||i[this.host];return t&&(this.currentHost=t),!!t&&this.currentHost}return!1},Je.updateBaseHere=function(t,e){if(this.isBase){this.here||(this.here={});let i=this.here,s=this.currentDimensions,n=t.active,r=t.localListener?t.originalWidth:t.w,o=t.localListener?t.originalHeight:t.h;if(s[0]!==r||s[1]!==o){this.basePaste||(this.basePaste=[]);let a,l,h=this.basePaste[0],c=s[0],u=s[1],d=r,f=o,p=t.x,m=t.y,g=c/d||1,y=u/f||1,b=Math.round;switch(i.w=c,i.h=u,e){case"contain":case"cover":h?(a=(d-c/y)/2,i.x=b((p-a)*y),i.y=b(m*y)):(l=(f-u/g)/2,i.x=b(p*g),i.y=b((m-l)*g));break;case"fill":i.x=b(p*g),i.y=b(m*y);break;case"none":default:a=(d-c)/2,l=(f-u)/2,i.x=b(p-a),i.y=b(m-l)}(i.x<0||i.x>c)&&(n=!1),(i.y<0||i.y>u)&&(n=!1),i.active=n}else i.x=t.x,i.y=t.y,i.w=r,i.h=o,i.active=n;t.baseActive=n}},Je.prepareStamp=function(){(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&(this.cleanDimensions(),this.dirtyAssetSubscribers=!0),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.dirtyAssetSubscribers&&(this.dirtyAssetSubscribers=!1,this.notifySubscribers())},Je.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=this.currentStampHandlePosition,i=this.currentScale,s=this.currentDimensions,n=-e[0]*i,r=-e[1]*i,o=s[0]*i,a=s[1]*i;t.rect(n,r,o,a)}},Je.updateHere=function(){this.here||(this.here={});let t=this.here,[e,i]=this.currentDimensions;t.w=e,t.h=i,t.x=-1e4,t.y=-1e4,t.active=!1;let s=this.currentHost;if(s){let e=s.here;if(e&&e.active){let{x:i,y:s,w:n,h:r}=e;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let o=ni(),a=o.engine,[l,h]=this.currentStampPosition;o.rotateDestination(a,l,h,this);let c=a.isPointInPath(this.pathObject,i,s);if(ri(o),t.active=c,c){let[e,n]=this.currentStampHandlePosition,{flipUpend:r,flipReverse:o,roll:a,scale:c}=this;if(c){let u=(i-l)/c,d=(s-h)/c;if(o&&(u=-u),r&&(d=-d),a){(o&&!r||!o&&r)&&(a=-a);let t=Fe(u,d);t.rotate(-a),[u,d]=t,Te(t)}u+=e,d+=n,t.x=u,t.y=d}}}}},Je.getEntityHits=function(){let t=[],e=[],i=[];return this.groupBuckets&&this.groupBuckets.forEach(t=>{t.visibility&&e.push(t.getAllArtefactsAt(this.here))},this),e.length&&(e=e.reduce((t,e)=>t.concat(e),[]),e.forEach(e=>{let s=e.artefact;s.visibility&&i.indexOf(s.name)<0&&(i.push(s.name),t.push(s))})),t},Je.rotateDestination=function(t,e,i,s){let n,r,o=s||this,a=o.mimic,l=o.pivot,h=o.currentRotation;if(a&&a.name&&o.useMimicFlip?(n=a.flipReverse?-1:1,r=a.flipUpend?-1:1):(n=o.flipReverse?-1:1,r=o.flipUpend?-1:1),a&&a.name&&o.useMimicRotation?h=a.currentRotation:l&&l.name&&o.addPivotRotation&&(h=l.currentRotation),h){h*=P;let s=Math.cos(h),o=Math.sin(h);t.setTransform(s*n,o*n,-o*r,s*r,e,i)}else t.setTransform(n,0,0,r,e,i);return this};const si=[];Je.poolDefs={element:null,engine:null,state:null,width:300,height:100,alpha:1,composite:"source-over"};const ni=function(){return si.length||si.push(oi({name:"pool_"+N(),isPool:!0})),si.shift()},ri=function(t){t&&"Cell"===t.type&&(t.engine.setTransform(1,0,0,1,0,0),si.push(t.setToDefaults()))},oi=function(t){return new Cell(t)};O.Cell=Cell;const Group=function(t={}){return this.makeName(t.name),this.register(),this.artefacts=[],this.artefactBuckets=[],this.set(this.defs),this.set(t),this};let ai=Group.prototype=Object.create(Object.prototype);ai.type="Group",ai.lib="group",ai.isArtefact=!1,ai.isAsset=!1,ai=ee(ai),ai=Ke(ai);ai.defs=_(ai.defs,{artefacts:null,order:0,visibility:!0,regionRadius:0}),ai.packetExclusions=Q(ai.packetExclusions,["artefactBuckets","batchResort"]),ai.postCloneAction=function(t,e){let s;return s=e.host?i[e.host]:this.currentHost?this.currentHost:!!this.host&&i[this.host],s&&(s.addGroups(t.name),t.host||(t.host=s.name)),t},ai.kill=function(t=!1){let e=this.name;return Object.entries(i).forEach(([t,i])=>{Array.isArray(i.groups)&&i.groups.indexOf(e)>=0&&(K(i.groups,e),i.batchResort=!0)}),Object.entries(a).forEach(([t,i])=>{Array.isArray(i.groups)&&i.groups.indexOf(e)>=0&&(K(i.groups,e),i.batchResort=!0)}),t&&this.artefactBuckets.forEach(t=>t.kill()),this.deregister()},ai.killArtefacts=function(){return this.artefactBuckets.forEach(t=>t.kill()),this};let li=ai.getters,hi=ai.setters;li.artefacts=function(){return[].concat(this.artefacts)},hi.artefacts=function(t){this.artefacts=[],this.addArtefacts(t)},hi.host=function(t){let e=this.getHost(t);e&&e.addGroups&&(this.host=t,e.addGroups(this.name),this.dirtyHost=!0)},hi.order=function(t){let e=this.getHost(this.host);this.order=t,e&&e.set({batchResort:!0})},ai.getHost=function(t){let e=this.currentHost;return!e||e.substring?i[t]||a[t]||i[e]||a[e]||null:e},ai.forceStamp=function(){var t=this;return new Promise(e=>{let i=t.visibility;t.visibility=!0,t.stamp().then(s=>{t.visibility=i,e(s)}).catch(s=>{t.visibility=i,e(s)})})},ai.stamp=function(){if(this.dirtyHost||!this.currentHost){this.dirtyHost=!1;let t=this.getHost(this.host);t?this.currentHost=t:this.dirtyHost=!0}let t=this;return new Promise((e,i)=>{if(t.visibility){let s=t.currentHost;if(s){t.sortArtefacts();let n=!!(t.stashOutput||!t.noFilters&&t.filters&&t.filters.length)&&ni();if(n&&n.element){let t=s.currentDimensions;t&&(n.element.width=t[0],n.element.height=t[1]),n.engine.save()}else s.engine&&s.engine.save();t.prepareStamp(n),t.stampAction(n).then(i=>{n?(n.engine.restore(),ri(n)):s.engine&&(s.engine.restore(),s.setEngineFromState(s.engine)),e(t.name)}).catch(t=>{n?(n.engine.restore(),ri(n)):s.engine&&(s.engine.restore(),s.setEngineFromState(s.engine)),i(t)})}else e(!1)}else e(!1)})},ai.sortArtefacts=function(){if(this.batchResort){this.batchResort=!1;let t=Math.floor,e=[];this.artefacts.forEach(s=>{let n=i[s],r=t(n.order)||0;e[r]||(e[r]=[]),e[r].push(n)}),this.artefactBuckets=e.reduce((t,e)=>t.concat(e),[])}},ai.prepareStamp=function(t){let e=this.currentHost;t&&(e=t),this.artefactBuckets.forEach(i=>{"entity"===i.lib&&(i.currentHost&&i.currentHost.name===e.name||(i.currentHost=e,t||(i.dirtyHost=!0))),i.noDeltaUpdates||i.updateByDelta(),i.prepareStamp()})},ai.stampAction=function(t){!this.currentHost||this.currentHost.stashOutput;!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let e=this,i=s=>new Promise((n,r)=>{let o=e.artefactBuckets[s];if(o&&o.stamp)o.stamp().then(()=>{i(s+1).then(t=>n(!0)).catch(t=>r(t))}).catch(t=>r(t));else if(t)if(!e.noFilters&&e.filters&&e.filters.length)e.applyFilters(t).then(t=>e.stashAction(t)).then(t=>n(!0)).catch(t=>r(t));else if(e.stashOutput){let i=t.element,s=t.engine,o=!(!e.currentHost||!e.currentHost.engine)&&e.currentHost.engine;if(o){o.save(),o.globalCompositeOperation="source-over",o.globalAlpha=1,o.setTransform(1,0,0,1,0,0),o.drawImage(i,0,0),o.restore();let t=s.getImageData(0,0,i.width,i.height);e.stashAction(t).then(t=>n(!0)).catch(t=>r(t))}else r("Could not find real engine")}else n(!0);else n(!0)});return i(0)},ai.applyFilters=function(t){let e=this;return new Promise((i,s)=>{let n=e.currentHost,r=t;n&&r||s("Group.applyFilters - no host");let o=function(){we(d),l.save(),l.setTransform(1,0,0,1,0,0),l.drawImage(h,0,0),l.restore()},a=n.element,l=n.engine,h=r.element,c=r.engine;e.isStencil&&(c.save(),c.globalCompositeOperation="source-in",c.globalAlpha=1,c.setTransform(1,0,0,1,0,0),c.drawImage(a,0,0),c.restore()),c.setTransform(1,0,0,1,0,0);let u=c.getImageData(0,0,h.width,h.height),d=ve();e.preprocessFilters(e.currentFilters),xe(d,{image:u,filters:e.currentFilters}).then(t=>{if(!t)throw new Error("image issue");c.globalCompositeOperation="source-over",c.globalAlpha=1,c.setTransform(1,0,0,1,0,0),c.putImageData(t,0,0),o(),i(t)}).catch(t=>{o(),s(t)})})},ai.stashAction=function(t){if(!t)return Promise.reject("No image data supplied to stashAction");if(this.stashOutput){this.stashOutput=!1;let e=this;return new Promise((i,s)=>{let[n,r,o,a]=e.getCellCoverage(t),l=ni(),h=l.engine,c=l.element;if(c.width=o,c.height=a,h.putImageData(t,-n,-r),e.stashedImageData=h.getImageData(0,0,o,a),e.stashOutputAsAsset)if(e.stashOutputAsAsset=!1,e.stashedImage)e.stashedImage.src=c.toDataURL();else{let t=e.stashedImage=document.createElement("img");t.id=e.name+"-groupimage",t.onload=function(){is.appendChild(t),Ne("#"+t.id)},t.src=c.toDataURL()}ri(l),i(!0)})}return Promise.resolve(!1)},ai.getCellCoverage=function(t){let e,i,s=t.width,n=t.height,r=t.data,o=0,a=0,l=s,h=n,c=3;for(i=0;ie&&(l=e),oi&&(h=i),a{t&&(t.substring?Q(this.artefacts,t):t.name&&Q(this.artefacts,t.name))},this),this.batchResort=!0,this},ai.removeArtefacts=function(...t){return t.forEach(t=>{t&&(t.substring?K(this.artefacts,t):t.name&&K(this.artefacts,t.name))},this),this.batchResort=!0,this},ai.moveArtefactsIntoGroup=function(...t){let e,s;return t.forEach(t=>{t&&(s=t.substring?i[t]:t,s&&s.isArtefact&&(e=s.group?s.group:!!s.host&&u[s.host]),e&&(e.removeArtefacts(t),e.batchResort=!0),Q(this.artefacts,t))},this),this.batchResort=!0,this},ai.clearArtefacts=function(){return this.artefacts.length=0,this.artefactBuckets.length=0,this.batchResort=!0,this},ai.updateArtefacts=function(t){return this.cascadeAction(t,"setDelta"),this},ai.setArtefacts=function(t){return this.cascadeAction(t,"set"),this},ai.updateByDelta=function(){return this.cascadeAction(!1,"updateByDelta"),this},ai.reverseByDelta=function(){return this.cascadeAction(!1,"reverseByDelta"),this},ai.addArtefactClasses=function(t){return this.cascadeAction(t,"addClasses"),this},ai.removeArtefactClasses=function(t){return this.cascadeAction(t,"removeClasses"),this},ai.cascadeAction=function(t,e){return this.artefacts.forEach(s=>{let n=i[s];n&&n[e]&&n[e](t)}),this},ai.setDeltaValues=function(t={}){return this.artefactBuckets.forEach(e=>e.setDeltaValues(t)),this},ai.addFiltersToEntitys=function(...t){return this.artefacts.forEach(e=>{let i=h[e];i&&i.addFilters&&i.addFilters(t)}),this},ai.removeFiltersFromEntitys=function(...t){return this.artefacts.forEach(e=>{let i=h[e];i&&i.removeFilters&&i.removeFilters(t)}),this},ai.clearFiltersFromEntitys=function(){return this.artefacts.forEach(t=>{let e=h[t];e&&e.clearFilters&&e.clearFilters()}),this},ai.getArtefactAt=function(t){let e=ni(),i=this.artefactBuckets;this.sortArtefacts();for(let s=i.length-1;s>=0;s--){let n=i[s];if(n){let i=n.checkHit(t,e);if(i)return ri(e),i}}return ri(e),!1},ai.getAllArtefactsAt=function(t){let e=ni(),i=this.artefactBuckets,s=[],n=[];this.sortArtefacts();for(let r=i.length-1;r>=0;r--){let o=i[r];if(o){let i=o.checkHit(t,e);if(i&&i.artefact){let t=i.artefact;s.indexOf(t.name)<0&&(s.push(t.name),n.push(i))}}}return ri(e),n},ai.getArtefactCollisions=function(t){if(!t||!t.isArtefact||!this.artefactBuckets.length)return[];if(t.substring&&(t=i[t]),!t.collides)return[];let e,s,n,r=this.artefactBuckets,o=[],[a,l]=t.cleanCollisionData();for(s=0,n=r.length;sh&&(o[s]=!1)}let g=ni();for(s=0,n=o.length;s!!t)};const ci=function(t){return new Group(t)};O.Group=Group;const Vector=function(t,e,i){return this.x=0,this.y=0,this.z=0,J(t)&&this.set(t,e,i),this};let ui=Vector.prototype=Object.create(Object.prototype);ui.type="Vector",ui.getXYCoordinate=function(){return[this.x,this.y]},ui.getXYZCoordinate=function(){return[this.x,this.y,this.z]},ui.setX=function(t){if(!J(t))throw new Error(`${this.name} Vector error - setX() arguments error: ${t}`);return this.x=t,this},ui.setY=function(t){if(!J(t))throw new Error(`${this.name} Vector error - setY() arguments error: ${t}`);return this.y=t,this},ui.setZ=function(t){if(!J(t))throw new Error(`${this.name} Vector error - setZ() arguments error: ${t}`);return this.z=t,this},ui.setXY=function(t,e){if(!tt(t,e))throw new Error(`${this.name} Vector error - setXY() arguments error: ${t}, ${e}`);return this.x=t,this.y=e,this},ui.set=function(t,e,i){return U(t)?this.setFromVector(t):Array.isArray(t)?this.setFromArray(t):tt(t,e)?this.setFromArray([t,e,i]):this},ui.setFromArray=function(t){if(!Array.isArray(t))throw new Error(`${this.name} Vector error - setFromArray() arguments error: ${t}`);let[e,i,s]=t;return V(e)&&(this.x=e),V(i)&&(this.y=i),V(s)&&(this.z=s),this},ui.setFromVector=function(t){if(!U(t))throw new Error(`${this.name} Vector error - setFromVector() arguments error: ${JSON.stringify(t)}`);let{x:e,y:i,z:s}=t;return V(e)&&(this.x=e),V(i)&&(this.y=i),V(s)&&(this.z=s),this},ui.zero=function(){return this.x=0,this.y=0,this.z=0,this},ui.vectorAdd=function(t={}){if(Array.isArray(t))return this.vectorAddArray(t);let{x:e,y:i,z:s}=t;return V(e)&&(this.x+=e),V(i)&&(this.y+=i),V(s)&&(this.z+=s),this},ui.vectorAddArray=function(t=[]){let[e,i,s]=t;return V(e)&&(this.x+=e),V(i)&&(this.y+=i),V(s)&&(this.z+=s),this},ui.vectorSubtract=function(t={}){if(Array.isArray(t))return this.vectorSubtractArray(t);let{x:e,y:i,z:s}=t;return V(e)&&(this.x-=e),V(i)&&(this.y-=i),V(s)&&(this.z-=s),this},ui.vectorSubtractArray=function(t){let[e,i,s]=t;return V(e)&&(this.x-=e),V(i)&&(this.y-=i),V(s)&&(this.z-=s),this},ui.scalarMultiply=function(t){if(!V(t))throw new Error(`${this.name} Vector error - scalarMultiply() argument not a number: ${t}`);return this.x*=t,this.y*=t,this.z*=t,this},ui.vectorMultiply=function(t={}){if(Array.isArray(t))return this.vectorMultiplyArray(t);let{x:e,y:i,z:s}=t;return V(e)&&(this.x*=e),V(i)&&(this.y*=i),V(s)&&(this.z*=s),this},ui.vectorMultiplyArray=function(t){let[e,i,s]=t;return V(e)&&(this.x*=e),V(i)&&(this.y*=i),V(s)&&(this.z*=s),this},ui.scalarDivide=function(t){if(!V(t))throw new Error(`${this.name} Vector error - scalarDivide() argument not a number: ${t}`);if(!t)throw new Error(`${this.name} Vector error - scalarDivide() division by zero: ${t}`);return this.x/=t,this.y/=t,this.z/=t,this},ui.getMagnitude=function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},ui.rotate=function(t){if(!V(t))throw new Error(`${this.name} Vector error - rotate() argument not a number: ${t}`);let e=Math.atan2(this.y,this.x);e+=.01745329251*t;let i=this.getMagnitude();return this.x=i*Math.cos(e),this.y=i*Math.sin(e),this},ui.reverse=function(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this},ui.normalize=function(){let t=this.getMagnitude();return t>0&&(this.x/=t,this.y/=t,this.z/=t),this};const di=[],fi=function(t,e,i){di.length||di.push(new Vector);let s=di.shift();return s.set(t,e,i),s},pi=function(t){t&&"Vector"===t.type&&di.push(t.zero())},mi=function(t,e,i){return new Vector(t,e,i)};O.Vector=Vector;const Quaternion=function(t={}){return this.name=t.name||"generic",this.n=t.n||1,this.v=mi(),this.set(t),this};let gi=Quaternion.prototype=Object.create(Object.prototype);gi.type="Quaternion",gi.set=function(t={}){if(q(t))return this.setFromQuaternion(t);if(t&&t.type&&"Vector"===t.type)return this.setFromVector(t);if(it(t.pitch,t.yaw,t.roll))return this.setFromEuler(t);let e,i,s,n,r,o=this.v;return r=!(!J(t.vector)&&!J(t.v))&&(t.vector||t.v),n=!(!J(t.scalar)&&!J(t.n))&&(t.scalar||t.n||0),e=r?r.x||0:t.x||!1,i=r?r.y||0:t.y||!1,s=r?r.z||0:t.z||!1,this.n=V(n)?n:this.n,o.x=V(e)?e:o.x,o.y=V(i)?i:o.y,o.z=V(s)?s:o.z,this},gi.setFromQuaternion=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - setFromQuaternion() bad argument: ${t}`);let e=this.v,i=t.v;return this.n=t.n,e.x=i.x,e.y=i.y,e.z=i.z,this},gi.setFromEuler=function(t={}){let e,i,s,n,r,o,a,l,h,c=Math.cos,u=Math.sin,d=this.v;return e=(t.pitch||t.x||0)*P,i=(t.yaw||t.y||0)*P,s=(t.roll||t.z||0)*P,n=c(e/2),r=c(i/2),o=c(s/2),a=u(e/2),l=u(i/2),h=u(s/2),d.x=a*r*o+n*l*h,d.y=n*l*o+a*r*h,d.z=n*r*h-a*l*o,this.n=n*r*o-a*l*h,this},gi.zero=function(){let t=this.v;return this.n=1,t.x=0,t.y=0,t.z=0,this},gi.getMagnitude=function(){let t=this.v;return Math.sqrt(this.n*this.n+t.x*t.x+t.y*t.y+t.z*t.z)},gi.normalize=function(){let t=this.getMagnitude(),e=this.v;if(!t)throw new Error(`${this.name} Quaternion error - normalize() division by zero: ${t}`);return this.n/=t,this.n=this.n>-1e-6&&this.n<1e-6?0:this.n,e.x/=t,e.x=e.x>-1e-6&&e.x<1e-6?0:e.x,e.y/=t,e.y=e.y>-1e-6&&e.y<1e-6?0:e.y,e.z/=t,e.z=e.z>-1e-6&&e.z<1e-6?0:e.z,this},gi.quaternionMultiply=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - quaternionMultiply() bad argument: ${t}`);let e=this.v,i=t.v,s=this.n,n=e.x,r=e.y,o=e.z,a=t.n,l=i.x,h=i.y,c=i.z;return this.n=s*a-n*l-r*h-o*c,e.x=s*l+n*a+r*c-o*h,e.y=s*h+r*a+o*l-n*c,e.z=s*c+o*a+n*h-r*l,this},gi.getAngle=function(t){let e;return t=!!J(t)&&t,e=2*Math.acos(this.n),t&&(e*=1/P),e>-1e-6&&e<1e-6?0:e},gi.quaternionRotate=function(t){if(!q(t))throw new Error(`${this.name} Quaternion error - quaternionRotate() bad argument: ${t}`);let e=bi(t),i=bi(this);return this.setFromQuaternion(e.quaternionMultiply(i)),ki(e),ki(i),this};const yi=[],bi=function(t){yi.length||yi.push(Si({name:"pool"}));let e=yi.shift();return e.set(t),e},ki=function(t){t&&"Quaternion"===t.type&&yi.push(t.zero())},Si=function(t={}){return new Quaternion(t)};function Oi(t={}){(t=_e(t=Ve(t=We(t=Ge(t=ze(t=Ye(t))))))).defs=_(t.defs,{domElement:"",pitch:0,yaw:0,offsetZ:0,css:null,classes:"",position:"absolute",checkForResize:!1,trackHere:"",activePadding:5}),t.packetExclusions=Q(t.packetExclusions,["domElement","pathCorners","rotation"]),t.packetFunctions=Q(t.packetFunctions,["onEnter","onLeave","onDown","onUp"]),t.processDOMPacketOut=function(t,e,i){return this.processFactoryPacketOut(t,e,i)},t.processFactoryPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){if(G(this.domElement)){let e=this.domElement,i=e.cloneNode(!0);i.querySelectorAll('[data-corner-div="sc"]').forEach(t=>i.removeChild(t)),t.outerHTML=i.outerHTML,t.host=e.parentElement.id}return t=this.handlePacketAnchor(t,e)},t.postCloneAction=function(t,e){return this.onEnter&&(t.onEnter=this.onEnter),this.onLeave&&(t.onLeave=this.onLeave),this.onDown&&(t.onDown=this.onDown),this.onUp&&(t.onUp=this.onUp),t};let e=t.setters,s=t.deltaSetters;e.trackHere=function(t){var e;J(t)&&(t?(Q(ne,this.name),"local"===t&&(U(e=this)&&(e.localMouseListener&&e.localMouseListener(),e.here||(e.here={}),e.here.originalWidth=e.currentDimensions[0],e.here.originalHeight=e.currentDimensions[1],e.localMouseListener=Lt("move",(function(t){e.here&&(e.here.x=Math.round(parseFloat(t.offsetX)),e.here.y=Math.round(parseFloat(t.offsetY)))}),e.domElement)))):(K(ne,this.name),function(t){U(t)&&(t.localMouseListener&&t.localMouseListener(),t.localMouseListener=!1)}(this)),this.trackHere=t)},e.position=function(t){this.position=t,this.dirtyPosition=!0},e.visibility=function(t){this.visibility=t,this.dirtyVisibility=!0},e.offsetZ=function(t){this.offsetZ=t,this.dirtyOffsetZ=!0},s.offsetZ=function(t){this.offsetZ+=t,this.dirtyOffsetZ=!0},e.roll=function(t){this.roll=this.checkRotationAngle(t),this.dirtyRotation=!0},s.roll=function(t){this.roll=this.checkRotationAngle(this.roll+t),this.dirtyRotation=!0},e.pitch=function(t){this.pitch=this.checkRotationAngle(t),this.dirtyRotation=!0},s.pitch=function(t){this.pitch=this.checkRotationAngle(this.pitch+t),this.dirtyRotation=!0},e.yaw=function(t){this.yaw=this.checkRotationAngle(t),this.dirtyRotation=!0},s.yaw=function(t){this.yaw=this.checkRotationAngle(this.yaw+t),this.dirtyRotation=!0},e.css=function(t){this.css=this.css?_(this.css,t):t,this.dirtyCss=!0},e.classes=function(t){this.classes=t,this.dirtyClasses=!0},e.domAttributes=function(t){this.updateDomAttributes(t)},t.checkRotationAngle=function(t){return(t<-180||t>180)&&(t+=t>0?-360:360),t},t.updateDomAttributes=function(t,e){if(this.domElement){let i=this.domElement;t.substring&&J(e)?e?i.setAttribute(t,e):i.removeAttribute(t):U(t)&&Object.entries(t).forEach(([t,e])=>{e?i.setAttribute(t,e):i.removeAttribute(t)})}return this},t.initializeDomLayout=function(t){let e=t.domElement,s=e.style;if(s.boxSizing="border-box",e&&t.setInitialDimensions){let n,r=e.getBoundingClientRect(),o=(e.style.transform,e.style.transformOrigin,!1);if(t&&t.host&&(o=t.host,o.substring&&i[o]&&(o=i[o])),this.currentDimensions[0]=r.width,this.currentDimensions[1]=r.height,t.width=r.width,t.height=r.height,e.className&&(t.classes=e.className),o&&o.domElement&&(n=o.domElement.getBoundingClientRect(),n&&(t.startX=r.left-n.left,t.startY=r.top-n.top)),"Stack"===this.type){J(t.perspective)||J(t.perspectiveZ)||(t.perspectiveZ=J(s.perspective)&&s.perspective?parseFloat(s.perspective):0);let e=s.perspectiveOrigin;e.length&&(e=e.split(" "),e.length>0&&!J(t.perspective)&&!J(t.perspectiveX)&&(t.perspectiveX=e[0]),J(t.perspective)||J(t.perspectiveY)||(e.length>1?t.perspectiveY=e[1]:t.perspectiveY=e[0]))}}},t.addClasses=function(t){if(t.substring){let e=this.classes;e+=" "+t,e=e.trim(),e=e.replace(/[\s\uFEFF\xA0]+/g," "),e!==this.classes&&(this.classes=e,this.dirtyClasses=!0)}return this},t.removeClasses=function(t){if(t.substring){let e,i=this.classes;t.split().forEach(t=>{e=new RegExp(" ?"+t+" ?"),i=i.replace(e," "),i=i.trim(),i=i.replace(/[\s\uFEFF\xA0]+/g," ")}),i!==this.classes&&(this.classes=i,this.dirtyClasses=!0)}return this},t.addPathCorners=function(){if(this.domElement&&!this.noUserInteraction){let t=function(){let t=document.createElement("div");return t.style.width=0,t.style.height=0,t.style.position="absolute",t},e=t(),i=t(),s=t(),n=t();e.style.top="0%",e.style.left="0%",e.setAttribute("data-corner-div","sc"),i.style.top="0%",i.style.left="100%",i.setAttribute("data-corner-div","sc"),s.style.top="100%",s.style.left="100%",s.setAttribute("data-corner-div","sc"),n.style.top="100%",n.style.left="0%",n.setAttribute("data-corner-div","sc");let r=this.domElement;r.appendChild(e),r.appendChild(i),r.appendChild(s),r.appendChild(n),this.pathCorners.push(e),this.pathCorners.push(i),this.pathCorners.push(s),this.pathCorners.push(n),this.currentCornersData||(this.currentCornersData=[])}return this},t.checkCornerPositions=function(t){let e=this.pathCorners;if(4===e.length){let i,s=this.getHere(),n=le.scrollX-(s.offsetX||0),r=le.scrollY-(s.offsetY||0),o=Math.round,a=[];const l=function(t){let e=t[0];e?(a.push(o(e.left+n)),a.push(o(e.top+r))):a.push(0,0)};switch(t){case"topLeft":return i=e[0].getClientRects(),l(i),a;case"topRight":return i=e[1].getClientRects(),l(i),a;case"bottomRight":return i=e[2].getClientRects(),l(i),a;case"bottomLeft":return i=e[3].getClientRects(),l(i),a;default:return e.forEach(t=>{G(t)&&(i=t.getClientRects(),l(i))}),a}}};const n=["topLeft","topRight","bottomRight","bottomLeft"];return t.getCornerCoordinate=function(t){return n.indexOf(t)>=0?this.checkCornerPositions(t):[].concat(this.currentStampPosition)},t.cleanPathObject=function(){if(this.dirtyPathObject=!1,this.domElement&&!this.noUserInteraction){this.pathCorners.length||this.addPathCorners(),this.currentCornersData||(this.currentCornersData=[]);let t=this.currentCornersData;t.length=0,t.push(...this.checkCornerPositions());let e=this.pathObject=new Path2D;e.moveTo(t[0],t[1]),e.lineTo(t[2],t[3]),e.lineTo(t[4],t[5]),e.lineTo(t[6],t[7]),e.closePath()}},t.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=requestCell(),s=!0);let n,r,o=e.engine,a=this.currentStampPosition;a[0],a[1];return i.some(t=>{if(Array.isArray(t))n=t[0],r=t[1];else{if(!tt(t,t.x,t.y))return!1;n=t.x,r=t.y}return!(!n.toFixed||!r.toFixed||isNaN(n)||isNaN(r))&&o.isPointInPath(this.pathObject,n,r)},this)?(s&&releaseCell(e),{x:n,y:r,artefact:this}):(s&&releaseCell(e),!1)},t.cleanRotation=function(){this.dirtyRotation=!1,this.rotation&&q(this.rotation)||(this.rotation=Si()),this.currentRotation&&q(this.rotation)||(this.currentRotation=Si());let t=this.rotation;t.setFromEuler({pitch:this.pitch||0,yaw:this.yaw||0,roll:this.roll||0}),1!==t.getMagnitude()&&t.normalize();let e=bi(),i=this.path,s=this.mimic,n=this.pivot,r=this.lockTo;i&&r.indexOf("path")>=0?e.set(t):s&&this.useMimicRotation&&r.indexOf("mimic")>=0?J(s.currentRotation)?(e.set(s.currentRotation),this.addOwnRotationToMimic&&e.quaternionRotate(t)):this.dirtyMimicRotation=!0:(e.set(t),n&&this.addPivotRotation&&r.indexOf("pivot")>=0&&(J(n.currentRotation)?e.quaternionRotate(n.currentRotation):this.dirtyPivotRotation=!0)),this.currentRotation.set(e),ki(e),this.dirtyPositionSubscribers=!0,this.mimicked&&this.mimicked.length&&(this.dirtyMimicRotation=!0)},t.cleanOffsetZ=function(){this.dirtyOffsetZ=!1},t.cleanContent=function(){this.dirtyContent=!1,this.domElement&&(this.dirtyDimensions=!0)},t.cleanDisplayShape=B,t.cleanDisplayArea=B,t.prepareStamp=function(){(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle||this.dirtyRotation)&&(this.dirtyPathObject=!0),this.dirtyContent&&this.cleanContent(),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyDisplayArea&&this.cleanDisplayArea(),this.dirtyDisplayShape&&this.cleanDisplayShape(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyOffsetZ&&this.cleanOffsetZ(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.pivoted.length&&(this.dirtyStampPositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject()},t.stamp=function(){let t=this;return new Promise((e,i)=>{t.domElement||i(!1);let s,n,r,o,a,[l,h]=t.currentStampPosition,[c,u]=t.currentStampHandlePosition,d=t.currentScale,f=t.currentRotation,p=`${c}px ${u}px 0`,m=`translate(${l-c}px,${h-u}px)`;(t.yaw||t.pitch||t.roll||t.pivot&&t.addPivotRotation||t.mimic&&t.useMimicRotation||t.path&&t.addPathRotation)&&(s=f.v,n=s.x,r=s.y,o=s.z,a=f.getAngle(!1),m+=` rotate3d(${n},${r},${o},${a}rad)`),t.offsetZ&&(m+=` translateZ(${t.offsetZ}px)`),1!==d&&(m+=` scale(${d},${d})`),m!==t.currentTransformString&&(t.currentTransformString=m,t.dirtyTransform=!0),p!==t.currentTransformOriginString&&(t.currentTransformOriginString=p,t.dirtyTransformOrigin=!0),(t.dirtyTransform||t.dirtyPerspective||t.dirtyPosition||t.dirtyDomDimensions||t.dirtyTransformOrigin||t.dirtyVisibility||t.dirtyCss||t.dirtyClasses||t.domShowRequired)&&(Ki(t.name),ts(!0)),t.dirtyPositionSubscribers&&t.updatePositionSubscribers(),(t.dirtyMimicRotation||t.dirtyPivotRotation)&&(t.dirtyMimicRotation=!1,t.dirtyPivotRotation=!1,t.dirtyRotation=!0),t.dirtyMimicScale&&(t.dirtyMimicScale=!1,t.dirtyScale=!0),e(!0)})},t.apply=function(){be(),this.prepareStamp();let t=this;this.stamp().then(()=>{es(t.name),t.dirtyPathObject=!0,t.cleanPathObject()}).catch(t=>console.log(t))},t}function Pi(t={}){let e={breakToBanner:3,breakToLandscape:1.5,breakToPortrait:.65,breakToSkyscraper:.35,actionBannerShape:null,actionLandscapeShape:null,actionRectangleShape:null,actionPortraitShape:null,actionSkyscraperShape:null,breakToSmallest:2e4,breakToSmaller:8e4,breakToLarger:18e4,breakToLargest:32e4,actionSmallestArea:null,actionSmallerArea:null,actionRegularArea:null,actionLargerArea:null,actionLargestArea:null};t.defs=_(t.defs,e),_(t,e),t.packetFunctions=Q(t.packetFunctions,["actionBannerShape","actionLandscapeShape","actionRectangleShape","actionPortraitShape","actionSkyscraperShape"]);let i=t.getters,s=t.setters;return i.displayShape=function(){return this.currentDisplayShape},i.displayShapeBreakpoints=function(){return{breakToBanner:this.breakToBanner,breakToLandscape:this.breakToLandscape,breakToPortrait:this.breakToPortrait,breakToSkyscraper:this.breakToSkyscraper,breakToSmallest:this.breakToSmallest,breakToSmaller:this.breakToSmaller,breakToLarger:this.breakToLarger,breakToLargest:this.breakToLargest}},s.displayShapeBreakpoints=function(t={}){for(let[e,i]of Object.entries(t))if(V(i))switch(e){case"breakToBanner":this.breakToBanner=i;break;case"breakToLandscape":this.breakToLandscape=i;break;case"breakToPortrait":this.breakToPortrait=i;break;case"breakToSkyscraper":this.breakToSkyscraper=i;break;case"breakToSmallest":this.breakToSmallest=i;break;case"breakToSmaller":this.breakToSmaller=i;break;case"breakToLarger":this.breakToLarger=i;break;case"breakToLargest":this.breakToLargest=i}this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},t.setDisplayShapeBreakpoints=s.displayShapeBreakpoints,s.breakToBanner=function(t){V(t)&&(this.breakToBanner=t),this.dirtyDisplayShape=!0},s.breakToLandscape=function(t){V(t)&&(this.breakToLandscape=t),this.dirtyDisplayShape=!0},s.breakToPortrait=function(t){V(t)&&(this.breakToPortrait=t),this.dirtyDisplayShape=!0},s.breakToSkyscraper=function(t){V(t)&&(this.breakToSkyscraper=t),this.dirtyDisplayShape=!0},s.breakToSmallest=function(t){V(t)&&(this.breakToSmallest=t),this.dirtyDisplayArea=!0},s.breakToSmaller=function(t){V(t)&&(this.breakToSmaller=t),this.dirtyDisplayArea=!0},s.breakToLarger=function(t){V(t)&&(this.breakToLarger=t),this.dirtyDisplayArea=!0},s.breakToLargest=function(t){V(t)&&(this.breakToLargest=t),this.dirtyDisplayArea=!0},s.actionBannerShape=function(t){W(t)&&(this.actionBannerShape=t),this.dirtyDisplayShape=!0},t.setActionBannerShape=s.actionBannerShape,s.actionLandscapeShape=function(t){W(t)&&(this.actionLandscapeShape=t),this.dirtyDisplayShape=!0},t.setActionLandscapeShape=s.actionLandscapeShape,s.actionRectangleShape=function(t){W(t)&&(this.actionRectangleShape=t),this.dirtyDisplayShape=!0},t.setActionRectangleShape=s.actionRectangleShape,s.actionPortraitShape=function(t){W(t)&&(this.actionPortraitShape=t),this.dirtyDisplayShape=!0},t.setActionPortraitShape=s.actionPortraitShape,s.actionSkyscraperShape=function(t){W(t)&&(this.actionSkyscraperShape=t),this.dirtyDisplayShape=!0},t.setActionSkyscraperShape=s.actionSkyscraperShape,s.actionSmallestArea=function(t){W(t)&&(this.actionSmallestArea=t),this.dirtyDisplayArea=!0},t.setActionSmallestArea=s.actionSmallestArea,s.actionSmallerArea=function(t){W(t)&&(this.actionSmallerArea=t),this.dirtyDisplayArea=!0},t.setActionSmallerArea=s.actionSmallerArea,s.actionRegularArea=function(t){W(t)&&(this.actionRegularArea=t),this.dirtyDisplayArea=!0},t.setActionRegularArea=s.actionRegularArea,s.actionLargerArea=function(t){W(t)&&(this.actionLargerArea=t),this.dirtyDisplayArea=!0},t.setActionLargerArea=s.actionLargerArea,s.actionLargestArea=function(t){W(t)&&(this.actionLargestArea=t),this.dirtyDisplayArea=!0},t.setActionLargestArea=s.actionLargestArea,t.initializeDisplayShapeActions=function(){this.actionBannerShape=B,this.actionLandscapeShape=B,this.actionRectangleShape=B,this.actionPortraitShape=B,this.actionSkyscraperShape=B,this.currentDisplayShape="",this.dirtyDisplayShape=!0,this.actionSmallestArea=B,this.actionSmallerArea=B,this.actionRegularArea=B,this.actionLargerArea=B,this.actionLargestArea=B,this.currentDisplayArea="",this.dirtyDisplayArea=!0},t.cleanDisplayShape=function(){this.dirtyDisplayShape=!1;let[t,e]=this.currentDimensions;if(t>0&&e>0){let i=t/e,s=this.currentDisplayShape,n=this.breakToBanner,r=this.breakToLandscape,o=this.breakToPortrait,a=this.breakToSkyscraper;return i>n?"banner"!==s&&(this.currentDisplayShape="banner",this.actionBannerShape(),!0):i>r?"landscape"!==s&&(this.currentDisplayShape="landscape",this.actionLandscapeShape(),!0):i0&&e>0){let i=t*e,s=this.currentDisplayArea,n=this.breakToLargest,r=this.breakToLarger,o=this.breakToSmaller,a=this.breakToSmallest;return i>n?"largest"!==s&&(this.currentDisplayArea="largest",this.actionLargestArea(),!0):i>r?"larger"!==s&&(this.currentDisplayArea="larger",this.actionLargerArea(),!0):i=0?t:"none"},xi.title=function(t){this.title=t,this.dirtyAria=!0},xi.label=function(t){this.label=t,this.dirtyAria=!0},xi.description=function(t){this.description=t,this.dirtyAria=!0},wi.backgroundColor=function(){return this.base.backgroundColor},xi.backgroundColor=function(t){this.base&&this.base.set({backgroundColor:t})},wi.alpha=function(){return this.base.alpha},xi.alpha=function(t){this.base&&this.base.set({alpha:t})},Ai.alpha=function(t){this.base&&this.base.deltaSet({alpha:t})},wi.composite=function(){return this.base.composite},xi.composite=function(t){this.base&&this.base.set({composite:t})},vi.setAsCurrentCanvas=function(){return this.base&&Wi(this),this},vi.setBase=function(t){return this.base&&(this.base.set(t),this.setBaseHelper()),this},vi.deltaSetBase=function(t){return this.base&&(this.base.deltaSet(t),this.setBaseHelper()),this},vi.updateBaseHere=function(){this.base&&this.base.updateBaseHere(this.here,this.fit)},vi.setBaseHelper=function(){let t={};this.base.dirtyScale&&(t.dirtyScale=!0),this.base.dirtyDimensions&&(t.dirtyDimensions=!0),this.base.dirtyLock&&(t.dirtyLock=!0),this.base.dirtyStart&&(t.dirtyStart=!0),this.base.dirtyOffset&&(t.dirtyOffset=!0),this.base.dirtyHandle&&(t.dirtyHandle=!0),this.base.dirtyRotation&&(t.dirtyRotation=!0),this.cleanCells(),this.base.prepareStamp(),this.updateCells(t)},vi.updateCells=function(t={}){this.cells.forEach(e=>{let i=a[e];i&&(t.dirtyScale&&(i.dirtyScale=!0),t.dirtyDimensions&&(i.dirtyDimensions=!0),t.dirtyLock&&(i.dirtyLock=!0),t.dirtyStart&&(i.dirtyStart=!0),t.dirtyOffset&&(i.dirtyOffset=!0),t.dirtyHandle&&(i.dirtyHandle=!0),t.dirtyRotation&&(i.dirtyRotation=!0))})},vi.buildCell=function(t={}){t.host||!1||(t.host=this.base.name);let e=oi(t);return this.addCell(e),this.cleanCells(),e},vi.cleanDimensionsAdditionalActions=function(){this.cells&&this.updateCells({dirtyDimensions:!0}),this.dirtyDomDimensions=!0,this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},vi.addCell=function(t){return(t=t.substring?t:t.name||!1)&&(Q(this.cells,t),a[t].prepareStamp(),this.dirtyCells=!0),t},vi.removeCell=function(t){return(t=t.substring?t:t.name||!1)&&(K(this.cells,t),this.dirtyCells=!0),this},vi.killCell=function(t){let e=t.substring?a[t]:t;return e&&e.kill(),this.dirtyCells=!0,this},vi.clear=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesClear[i];r?r.clear().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):s(!0)});return e(0)},vi.compile=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesCompile[i];r?r.compile().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):(t.prepareStamp(),t.stamp().then(t=>s(!0)).catch(t=>n(t)))});return e(0)},vi.show=function(){let t=this;t.dirtyCells&&t.cleanCells();let e=i=>new Promise((s,n)=>{let r=t.cellBatchesShow[i];r?r.show().then(t=>{e(i+1).then(t=>s(!0)).catch(t=>n(t))}).catch(t=>n(t)):(t.engine.clearRect(0,0,t.localWidth,t.localHeight),t.base.show().then(t=>{es(),this.dirtyAria&&this.cleanAria(),s(!0)}).catch(t=>n(t)))});return e(0)},vi.render=function(){let t=this;return new Promise(e=>{t.clear().then(()=>t.compile()).then(()=>t.show()).then(()=>e(!0)).catch(()=>e(!1))})},vi.cleanCells=function(){this.dirtyCells=!1;let t,e=[],i=[],s=[];this.cells.forEach(n=>{let r=a[n];r&&(r.cleared&&e.push(r),r.compiled&&(t=r.compileOrder,i[t]||(i[t]=[]),i[t].push(r)),r.shown&&(t=r.showOrder,s[t]||(s[t]=[]),s[t].push(r)))}),this.cellBatchesClear=[].concat(e),this.cellBatchesCompile=i.reduce((t,e)=>t.concat(e),[]),this.cellBatchesShow=s.reduce((t,e)=>t.concat(e),[])},vi.cascadeEventAction=function(t){this.currentActiveEntityNames||(this.currentActiveEntityNames=[]);let e=this.currentActiveEntityNames,s=[],n=[],r=[],o=[],l=[],h=[];this.cells.forEach(t=>{let e=a[t];e&&(e.shown||e.isBase)&&s.push(e.getEntityHits())}),s=s.reduce((t,e)=>t.concat(e),[]),s.forEach(t=>{let i=t.name;n.indexOf(i)<0&&(n.push(i),e.indexOf(i)<0?(r.push(t),o.push(i)):(l.push(t),h.push(i)))});let c=r.concat(l),u=function(){e.length&&(o.forEach(t=>K(e,t)),h.forEach(t=>K(e,t)),e.forEach(t=>{let e=i[t];e&&e.onLeave()}))};switch(t){case"down":c.forEach(t=>t.onDown());break;case"up":c.forEach(t=>t.onUp());break;case"enter":r.forEach(t=>t.onEnter());break;case"leave":u();break;case"move":u(),r.forEach(t=>t.onEnter())}return this.currentActiveEntityNames=o.concat(h),this.currentActiveEntityNames},vi.cleanAria=function(){this.dirtyAria=!1,this.domElement.setAttribute("title",this.title),this.ariaLabelElement.textContent=this.label,this.ariaDescriptionElement.textContent=this.description};const Ci=function(t){return new Canvas(t)};O.Canvas=Canvas;const Element=function(t={}){let e=t.domElement;return this.makeName(t.name),this.register(),e&&(t.text?e.textContent=t.text:t.content&&(e.innerHTML=t.content)),this.initializePositions(),this.dimensions[0]=this.dimensions[1]=100,this.pathCorners=[],this.css={},this.here={},this.initializeDomLayout(t),this.set(this.defs),this.set(t),e=this.domElement,e&&(e.id=this.name),this.apply(),this};let Di=Element.prototype=Object.create(Object.prototype);Di.type="Element",Di.lib="element",Di.isArtefact=!0,Di.isAsset=!1,Di=ee(Di),Di=Oi(Di),Di.factoryKill=function(){K(ne,this.name),this.domElement.remove()};let Ri=Di.setters;Ri.text=function(t){if(G(this.domElement)){let e=this.domElement,i=e.querySelectorAll('[data-corner-div="sc"]');e.textContent=t,i.forEach(t=>e.appendChild(t)),this.dirtyContent=!0}},Ri.content=function(t){if(this.domElement){let e=this.domElement,i=e.querySelectorAll('[data-corner-div="sc"]');e.innerHTML=t,i.forEach(t=>e.appendChild(t)),this.dirtyContent=!0}},Di.cleanDimensionsAdditionalActions=function(){this.dirtyDomDimensions=!0},Di.addCanvas=function(t={}){if(!this.canvas){let e=document.createElement("canvas"),i=this.domElement,s=i.getBoundingClientRect();window.getComputedStyle(i);i.parentNode.insertBefore(e,this.domElement);let n=Ci({name:this.name+"-canvas",domElement:e,position:"absolute",width:s.width,height:s.height,mimic:this.name,lockTo:"mimic",useMimicDimensions:!0,useMimicScale:!0,useMimicStart:!0,useMimicHandle:!0,useMimicOffset:!0,useMimicRotation:!0,addOwnDimensionsToMimic:!1,addOwnScaleToMimic:!1,addOwnStartToMimic:!1,addOwnHandleToMimic:!1,addOwnOffsetToMimic:!1,addOwnRotationToMimic:!1});return n.set(t),this.canvas=n,n}};const Ei=function(t){return new Element(t)};O.Element=Element;const Stack=function(t={}){let e,i;return this.makeName(t.name),this.register(),this.initializePositions(),this.initializeCascade(),this.dimensions[0]=300,this.dimensions[1]=150,this.pathCorners=[],this.css={},this.here={},this.perspective={x:"50%",y:"50%",z:0},this.dirtyPerspective=!0,this.initializeDomLayout(t),e=ci({name:this.name,host:this.name}),this.addGroups(e.name),this.set(this.defs),this.initializeDisplayShapeActions(),this.set(t),i=this.domElement,i&&"root"===i.getAttribute("data-group")&&(Q(Bi,this.name),Li()),this};let Fi=Stack.prototype=Object.create(Object.prototype);Fi.type="Stack",Fi.lib="stack",Fi.isArtefact=!0,Fi.isAsset=!1,Fi=ee(Fi),Fi=Ze(Fi),Fi=Oi(Fi),Fi=Pi(Fi);Fi.defs=_(Fi.defs,{position:"relative",perspective:null,trackHere:"subscribe",isResponsive:!1,containElementsInHeight:!1}),Fi.stringifyFunction=B,Fi.processPacketOut=B,Fi.finalizePacketOut=B,Fi.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},Fi.clone=$,Fi.factoryKill=function(){let t=this.name;K(Bi,t),Li(),K(ne,t),u[t]&&u[t].kill(),Object.entries(i).forEach(([e,i])=>{i.host===t&&i.kill()}),this.domElement.remove()};let Ti=Fi.getters,Hi=Fi.setters,Mi=Fi.deltaSetters;Ti.perspectiveX=function(){return this.perspective.x},Ti.perspectiveY=function(){return this.perspective.y},Ti.perspectiveZ=function(){return this.perspective.z},Hi.perspectiveX=function(t){this.perspective.x=t,this.dirtyPerspective=!0},Hi.perspectiveY=function(t){this.perspective.y=t,this.dirtyPerspective=!0},Hi.perspectiveZ=function(t){this.perspective.z=t,this.dirtyPerspective=!0},Hi.perspective=function(t={}){this.perspective.x=J(t.x)?t.x:this.perspective.x,this.perspective.y=J(t.y)?t.y:this.perspective.y,this.perspective.z=J(t.z)?t.z:this.perspective.z,this.dirtyPerspective=!0},Mi.perspectiveX=function(t){this.perspective.x=H(this.perspective.x,t),this.dirtyPerspective=!0},Mi.perspectiveY=function(t){this.perspective.y=H(this.perspective.y,t),this.dirtyPerspective=!0},Fi.updateArtefacts=function(t={}){this.groupBuckets.forEach(e=>{e.artefactBuckets.forEach(e=>{t.dirtyScale&&(e.dirtyScale=!0),t.dirtyDimensions&&(e.dirtyDimensions=!0),t.dirtyLock&&(e.dirtyLock=!0),t.dirtyStart&&(e.dirtyStart=!0),t.dirtyOffset&&(e.dirtyOffset=!0),t.dirtyHandle&&(e.dirtyHandle=!0),t.dirtyRotation&&(e.dirtyRotation=!0),t.dirtyPathObject&&(e.dirtyPathObject=!0)})})},Fi.cleanDimensionsAdditionalActions=function(){this.groupBuckets&&this.updateArtefacts({dirtyDimensions:!0,dirtyPath:!0,dirtyStart:!0,dirtyHandle:!0}),this.dirtyDomDimensions=!0,this.dirtyPath=!0,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyDisplayShape=!0,this.dirtyDisplayArea=!0},Fi.cleanPerspective=function(){this.dirtyPerspective=!1;let t=this.perspective;this.domPerspectiveString=`perspective-origin: ${t.x} ${t.y}; perspective: ${t.z}px;`,this.domShowRequired=!0,this.groupBuckets&&this.updateArtefacts({dirtyHandle:!0,dirtyPathObject:!0})},Fi.checkResponsive=function(){this.isResponsive&&this.trackHere&&(this.currentVportWidth||(this.currentVportWidth=le.w),this.currentVportHeight||(this.currentVportHeight=le.h),this.dirtyHeight&&this.containElementsInHeight&&(console.log("stack height final fixes need to be done"),this.dirtyHeight=!1),this.currentVportWidth!==le.w&&(console.log("need to update for resized viewport width"),this.currentVportWidth=le.w,this.containElementsInHeight&&(this.dirtyHeight=!0)),this.currentVportHeight!==le.h&&(console.log("need to update for resized viewport height"),this.currentVportHeight=le.h))},Fi.clear=function(){return this.checkResponsive(),Promise.resolve(!0)},Fi.compile=function(){let t=this;return new Promise((e,i)=>{t.sortGroups(),t.prepareStamp(),t.stamp().then(()=>{let e=[];return t.groupBuckets.forEach(t=>e.push(t.stamp())),Promise.all(e)}).then(()=>e(!0)).catch(t=>i(!1))})},Fi.show=function(){return new Promise(t=>{es(),t(!0)})},Fi.render=function(){let t=this;return new Promise((e,i)=>{t.compile().then(()=>t.show()).then(()=>e(!0)).catch(t=>i(!1))})},Fi.addExistingDomElements=function(t){let e,i,s,n,r;if(J(t))for(e=t.substring?document.querySelectorAll(t):[].concat(t),n=0,r=e.length;n{ji=!0},$i=[],ji=!0;const Ni=function(t){let e=t.getAttribute("data-group"),i=t.id||t.getAttribute("name"),s="absolute";e||(t.setAttribute("data-group","root"),e="root",s="relative"),i||(i=N(),t.id=i);let n=Ii({name:i,domElement:t,group:e,host:e,position:s,setInitialDimensions:!0});return Xi(t,i),n},Xi=function(t,e){let i=t.getBoundingClientRect(),s=0;Array.from(t.children).forEach(t=>{if(null!=t.getAttribute("data-stack")||z(t)||"SCRIPT"===t.tagName)t.setAttribute("data-group",e);else{let n=t.getBoundingClientRect(),r=window.getComputedStyle(t),o=parseFloat(r.marginTop)+parseFloat(r.borderTopWidth)+parseFloat(r.paddingTop)+parseFloat(r.paddingBottom)+parseFloat(r.borderBottomWidth)+parseFloat(r.marginBottom);s=s||n.top-i.top;let a={name:t.id||t.getAttribute("name"),domElement:t,group:e,host:e,position:"absolute",width:n.width,height:n.height,startX:n.left-i.left,startY:s,classes:t.className?t.className:""};s+=o+n.height,Ei(a)}})},Yi=function(t){let e=t.getAttribute("data-group"),i=t.id||t.getAttribute("name"),s="absolute";return e||(t.setAttribute("data-group","root"),e="root",s=t.style.position),i||(i=N(),t.id=i),Ci({name:i,domElement:t,group:e,host:e,position:s,setInitialDimensions:!0})};let zi=null,Gi=null;const Wi=function(t){let e=!1;if(t)if(t.substring){let i=o[t];i&&(zi=i,e=!0)}else"Canvas"===t.type&&(zi=t,e=!0);if(e&&zi.base){let t=u[zi.base.name];t&&(Gi=t)}},Vi=function(...t){return _i(t),Zi("clear")},Ui=function(...t){return _i(t),Zi("compile")},qi=function(...t){return _i(t),Zi("show")},_i=function(t){t.length?$i=t:ji&&function(){let t=Math.floor;if(ji){ji=!1;let e,s,n=[];Bi.forEach(r=>{e=i[r],e&&(s=t(e.order)||0,n[s]||(n[s]=[]),n[s].push(e.name))}),$i=n.reduce((t,e)=>t.concat(e),[])}}()},Zi=function(t){return new Promise((e,s)=>{let n,r=[];$i.forEach(e=>{n=i[e],n&&n[t]&&r.push(n[t]())}),Promise.all(r).then(()=>e(!0)).catch(t=>s(t))})},Qi=[],Ki=function(t=""){if(!t)throw new Error("core/document addDomShowElement() error - false argument supplied: "+t);if(!t.substring)throw new Error("core/document addDomShowElement() error - argument not a string: "+t);Q(Qi,t)};let Ji=!1;const ts=function(t=!0){Ji=t},es=function(t=""){if(Ji||t){let e,s,n,r,o,a,l,h,c,u,d,f,p,m,g,y,b,k;for(t?e=[t]:(Ji=!1,e=[].concat(Qi),Qi.length=0),s=0,n=e.length;sconsole.log(t))):(l.width=u+"px",l.height=d?d+"px":"auto")),o.dirtyTransformOrigin&&(o.dirtyTransformOrigin=!1,l.transformOrigin=o.currentTransformOriginString),o.dirtyTransform&&(o.dirtyTransform=!1,l.transform=o.currentTransformString),o.dirtyVisibility&&(o.dirtyVisibility=!1,l.display=o.visibility?"block":"none"),o.dirtyCss)for(o.dirtyCss=!1,m=o.css||{},g=Object.keys(m),f=0,p=g.length;f{let s=Object.assign({},t);s.name=`${s.name}_${i.name}`,s.target=i,e.push(new RenderAnimation(s))}),e}e=t.target.substring?i[t.target]:t.target}else e={clear:Vi,compile:Ui,show:qi};if(!(e&&e.clear&&e.compile&&e.show))return!1;this.makeName(t.name),this.order=J(t.order)?t.order:this.defs.order,this.onRun=t.onRun||B,this.onHalt=t.onHalt||B,this.onKill=t.onKill||B,this.target=e,this.commence=t.commence||B,this.afterClear=t.afterClear||B,this.afterCompile=t.afterCompile||B,this.afterShow=t.afterShow||B,this.afterCreated=t.afterCreated||B,this.error=t.error||B,this.readyToInitialize=!0;let s=this;return this.fn=function(){return new Promise((t,e)=>{Promise.resolve(s.commence()).then(()=>s.target.clear()).then(()=>Promise.resolve(s.afterClear())).then(()=>s.target.compile()).then(()=>Promise.resolve(s.afterCompile())).then(()=>s.target.show()).then(()=>Promise.resolve(s.afterShow())).then(()=>{s.readyToInitialize&&(s.afterCreated(),s.readyToInitialize=!1),t(!0)}).catch(t=>{s.error(t),e(t)})})},this.register(),t.observer&&(this.observer=Bt(this,this.target)),t.delay||this.run(),this};let ns=RenderAnimation.prototype=Object.create(Object.prototype);ns.type="RenderAnimation",ns.lib="animation",ns.isArtefact=!1,ns.isAsset=!1,ns=ee(ns);ns.defs=_(ns.defs,{order:1,onRun:null,onHalt:null,onKill:null,commence:null,afterClear:null,afterCompile:null,afterShow:null,afterCreated:null,error:null,target:null}),ns.stringifyFunction=B,ns.processPacketOut=B,ns.finalizePacketOut=B,ns.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},ns.clone=$,ns.kill=function(){return this.onKill(),K(R,this.name),E(),this.deregister(),!0},ns.run=function(){return this.onRun(),Q(R,this.name),E(),this},ns.isRunning=function(){return R.indexOf(this.name)>=0},ns.halt=function(){return this.onHalt(),K(R,this.name),E(),this};const rs=function(t){return new RenderAnimation(t)};O.RenderAnimation=RenderAnimation;const UnstackedElement=function(t){let e=t.id||t.name;return this.makeName(e),this.register(),t.setAttribute("data-scrawl-name",this.name),this.domElement=t,this.elementComputedStyles=window.getComputedStyle(t),this.hostStyles={},this.canvasStartX=0,this.canvasStartY=0,this.canvasWidth=0,this.canvasHeight=0,this.canvasZIndex=0,this};let os=UnstackedElement.prototype=Object.create(Object.prototype);os.type="UnstackedElement",os.lib="unstackedelement",os.isArtefact=!1,os.isAsset=!1,os=ee(os);os.defs=_(os.defs,{canvasOnTop:!1});os.getters,os.setters,os.deltaSetters;os.demolish=function(t=!1){return!0},os.addCanvas=function(t={}){if(!this.canvas){let e=document.createElement("canvas"),i=this.domElement,s=i.style;"static"===s.position&&(s.position="relative"),i.prepend(e);let n=Ci({name:this.name+"-canvas",domElement:e,position:"absolute"});return this.canvas=n,n.set(t),this.updateCanvas(),n}},os.includedStyles=["width","height","zIndex","borderBottomLeftRadius","borderBottomRightRadius","borderTopLeftRadius","borderTopRightRadius"],os.mimickedStyles=["borderBottomLeftRadius","borderBottomRightRadius","borderTopLeftRadius","borderTopRightRadius"],os.checkElementStyleValues=function(){let t={},e=this.domElement,i=this.canvas;if(e&&i&&i.domElement){let s=this.hostStyles,n=this.elementComputedStyles,r=i.domElement,o=this.includedStyles,a=Math.max,{x:l,y:h,width:c,height:u}=e.getBoundingClientRect(),{x:d,y:f}=r.getBoundingClientRect();o.forEach(e=>{switch(e){case"width":let i=a(parseFloat(n.width),c);this.canvasWidth!==i&&(this.canvasWidth=i,this.dirtyDimensions=!0);break;case"height":let r=a(parseFloat(n.height),u);this.canvasHeight!==r&&(this.canvasHeight=r,this.dirtyDimensions=!0);break;case"zIndex":let o="auto"===n.zIndex?0:parseInt(n.zIndex,10);o=this.canvasOnTop?o+1:o-1,this.canvasZIndex!==o&&(this.canvasZIndex=o,this.dirtyZIndex=!0);break;default:let l=s[e],h=n[e];J(l)&&l===h||(s[e]=h,t[e]=h)}});let p=l-d,m=h-f;(p||m)&&(this.canvasStartX+=p,this.canvasStartY+=m,this.dirtyStart=!0)}return t},os.updateCanvas=function(){if(this.canvas&&this.canvas.domElement){let t=this.canvas,e=t.domElement.style,i=this.mimickedStyles,s=this.checkElementStyleValues();for(let[t,n]of Object.entries(s))i.indexOf(t)>=0&&(e[t]=n);if(this.dirtyStart&&(this.dirtyStart=!1,t.set({startX:this.canvasStartX,startY:this.canvasStartY})),this.dirtyDimensions){this.dirtyDimensions=!1;let e=this.canvasWidth,i=this.canvasHeight;t.set({width:e,height:i}),t.dirtyDimensions=!0,t.base.set({width:e,height:i}),t.base.dirtyDimensions=!0,t.cleanDimensions(),t.base.cleanDimensions()}this.dirtyZIndex&&(this.dirtyZIndex=!1,e.zIndex=this.canvasZIndex)}};O.UnstackedElement=UnstackedElement;const as=function(t,e,s,n){let r=i[t.id];if(!r)return!1;e.isComponent=!0;let o=r.addCanvas(e);s.name=r.name+"-animation",s.target=o;let a=rs(s),l=Bt(a,r,n);return{element:r,canvas:o,animation:a,demolish:()=>{l(),a.kill(),o.demolish(),r.demolish(!0)}}},ls=function(t,e,i,s,n){let r,o=t.id;o&&S[o]?r=S[o]:r=new UnstackedElement(t),e.isComponent=!0;let a=!!n&&r.addCanvas(e);i.name=r.name+"-animation",a&&(i.afterClear||(i.afterClear=()=>r.updateCanvas()),i.target=a);let l=rs(i),h=Bt(l,r,s);return{element:r,canvas:a,animation:l,demolish:()=>{h(),l.kill(),a&&a.demolish(),r.demolish(!0)}}},hs=["artefact","group","animation","tween","styles"],cs=t=>{if(t&&t.substring){let e;return!!hs.some(i=>(e=x[i][t],e))&&e}return!1};function us(t={}){t.defs=_(t.defs,{order:1,ticker:"",targets:null,time:0,action:null,reverseOnCycleEnd:!1,reversed:!1}),t.kill=function(){let t,i=this.ticker;return i===this.name+"_ticker"?(t=e[i],t&&t.kill()):i&&this.removeFromTicker(i),this.deregister(),!0};let i=t.getters,s=t.setters;return i.targets=function(){return[].concat(this.targets)},s.targets=function(t=[]){this.setTargets(t)},s.action=function(t){this.action=t,"function"!=typeof this.action&&(this.action=B)},t.calculateEffectiveTime=function(t){let i,s=et(t,this.time),n=M(s),r=n[1],o=n[0],a=0;return this.effectiveTime=0,"%"===o&&r<=100?this.ticker&&(i=e[this.ticker],i&&(a=i.effectiveDuration,this.effectiveTime=a*(r/100))):this.effectiveTime=r,this},t.addToTicker=function(t){let i;return J(t)&&(this.ticker&&this.ticker!==t&&this.removeFromTicker(this.ticker),i=e[t],J(i)&&(this.ticker=t,i.subscribe(this.name),this.calculateEffectiveTime())),this},t.removeFromTicker=function(t){let i;return(t=J(t)?t:this.ticker)&&(i=e[t],J(i)&&(this.ticker="",i.unsubscribe(this.name))),this},t.setTargets=function(t){t=[].concat(t);let e=[];return t.forEach(t=>{if(W(t))W(t.set)&&e.push(t);else if(U(t)&&J(t.name))e.push(t);else{let i=cs(t);i&&e.push(i)}}),this.targets=e,this},t.addToTargets=function(t){return(t=[].concat(t)).forEach(t=>{"function"==typeof t?"function"==typeof t.set&&this.targets.push(t):(result=cs(t),result&&this.targets.push(result))},this),this},t.removeFromTargets=function(t){t=[].concat(t);let e=[],i=[].concat(this.targets);return i.forEach(t=>{let i=t.type||"unknown",s=t.name||"unnamed";"unknown"!==i&&"unnamed"!==s&&e.push(`${i}_${s}`)}),t.forEach(t=>{let s;if(s="function"==typeof t?t:cs(t),s){let t=s.type||"unknown",n=s.name||"unnamed";if("unknown"!==t&&"unnamed"!==n){let s=`${t}_${n}`,r=e.indexOf(s);r>=0&&(i[r]=!1)}}}),this.targets=[],i.forEach(t=>{t&&this.targets.push(t)},this),this},t.checkForTarget=function(t){return!!t.substring&&this.targets.some(e=>e.name===t)},t}const Action=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.calculateEffectiveTime(),J(t.ticker)&&this.addToTicker(t.ticker),this};let ds=Action.prototype=Object.create(Object.prototype);ds.type="Action",ds.lib="tween",ds.isArtefact=!1,ds.isAsset=!1,ds=ee(ds),ds=us(ds);ds.defs=_(ds.defs,{revert:null}),ds.packetExclusions=Q(ds.packetExclusions,["targets"]),ds.packetFunctions=Q(ds.packetFunctions,["revert","action"]),ds.finalizePacketOut=function(t,e){return Array.isArray(this.targets)&&(t.targets=this.targets.map(t=>t.name)),t};let fs=ds.setters;fs.revert=function(t){this.revert=t,"function"!=typeof this.revert&&(this.revert=B)},fs.triggered=function(t){this.triggered!==t&&(t?this.action():this.revert(),this.triggered=t)},ds.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs,n=!!J(t.ticker)&&this.ticker;Object.entries(t).forEach(([t,n])=>{"name"!==t&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this),n?(this.ticker=n,this.addToTicker(t.ticker)):J(t.time)&&this.calculateEffectiveTime()}return this},ds.getEndTime=function(){return this.effectiveTime},ds.update=function(t){let e=this.reversed,i=this.effectiveTime,s=this.triggered,n=(this.reverseOnCycleEnd,t.tick),r=t.reverseTick,o=t.willLoop;t.next;return e?r>=i?s||(this.action(),this.triggered=!0):s&&(this.revert(),this.triggered=!1):n>=i?s||(this.action(),this.triggered=!0):s&&(this.revert(),this.triggered=!1),o&&(this.triggered=!this.triggered),!0};function ps(t={}){(t=Ke(t=_e(t=Ve(t=We(t=Ge(t=ze(t=Ye(t)))))))).defs=_(t.defs,{method:"fill",pathObject:null,winding:"nonzero",flipReverse:!1,flipUpend:!1,scaleOutline:!0,lockFillStyleToEntity:!1,lockStrokeStyleToEntity:!1,onEnter:null,onLeave:null,onDown:null,onUp:null}),t.packetExclusions=Q(t.packetExclusions,["state"]),t.packetFunctions=Q(t.packetFunctions,["onEnter","onLeave","onDown","onUp"]),t.processEntityPacketOut=function(t,e,i){return this.processFactoryPacketOut(t,e,i)},t.processFactoryPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},t.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];return t=_(t,i),t=this.handlePacketAnchor(t,e)},t.postCloneAction=function(t,e){return this.onEnter&&(t.onEnter=this.onEnter),this.onLeave&&(t.onLeave=this.onLeave),this.onDown&&(t.onDown=this.onDown),this.onUp&&(t.onUp=this.onUp),e.sharedState&&(t.state=this.state),e.anchor&&(e.anchor.host=t,J(e.anchor.focusAction)||(e.anchor.focusAction=this.anchor.focusAction),J(e.anchor.blurAction)||(e.anchor.blurAction=this.anchor.blurAction),t.buildAnchor(e.anchor),e.anchor.clickAction||(t.anchor.clickAction=this.anchor.clickAction)),t};let e=t.getters,i=t.setters;t.deltaSetters;return e.group=function(){return this.group?this.group.name:""},i.lockStylesToEntity=function(t){this.lockFillStyleToEntity=t,this.lockStrokeStyleToEntity=t},t.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):null)}},t.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=r?r.setters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=l:void 0!==a[t]&&(r[t]=l))},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e,i,s=this.deltaSetters,n=this.defs,r=this.state,o=r?r.deltaSetters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=H(this[t],l):void 0!==a[t]&&(r[t]=H(r[t],l)))},this)}return this},t.entityInit=function(t={}){this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.state=De(),t.group||(t.group=Gi),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.set(t),this.midInitActions(t),this.purge&&this.purgeArtefact(this.purge)},t.midInitActions=B,t.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1,this.dirtyDimensions=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.anchor&&this.dirtyAnchorHold&&(this.dirtyAnchorHold=!1,this.buildAnchor(this.anchor))},t.cleanPathObject=B,t.stamp=function(t=!1,e,i){let s=!(this.noFilters||!this.filters||!this.filters.length);return t?(e&&"Cell"===e.type&&(this.currentHost=e),i&&(this.set(i),this.prepareStamp()),s?this.filteredStamp():this.regularStamp()):this.visibility?this.stashOutput||s?this.filteredStamp():this.regularStamp():Promise.resolve("entity.stamp")},t.regularStamp=function(){let t=this;return new Promise((e,i)=>{t.currentHost&&(t.regularStampSynchronousActions(),e("entity.regularStamp resolving")),i("entity.regularStamp - no currentHost")})},t.filteredStamp=function(){!this.dirtyFilters&&this.currentFilters||this.cleanFilters();let t=this;return new Promise((e,i)=>{let s=function(){if(u&&we(u),o.save(),o.globalAlpha=t.state&&t.state.globalAlpha?t.state.globalAlpha:1,o.globalCompositeOperation=t.state&&t.state.globalCompositeOperation?t.state.globalCompositeOperation:"source-over",o.setTransform(1,0,0,1,0,0),o.drawImage(h,0,0),t.stashOutput){t.stashOutput=!1;let[e,i,s,n]=t.getCellCoverage(c.getImageData(0,0,h.width,h.height));if(t.stashedImageData=c.getImageData(e,i,s,n),t.stashOutputAsAsset)if(t.stashOutputAsAsset=!1,h.width=s,h.height=n,c.putImageData(t.stashedImageData,0,0),t.stashedImage)t.stashedImage.src=h.toDataURL();else{let e=t.stashedImage=document.createElement("img");e.id=t.name+"-image",e.onload=function(){is.appendChild(e),Ne("#"+e.id)},e.src=h.toDataURL()}}ri(l),o.restore(),t.currentHost=n,t.noCanvasEngineUpdates=m},n=t.currentHost,r=n.element,o=n.engine,a=n.currentDimensions,l=ni(),h=l.element,c=l.engine;t.currentHost=l;let u,d,f=h.width=a[0]||r.width,p=h.height=a[1]||r.height,m=t.noCanvasEngineUpdates;t.noCanvasEngineUpdates=!1,t.regularStampSynchronousActions(),!t.noFilters&&t.filters&&t.filters.length?(t.isStencil&&(c.save(),c.globalCompositeOperation="source-in",c.globalAlpha=1,c.setTransform(1,0,0,1,0,0),c.drawImage(r,0,0),c.restore()),c.setTransform(1,0,0,1,0,0),d=c.getImageData(0,0,f,p),u=ve(),t.preprocessFilters(t.currentFilters),xe(u,{image:d,filters:t.currentFilters}).then(t=>{if(!t)throw new Error("image issue");c.globalCompositeOperation="source-over",c.globalAlpha=1,c.setTransform(1,0,0,1,0,0),c.putImageData(t,0,0),s(),e(!0)}).catch(t=>{s(),i(t)})):(s(),e(!0))})},t.getCellCoverage=function(t){let e,i,s=t.width,n=t.height,r=t.data,o=0,a=0,l=s,h=n,c=3;for(i=0;ie&&(l=e),oi&&(h=i),a=1)return.9999;let e=this.unitPositions;if(e&&e.length){let e,i,s,n,r,o,a,l=this.length,h=this.unitProgression,c=this.unitPositions,u=t*l,d=-1;for(let t=0,e=h.length;t=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers()},t.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0},t.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.dirtyDimensions&&(this.cleanSpecies(),this.pathCalculatedOnce=!1),this.calculateLocalPath(this.pathDefinition),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyHandle&&this.cleanHandle(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions();let t=this.currentStampHandlePosition;this.pathObject=new Path2D(`m${-t[0]},${-t[1]}${this.localPath}`)}},t.calculateLocalPath=function(t,e){let i;if(this.pathCalculatedOnce||(i=function(t,e,i,s,n){let r,o,a,l,h=[],c=[],u="",d="",f=[],p=[],m=[],g=t.match(/([A-Za-z][0-9. ,\-]*)/g),y=0,b={},k=0,S=0,O=0,P=0,v=[],w=[],x=[],A=[],C=0,D=0,R=t=>{c.push({c:u.toLowerCase(),p:t||null,x:O,y:P,cx:k,cy:S,rx:C,ry:D}),s||(v.push(k),w.push(S)),O=k,P=S};for(r=0,o=g.length;r0&&c[r-1],{c:s,p:n,x:o,y:a,cx:l,cy:h,rx:u,ry:d}=e;if(n)switch(s){case"h":f[r]=["linear",o,a,n[0]+o,a];break;case"v":f[r]=["linear",o,a,o,n[0]+a];break;case"m":f[r]=["move",o,a];break;case"l":f[r]=["linear",o,a,n[0]+o,n[1]+a];break;case"t":i&&(i.rx||i.ry)?(ys(t,i.rx-l,i.ry-h),bs(t,180),f[r]=["quadratic",o,a,t.x+l,t.y+h,n[0]+o,n[1]+a]):f[r]=["quadratic",o,a,o,a,n[0]+o,n[1]+a];break;case"q":f[r]=["quadratic",o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a];break;case"s":i&&(i.rx||i.ry)?(ys(t,i.rx-l,i.ry-h),bs(t,180),f[r]=["bezier",o,a,t.x+l,t.y+h,n[0]+o,n[1]+a,n[2]+o,n[3]+a]):f[r]=["bezier",o,a,o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a];break;case"c":f[r]=["bezier",o,a,n[0]+o,n[1]+a,n[2]+o,n[3]+a,n[4]+o,n[5]+a];break;case"a":f[r]=["linear",o,a,n[5]+o,n[6]+a];break;case"z":isNaN(o)&&(o=0),isNaN(a)&&(a=0),f[r]=["close",o,a];break;default:isNaN(o)&&(o=0),isNaN(a)&&(a=0),f[r]=["unknown",o,a]}else f[r]=["no-points-"+s,o,a]}for(b.units=f,r=0,o=f.length;rt+e,0);let e=0;for(r=0,o=p.length;r{let e=i[t];e&&(e.currentPathData=!1,e.dirtyStart=!0,e.addPathHandle&&(e.dirtyHandle=!0),e.addPathOffset&&(e.dirtyOffset=!0),e.addPathRotation&&(e.dirtyRotation=!0),"Polyline"===e.type?e.dirtyPins=!0:"Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||e.dirtyPins.push(this.name))},this)},t.draw=function(t){t.stroke(this.pathObject),this.showBoundingBox&&this.drawBoundingBox(t)},t.fill=function(t){t.fill(this.pathObject,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.drawAndFill=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),t.fill(e,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.fillAndDraw=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),t.fill(e,this.winding),t.stroke(e),this.showBoundingBox&&this.drawBoundingBox(t)},t.drawThenFill=function(t){let e=this.pathObject;t.stroke(e),t.fill(e,this.winding),this.showBoundingBox&&this.drawBoundingBox(t)},t.fillThenDraw=function(t){let e=this.pathObject;t.fill(e,this.winding),t.stroke(e),this.showBoundingBox&&this.drawBoundingBox(t)},t.clear=function(t){let e=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fill(this.pathObject,this.winding),t.globalCompositeOperation=e,this.showBoundingBox&&this.drawBoundingBox(t)},t.drawBoundingBox=function(t){t.save(),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.strokeRect(...this.getBoundingBox()),t.restore()},t.getBoundingBox=function(){let t=Math.floor,e=Math.ceil,i=this.minimumBoundingBoxDimensions,[s,n,r,o]=this.localBox,[a,l]=this.currentStampHandlePosition,[h,c]=this.currentStampPosition;return r"string"!=typeof t?"":t.charAt(0).toUpperCase()+t.slice(1);function vs(t={}){t.defs=_(t.defs,{end:null,endPivot:"",endPivotCorner:"",addEndPivotHandle:!1,addEndPivotOffset:!1,endPath:"",endPathPosition:0,addEndPathHandle:!1,addEndPathOffset:!0,endParticle:"",endLockTo:"",useStartAsControlPoint:!1}),t.packetExclusions=Q(t.packetExclusions,["controlledLineOffset"]),t.packetExclusionsByRegex=Q(t.packetExclusionsByRegex,[]),t.packetCoordinates=Q(t.packetCoordinates,["end"]),t.packetObjects=Q(t.packetObjects,["endPivot","endPath"]),t.packetFunctions=Q(t.packetFunctions,[]),t.factoryKill=function(){Object.entries(i).forEach(([t,e])=>{e.name!==this.name&&(e.startControlPivot&&e.startControlPivot.name===this.name&&e.set({startControlPivot:!1}),e.controlPivot&&e.controlPivot.name===this.name&&e.set({controlPivot:!1}),e.endControlPivot&&e.endControlPivot.name===this.name&&e.set({endControlPivot:!1}),e.endPivot&&e.endPivot.name===this.name&&e.set({endPivot:!1}),e.startControlPath&&e.startControlPath.name===this.name&&e.set({startControlPath:!1}),e.controlPath&&e.controlPath.name===this.name&&e.set({controlPath:!1}),e.endControlPath&&e.endControlPath.name===this.name&&e.set({endControlPath:!1}),e.endPath&&e.endPath.name===this.name&&e.set({endPath:!1}))})};t.getters;let e=t.setters,s=t.deltaSetters;return e.useStartAsControlPoint=function(t){if(this.useStartAsControlPoint=t,!t){let t=this.controlledLineOffset;t[0]=0,t[1]=0}this.updateDirty()},e.endPivot=function(t){this.setControlHelper(t,"endPivot","end"),this.updateDirty(),this.dirtyEnd=!0},e.endParticle=function(t){this.setControlHelper(t,"endParticle","end"),this.updateDirty(),this.dirtyEnd=!0},e.endPath=function(t){this.setControlHelper(t,"endPath","end"),this.updateDirty(),this.dirtyEnd=!0},e.endPathPosition=function(t){this.endPathPosition=t,this.dirtyEnd=!0,this.currentEndPathData=!1},s.endPathPosition=function(t){this.endPathPosition+=t,this.dirtyEnd=!0,this.currentEndPathData=!1},e.endX=function(t){null!=t&&(this.end[0]=t,this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1)},e.endY=function(t){null!=t&&(this.end[1]=t,this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1)},e.end=function(t,e){this.setCoordinateHelper("end",t,e),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.endX=function(t){let e=this.end;e[0]=H(e[0],t),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.endY=function(t){let e=this.end;e[1]=H(e[1],t),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},s.end=function(t,e){this.setDeltaCoordinateHelper("end",t,e),this.updateDirty(),this.dirtyEnd=!0,this.currentEndPathData=!1},e.endLockTo=function(t){this.endLockTo=t,this.updateDirty(),this.dirtyEndLock=!0,this.currentEndPathData=!1},t.curveInit=function(t){this.end=He(),this.currentEnd=He(),this.endLockTo="coord",this.dirtyEnd=!0,this.dirtyPins=[],this.controlledLineOffset=He()},t.setControlHelper=function(t,e,s){if(Y(t)&&!t)this[e]=null,"startControl"===s?this.dirtyStartControlLock=!0:"control"===s?this.dirtyControlLock=!0:"endControl"===s?this.dirtyEndControlLock=!0:this.dirtyEndLock=!0;else if(t){let n=this[e],r=t.substring?i[t]:t;e.indexOf("Pivot")>0?r&&r.isArtefact&&(n&&n.isArtefact&&K(n.pivoted,this.name),Q(r.pivoted,this.name),this[e]=r):e.indexOf("Path")>0?r&&r.isArtefact&&(n&&n.isArtefact&&K(n.pathed,this.name),Q(r.pathed,this.name),this[e]=r):e.indexOf("Particle")>0&&(r=t.substring?d[t]:t,r||(this.updateDirty(),"startControl"===s?this.dirtyStartControl=!0:"control"===s?this.dirtyControl=!0:"endControl"===s?this.dirtyEndControl=!0:this.dirtyEnd=!0,this[e]=t))}},t.buildPathPositionObject=function(t,e){if(t){let i,s,[n,...r]=t;switch(n){case"linear":i=this.positionPointOnPath(this.getLinearXY(e,...r)),s=this.getLinearAngle(e,...r);break;case"quadratic":i=this.positionPointOnPath(this.getQuadraticXY(e,...r)),s=this.getQuadraticAngle(e,...r);break;case"bezier":i=this.positionPointOnPath(this.getBezierXY(e,...r)),s=this.getBezierAngle(e,...r)}let o=0;this.flipReverse&&o++,this.flipUpend&&o++,1===o&&(s=-s),s+=this.roll;this.currentStampPosition;let a=this.controlledLineOffset;this.localBox;return i.x+=a[0],i.y+=a[1],i.angle=s,i}return!1},t.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1),this.dirtyPins.length&&this.preparePinsForStamp(),this.dirtyLock&&this.cleanLock(),this.dirtyStartControlLock&&this.cleanControlLock("startControl"),this.dirtyEndControlLock&&this.cleanControlLock("endControl"),this.dirtyControlLock&&this.cleanControlLock("control"),this.dirtyEndLock&&this.cleanControlLock("end"),(this.dirtyScale||this.dirtySpecies||this.dirtyDimensions||this.dirtyStart||this.dirtyStartControl||this.dirtyEndControl||this.dirtyControl||this.dirtyEnd||this.dirtyHandle)&&(this.dirtyPathObject=!0,this.useStartAsControlPoint&&this.dirtyStart&&(this.dirtySpecies=!0,this.pathCalculatedOnce=!1),(this.dirtyScale||this.dirtySpecies||this.dirtyStartControl||this.dirtyEndControl||this.dirtyControl||this.dirtyEnd)&&(this.pathCalculatedOnce=!1)),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.useStartAsControlPoint&&(this.dirtySpecies=!0,this.dirtyPathObject=!0,this.pathCalculatedOnce=!1)),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),(this.dirtyStartControl||"particle"===this.startControlLockTo)&&this.cleanControl("startControl"),(this.dirtyEndControl||"particle"===this.endControlLockTo)&&this.cleanControl("endControl"),(this.dirtyControl||"particle"===this.controlLockTo)&&this.cleanControl("control"),(this.dirtyEnd||"particle"===this.endLockTo)&&this.cleanControl("end"),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&(this.updatePositionSubscribers(),this.updateControlPathSubscribers())},t.cleanControlLock=function(t){let e=Ps(t);this[`dirty${e}Lock`]=!1,this["dirty"+e]=!0},t.cleanControl=function(t){let e=Ps(t);this["dirty"+e]=!1;let s,n,r=t+"Path",o=t+"Particle",a=this[t+"Pivot"],l=this[r],h=this[o];a&&a.substring&&(s=i[a],s&&(a=s)),l&&l.substring&&(s=i[l],s&&(l=s)),h&&h.substring&&(s=d[h],s&&(h=s));let c,u,f,p,m,g,y,b=this[t+"LockTo"],k=this[t],S=this["current"+e];switch(("pivot"!==b||a&&!a.substring)&&("path"!==b||l&&!l.substring)&&("particle"!==b||h&&!h.substring)||(b="coord"),b){case"pivot":this.pivotCorner&&a.getCornerCoordinate?[c,u]=a.getCornerCoordinate(this[t+"PivotCorner"]):[c,u]=a.currentStampPosition,this.addPivotOffset||([f,p]=a.currentOffset,c-=f,u-=p);break;case"path":n=this.getControlPathData(l,t,e),c=n.x,u=n.y,this.addPathOffset||(c-=l.currentOffset[0],u-=l.currentOffset[1]);break;case"particle":c=h.position.x,u=h.position.y,this.pathCalculatedOnce=!1;break;case"mouse":m=this.getHere(),c=m.x||0,u=m.y||0;break;default:c=u=0,g=this.getHost(),g&&(y=g.currentDimensions,y&&(this.cleanPosition(S,k,y),[c,u]=S))}S[0]=c,S[1]=u,this.dirtySpecies=!0,this.dirtyPathObject=!0,this.dirtyPositionSubscribers=!0},t.getControlPathData=function(t,e,i){let s=this[`current${i}PathData`];if(s)return s;let n=this[e+"PathPosition"],r=n,o=t.getPathPositionData(n);if(n<0&&(n+=1),n>1&&(n%=1),n=parseFloat(n.toFixed(6)),n!==r&&(this[e+"PathPosition"]=n),o)return this[`current${i}PathData`]=o,o;{let t=this.getHost();if(t){let s=t.currentDimensions;if(s){let t=this["current"+i];return this.cleanPosition(t,this[e],s),{x:t[0],y:t[1]}}}return{x:0,y:0}}},t.updateControlPathSubscribers=function(){[].concat(this.endSubscriber,this.endControlSubscriber,this.controlSubscriber,this.startControlSubscriber).forEach(t=>{let e=i[t];e&&("Line"!==e.type&&"Quadratic"!==e.type&&"Bezier"!==e.type||("Quadratic"===e.type?(e.dirtyControl=!0,e.currentControlPathData=!1):"Bezier"===e.type&&(e.dirtyStartControl=!0,e.dirtyEndControl=!0,e.currentStartControlPathData=!1,e.currentEndControlPathData=!1),e.currentEndPathData=!1,e.dirtyEnd=!0),e.currentPathData=!1,e.dirtyStart=!0)})},t}const Bezier=function(t={}){return this.startControl=He(),this.endControl=He(),this.currentStartControl=He(),this.currentEndControl=He(),this.startControlLockTo="coord",this.endControlLockTo="coord",this.curveInit(t),this.shapeInit(t),this.dirtyStartControl=!0,this.dirtyEndControl=!0,this};let ws=Bezier.prototype=Object.create(Object.prototype);ws.type="Bezier",ws.lib="entity",ws.isArtefact=!0,ws.isAsset=!1,ws=ee(ws),ws=Os(ws),ws=vs(ws);ws.defs=_(ws.defs,{startControl:null,startControlPivot:"",startControlPivotCorner:"",addStartControlPivotHandle:!1,addStartControlPivotOffset:!1,startControlPath:"",startControlPathPosition:0,addStartControlPathHandle:!1,addStartControlPathOffset:!0,startControlParticle:"",endControl:null,endControlPivot:"",endControlPivotCorner:"",addEndControlPivotHandle:!1,addEndControlPivotOffset:!1,endControlPath:"",endControlPathPosition:0,addEndControlPathHandle:!1,addEndControlPathOffset:!0,endControlParticle:"",startControlLockTo:"",endControlLockTo:""}),ws.packetExclusions=Q(ws.packetExclusions,[]),ws.packetExclusionsByRegex=Q(ws.packetExclusionsByRegex,[]),ws.packetCoordinates=Q(ws.packetCoordinates,["startControl","endControl"]),ws.packetObjects=Q(ws.packetObjects,["startControlPivot","startControlPath","endControlPivot","endControlPath"]),ws.packetFunctions=Q(ws.packetFunctions,[]);ws.getters;let xs=ws.setters,As=ws.deltaSetters;xs.endControlPivot=function(t){this.setControlHelper(t,"endControlPivot","endControl"),this.updateDirty(),this.dirtyEndControl=!0},xs.endControlParticle=function(t){this.setControlHelper(t,"endControlParticle","endControl"),this.updateDirty(),this.dirtyEndControl=!0},xs.endControlPath=function(t){this.setControlHelper(t,"endControlPath","endControl"),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},xs.endControlPathPosition=function(t){this.endControlPathPosition=t,this.dirtyEndControl=!0,this.currentEndControlPathData=!1},As.endControlPathPosition=function(t){this.endControlPathPosition+=t,this.dirtyEndControl=!0,this.currentEndControlPathData=!1},xs.startControlPivot=function(t){this.setControlHelper(t,"startControlPivot","startControl"),this.updateDirty(),this.dirtyStartControl=!0},xs.startControlParticle=function(t){this.setControlHelper(t,"startControlParticle","startControl"),this.updateDirty(),this.dirtyStartControl=!0},xs.startControlPath=function(t){this.setControlHelper(t,"startControlPath","startControl"),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},xs.startControlPathPosition=function(t){this.startControlPathPosition=t,this.dirtyStartControl=!0,this.currentStartControlPathData=!1},As.startControlPathPosition=function(t){this.startControlPathPosition+=t,this.dirtyStartControl=!0,this.currentStartControlPathData=!1},xs.startControlX=function(t){null!=t&&(this.startControl[0]=t,this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1)},xs.startControlY=function(t){null!=t&&(this.startControl[1]=t,this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1)},xs.startControl=function(t,e){this.setCoordinateHelper("startControl",t,e),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},As.startControlX=function(t){let e=this.startControl;e[0]=H(e[0],t),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},As.startControlY=function(t){let e=this.startControl;e[1]=H(e[1],t),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},As.startControl=function(t,e){this.setDeltaCoordinateHelper("startControl",t,e),this.updateDirty(),this.dirtyStartControl=!0,this.currentStartControlPathData=!1},xs.endControlX=function(t){null!=t&&(this.endControl[0]=t,this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1)},xs.endControlY=function(t){null!=t&&(this.endControl[1]=t,this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1)},xs.endControl=function(t,e){this.setCoordinateHelper("endControl",t,e),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},As.endControlX=function(t){let e=this.endControl;e[0]=H(e[0],t),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},As.endControlY=function(t){let e=this.endControl;e[1]=H(e[1],t),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},As.endControl=function(t,e){this.setDeltaCoordinateHelper("endControl",t,e),this.updateDirty(),this.dirtyEndControl=!0,this.currentEndControlPathData=!1},xs.startControlLockTo=function(t){this.startControlLockTo=t,this.updateDirty(),this.dirtyStartControlLock=!0},xs.endControlLockTo=function(t){this.endControlLockTo=t,this.updateDirty(),this.dirtyEndControlLock=!0,this.currentEndControlPathData=!1},ws.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeBezierPath(),this.pathDefinition=t},ws.makeBezierPath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentStartControl,[n,r]=this.currentEndControl,[o,a]=this.currentEnd;return`m0,0c${(i-t).toFixed(2)},${(s-e).toFixed(2)} ${(n-t).toFixed(2)},${(r-e).toFixed(2)} ${(o-t).toFixed(2)},${(a-e).toFixed(2)}`},ws.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyStartControl=!0,this.dirtyEndControl=!0,this.dirtyEnd=!0},ws.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath,i=this.startControlPivot,s=this.startControlPath,n=this.endControlPivot,r=this.endControlPath;this.dirtyPins.forEach(o=>{(i&&i.name===o||s&&s.name===o)&&(this.dirtyStartControl=!0,this.startControlLockTo.includes("path")&&(this.currentStartControlPathData=!1)),(n&&n.name===o||r&&r.name===o)&&(this.dirtyEndControl=!0,this.endControlLockTo.includes("path")&&(this.currentEndControlPathData=!1)),(t&&t.name===o||e&&e.name===o)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};O.Bezier=Bezier;const Cog=function(t={}){return this.shapeInit(t),this};let Cs=Cog.prototype=Object.create(Object.prototype);Cs.type="Cog",Cs.lib="entity",Cs.isArtefact=!0,Cs.isAsset=!1,Cs=ee(Cs),Cs=Os(Cs);Cs.defs=_(Cs.defs,{outerRadius:0,innerRadius:0,outerControlsDistance:0,innerControlsDistance:0,outerControlsOffset:0,innerControlsOffset:0,points:0,twist:0,curve:"bezier"});let Ds=Cs.setters,Rs=Cs.deltaSetters;Ds.outerRadius=function(t){this.outerRadius=t,this.updateDirty()},Rs.outerRadius=function(t){this.outerRadius+=t,this.updateDirty()},Ds.innerRadius=function(t){this.innerRadius=t,this.updateDirty()},Rs.innerRadius=function(t){this.innerRadius+=t,this.updateDirty()},Ds.outerControlsDistance=function(t){this.outerControlsDistance=t,this.updateDirty()},Rs.outerControlsDistance=function(t){this.outerControlsDistance+=t,this.updateDirty()},Ds.innerControlsDistance=function(t){this.innerControlsDistance=t,this.updateDirty()},Rs.innerControlsDistance=function(t){this.innerControlsDistance+=t,this.updateDirty()},Ds.outerControlsOffset=function(t){this.outerControlsOffset=t,this.updateDirty()},Rs.outerControlsOffset=function(t){this.outerControlsOffset+=t,this.updateDirty()},Ds.innerControlsOffset=function(t){this.innerControlsOffset=t,this.updateDirty()},Rs.innerControlsOffset=function(t){this.innerControlsOffset+=t,this.updateDirty()},Ds.points=function(t){this.points=t,this.updateDirty()},Rs.points=function(t){this.points+=t,this.updateDirty()},Ds.twist=function(t){this.twist=t,this.updateDirty()},Rs.twist=function(t){this.twist+=t,this.updateDirty()},Ds.curve=function(t){t&&["line","quadratic","bezier"].indexOf(t)>=0?(this.curve=t,this.updateDirty()):(this.curve="bezier",this.updateDirty())},Cs.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeCogPath(),this.pathDefinition=t},Cs.makeCogPath=function(){let t,e,i,s,n,r,o,{points:a,twist:l,outerRadius:h,innerRadius:c,outerControlsDistance:u,innerControlsDistance:d,outerControlsOffset:f,innerControlsOffset:p,curve:m}=this,g=360/a,y=[],b="";if(h.substring||c.substring||u.substring||d.substring||f.substring||p.substring){let t=this.getHost();if(t){let[e,i]=t.currentDimensions;h=h.substring?parseFloat(h)/100*e:h,c=c.substring?parseFloat(c)/100*e:c,u=u.substring?parseFloat(u)/100*e:u,d=d.substring?parseFloat(d)/100*e:d,f=f.substring?parseFloat(f)/100*e:f,p=p.substring?parseFloat(p)/100*e:p}}let k=fi({x:0,y:-h}),S=fi({x:0,y:-c}),O=fi({x:u+f,y:-h}),P=fi({x:-d+p,y:-c}),v=fi({x:d+p,y:-c}),w=fi({x:-u+f,y:-h});if(P.rotate(-g/2),P.rotate(l),S.rotate(-g/2),S.rotate(l),v.rotate(-g/2),v.rotate(l),t=k.x,e=k.y,y.push(t),"bezier"==m)for(o=0;o{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;U(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),U(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),this.deregister(),this},Es.get=function(t){if(J(t)){if("random"===t)return this.generateRandomColor(),this.get();if(t.toFixed)return this.getRangeColor(t);{let e=this.getters[t];if(e)return e.call(this);{let e=this.defs[t];if(void 0!==e){let i=this[t];return void 0!==i?i:e}return undef}}}{let{r:t,g:e,b:i,a:s}=this;return this.opaque?`rgb(${t}, ${e}, ${i})`:`rgba(${t}, ${e}, ${i}, ${s})`}},Es.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs;Object.entries(t).forEach(([t,n])=>{"name"!==t&&(e=i[t],e?e.call(this,n):void 0!==s[t]&&(this[t]=n))},this)}return t.random?this.generateRandomColor(t):this.checkValues(),this};let Fs=Es.setters;Fs.color=function(t){this.convert(t)},Es.setColor=function(t){return this.convert(t),this},Fs.minimumColor=function(t){let{r:e,g:i,b:s,a:n}=this;this.convert(t),this.rMin=this.r,this.gMin=this.g,this.bMin=this.b,this.aMin=this.a,this.r=e,this.g=i,this.b=s,this.a=n},Es.setMinimumColor=function(t){let{r:e,g:i,b:s,a:n}=this;return this.convert(t),this.rMin=this.r,this.gMin=this.g,this.bMin=this.b,this.aMin=this.a,this.r=e,this.g=i,this.b=s,this.a=n,this},Fs.maximumColor=function(t){let{r:e,g:i,b:s,a:n}=this;this.convert(t),this.rMax=this.r,this.gMax=this.g,this.bMax=this.b,this.aMax=this.a,this.r=e,this.g=i,this.b=s,this.a=n},Es.setMaximumColor=function(t){let{r:e,g:i,b:s,a:n}=this;return this.convert(t),this.rMax=this.r,this.gMax=this.g,this.bMax=this.b,this.aMax=this.a,this.r=e,this.g=i,this.b=s,this.a=n,this},Es.getData=function(){return this.autoUpdate&&this.update(),this.checkValues(),this.get()},Es.generateRandomColor=function(t={}){let e=Math.round,i=Math.random,s=this.rMax=et(t.rMax,this.rMax,255),n=this.gMax=et(t.gMax,this.gMax,255),r=this.bMax=et(t.bMax,this.bMax,255),o=this.rMin=et(t.rMin,this.rMin,0),a=this.gMin=et(t.gMin,this.gMin,0),l=this.bMin=et(t.bMin,this.bMin,0);return this.r=t.r||e(i()*(s-o)+o),this.g=t.g||e(i()*(n-a)+a),this.b=t.b||e(i()*(r-l)+l),this.opaque||(aMax=this.aMax=et(t.aMax,this.aMax,1),aMin=this.aMin=et(t.aMin,this.aMin,0),this.a=t.a||i()*(aMax-aMin)+aMin),this.checkValues(),this},Es.getRangeColor=function(t){if(J(t)&&t.toFixed){let e=Math.floor,{rMin:i,gMin:s,bMin:n,aMin:r,rMax:o,gMax:a,bMax:l,aMax:h,easing:c}=this;if("linear"!==c)switch(c){case"easeOutSine":t=rt(t);break;case"easeInSine":t=ot(t);break;case"easeOutInSine":t=at(t);break;case"easeOutQuad":t=lt(t);break;case"easeInQuad":t=ht(t);break;case"easeOutInQuad":t=ct(t);break;case"easeOutCubic":t=ut(t);break;case"easeInCubic":t=dt(t);break;case"easeOutInCubic":t=ft(t);break;case"easeOutQuart":t=pt(t);break;case"easeInQuart":t=mt(t);break;case"easeOutInQuart":t=gt(t);break;case"easeOutQuint":t=yt(t);break;case"easeInQuint":t=bt(t);break;case"easeOutInQuint":t=kt(t);break;case"easeOutExpo":t=St(t);break;case"easeInExpo":t=Ot(t);break;case"easeOutInExpo":t=Pt(t);break;case"easeOutCirc":t=vt(t);break;case"easeInCirc":t=wt(t);break;case"easeOutInCirc":t=At(t);break;case"easeOutBack":t=Ct(t);break;case"easeInBack":t=Dt(t);break;case"easeOutInBack":t=Rt(t);break;case"easeOutElastic":t=Et(t);break;case"easeInElastic":t=Ft(t);break;case"easeOutInElastic":t=Tt(t);break;case"easeOutBounce":t=Ht(t);break;case"easeInBounce":t=Mt(t);break;case"easeOutInBounce":t=It(t)}t>1?t=1:t<0&&(t=0),isNaN(t)&&(t=1);let u=e(i+(o-i)*t),d=e(s+(a-s)*t),f=e(n+(l-n)*t),p=e(r+(h-r)*t);return this.opaque?`rgb(${u}, ${d}, ${f})`:`rgba(${u}, ${d}, ${f}, ${p})`}{let{r:t,g:e,b:i,a:s}=this;return this.opaque?`rgb(${t}, ${e}, ${i})`:`rgba(${t}, ${e}, ${i}, ${s})`}},Es.checkValues=function(){let t=Math.floor,e=t(this.r)||0,i=t(this.g)||0,s=t(this.b)||0,n=this.a;return this.r=e>255?255:e<0?0:e,this.g=i>255?255:i<0?0:i,this.b=s>255?255:s<0?0:s,this.a=n>1?1:n<0?0:n,this},Es.updateArray=["r","g","b","a"],Es.update=function(){J(this.rCurrent)||(this.rCurrent=this.r),J(this.gCurrent)||(this.gCurrent=this.g),J(this.bCurrent)||(this.bCurrent=this.b),J(this.aCurrent)||(this.aCurrent=this.a);let t=this.updateArray;return(this.rShift||this.gShift||this.bShift||this.aShift)&&t.forEach(t=>{let e=this[t+"Shift"];if(e){this[t+"Current"]+=e;let i=this[t],s=this[t+"Min"],n=this[t+"Max"],r=this[t+"Bounce"],o=this[t+"Current"],a=Math.floor(o+e);(a>n||a(n+s)/2?n:s,e=0)),this[t]=a,this[t+"Shift"]=e}},this),this},Es.updateByDelta=Es.update,Es.convert=function(t){let e,i,s,n,r,o=Math.round;return(t=t.substring?t:"").length&&(t.toLowerCase(),e=0,i=0,s=0,n=1,"#"===t[0]?4==t.length?(e=parseInt(t[1]+t[1],16),i=parseInt(t[2]+t[2],16),s=parseInt(t[3]+t[3],16)):5==t.length?(e=parseInt(t[1]+t[1],16),i=parseInt(t[2]+t[2],16),s=parseInt(t[3]+t[3],16),n=parseInt(t[4]+t[4],16)/255):7==t.length?(e=parseInt(t[1]+t[2],16),i=parseInt(t[3]+t[4],16),s=parseInt(t[5]+t[6],16)):9==t.length&&(e=parseInt(t[1]+t[2],16),i=parseInt(t[3]+t[4],16),s=parseInt(t[5]+t[6],16),n=parseInt(t[7]+t[8],16)/255):/rgb\(/.test(t)?(r=t.match(/([0-9.]+\b)/g),/%/.test(t)?(e=o(parseFloat(r[0])/100*255),i=o(parseFloat(r[1])/100*255),s=o(parseFloat(r[2])/100*255)):(e=o(parseFloat(r[0])),i=o(parseFloat(r[1])),s=o(parseFloat(r[2])))):/rgba\(/.test(t)?(r=t.match(/([0-9.]+\b)/g),/%/.test(t)?(e=o(parseFloat(r[0])/100*255),i=o(parseFloat(r[1])/100*255),s=o(parseFloat(r[2])/100*255),n=parseFloat(r[3])/100):(e=o(parseFloat(r[0])),i=o(parseFloat(r[1])),s=o(parseFloat(r[2])),n=r[3])):/hsl\(/.test(t)||/hsla\(/.test(t)?(r=t.match(/([0-9.]+\b)/g),this.setFromHSL(parseFloat(r[0]),parseFloat(r[1]),parseFloat(r[2]),parseFloat(r[3]))):"transparent"===t?(e=0,i=0,s=0,n=0):(r=this.colorLibrary[t],r&&(e=parseInt(r[0]+r[1],16),i=parseInt(r[2]+r[3],16),s=parseInt(r[4]+r[5],16),n=1)),this.r=e,this.g=i,this.b=s,this.a=n,this.checkValues()),this},Es.getHSLfromRGB=function(t,e,i){let s=Math.min(t,e,i),n=Math.max(t,e,i),r=(s+n)/2,o=0;s!==n&&(o=r<=.5?(n-s)/(n+s):(n-s)/(2-n-s));let a=0;return a=n===t?(e-i)/(n-s):n===e?2+(i-t)/(n-s):4+(t-e)/(n-s),a*=60,a<0&&(a+=360),[a,o,r]},Es.getHSL=function(){let{r:t,g:e,b:i}=this,s=Math.min(t,e,i),n=Math.max(t,e,i),r=(s+n)/2,o=0;s!==n&&(o=r<=.5?(n-s)/(n+s):(n-s)/(2-n-s));let a=0;return a=n===t?(e-i)/(n-s):n===e?2+(i-t)/(n-s):4+(t-e)/(n-s),a*=60,a<0&&(a+=360),[a,o,r]},Es.getRGBfromHSL=function(t,e,i,s){if(!e){let t=Math.floor(255*i);return[t,t,t]}let n=i<.5?i*(e+1):i+e-i*e,r=2*i-n;const o=function(t,e,i){return 6*t<1?i+6*(e-i)*t:2*t<1?e:2*t<2?i+6*(e-i)*(.666*t):i};let a=(t/=360)+.333,l=t,h=t-.333;a<0&&(a+=1),a>1&&(a-=1),l<0&&(l+=1),l>1&&(l-=1),h<0&&(h+=1),h>1&&(h-=1);let c=255*o(a,n,r),u=255*o(l,n,r),d=255*o(h,n,r);return null==s?[c,u,d]:[c,u,d,255*s]},Es.setFromHSL=function(t,e,i,s){if(!e){let t=Math.floor(255*i);return[t,t,t]}let n=i<.5?i*(e+1):i+e-i*e,r=2*i-n;const o=function(t,e,i){return 6*t<1?i+6*(e-i)*t:2*t<1?e:2*t<2?i+6*(e-i)*(.666*t):i};let a=(t/=360)+.333,l=t,h=t-.333;a<0&&(a+=1),a>1&&(a-=1),l<0&&(l+=1),l>1&&(l-=1),h<0&&(h+=1),h>1&&(h-=1),this.r=255*o(a,n,r),this.g=255*o(l,n,r),this.b=255*o(h,n,r),null!=s&&(this.a=255*s)},Es.colorLibrary={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffe4c4",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"778899",lightslategrey:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"};const Ts=function(t){return new Color(t)};O.Color=Color;const ParticleHistory=function(t){let e=[];return Object.setPrototypeOf(e,ParticleHistory.prototype),t&&e.set(t),e};let Hs=ParticleHistory.prototype=Object.create(Array.prototype);Hs.constructor=ParticleHistory,Hs.type="ParticleHistory";const Ms=[],Is=function(t){t&&"ParticleHistory"===t.type&&(t.length=0,Ms.push(t),Ms.length>100&&(Ms.length=0))};O.ParticleHistory=ParticleHistory;const Particle=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.initializePositions(),this.set(t),this};let Bs=Particle.prototype=Object.create(Object.prototype);Bs.type="Particle",Bs.lib="particle",Bs.isArtefact=!1,Bs.isAsset=!1,Bs=ee(Bs);Bs.defs=_(Bs.defs,{position:null,velocity:null,load:null,history:null,historyLength:1,engine:"euler",forces:null,mass:1,fill:"#000000",stroke:"#000000"}),Bs.packetExclusions=Q(Bs.packetExclusions,[]),Bs.packetExclusionsByRegex=Q(Bs.packetExclusionsByRegex,["^(local|dirty|current)"]),Bs.packetCoordinates=Q(Bs.packetCoordinates,[]),Bs.packetObjects=Q(Bs.packetObjects,["position","velocity","acceleration"]),Bs.packetFunctions=Q(Bs.packetFunctions,[]),Bs.factoryKill=function(){this.history.forEach(t=>Is(t));let t=[];m.forEach(e=>{let i=p[e];(i.particleFrom&&i.particleFrom.name===this.name||i.particleTo&&i.particleTo.name===this.name)&&t.push[i]}),t.forEach(t=>t.kill())};let Ls=Bs.getters,$s=Bs.setters,js=Bs.deltaSetters;Ls.positionX=function(){return this.position.x},Ls.positionY=function(){return this.position.y},Ls.positionZ=function(){return this.position.z},Ls.position=function(){let t=this.position;return[t.x,t.y,t.z]},$s.positionX=function(t){this.position.x=t},$s.positionY=function(t){this.position.y=t},$s.positionZ=function(t){this.position.z=t},$s.position=function(t){this.position.set(t)},js.positionX=function(t){this.position.x+=t},js.positionY=function(t){this.position.y+=t},js.positionZ=function(t){this.position.z+=t},js.position=B,Ls.velocityX=function(){return this.velocity.x},Ls.velocityY=function(){return this.velocity.y},Ls.velocityZ=function(){return this.velocity.z},Ls.velocity=function(){let t=this.velocity;return[t.x,t.y,t.z]},$s.velocityX=function(t){this.velocity.x=t},$s.velocityY=function(t){this.velocity.y=t},$s.velocityZ=function(t){this.velocity.z=t},$s.velocity=function(t,e,i){this.velocity.set(t,e,i)},js.velocityX=function(t){this.velocity.x+=t},js.velocityY=function(t){this.velocity.y+=t},js.velocityZ=function(t){this.velocity.z+=t},js.velocity=B,$s.forces=function(t){t&&(Array.isArray(t)?(this.forces.length=0,this.forces=this.forces.concat(t)):this.forces.push(t))},$s.load=B,$s.history=B,js.load=B,Bs.initializePositions=function(){this.initialPosition=mi(),this.position=mi(),this.velocity=mi(),this.load=mi(),this.forces=[],this.history=[],this.isRunning=!1},Bs.applyForces=function(t,e){this.load.zero(),this.isBeingDragged||this.forces.forEach(i=>{let s=f[i];s&&s.action&&s.action(this,t,e)})},Bs.update=function(t,e){this.isBeingDragged?this.position.setFromVector(this.isBeingDragged).vectorAdd(this.dragOffset):Gs[this.engine].call(this,t*e.tickMultiplier)},Bs.manageHistory=function(t,e){let{history:i,remainingTime:s,position:n,historyLength:r,hasLifetime:o,distanceLimit:a,initialPosition:l,killBeyondCanvas:h}=this,c=!0,u=0;if(o)if(u=s-t,u<=0){let t=i.pop();Is(t),c=!1,i.length||(this.isRunning=!1)}else this.remainingTime=u;let d=i[i.length-1];if(d){let[t,i,s,n]=d;if(h){let t=e.element.width,i=e.element.height;(s<0||n<0||s>t||n>i)&&(c=!1,this.isRunning=!1)}if(a){let t=fi(l);t.vectorSubtractArray([s,n,i]),t.getMagnitude()>a&&(c=!1,this.isRunning=!1),pi(t)}}if(c){let{x:t,y:e,z:s}=n,o=(Ms.length||Ms.push(new ParticleHistory),Ms.shift());if(o.push(u,s,t,e),i.unshift(o),i.length>r){i.splice(r).forEach(t=>Is(t))}}},Bs.run=function(t,e,i){this.hasLifetime=!1,t&&(this.remainingTime=t,this.hasLifetime=!0),this.distanceLimit=0,e&&(this.initialPosition.set(this.position),this.distanceLimit=e),this.killBeyondCanvas=i,this.isRunning=!0};const Ns=function(t){return new Particle(t)};O.Particle=Particle;const Xs=[],Ys=function(t){Xs.length||Xs.push(new Particle);let e=Xs.shift();return e.set(t),e},zs=function(t){if(t&&"Particle"===t.type&&(t.history.forEach(t=>Is(t)),t.history.length=0,t.set(t.defs),Xs.push(t),Xs.length>50)){let t=[].concat(Xs);Xs.length=0,t.forEach(t=>t.kill())}},Gs={euler:function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=fi(),o=fi(i);r.setFromVector(s).scalarDivide(n),o.vectorAdd(r.scalarMultiply(t)),i.setFromVector(o),e.vectorAdd(o.scalarMultiply(t)),pi(r)},"improved-euler":function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=fi(),o=fi(),a=fi(),l=fi(i);r.setFromVector(s).scalarDivide(n).scalarMultiply(t),o.setFromVector(s).vectorAdd(r).scalarDivide(n).scalarMultiply(t),a.setFromVector(r).vectorAdd(o).scalarDivide(2),l.vectorAdd(a),i.setFromVector(l),e.vectorAdd(l.scalarMultiply(t)),pi(r)},"runge-kutta":function(t){let{position:e,velocity:i,load:s,mass:n}=this,r=fi(),o=fi(),a=fi(),l=fi(),h=fi(),c=fi(i);r.setFromVector(s).scalarDivide(n).scalarMultiply(t).scalarDivide(2),o.setFromVector(s).vectorAdd(r).scalarDivide(n).scalarMultiply(t).scalarDivide(2),a.setFromVector(s).vectorAdd(o).scalarDivide(n).scalarMultiply(t).scalarDivide(2),l.setFromVector(s).vectorAdd(a).scalarDivide(n).scalarMultiply(t).scalarDivide(2),o.scalarMultiply(2),a.scalarMultiply(2),h.setFromVector(r).vectorAdd(o).vectorAdd(a).vectorAdd(l).scalarDivide(6),c.vectorAdd(h),i.setFromVector(c),e.vectorAdd(c.scalarMultiply(t)),pi(r)}},Emitter=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.fillColorFactory=Ts({name:this.name+"-fillColorFactory"}),this.strokeColorFactory=Ts({name:this.name+"-strokeColorFactory"}),this.range=mi(),this.rangeFrom=mi(),this.preAction=B,this.stampAction=B,this.postAction=B,this.particleStore=[],this.deadParticles=[],this.liveParticles=[],t.group||(t.group=Gi),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let Ws=Emitter.prototype=Object.create(Object.prototype);Ws.type="Emitter",Ws.lib="entity",Ws.isArtefact=!0,Ws.isAsset=!1,Ws=ee(Ws),Ws=ps(Ws);Ws.defs=_(Ws.defs,{world:null,artefact:null,range:null,rangeFrom:null,generationRate:0,particleCount:0,generateAlongPath:!1,generateInArea:!1,generateFromExistingParticles:!1,generateFromExistingParticleHistories:!1,limitDirectionToAngleMultiples:0,generationChoke:15,killAfterTime:0,killAfterTimeVariation:0,killRadius:0,killRadiusVariation:0,killBeyondCanvas:!1,historyLength:1,forces:null,mass:1,massVariation:0,engine:"euler",hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000",resetAfterBlur:3}),Ws.packetExclusions=Q(Ws.packetExclusions,["forces","particleStore","deadParticles","liveParticles","fillColorFactory","strokeColorFactory"]),Ws.packetExclusionsByRegex=Q(Ws.packetExclusionsByRegex,[]),Ws.packetCoordinates=Q(Ws.packetCoordinates,[]),Ws.packetObjects=Q(Ws.packetObjects,["world","artefact","generateInArea","generateAlongPath"]),Ws.packetFunctions=Q(Ws.packetFunctions,["preAction","stampAction","postAction"]),Ws.finalizePacketOut=function(t,e){let i=e.forces||this.forces||!1;if(i){let e=[];i.forEach(t=>{t.substring?e.push(t):U(t)&&t.name&&e.push(t.name)}),t.forces=e}let s=[];return this.particleStore.forEach(t=>s.push(t.saveAsPacket())),t.particleStore=s,t},Ws.postCloneAction=function(t,e){return t},Ws.factoryKill=function(t,e){this.isRunning=!1,t&&this.artefact.kill(),e&&this.world.kill(),this.fillColorFactory.kill(),this.strokeColorFactory.kill(),this.deadParticles.forEach(t=>t.kill()),this.liveParticles.forEach(t=>t.kill()),this.particleStore.forEach(t=>t.kill())};Ws.getters;let Vs=Ws.setters,Us=Ws.deltaSetters;Vs.rangeX=function(t){this.range.x=t},Vs.rangeY=function(t){this.range.y=t},Vs.rangeZ=function(t){this.range.z=t},Vs.range=function(t){this.range.set(t)},Vs.rangeFromX=function(t){this.rangeFrom.x=t},Vs.rangeFromY=function(t){this.rangeFrom.y=t},Vs.rangeFromZ=function(t){this.rangeFrom.z=t},Vs.rangeFrom=function(t){this.rangeFrom.set(t)},Vs.preAction=function(t){W(t)&&(this.preAction=t)},Vs.stampAction=function(t){W(t)&&(this.stampAction=t)},Vs.postAction=function(t){W(t)&&(this.postAction=t)},Vs.world=function(t){let e;t.substring?e=g[t]:U(t)&&"World"===t.type&&(e=t),e&&(this.world=e)},Vs.artefact=function(t){let e;t.substring?e=i[t]:U(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},Vs.generateAlongPath=function(t){let e;t.substring?e=i[t]:U(t)&&t.isArtefact&&(e=t),e&&e.useAsPath?this.generateAlongPath=e:this.generateAlongPath=!1},Vs.generateInArea=function(t){let e;t.substring?e=i[t]:U(t)&&t.isArtefact&&(e=t),this.generateInArea=e||!1},Vs.fillColor=function(t){this.fillColorFactory.set({color:t})},Vs.fillMinimumColor=function(t){this.fillColorFactory.set({minimumColor:t})},Vs.fillMaximumColor=function(t){this.fillColorFactory.set({maximumColor:t})},Vs.strokeColor=function(t){this.strokeColorFactory.set({color:t})},Vs.strokeMinimumColor=function(t){this.strokeColorFactory.set({minimumColor:t})},Vs.strokeMaximumColor=function(t){this.strokeColorFactory.set({maximumColor:t})},Vs.hitRadius=function(t){t.toFixed&&(this.hitRadius=t,this.width=this.height=2*t)},Us.hitRadius=function(t){t.toFixed&&(this.hitRadius+=t,this.width=this.height=2*this.hitRadius)},Vs.width=function(t){t.toFixed&&(this.hitRadius=t/2,this.width=this.height=t)},Us.width=function(t){t.toFixed&&(this.hitRadius=t/2,this.width=this.height=t)},Vs.height=Vs.width,Us.height=Us.width,Ws.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1,this.dirtyDimensions=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions();let t=Date.now(),{particleStore:e,deadParticles:i,liveParticles:s,particleCount:n,generationRate:r,generatorChoke:o,resetAfterBlur:a}=this;o||(this.generatorChoke=o=t),e.forEach(t=>{t.isRunning?s.push(t):i.push(t)}),e.length=0,i.forEach(t=>zs(t)),i.length=0,e.push(...s),s.length=0;let l=t-o;if(l/1e3>a&&(l=0,this.generatorChoke=t),l>0&&r){let i=Math.floor(r/1e3*l);if(n){let t=n-e.length;t<=0?i=0:tD.isPointInPath(E,...t,$);A.rotateDestination(D,o,a,C);t:for(n=0;n1?([l,g,...y]=a[Math.floor(Math.random()*a.length)],y?C.setFromArray(y):C.setFromVector(o.position)):C.setFromVector(o.position)):C.setFromArray(b),r=Ys(),r.set({positionX:C.x,positionY:C.y,positionZ:C.z,historyLength:h,engine:c,forces:u,mass:i(d,f),fill:p.get("random"),stroke:m.get("random")}),E?(C.zero(),e=Math.floor(360/E),C.x=s(I,T),C.rotate(Math.floor(Math.random()*e)*E),r.set({velocityX:C.x,velocityY:C.y,velocityZ:s(L,M)})):r.set({velocityX:s(I,T),velocityY:s(B,H),velocityZ:s(L,M)}),pi(C),r.velocity.rotate(x);let t=Math.abs(i(S,O)),n=Math.abs(i(P,v));r.run(t,n,w),k.push(r)}}else if(D){let e,o,a=k.length,l=fi();for(n=0;na&&(i.forEach(t=>zs(t)),i.length=0,f=.016),i.forEach(e=>e.applyForces(t,d)),i.forEach(e=>e.update(f,t)),s.call(this,d),i.forEach(t=>{t.manageHistory(f,d),n.call(this,e,t,d)}),r.call(this,d),l){let t=d.engine;t.save(),t.lineWidth=1,t.strokeStyle=c,t.setTransform(1,0,0,1,0,0),t.beginPath(),t.arc(u[0],u[1],h,0,2*Math.PI),t.stroke(),t.restore()}this.lastUpdated=p},Ws.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;let i,s,n=Array.isArray(t)?t:[t],r=this.currentStampPosition,o=!1;if(n.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!tt(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=fi(r).vectorSubtract(t);return e.getMagnitude(){let{mass:s,load:n}=t,r=fi();r.setFromVector(e.gravity).scalarMultiply(s),n.vectorAdd(r),pi(r)}});const Palette=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.colors=t.colors||{"0 ":[0,0,0,1],"999 ":[255,255,255,1]},this.stops=Array(1e3),this.set(t),this.dirtyPalette=!0,this};let Zs=Palette.prototype=Object.create(Object.prototype);Zs.type="Palette",Zs.lib="palette",Zs.isArtefact=!1,Zs.isAsset=!1,Zs=ee(Zs);Zs.defs=_(Zs.defs,{colors:null,stops:null,cyclic:!1}),Zs.packetExclusions=Q(Zs.packetExclusions,["stops"]);Zs.getters;let Qs=Zs.setters;Qs.colors=function(t){if(U(t)){let e=this.factory;Object.entries(t).forEach(([i,s])=>{s.substring&&(e.convert(s),t[i]=[e.r,e.g,e.b,e.a])}),this.colors=t,this.dirtyPalette=!0}},Qs.stops=B,Zs.recalculateHold=[],Zs.recalculate=function(){let t,e,i,s,n,r,o,a,l,h,c,u,d,f,p,m=this.colors,g=this.stops,y=this.makeColorString,b=this.recalculateHold;for(t=Object.keys(m),t=t.map(t=>parseInt(t,10)),t.sort((t,e)=>t-e),g.fill("rgba(0,0,0,0)"),this.dirtyPalette=!1,e=0,i=t.length-1;e999&&(r-=1e3);else{if(r=t[0],r>0)for(c=g[r],e=0,i=r;et=(t=ti?i:t;return e=o(r(t[0]),0,255),i=o(r(t[1]),0,255),s=o(r(t[2]),0,255),n=o(t[3],0,1),`rgba(${e},${i},${s},${n})`},Zs.updateColor=function(t,e){let i=this.factory;tt(t,e)&&(t=t.substring?parseInt(t,10):Math.floor(t))>=0&&t<=999&&(i.convert(e),t+=" ",this.colors[t]=[i.r,i.g,i.b,i.a],this.dirtyPalette=!0)},Zs.removeColor=function(t){J(t)&&(t=t.substring?parseInt(t,10):Math.floor(t))>=0&&t<=999&&(t+=" ",delete this.colors[t],this.dirtyPalette=!0)},Zs.addStopsToGradient=function(t,e,i,s){let n,r,o,a,l,h,c=this.stops,u=Object.keys(this.colors);if(t){if(u=u.map(t=>parseInt(t,10)),u.sort((t,e)=>t-e),tt(e,i)||(e=0,i=999),e===i)return c[e]||"rgba(0,0,0,0)";if(ee&&l0&&r<1&&t.addColorStop(r,c[l]));else if(s)for(t.addColorStop(0,c[e]),t.addColorStop(1,c[i]),h=999-e,n=h+i,o=0,a=u.length;oe)r=(l-e)/n;else{if(!(l0&&r<1&&t.addColorStop(r,c[l])}else for(t.addColorStop(0,c[e]),t.addColorStop(1,c[i]),n=e-i,o=0,a=u.length;oi&&(r=1-(l-i)/n,r>0&&r<1&&t.addColorStop(r,c[l]));return t}return"rgba(0,0,0,0)"},Zs.factory=Ts({name:"palette-factory-color-calculator",opaque:!1});function Ks(t={}){t.defs=_(t.defs,{start:null,end:null,palette:null,paletteStart:0,paletteEnd:999,cyclePalette:!1}),t.finalizePacketOut=function(t,e){return e.colors?t.colors=e.colors:this.palette&&this.palette.colors?t.colors=this.palette.colors:t.colors={"0 ":[0,0,0,1],"999 ":[255,255,255,1]},t},t.kill=function(){let t=this.name;return this.palette&&this.palette.kill&&this.palette.kill(),Object.entries(h).forEach(([e,i])=>{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;U(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),U(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),this.deregister(),this};let e=t.getters,i=t.setters,s=t.deltaSetters;return e.startX=function(){return this.currentStart[0]},e.startY=function(){return this.currentStart[1]},i.startX=function(t){null!=t&&(this.start[0]=t,this.dirtyStart=!0)},i.startY=function(t){null!=t&&(this.start[1]=t,this.dirtyStart=!0)},i.start=function(t,e){this.setCoordinateHelper("start",t,e),this.dirtyStart=!0},s.startX=function(t){let e=this.start;e[0]=H(e[0],t),this.dirtyStart=!0},s.startY=function(t){let e=this.start;e[1]=H(e[1],t),this.dirtyStart=!0},s.start=function(t,e){this.setDeltaCoordinateHelper("start",t,e),this.dirtyStart=!0},e.endX=function(){return this.currentEnd[0]},e.endY=function(){return this.currentEnd[1]},i.endX=function(t){null!=t&&(this.end[0]=t,this.dirtyEnd=!0)},i.endY=function(t){null!=t&&(this.end[1]=t,this.dirtyEnd=!0)},i.end=function(t,e){this.setCoordinateHelper("end",t,e),this.dirtyEnd=!0},s.endX=function(t){let e=this.end;e[0]=H(e[0],t),this.dirtyEnd=!0},s.endY=function(t){let e=this.end;e[1]=H(e[1],t),this.dirtyEnd=!0},s.end=function(t,e){this.setDeltaCoordinateHelper("end",t,e),this.dirtyEnd=!0},i.palette=function(t={}){"Palette"===t.type&&(this.palette=t)},i.paletteStart=function(t){t.toFixed&&(this.paletteStart=t,(t<0||t>999)&&(this.paletteStart=t>500?999:0))},s.paletteStart=function(t){let e;t.toFixed&&(e=this.paletteStart+t,(e<0||e>999)&&(e=this.cyclePalette?e>500?e-1e3:e+1e3:t>500?999:0),this.paletteStart=e)},i.paletteEnd=function(t){t.toFixed&&(this.paletteEnd=t,(t<0||t>999)&&(this.paletteEnd=t>500?999:0))},s.paletteEnd=function(t){let e;t.toFixed&&(e=this.paletteEnd+t,(e<0||e>999)&&(e=this.cyclePalette?e>500?e-1e3:e+1e3:t>500?999:0),this.paletteEnd=e)},i.colors=function(t){let e=this.palette;e&&e.colors&&e.set({colors:t})},i.delta=function(t={}){t&&(this.delta=Z(this.delta,t))},t.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.palette;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},t.set=function(t={}){if(Object.keys(t).length){let e=this.setters,i=this.defs,s=this.palette,n=s?s.setters:{},r=s?s.defs:{};Object.entries(t).forEach(([t,o])=>{if(t&&"name"!==t&&null!=o){let a=e[t],l=!1;a||(a=n[t],l=!0),a?a.call(l?this.palette:this,o):void 0!==i[t]?this[t]=o:void 0!==r[t]&&(s[t]=o)}},this)}return this},t.setDelta=function(t={}){if(Object.keys(t).length){let e=this.deltaSetters,i=this.defs,s=this.palette,n=s?s.deltaSetters:{},r=s?s.defs:{};Object.entries(t).forEach(([t,o])=>{if(t&&"name"!==t&&null!=o){let a=e[t],l=!1;a||(a=n[t],l=!0),a?a.call(l?this.palette:this,o):void 0!==i[t]?this[t]=H(this[t],o):void 0!==r[t]&&(s[t]=H(this[t],o))}},this)}return this},t.setCoordinateHelper=function(t,e,i){let s=this[t];Array.isArray(e)?(s[0]=e[0],s[1]=e[1]):(s[0]=e,s[1]=i)},t.setDeltaCoordinateHelper=function(t,e,i){let s=this[t],n=s[0],r=s[1];Array.isArray(e)?(s[0]=H(n,e[0]),s[1]=H(r,e[1])):(s[0]=H(n,e),s[1]=H(r,i))},t.updateByDelta=function(){return this.setDelta(this.delta),this},t.stylesInit=function(t={}){this.makeName(t.name),this.register(),this.gradientArgs=[],this.start=He(),this.end=He(),this.currentStart=He(),this.currentEnd=He(),this.palette=function(t){return new Palette(t)}({name:this.name+"_palette"}),this.delta={},this.set(this.defs),this.set(t)},t.getData=function(t,e){return this.palette&&this.palette.dirtyPalette&&this.palette.recalculate(),this.cleanStyle(t,e),this.finalizeCoordinates(t),this.buildStyle(e)},t.cleanStyle=function(t={},e={}){let i,s,n,r;t.lockFillStyleToEntity||t.lockStrokeStyleToEntity?(i=t.currentDimensions,r=t.currentScale,s=i[0]*r,n=i[1]*r):(i=e.currentDimensions,s=i[0],n=i[1]),this.cleanPosition(this.currentStart,this.start,[s,n]),this.cleanPosition(this.currentEnd,this.end,[s,n]),this.cleanRadius(s)},t.cleanPosition=function(t,e,i){let s,n;for(let r=0;r<2;r++)s=e[r],n=i[r],s.toFixed?t[r]=s:t[r]="left"===s||"top"===s?0:"right"===s||"bottom"===s?n:"center"===s?n/2:parseFloat(s)/100*n},t.finalizeCoordinates=function(t={}){this.currentStart,this.currentEnd;let e,i,s=t.currentStampPosition,n=t.currentStampHandlePosition,r=t.currentScale;t.lockFillStyleToEntity||t.lockStrokeStyleToEntity?(e=-n[0]*r||0,i=-n[1]*r||0):(e=-s[0]||0,i=-s[1]||0),this.updateGradientArgs(e,i)},t.cleanRadius=B,t.buildStyle=function(t){return"rgba(0,0,0,0)"},t.addStopsToGradient=function(t,e,i,s){return this.palette?this.palette.addStopsToGradient(t,e,i,s):t},t.updateColor=function(t,e){return this.palette&&this.palette.updateColor(t,e),this},t.removeColor=function(t){return this.palette&&this.palette.removeColor(t),this},t}O.Palette=Palette;const Gradient=function(t={}){return this.stylesInit(t),this};let Js=Gradient.prototype=Object.create(Object.prototype);Js.type="Gradient",Js.lib="styles",Js.isArtefact=!1,Js.isAsset=!1,Js=ee(Js),Js=Ks(Js),Js.packetObjects=Q(Js.packetObjects,["palette"]),Js.buildStyle=function(t={}){if(t){let e=t.engine;if(e){let t=e.createLinearGradient(...this.gradientArgs);return this.addStopsToGradient(t,this.paletteStart,this.paletteEnd,this.cyclePalette)}}return"rgba(0,0,0,0)"},Js.updateGradientArgs=function(t,e){let i=this.gradientArgs,s=this.currentStart,n=this.currentEnd,r=s[0]+t,o=s[1]+e,a=n[0]+t,l=n[1]+e;r===a&&o===l&&a++,i.length=0,i.push(r,o,a,l)};O.Gradient=Gradient;const Grid=function(t={}){return this.tileFill=[],this.tileSources=[],this.entityInit(t),t.tileSources||(this.tileSources=[].concat([{type:"color",source:"#000000"},{type:"color",source:"#ffffff"}])),t.tileFill?Array.isArray(t.tileFill)&&this.tileFill.length===t.tileFill.length&&(this.tileFill=t.tileFill):(this.tileFill.length=this.columns*this.rows,this.tileFill.fill(0)),this.tilePaths=[],this.tileRealCoordinates=[],this.tileVirtualCoordinates=[],t.dimensions||(t.width||(this.currentDimensions[0]=this.dimensions[0]=20),t.height||(this.currentDimensions[1]=this.dimensions[1]=20)),this};let tn=Grid.prototype=Object.create(Object.prototype);tn.type="Grid",tn.lib="entity",tn.isArtefact=!0,tn.isAsset=!1,tn=ee(tn),tn=ps(tn);tn.defs=_(tn.defs,{columns:2,rows:2,columnGutterWidth:1,rowGutterWidth:1,tileSources:null,tileFill:null,gutterColor:"#808080"}),tn.packetExclusions=Q(tn.packetExclusions,["tileSources"]),tn.finalizePacketOut=function(t,e){let i=t.tileSources=[];this.tileSources.forEach(t=>{i.push({type:t.type,source:U(t.source)?t.source.name:t.source})}),U(t.gutterColor)&&(t.gutterColor=t.gutterColor.name);let s=JSON.parse(this.state.saveAsPacket(e))[3];return t=_(t,s),t=this.handlePacketAnchor(t,e)};tn.getters;let en=tn.setters,sn=tn.deltaSetters;en.columns=function(t){if(V(t)&&(Number.isInteger(t)||(t=parseInt(t,10)),t!==this.columns)){let e,i,s,n=this.tileFill,r=this.columns,o=[];for(this.columns=t,e=0,i=this.rows;e{V(t)&&(i[t]=e)}))},tn.setTileSourceTo=function(t,e){V(t)&&U(e)&&e.type&&e.source&&(this.tileSources[t]=e)},tn.removeTileSource=function(t){V(t)&&t&&(this.tileSources[t]=null,this.tileFill=this.tileFill.map(e=>e===t?0:e))},tn.getTileSource=function(t,e){if(V(t))return V(e)?this.tileFill[t*this.rows+e]:this.tileFill[t]},tn.getTilesUsingSource=function(t){let e=[];return V(t)&&this.tileFill.forEach((i,s)=>i==t&&e.push(s)),e},tn.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=new Path2D,i=new Path2D,s=this.currentStampHandlePosition,n=this.currentScale,r=this.currentDimensions,o=-s[0]*n,a=-s[1]*n,l=r[0]*n,h=r[1]*n;t.rect(o,a,l,h);let c,u,d,f,p=this.columns,m=this.rows,g=l/p,y=h/m,b=this.tilePaths,k=this.tileRealCoordinates,S=this.tileVirtualCoordinates;for(e.moveTo(o,a),e.lineTo(o+l,a),c=1;c<=m;c++){let t=a+c*y;e.moveTo(o,t),e.lineTo(o+l,t)}for(this.rowLines=e,i.moveTo(o,a),i.lineTo(o,a+h),u=1;u<=p;u++){let t=o+u*g;i.moveTo(t,a),i.lineTo(t,a+h)}for(this.columnLines=i,b.length=0,k.length=0,S.length=0,c=0;c{if(r&&r.type)switch(r.type){case"color":t.fillStyle=r.source;break;case"cellGradient":this.lockFillStyleToEntity=!1,t.fillStyle=r.source.getData(this,this.currentHost);break;case"gridGradient":this.lockFillStyleToEntity=!0,t.fillStyle=r.source.getData(this,this.currentHost)}let y=o.map(t=>t===g);if(y.length)switch(r.type){case"gridPicture":e=r.source.substring?h[r.source]:r.source,e.simpleStamp&&(n.width=m[0]*p,n.height=m[1]*p,s.globalCompositeOperation="source-over",s.fillStyle="#000000",y.forEach((t,e)=>{t&&s.fillRect(c[e][0],c[e][1],d,f)}),s.globalCompositeOperation="source-in",e.simpleStamp(i,{startX:0,startY:0,width:m[0]*p,height:m[1]*p,method:"fill"}),t.drawImage(n,l[0][0],l[0][1]));break;case"tilePicture":e=r.source.substring?h[r.source]:r.source,e.simpleStamp&&(n.width=d,n.height=f,s.globalCompositeOperation="source-over",e.simpleStamp(i,{startX:0,startY:0,width:d,height:f,method:"fill"}),y.forEach((e,i)=>e&&t.drawImage(n,l[i][0],l[i][1])));break;default:y.forEach((e,i)=>e&&t.fill(a[i],u))}});let g,y=this.gutterColor,b=this.rowGutterWidth,k=this.columnGutterWidth;if(J(y)){switch(y.substring?g={type:"color",source:this.gutterColor}:U(y)?g=y:V(y)&&U(r[y])&&(g=r[y]),g.type){case"cellGradient":this.lockFillStyleToEntity=!1,t.strokeStyle=g.source.getData(this,this.currentHost);break;case"gridGradient":this.lockFillStyleToEntity=!0,t.strokeStyle=g.source.getData(this,this.currentHost);break;case"color":t.strokeStyle=g.source}switch(g.type){case"gridPicture":case"tilePicture":if((b||k)&&(e=g.source.substring?h[g.source]:g.source,e.simpleStamp)){let r=this.currentStampHandlePosition,o=this.currentScale,a=r[0]*o,h=r[1]*o;n.width=m[0]*o,n.height=m[1]*o,s.globalCompositeOperation="source-over",s.strokeStyle="#000000",s.translate(a,h),b&&(s.lineWidth=b,s.stroke(this.rowLines)),k&&(s.lineWidth=k,s.stroke(this.columnLines)),s.globalCompositeOperation="source-in",e.simpleStamp(i,{startX:0,startY:0,width:m[0]*o,height:m[1]*o,method:"fill"}),t.drawImage(n,l[0][0],l[0][1]),s.translate(0,0)}break;default:b&&(t.lineWidth=b,t.stroke(this.rowLines)),k&&(t.lineWidth=k,t.stroke(this.columnLines))}}ri(i),t.restore()},tn.fill=function(t){this.performFill(t)},tn.drawAndFill=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),this.performFill(t)},tn.fillAndDraw=function(t){let e=this.pathObject;t.stroke(e),this.currentHost.clearShadow(),this.performFill(t),t.stroke(e)},tn.drawThenFill=function(t){let e=this.pathObject;t.stroke(e),this.performFill(t)},tn.fillThenDraw=function(t){let e=this.pathObject;this.performFill(t),t.stroke(e)},tn.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;this.pathObject&&!this.dirtyPathObject||this.cleanPathObject();let i=Array.isArray(t)?t:[t],s=!1;e||(e=ni(),s=!0);let n,r,o,a=e.engine,l=this.currentStampPosition,h=l[0],c=l[1],u=new Set,d=this.tilePaths;const f=t=>{let e,i;if(Array.isArray(t))e=t[0],i=t[1];else{if(!tt(t,t.x,t.y))return[!1];e=t.x,i=t.y}return!e.toFixed||!i.toFixed||isNaN(e)||isNaN(i)?[!1]:[!0,e,i]};return e.rotateDestination(a,h,c,this),i.some(t=>([n,r,o]=f(t),!!n&&a.isPointInPath(this.pathObject,r,o,this.winding)),this)?(i.forEach(t=>{[n,r,o]=f(t),n&&d.some((t,e)=>!!a.isPointInPath(t,r,o,this.winding)&&(u.add(e),!0))}),s&&ri(e),{x:r,y:o,tiles:[...u],artefact:this}):(s&&ri(e),!1)};O.Grid=Grid;const Line=function(t={}){return this.curveInit(t),this.shapeInit(t),this};let nn=Line.prototype=Object.create(Object.prototype);nn.type="Line",nn.lib="entity",nn.isArtefact=!0,nn.isAsset=!1,nn=ee(nn),nn=Os(nn),nn=vs(nn),nn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeLinePath(),this.pathDefinition=t},nn.makeLinePath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentEnd;return`m0,0l${(i-t).toFixed(2)},${(s-e).toFixed(2)}`},nn.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyEnd=!0},nn.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath;this.dirtyPins.forEach(i=>{(t&&t.name===i||e&&e.name===i)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};O.Line=Line;const Loom=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.state=De(),t.group||(t.group=Gi),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.delta={},this.set(t),this.fromPathData=[],this.toPathData=[],this.watchFromPath=null,this.watchIndex=-1,this.engineInstructions=[],this.engineDeltaLengths=[],this};let rn=Loom.prototype=Object.create(Object.prototype);rn.type="Loom",rn.lib="entity",rn.isArtefact=!0,rn.isAsset=!1,rn=ee(rn),rn=_e(rn);rn.defs=_(rn.defs,{fromPath:null,toPath:null,fromPathStart:0,fromPathEnd:1,toPathStart:0,toPathEnd:1,synchronizePathCursors:!0,loopPathCursors:!0,constantPathSpeed:!0,isHorizontalCopy:!0,showBoundingBox:!1,boundingBoxColor:"#000000",source:null,sourceIsVideoOrSprite:!1,interferenceLoops:2,interferenceFactor:1.03,visibility:!0,order:0,delta:null,host:null,group:null,anchor:null,noCanvasEngineUpdates:!1,noDeltaUpdates:!1,onEnter:null,onLeave:null,onDown:null,onUp:null,noUserInteraction:!1,method:"fill"}),rn.packetExclusions=Q(rn.packetExclusions,["pathObject","state"]),rn.packetExclusionsByRegex=Q(rn.packetExclusionsByRegex,["^(local|dirty|current)","Subscriber$"]),rn.packetCoordinates=Q(rn.packetCoordinates,[]),rn.packetObjects=Q(rn.packetObjects,["group","fromPath","toPath","source"]),rn.packetFunctions=Q(rn.packetFunctions,["onEnter","onLeave","onDown","onUp"]),rn.processPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},rn.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];return t=_(t,i),t=this.handlePacketAnchor(t,e)},rn.handlePacketAnchor=function(t,e){if(this.anchor){let i=JSON.parse(this.anchor.saveAsPacket(e))[3];t.anchor=i}return t},rn.clone=$;let on=rn.getters,an=rn.setters,ln=rn.deltaSetters;rn.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},rn.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=r?r.setters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=l:void 0!==a[t]&&(r[t]=l))},this)}return this},rn.setDelta=function(t={}){if(Object.keys(t).length){let e,i,s=this.deltaSetters,n=this.defs,r=this.state,o=r?r.deltaSetters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=addStrings(this[t],l):void 0!==a[t]&&(r[t]=addStrings(r[t],l)))},this)}return this},an.host=function(t){if(t){let e=i[t];e&&e.here?this.host=e.name:this.host=t}else this.host=""},on.group=function(){return this.group?this.group.name:""},an.group=function(t){let e;t&&(this.group&&"Group"===this.group.type&&this.group.removeArtefacts(this.name),t.substring?(e=group[t],this.group=e||t):this.group=t),this.group&&"Group"===this.group.type&&this.group.addArtefacts(this.name)},rn.getHere=function(){return currentCorePosition},an.delta=function(t={}){t&&(this.delta=Z(this.delta,t))},an.fromPath=function(t){if(t){let e=this.fromPath,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&removeItem(e.pathed,n),Q(s.pathed,n),this.fromPath=s,this.dirtyStart=!0)}},an.toPath=function(t){if(t){let e=this.toPath,s=t.substring?i[t]:t,n=this.name;s&&s.name&&s.useAsPath&&(e&&e.name!==s.name&&removeItem(e.pathed,n),Q(s.pathed,n),this.toPath=s,this.dirtyStart=!0)}},an.source=function(t){if((t=t.substring?i[t]:t)&&"Picture"===t.type){let e=this.source;e&&"Picture"===e.type&&e.imageUnsubscribe(this.name),this.source=t,t.imageSubscribe(this.name),this.dirtyInput=!0}},an.isHorizontalCopy=function(t){this.isHorizontalCopy=!!t,this.dirtyPathData=!0},an.synchronizePathCursors=function(t){this.synchronizePathCursors=!!t,t&&(this.toPathStart=this.fromPathStart,this.toPathEnd=this.fromPathEnd),this.dirtyPathData=!0},an.loopPathCursors=function(t){if(this.loopPathCursors=!!t,t){let t,e=Math.floor;t=this.fromPathStart,(t<0||t>1)&&(this.fromPathStart=t-e(t)),t=this.fromPathEnd,(t<0||t>1)&&(this.fromPathEnd=t-e(t)),t=this.toPathStart,(t<0||t>1)&&(this.toPathStart=t-e(t)),t=this.toPathEnd,(t<0||t>1)&&(this.toPathEnd=t-e(t))}this.dirtyOutput=!0},an.fromPathStart=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.fromPathStart=t,this.synchronizePathCursors&&(this.toPathStart=t),this.dirtyPathData=!0},ln.fromPathStart=function(t){let e=this.fromPathStart+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.fromPathStart=e,this.synchronizePathCursors&&(this.toPathStart=e),this.dirtyPathData=!0},an.fromPathEnd=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.fromPathEnd=t,this.synchronizePathCursors&&(this.toPathEnd=t),this.dirtyPathData=!0},ln.fromPathEnd=function(t){let e=this.fromPathEnd+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.fromPathEnd=e,this.synchronizePathCursors&&(this.toPathEnd=e),this.dirtyPathData=!0},an.toPathStart=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.toPathStart=t,this.synchronizePathCursors&&(this.fromPathStart=t),this.dirtyPathData=!0},ln.toPathStart=function(t){let e=this.toPathStart+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.toPathStart=e,this.synchronizePathCursors&&(this.fromPathStart=e),this.dirtyPathData=!0},an.toPathEnd=function(t){this.loopPathCursors&&(t<0||t>1)&&(t-=Math.floor(t)),this.toPathEnd=t,this.synchronizePathCursors&&(this.fromPathEnd=t),this.dirtyPathData=!0},ln.toPathEnd=function(t){let e=this.toPathEnd+=t;this.loopPathCursors&&(e<0||e>1)&&(e-=Math.floor(e)),this.toPathEnd=e,this.synchronizePathCursors&&(this.fromPathEnd=e),this.dirtyPathData=!0},rn.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=i[this.host];if(t)return this.currentHost=t,this.dirtyHost=!0,this.currentHost}return currentCorePosition},rn.updateByDelta=function(){return this.setDelta(this.delta),this},rn.reverseByDelta=function(){let t={};return Object.entries(this.delta).forEach(([e,i])=>{i=i.substring?-parseFloat(i)+"%":-i,t[e]=i}),this.setDelta(t),this},rn.setDeltaValues=function(t={}){let e,i,s=this.delta;return Object.entries(t).forEach(([t,n])=>{if(xt(s[t]))switch(i=n,e=s[t],i){case"reverse":e.toFixed&&(s[t]=-e);break;case"zero":e.toFixed&&(s[t]=0)}}),this},rn.midInitActions=B,rn.cleanCollisionData=function(){return[0,[]]},rn.getSensors=function(){return[]},rn.prepareStamp=function(){let t=this.fromPath,e=this.toPath,[i,s,n,r]=this.getBoundingBox();if(!this.dirtyPathData){let{x:i,y:s}=t.getPathPositionData(0),{x:n,y:r}=t.getPathPositionData(1),{x:o,y:a}=e.getPathPositionData(0),{x:l,y:h}=e.getPathPositionData(1),c=[i,s,n,r,o,a,l,h];this.pathTests&&!this.pathTests.some((t,e)=>t!==c[e])||(this.pathTests=c,this.dirtyPathData=!0)}if(this.dirtyPathData||!this.fromPathData.length){this.dirtyPathData=!1,this.watchIndex=-1,this.engineInstructions.length=0,this.engineDeltaLengths.length=0;let n=Math.ceil,r=Math.max,o=Math.min,a=this.fromPathData;a.length=0;let l=this.toPathData;if(l.length=0,t&&e){let h,c,u,d,f=n(t.length),p=n(e.length);h=this.setSourceDimension(r(f,p));let m,g,y,b=this.fromPathStart,k=this.fromPathEnd,S=this.toPathStart,O=this.toPathEnd,P=this.constantPathSpeed;m=b{t.cleanInput().catch(i=>{console.log(t.name+" - cleanInput Error: source has a zero dimension"),e(!1)}).then(e=>(t.sourceImageData=e,t.cleanOutput())).then(e=>(t.output=e,t.regularStamp())).then(t=>{e(!0)}).catch(t=>{i(t)})}):i?new Promise((e,i)=>{t.cleanOutput().then(e=>(t.output=e,t.regularStamp())).then(t=>{e(!0)}).catch(t=>{i(t)})}):this.regularStamp()}return Promise.resolve(!1)},rn.cleanInput=function(){let t=this;return new Promise((e,i)=>{t.dirtyInput=!1,t.setSourceDimension();let s=t.sourceDimension;s||(t.dirtyInput=!0,i());let n=ni(),r=n.engine,o=n.element;o.width=s,o.height=s,r.setTransform(1,0,0,1,0,0),t.source.stamp(!0,n,{startX:0,startY:0,handleX:0,handleY:0,offsetX:0,offsetY:0,roll:0,scale:1,width:s,height:s,method:"fill"}).then(t=>{let i=r.getImageData(0,0,s,s);ri(n),e(i)}).catch(t=>{ri(n),i(t)})})},rn.cleanOutput=function(){let t=this;return new Promise((e,i)=>{t.dirtyOutput=!1,t.setSourceDimension();let s=t.sourceDimension,n=t.sourceImageData;if(s&&n){let i,r,o,a,l,h,c,u,d,f,p,m=Math.hypot,g=Math.floor,y=Math.ceil,b=Math.atan2,k=Math.cos,S=Math.sin,O=t.fromPathData,P=t.toPathData,v=O.length,w=t.fromPathStart*v,x=t.fromPathSteps||1,A=t.toPathStart*v,C=t.toPathSteps||1,D=.5*Math.PI,R=D-1.5708,E=t.isHorizontalCopy,F=t.loopPathCursors,T=t.watchFromPath,H=t.watchIndex,M=t.engineInstructions,I=t.engineDeltaLengths,[B,L,$,j]=t.getBoundingBox(),N=ni(),X=N.engine,Y=N.element;Y.width=s,Y.height=s,X.setTransform(1,0,0,1,0,0),X.putImageData(n,0,0);let z=ni(),G=z.engine,W=z.element;if(W.width=$,W.height=j,G.globalAlpha=t.state.globalAlpha,G.setTransform(1,0,0,1,0,0),!M.length){for(let t=0;t=0&&A>=0?([i,r]=O[g(w)],[o,a]=P[g(A)],l=o-i,h=a-r,c=m(l,h),E?(u=-b(l,h)+D,d=k(u),f=S(u),M.push([d,f,-f,d,i,r]),I.push(c)):(u=-b(l,h)+R,d=k(u),f=S(u),M.push([d,f,-f,d,i,r,c]),I.push(c))):(M.push(!1),I.push(!1)),w+=x,A+=C,F&&(w>=v&&(w-=v),A>=v&&(A-=v));H<0&&(H=0),t.watchIndex=H}if(E)for(let t=0;t=s&&(H=0);else for(let t=0;t=s&&(H=0);let V=t.interferenceFactor,U=t.interferenceLoops,q=y($*V),_=y(j*V);Y.width=q,Y.height=_,G.setTransform(1,0,0,1,0,0),X.setTransform(1,0,0,1,0,0);for(let t=0;t{t.currentHost&&(t.regularStampSynchronousActions(),e(!0)),i(new Error(t.name+" has no current host"))})},rn.regularStampSynchronousActions=function(){let t=this.currentHost;if(t){let e=t.engine;this.noCanvasEngineUpdates||t.setEngine(this),this[this.method](e)}},rn.getBoundingBox=function(){let t=this.fromPath,e=this.toPath;if(t&&e){if(this.dirtyStart)if(t.getBoundingBox&&e.getBoundingBox){this.dirtyStart=!1;let[i,s,n,r,o,a]=t.getBoundingBox(),[l,h,c,u,d,f]=e.getBoundingBox();(isNaN(i)||isNaN(s)||isNaN(n)||isNaN(r)||isNaN(o)||isNaN(a)||isNaN(l)||isNaN(h)||isNaN(c)||isNaN(u)||isNaN(d)||isNaN(f))&&(this.dirtyStart=!0),i==l&&s==h&&n==c&&r==u&&o==d&&a==f&&(this.dirtyStart=!0),i+=o,s+=a,l+=d,h+=f;let p=Math.min(i,l),m=Math.max(i+n,l+c),g=Math.min(s,h),y=Math.max(s+r,h+u);this.boundingBox=[p,g,m-p,y-g],this.dirtyPathData=!0}else this.boundingBox=[0,0,0,0]}else this.boundingBox=[0,0,0,0];return this.boundingBox},rn.fill=function(t){this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.draw=function(t){this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.drawAndFill=function(t){this.doStroke(t),this.currentHost.clearShadow(),this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.fillAndDraw=function(t){this.doFill(t),this.currentHost.clearShadow(),this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.drawThenFill=function(t){this.doStroke(t),this.doFill(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.fillThenDraw=function(t){this.doFill(t),this.doStroke(t),this.showBoundingBox&&this.drawBoundingBox(t)},rn.clear=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element,s=t.globalCompositeOperation;if(e&&i){let i=ni(),n=i.engine,r=i.element,[o,a,l,h]=this.getBoundingBox();r.width=l,r.height=h,n.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.globalCompositeOperation="destination-out",t.drawImage(r,0,0,l,h,o,a,l,h),t.globalCompositeOperation=s,ri(i),this.showBoundingBox&&this.drawBoundingBox(t)}},rn.none=B,rn.doStroke=function(t){let e=this.fromPath,i=this.toPath;if(e&&e.getBoundingBox&&i&&i.getBoundingBox){let s=this.currentHost;if(s){let n=e.currentStampPosition,r=e.getPathPositionData(1),o=i.currentStampPosition,a=i.getPathPositionData(1);s.rotateDestination(t,n[0],n[1],e),t.stroke(e.pathObject),s.rotateDestination(t,o[0],o[1],e),t.stroke(i.pathObject),t.setTransform(1,0,0,1,0,0),t.beginPath(),t.moveTo(r.x,r.y),t.lineTo(a.x,a.y),t.moveTo(...o),t.lineTo(...n),t.closePath(),t.stroke()}}},rn.doFill=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element;if(e&&i){let i=ni(),s=i.engine,n=i.element,[r,o,a,l]=this.getBoundingBox();n.width=a,n.height=l,s.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.drawImage(n,0,0,a,l,r,o,a,l),ri(i)}},rn.drawBoundingBox=function(t){this.dirtyStart&&this.getBoundingBox(),t.save();let e=t.getTransform();t.setTransform(1,0,0,1,0,0),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.strokeRect(...this.boundingBox),t.restore(),t.setTransform(e)},rn.checkHit=function(t=[]){if(this.noUserInteraction)return!1;let e,i,s,n,r,o=Array.isArray(t)?t:[t],a=!(!this.output||!this.output.data)&&this.output.data;if(a){let[t,l,h,c]=this.getBoundingBox();if(o.some(o=>{if(Array.isArray(o))e=o[0],i=o[1];else{if(!tt(o,o.x,o.y))return!1;e=o.x,i=o.y}return!(!e.toFixed||!i.toFixed||isNaN(e)||isNaN(i))&&(s=e-t,n=i-l,!(s<0||s>h||n<0||n>c)&&(r=4*(n*h+s)+3,!!a&&a[r]>0))},this))return{x:e,y:i,artefact:this}}return!1};O.Loom=Loom;const hn=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.state=De(),t.group||(t.group=Gi),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.delta={},this.set(t),this.fromPathData=[],this.toPathData=[],this.watchFromPath=null,this.watchIndex=-1,this.engineInstructions=[],this.engineDeltaLengths=[],this};let cn=hn.prototype=Object.create(Object.prototype);cn.type="Mesh",cn.lib="entity",cn.isArtefact=!0,cn.isAsset=!1,cn=ee(cn),cn=_e(cn);cn.defs=_(cn.defs,{net:null,isHorizontalCopy:!0,source:null,sourceIsVideoOrSprite:!1,interferenceLoops:2,interferenceFactor:1.03,visibility:!0,order:0,delta:null,host:null,group:null,anchor:null,noCanvasEngineUpdates:!1,noDeltaUpdates:!1,onEnter:null,onLeave:null,onDown:null,onUp:null,noUserInteraction:!1,method:"fill"}),cn.packetExclusions=Q(cn.packetExclusions,["pathObject","state"]),cn.packetExclusionsByRegex=Q(cn.packetExclusionsByRegex,["^(local|dirty|current)","Subscriber$"]),cn.packetCoordinates=Q(cn.packetCoordinates,[]),cn.packetObjects=Q(cn.packetObjects,["group","net","source"]),cn.packetFunctions=Q(cn.packetFunctions,["onEnter","onLeave","onDown","onUp"]),cn.processPacketOut=function(t,e,i){let s=!0;return i.indexOf(t)<0&&e===this.defs[t]&&(s=!1),s},cn.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];return t=_(t,i),t=this.handlePacketAnchor(t,e)},cn.handlePacketAnchor=function(t,e){if(this.anchor){let i=JSON.parse(this.anchor.saveAsPacket(e))[3];t.anchor=i}return t},cn.clone=$;let un=cn.getters,dn=cn.setters;cn.deltaSetters;cn.get=function(t){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}},cn.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=r?r.setters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=l:void 0!==a[t]&&(r[t]=l))},this)}return this},cn.setDelta=function(t={}){if(Object.keys(t).length){let e,i,s=this.deltaSetters,n=this.defs,r=this.state,o=r?r.deltaSetters:{},a=r?r.defs:{};Object.entries(t).forEach(([t,l])=>{t&&"name"!==t&&null!=l&&(e=s[t],i=!1,e||(e=o[t],i=!0),e?e.call(i?this.state:this,l):void 0!==n[t]?this[t]=addStrings(this[t],l):void 0!==a[t]&&(r[t]=addStrings(r[t],l)))},this)}return this},dn.host=function(t){if(t){let e=i[t];e&&e.here?this.host=e.name:this.host=t}else this.host=""},un.group=function(){return this.group?this.group.name:""},dn.group=function(t){let e;t&&(this.group&&"Group"===this.group.type&&this.group.removeArtefacts(this.name),t.substring?(e=group[t],this.group=e||t):this.group=t),this.group&&"Group"===this.group.type&&this.group.addArtefacts(this.name)},cn.getHere=function(){return currentCorePosition},dn.delta=function(t={}){t&&(this.delta=Z(this.delta,t))},dn.net=function(t){t&&(t=t.substring?i[t]:t)&&"Net"===t.type&&(this.net=t,this.dirtyStart=!0)},dn.source=function(t){if((t=t.substring?i[t]:t)&&"Picture"===t.type){let e=this.source;e&&"Picture"===e.type&&e.imageUnsubscribe(this.name),this.source=t,t.imageSubscribe(this.name),this.dirtyInput=!0}},dn.isHorizontalCopy=function(t){this.isHorizontalCopy=!!t,this.dirtyPathData=!0},cn.getHost=function(){if(this.currentHost)return this.currentHost;if(this.host){let t=i[this.host];if(t)return this.currentHost=t,this.dirtyHost=!0,this.currentHost}return currentCorePosition},cn.updateByDelta=function(){return this.setDelta(this.delta),this},cn.reverseByDelta=function(){let t={};return Object.entries(this.delta).forEach(([e,i])=>{i=i.substring?-parseFloat(i)+"%":-i,t[e]=i}),this.setDelta(t),this},cn.setDeltaValues=function(t={}){let e,i,s=this.delta;return Object.entries(t).forEach(([t,n])=>{if(xt(s[t]))switch(i=n,e=s[t],i){case"reverse":e.toFixed&&(s[t]=-e);break;case"zero":e.toFixed&&(s[t]=0)}}),this},cn.midInitActions=B,cn.cleanCollisionData=function(){return[0,[]]},cn.getSensors=function(){return[]},cn.prepareStamp=function(){this.badNet=!0,this.dirtyParticles=!1;let{net:t,particlePositions:e}=this;if(t&&t.particleStore&&t.particleStore.length>3){let{rows:i,columns:s,particleStore:n}=t;if(i&&s){this.badNet=!1,this.rows=i,this.columns=s,e||(e=[]);let t=[];n.forEach(e=>{let i=e.position,{x:s,y:n}=i;t.push([s,n])});let r=t.join(",");e.join(",")!==r&&(this.particlePositions=t,this.dirtyInput=!0),this.sourceIsVideoOrSprite&&(this.dirtyInput=!0)}}},cn.setSourceDimension=function(){if(!this.badNet){const{columns:t,rows:e,particlePositions:i}=this,s=[],n=[],r=[],o=[],a=[],l=[],h=[],c=[],u=[];let d,f,p,m,g,y,b,k,S,O,P,v,w,x,A,C,D,R;for(p=0;pt&&(this.sourceDimension=t)}for(A=0,C=n.length;A{t.cleanInput().catch(i=>{console.log(t.name+" - cleanInput Error: source has a zero dimension"),e(!1)}).then(e=>(t.sourceImageData=e,t.cleanOutput())).then(e=>(t.output=e,t.regularStamp())).then(t=>{e(!0)}).catch(t=>{i(t)})}):this.regularStamp()}return Promise.resolve(!1)},cn.cleanInput=function(){let t=this;return new Promise((e,i)=>{t.dirtyInput=!1,t.setSourceDimension();let s=t.sourceDimension;s||(t.dirtyInput=!0,i());let n=ni(),r=n.engine,o=n.element;o.width=s,o.height=s,r.setTransform(1,0,0,1,0,0),t.source.stamp(!0,n,{startX:0,startY:0,handleX:0,handleY:0,offsetX:0,offsetY:0,roll:0,scale:1,width:s,height:s,method:"fill"}).then(t=>{let i=r.getImageData(0,0,s,s);ri(n),e(i)}).catch(t=>{ri(n),i(t)})})},cn.cleanOutput=function(){let t=this;return new Promise((e,i)=>{const s=Math.PI/2;t.dirtyOutput=!1;let{sourceDimension:n,sourceImageData:r,columns:o,rows:a,struts:l,boundingBox:h}=t;if(n=Math.ceil(n),r&&a-1>0){let[i,c,u,d]=h;u+=i,d+=c;const f=ni(),p=f.engine,m=f.element;m.width=n,m.height=n,p.setTransform(1,0,0,1,0,0),p.putImageData(r,0,0);const g=ni(),y=g.engine,b=g.element;b.width=u,b.height=d,y.globalAlpha=t.state.globalAlpha,y.setTransform(1,0,0,1,0,0);const k=parseFloat((n/(a-1)).toFixed(4)),S=parseFloat((n/(o-1)).toFixed(4));let O,P,v,w,x,A,C,D,R,E,F,T,H,M,I,B,L,$,j,N,X,Y,z;for(X=0,Y=a-1;Xn?n-M:k;y.drawImage(m,H,M,1,i,0,0,1,L)}}let G=t.interferenceFactor,W=t.interferenceLoops,V=Math.ceil(u*G),U=Math.ceil(d*G);m.width=V,m.height=U,y.setTransform(1,0,0,1,0,0),p.setTransform(1,0,0,1,0,0);for(let t=0;t{t.currentHost&&(t.regularStampSynchronousActions(),e(!0)),i(new Error(t.name+" has no current host"))})},cn.regularStampSynchronousActions=function(){let t=this.currentHost;if(t){let e=t.engine;this.noCanvasEngineUpdates||t.setEngine(this),this[this.method](e)}},cn.fill=function(t){this.doFill(t)},cn.draw=function(t){this.doStroke(t)},cn.drawAndFill=function(t){this.doStroke(t),this.currentHost.clearShadow(),this.doFill(t)},cn.fillAndDraw=function(t){this.doFill(t),this.currentHost.clearShadow(),this.doStroke(t)},cn.drawThenFill=function(t){this.doStroke(t),this.doFill(t)},cn.fillThenDraw=function(t){this.doFill(t),this.doStroke(t)},cn.clear=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element,s=t.globalCompositeOperation;if(e&&i){let n=ni(),r=n.engine,o=n.element,a=i.width,l=i.height;o.width=a,o.height=l,r.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.globalCompositeOperation="destination-out",t.drawImage(o,0,0),t.globalCompositeOperation=s,ri(n)}},cn.none=B,cn.doStroke=function(t){t.setTransform(1,0,0,1,0,0),t.stroke(this.pathObject)},cn.doFill=function(t){let e=this.output,i=!!this.currentHost&&this.currentHost.element;if(e&&i){let s=ni(),n=s.engine,r=s.element,o=i.width,a=i.height;r.width=o,r.height=a,n.putImageData(e,0,0),t.setTransform(1,0,0,1,0,0),t.drawImage(r,0,0),ri(s)}},cn.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;if(!this.pathObject)return!1;let i=Array.isArray(t)?t:[t],s=!1;e||(e=ni(),s=!0);let n,r,o=e.engine;if(i.some(t=>{if(Array.isArray(t))n=t[0],r=t[1];else{if(!tt(t,t.x,t.y))return!1;n=t.x,r=t.y}return!(!n.toFixed||!r.toFixed||isNaN(n)||isNaN(r))&&o.isPointInPath(this.pathObject,n,r,this.winding)},this)){let t={x:n,y:r,artefact:this};return s&&ri(e),t}return s&&ri(e),!1};O.Mesh=hn;const Spring=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this.action||(this.action=B),this};let fn=Spring.prototype=Object.create(Object.prototype);fn.type="Spring",fn.lib="spring",fn.isArtefact=!1,fn.isAsset=!1,fn=ee(fn);fn.defs=_(fn.defs,{particleFrom:null,particleFromIsStatic:!1,particleTo:null,particleToIsStatic:!1,springConstant:50,damperConstant:10,restLength:1}),fn.packetObjects=Q(fn.packetObjects,["particleFrom","particleTo"]),fn.kill=function(){return this.deregister(),!0};let pn=fn.setters;pn.particleFrom=function(t){t.substring&&(t=d[t]),t&&"Particle"===t.type&&(this.particleFrom=t)},pn.particleTo=function(t){t.substring&&(t=d[t]),t&&"Particle"===t.type&&(this.particleTo=t)},fn.applySpring=function(){let{particleFrom:t,particleTo:e,particleFromIsStatic:i,particleToIsStatic:s,springConstant:n,damperConstant:r,restLength:o}=this;if(t&&e){let{position:a,velocity:l,load:h}=t,{position:c,velocity:u,load:d}=e,f=fi(u).vectorSubtract(l),p=fi(c).vectorSubtract(a),m=fi(p).normalize(),g=fi(m);m.scalarMultiply(n*(p.getMagnitude()-o)),f.vectorMultiply(g).scalarMultiply(r).vectorMultiply(g);let y=fi(m).vectorAdd(f);i||h.vectorAdd(y),s||d.vectorSubtract(y),pi(f)}};const mn=function(t){return new Spring(t)};O.Spring=Spring;const Net=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.generate=B,this.postGenerate=B,this.stampAction=B,this.particleStore=[],this.springs=[],t.group||(t.group=Gi),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let gn=Net.prototype=Object.create(Object.prototype);gn.type="Net",gn.lib="entity",gn.isArtefact=!0,gn.isAsset=!1,gn=ee(gn),gn=ps(gn);gn.defs=_(gn.defs,{world:null,artefact:null,historyLength:1,forces:null,mass:1,engine:"euler",springConstant:50,damperConstant:10,restLength:1,showSprings:!1,showSpringsColor:"#000000",rows:0,columns:0,rowDistance:0,columnDistance:0,shapeTemplate:null,precision:20,joinTemplateEnds:!1,particlesAreDraggable:!1,hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000",resetAfterBlur:3}),gn.packetExclusions=Q(gn.packetExclusions,["forces","springs","particleStore"]),gn.packetExclusionsByRegex=Q(gn.packetExclusionsByRegex,[]),gn.packetCoordinates=Q(gn.packetCoordinates,[]),gn.packetObjects=Q(gn.packetObjects,["world","artefact","shapeTemplate"]),gn.packetFunctions=Q(gn.packetFunctions,["generate","postGenerate","stampAction"]),gn.finalizePacketOut=function(t,e){let i=e.forces||this.forces||!1;if(i){let e=[];i.forEach(t=>{t.substring?e.push(t):U(t)&&t.name&&e.push(t.name)}),t.forces=e}let s=[];return this.particleStore.forEach(t=>s.push(t.saveAsPacket())),t.particleStore=s,t},gn.postCloneAction=function(t,e){return t},gn.factoryKill=function(t,e){this.isRunning=!1,t&&(this.artefact.kill(),this.shapeTemplate&&this.shapeTemplate.kill()),e&&this.world.kill(),this.purgeParticlesFromLibrary()},gn.purgeParticlesFromLibrary=function(){let{particleStore:t,springs:e}=this;s.forEach(t=>{let e=i[t];e&&(e.particle&&!e.particle.substring&&e.particle.name&&(e.particle=e.particle.name),"Polyline"===e.type&&e.useParticlesAsPins&&e.pins.forEach((t,i)=>{U(t)&&"Particle"===t.type&&(e.pins[i]=t.name,e.dirtyPins=!0)}))}),t.forEach(t=>t.kill()),t.length=0,e.forEach(t=>t.kill()),e.length=0};gn.getters;let yn=gn.setters;gn.deltaSetters;yn.generate=function(t){W(t)?this.generate=t:t.substring&&bn[t]&&(this.generate=bn[t])},yn.postGenerate=function(t){W(t)&&(this.postGenerate=t)},yn.stampAction=function(t){W(t)&&(this.stampAction=t)},yn.world=function(t){let e;t.substring?e=g[t]:U(t)&&"World"===t.type&&(e=t),e&&(this.world=e)},yn.artefact=function(t){let e;t.substring?e=i[t]:U(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},yn.shapeTemplate=function(t){let e;t.substring?e=h[t]:U(t)&&t.isArtefact&&J(t.species)&&(e=t),e&&(this.shapeTemplate=e)},gn.regularStampSynchronousActions=function(){let{world:t,artefact:e,particleStore:i,springs:s,generate:n,postGenerate:r,stampAction:o,lastUpdated:a,resetAfterBlur:l,showSprings:h,showSpringsColor:c,showHitRadius:u,hitRadius:d,hitRadiusColor:f}=this,p=1,m="source-over";this.state&&(p=this.state.globalAlpha,m=this.state.globalCompositeOperation);let g=this.currentHost,y=.016,b=Date.now();if(a&&(y=(b-a)/1e3),y>l&&(this.purgeParticlesFromLibrary(),y=.016),i.length||(n.call(this,g),r.call(this)),i.forEach(e=>e.applyForces(t,g)),s.forEach(t=>t.applySpring()),i.forEach(e=>e.update(y,t)),h){let t=g.engine;t.save(),t.globalAlpha=p,t.globalCompositeOperation=m,t.strokeStyle=c,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.shadowColor="rgba(0,0,0,0)",t.lineWidth=1,t.setTransform(1,0,0,1,0,0),t.beginPath(),s.forEach(e=>{let{particleFrom:i,particleTo:s}=e;t.moveTo(i.position.x,i.position.y),t.lineTo(s.position.x,s.position.y)}),t.stroke(),t.restore()}if(i.forEach(t=>{t.manageHistory(y,g),o.call(this,e,t,g)}),u){let t=g.engine;t.save(),t.globalAlpha=p,t.globalCompositeOperation=m,t.lineWidth=1,t.strokeStyle=f,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.shadowColor="rgba(0,0,0,0)",t.setTransform(1,0,0,1,0,0),t.beginPath(),i.forEach(e=>{t.moveTo(e.position.x,e.position.y),t.arc(e.position.x,e.position.y,d,0,2*Math.PI)}),t.stroke(),t.restore()}this.lastUpdated=b},gn.restart=function(){return this.purgeParticlesFromLibrary(),this.lastUpdated=Date.now(),this},gn.checkHit=function(t=[],e){if(this.lastHitParticle=null,!this.particlesAreDraggable)return!1;if(this.noUserInteraction)return!1;let i,s,n,r,o,a=Array.isArray(t)?t:[t],l=this.particleStore,h=!1;if(a.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!tt(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=fi();for(n=0,r=l.length;n0&&h>0){let f,p,k,S,O,[P,v]=this.currentStampPosition,[w,x]=t.currentDimensions,A=c.substring?parseFloat(c)/100*x:c,C=u.substring?parseFloat(u)/100*x:u;for(S=0;S0&&h>0){let f,p,k,S,O,[P,v]=this.currentStampPosition,[w,x]=t.currentDimensions,A=c.substring?parseFloat(c)/100*x:c,C=u.substring?parseFloat(u)/100*x:u;for(S=0;S0;O--)R=d[`${m}-${O}-${S}`],E=d[`${m}-${O-1}-${S+1}`],D(R,E,`${m}-${O}-${S}~${m}-${O-1}-${S+1}`)}},"weak-shape":function(t){let{particleStore:e,artefact:i,historyLength:s,engine:n,forces:r,springs:o,mass:a,showSprings:l,showSpringsColor:h,name:c,springConstant:u,damperConstant:f,restLength:p,shapeTemplate:m,precision:g,joinTemplateEnds:y}=this;const b=function(t,e,i){let s,n,r;s=fi(t.position).vectorSubtract(e.position),n=s.getMagnitude(),r=mn({name:i,particleFrom:t,particleTo:e,springConstant:u,damperConstant:f,restLength:n*p}),o.push(r),pi(s)};let k,S,O,P;if(m&&g){for(k=0;k=0){for(k=0;k=0||t.type;let[w,x]=e.get("position");v=Ns({name:f+"-hub",positionX:w,positionY:x,positionZ:0,velocityX:0,velocityY:0,velocityZ:0,historyLength:r,engine:o,forces:a,mass:h,fill:n.get("fillStyle"),stroke:n.get("strokeStyle")}),v.run(0,0,!1),s.forEach((t,e)=>b(t,v,`${f}-${e}-hub`)),s.push(v)}}};O.Net=Net;const kn=function(t={}){this.makeName(t.name),this.register();let e=document.createElement("canvas");return e.id=this.name,this.installElement(e),this.perm=[],this.permMod8=[],this.values=[],this.grad=[],this.subscribers=[],this.colorFactory=Ts({name:this.name+"-color-factory",minimumColor:t.gradientStart||"red",maximumColor:t.gradientEnd||"green"}),this.set(this.defs),this.set(t),t.subscribe&&this.subscribers.push(t.subscribe),this.dirtyOutput=!0,this};let Sn=kn.prototype=Object.create(Object.prototype);Sn.type="Noise",Sn.lib="asset",Sn.isArtefact=!1,Sn.isAsset=!0,Sn=ee(Sn),Sn=Me(Sn),Sn=Qe(Sn);Sn.defs=_(Sn.defs,{width:300,height:150,color:"monochrome",monochromeStart:0,monochromeRange:255,gradientStart:"#ff0000",gradientEnd:"#00ff00",hueStart:0,hueRange:120,saturation:100,luminosity:50,noiseEngine:"simplex",seed:"any_random_string_will_do",size:256,scale:50,octaves:1,octaveFunction:"none",persistence:.5,lacunarity:2,smoothing:"quintic",sumFunction:"none",sineFrequencyCoeff:1,modularAmplitude:5}),delete Sn.defs.source,delete Sn.defs.sourceLoaded,Sn.stringifyFunction=B,Sn.processPacketOut=B,Sn.finalizePacketOut=B,Sn.saveAsPacket=function(){return`[${this.name}, ${this.type}, ${this.lib}, {}]`},Sn.clone=$;Sn.getters;let On=Sn.setters;Sn.deltaSetters;On.source=B,On.subscribers=B,On.octaveFunction=function(t){this.octaveFunction=this.octaveFunctions[t]||L,this.dirtyNoise=!0,this.dirtyOutput=!0},On.sumFunction=function(t){this.sumFunction=this.sumFunctions[t]||L,this.dirtyNoise=!0,this.dirtyOutput=!0},On.smoothing=function(t){this.smoothing=this.smoothingFunctions[t]||L,this.dirtyNoise=!0,this.dirtyOutput=!0},On.noiseEngine=function(t){this.noiseEngine=this.noiseEngines[t]||this.noiseEngines.simplex,this.dirtyNoise=!0,this.dirtyOutput=!0},On.octaves=function(t){t.toFixed&&(this.octaves=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.seed=function(t){t.substring&&(this.seed=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},Sn.supportedColorSchemes=["monochrome","gradient","hue"],On.color=function(t){this.supportedColorSchemes.indexOf(t)>=0&&(this.color=t,this.dirtyOutput=!0)},On.scale=function(t){t.toFixed&&(this.scale=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.size=function(t){t.toFixed&&(this.size=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.persistence=function(t){t.toFixed&&(this.persistence=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.lacunarity=function(t){t.toFixed&&(this.lacunarity=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.gradientStart=function(t){t.substring&&(this.colorFactory.setMinimumColor(t),this.dirtyOutput=!0)},On.gradientEnd=function(t){t.substring&&(this.colorFactory.setMaximumColor(t),this.dirtyOutput=!0)},On.monochromeStart=function(t){t.toFixed&&t>=0&&(this.monochromeStart=t%360,this.dirtyOutput=!0)},On.monochromeRange=function(t){t.toFixed&&t>=-255&&t<256&&(this.monochromeRange=Math.floor(t),this.dirtyOutput=!0)},On.hueStart=function(t){t.toFixed&&(this.hueStart=t,this.dirtyOutput=!0)},On.hueRange=function(t){t.toFixed&&(this.hueRange=t,this.dirtyOutput=!0)},On.saturation=function(t){t.toFixed&&t>=0&&t<=100&&(this.saturation=Math.floor(t),this.dirtyOutput=!0)},On.luminosity=function(t){t.toFixed&&t>=0&&t<=100&&(this.luminosity=Math.floor(t),this.dirtyOutput=!0)},On.sineFrequencyCoeff=function(t){t.toFixed&&(this.sineFrequencyCoeff=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.modularAmplitude=function(t){t.toFixed&&(this.modularAmplitude=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.width=function(t){t.toFixed&&(this.width=t,this.sourceNaturalWidth=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},On.height=function(t){t.toFixed&&(this.height=t,this.sourceNaturalHeight=t,this.dirtyNoise=!0,this.dirtyOutput=!0)},Sn.installElement=function(t){return this.element=t,this.engine=this.element.getContext("2d"),this},Sn.checkSource=function(t,e){this.notifySubscribers()},Sn.getData=function(t,e){return this.notifySubscribers(),this.buildStyle(e)},Sn.notifySubscribers=function(){(this.dirtyOutput||this.dirtyNoise)&&this.cleanOutput(),this.subscribers.forEach(t=>this.notifySubscriber(t),this)},Sn.notifySubscriber=function(t){t.sourceNaturalWidth=this.width,t.sourceNaturalHeight=this.height,t.sourceLoaded=!0,t.source=this.element,t.dirtyImage=!0,t.dirtyCopyStart=!0,t.dirtyCopyDimensions=!0,t.dirtyImageSubscribers=!0},Sn.cleanOutput=function(){this.dirtyNoise&&this.cleanNoise(),this.dirtyOutput&&this.paintCanvas()},Sn.cleanNoise=function(){if(this.dirtyNoise){this.dirtyNoise=!1;let{noiseEngine:t,seed:e,width:i,height:s,element:n,engine:r,octaves:o,lacunarity:a,persistence:l,scale:h,octaveFunction:c,sumFunction:u}=this;if(t&&t.init){this.rndEngine=function(t){return new nt(t)}(e),this.generatePermutationTable(),t.init.call(this);let n,r,d,f,p,m,g,y,b=[];for(r=0;r0?a+l>255&&(l=255-a):l<0&&a-l<0&&(l=a);for(let e=0;em&&(S=1,O=0),b=h[g+S+l[y+O]],k+=i(p-S+n,m-O+n,b),.5+35*k}},value:{init:function(){const{values:t,size:e,rndEngine:i}=this;t.length=0;for(let s=0;s{this[e]=t},this)},Pn.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=addStrings(this[e],t)},this)},Pn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeOvalPath(),this.pathDefinition=t},Pn.makeOvalPath=function(){let t,e,i=parseFloat(this.offshootA.toFixed(6)),s=parseFloat(this.offshootB.toFixed(6)),n=this.radiusX,r=this.radiusY;if(n.substring||r.substring){let i=this.getHost();if(i){let[s,o]=i.currentDimensions;t=2*(n.substring?parseFloat(n)/100*s:n),e=2*(r.substring?parseFloat(r)/100*o:r)}}else t=2*n,e=2*r;let o=parseFloat((t*this.intersectX).toFixed(2)),a=parseFloat((t-o).toFixed(2)),l=parseFloat((e*this.intersectY).toFixed(2)),h=parseFloat((e-l).toFixed(2)),c="m0,0";return c+=`c${a*i},${l*s} ${a-a*s},${l-l*i}, ${a},${l} `,c+=`${-a*s},${h*i} ${a*i-a},${h-h*s} ${-a},${h} `,c+=`${-o*i},${-h*s} ${o*s-o},${h*i-h} ${-o},${-h} `,c+=`${o*s},${-l*i} ${o-o*i},${l*s-l} ${o},${-l}z`,c},Pn.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};O.Oval=Oval;const VideoAsset=function(t={}){return this.assetConstructor(t)};let xn=VideoAsset.prototype=Object.create(Object.prototype);xn.type="Video",xn.lib="asset",xn.isArtefact=!1,xn.isAsset=!0,xn=ee(xn),xn=Me(xn),xn.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},xn.stringifyFunction=B,xn.processPacketOut=B,xn.finalizePacketOut=B,xn.clone=$;xn.getters;let An=xn.setters;xn.deltaSetters;An.source=function(t={}){t&&("VIDEO"===t.tagName.toUpperCase()&&(this.source=t,this.sourceNaturalWidth=t.videoWidth||0,this.sourceNaturalHeight=t.videoHeight||0,this.sourceLoaded=t.readyState>2),this.sourceLoaded&&this.notifySubscribers())},xn.checkSource=function(t,e){let i=this.source;i&&i.readyState>2?(this.sourceLoaded=!0,this.sourceNaturalWidth===i.videoWidth&&this.sourceNaturalHeight===i.videoHeight&&this.sourceNaturalWidth===t&&this.sourceNaturalHeight===e||(this.sourceNaturalWidth=i.videoWidth,this.sourceNaturalHeight=i.videoHeight,this.notifySubscribers())):this.sourceLoaded=!1},xn.addTextTrack=function(t,e,i){let s=this.source;s&&s.addTextTrack&&s.addTextTrack(t,e,i)},xn.captureStream=function(){let t=this.source;return!(!t||!t.captureStream)&&t.captureStream()},xn.canPlayType=function(t){let e=this.source;return e?e.canPlayType(t):"maybe"},xn.fastSeek=function(t){let e=this.source;e&&e.fastSeek&&e.fastSeek(t)},xn.load=function(){let t=this.source;t&&t.load()},xn.pause=function(){let t=this.source;t&&t.pause()},xn.play=function(){let t=this.source;return t?t.play().catch(t=>console.log(t.code,t.name,t.message)):Promise.reject("Source not defined")},xn.setMediaKeys=function(t){let e=this.source;return e?e.setMediaKeys?e.setMediaKeys(t):Promise.reject("setMediaKeys not supported"):Promise.reject("Source not defined")},xn.setSinkId=function(){let t=this.source;return t?t.setSinkId?t.setSinkId():Promise.reject("setSinkId not supported"):Promise.reject("Source not defined")};const Cn=["video_audioTracks","video_autoPlay","video_buffered","video_controller","video_controls","video_controlsList","video_crossOrigin","video_currentSrc","video_currentTime","video_defaultMuted","video_defaultPlaybackRate","video_disableRemotePlayback","video_duration","video_ended","video_error","video_loop","video_mediaGroup","video_mediaKeys","video_muted","video_networkState","video_paused","video_playbackRate","video_readyState","video_seekable","video_seeking","video_sinkId","video_src","video_srcObject","video_textTracks","video_videoTracks","video_volume"],Dn=["video_autoPlay","video_controller","video_controls","video_crossOrigin","video_currentTime","video_defaultMuted","video_defaultPlaybackRate","video_disableRemotePlayback","video_loop","video_mediaGroup","video_muted","video_playbackRate","video_src","video_srcObject","video_volume"],Rn=function(...t){let e=/.*\/(.*?)\./,i="";if(t.length){let s,n,r,o,a,l,h=!1,c=t[0];if(c.substring){let i=e.exec(c);s=i&&i[1]?i[1]:"",a=[...t],n="",r=!1,o=null,l="auto",h=!0}else c&&c.src&&(s=c.name||"",a=[...c.src],n=c.className||"",r=c.visibility||!1,o=document.querySelector(o),l=c.preload||"auto",h=!0);let u=En({name:s});if(h){let t=document.createElement("video");t.name=s,t.className=n,t.style.display=r?"block":"none",t.crossOrigin="anonymous",t.preload=l,a.forEach(e=>{let i=document.createElement("source");i.src=e,t.appendChild(i)}),t.onload=()=>{u.set({source:t}),o&&o.appendChild(t)},u.set({source:t}),i=s}}return i},En=function(t){return new VideoAsset(t)};O.VideoAsset=VideoAsset;const SpriteAsset=function(t={}){return this.assetConstructor(t),this};let Fn=SpriteAsset.prototype=Object.create(Object.prototype);Fn.type="Sprite",Fn.lib="asset",Fn.isArtefact=!1,Fn.isAsset=!0,Fn=ee(Fn),Fn=Me(Fn);Fn.defs=_(Fn.defs,{manifest:null}),Fn.saveAsPacket=function(){return[this.name,this.type,this.lib,{}]},Fn.stringifyFunction=B,Fn.processPacketOut=B,Fn.finalizePacketOut=B,Fn.clone=$;Fn.getters;let Tn=Fn.setters;Fn.deltaSetters;Tn.source=function(t=[]){if(t&&t[0]){this.sourceHold||(this.sourceHold={});let e=this.sourceHold;t.forEach(t=>{let i=t.id||t.name;i&&(e[i]=t)}),this.source=t[0],this.sourceNaturalWidth=t[0].naturalWidth,this.sourceNaturalHeight=t[0].naturalHeight,this.sourceLoaded=t[0].complete}},Fn.checkSource=B;const Hn=function(...t){let e=/.*\/(.*?)\./,i=/\.(jpeg|jpg|png|gif|webp|svg|JPEG|JPG|PNG|GIF|WEBP|SVG)/,s=[];return t.forEach(t=>{let n,r,o,a,l,h=!1,c=!1;if(t.substring){let s=e.exec(t);n=s&&s[1]?s[1]:"",r=[t],o="",a=!1,l=t.replace(i,".json"),c=!0}else U(t)&&t.imageSrc&&t.manifestSrc?(n=t.name||"",r=Array.isArray(t.imageSrc)?t.imageSrc:[t.imageSrc],l=t.manifestSrc,o=t.className||"",a=t.visibility||!1,h=document.querySelector(t.parent),c=!0):s.push(!1);if(c){let t=Mn({name:n});U(l)?t.manifest=l:fetch(l).then(t=>{if(200!==t.status)throw new Error("Failed to load manifest");return t.json()}).then(e=>t.manifest=e).catch(t=>console.log(t.message));let c=[];r.forEach(t=>{let s,r,l=document.createElement("img");i.test(t)&&(r=e.exec(t),s=r&&r[1]?r[1]:""),l.name=s||n,l.className=o,l.crossorigin="anonymous",l.style.display=a?"block":"none",h&&h.appendChild(l),l.src=t,c.push(l)}),t.set({source:c}),s.push(n)}else s.push(!1)}),s},Mn=function(t){return new SpriteAsset(t)};function In(t={}){t.defs=_(t.defs,{asset:null,removeAssetOnKill:!1,spriteIsRunning:!1,spriteLastFrameChange:0,spriteCurrentFrame:0,spriteTrack:"default",spriteForward:!0,spriteFrameDuration:100,spriteWillLoop:!0});let e=t.setters;return e.asset=function(t){let e=this.asset,i=t&&t.name?t.name:t;e&&!e.substring&&e.unsubscribe(this),this.asset=i,this.dirtyAsset=!0},e.imageSource=function(t){let e=je(t);if(e){let t=n[e[0]];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},e.videoSource=function(t){let e=Rn(t);if(e){let t=n[e];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},e.spriteSource=function(t){let e=Hn(t);if(e){let t=n[e];if(t){let e=this.asset;e&&e.unsubscribe&&e.unsubscribe(this),t.subscribe(this)}}},t.cleanAsset=function(){let t=this.asset;if(t&&t.substring){let e=n[t];e&&(this.dirtyAsset=!1,e.subscribe(this))}},t.videoAction=function(t,...e){let i=this.asset;if(i&&"Video"===i.type)return i[t](...e)},t.videoPromiseAction=function(t,...e){let i=this.asset;return i&&"Video"===i.type?i[t](...e):Promise.reject("Asset not a video")},t.videoAddTextTrack=function(t,e,i){return this.videoAction("addTextTrack",t,e,i)},t.videoCaptureStream=function(){return this.videoAction("captureStream")},t.videoCanPlayType=function(t){return this.videoAction("canPlayType",t)},t.videoFastSeek=function(t){return this.videoAction("fastSeek",t)},t.videoLoad=function(){return this.videoAction("load")},t.videoPause=function(){return this.videoAction("pause")},t.videoPlay=function(){return this.videoPromiseAction("play")},t.videoSetMediaKeys=function(t){return this.videoPromiseAction("setMediaKeys",t)},t.videoSetSinkId=function(){return this.videoPromiseAction("setSinkId")},t.checkSpriteFrame=function(){let t=this.asset;if(t&&"Sprite"===t.type){let e=this.copyArray;if(this.spriteIsRunning){let i=this.spriteLastFrameChange,s=this.spriteFrameDuration,n=Date.now();if(n>i+s){let i=t.manifest;if(i){let s=i[this.spriteTrack],r=s.length,o=this.spriteCurrentFrame,a=this.spriteWillLoop;o=this.spriteForward?o+1:o-1,o<0&&(o=a?r-1:0),o>=r&&(o=a?0:r-1);let[l,h,c,u,d]=s[o];if(e.length=0,e.push(h,c,u,d),this.dirtyCopyStart=!1,this.dirtyCopyDimensions=!1,l!==(this.source.id||this.source.name)){let e=t.sourceHold[l];e&&(this.source=e)}this.spriteCurrentFrame=o,this.spriteLastFrameChange=n}}}else{let[i,s,n,r,o]=t.manifest[this.spriteTrack][this.spriteCurrentFrame],[a,l,h,c]=e;a===s&&l===n&&h===r&&c===o||(e.length=0,e.push(s,n,r,o),this.dirtyCopyStart=!1,this.dirtyCopyDimensions=!1)}}},t.playSprite=function(t,e,i,s,n){J(t)&&(this.spriteFrameDuration=t),J(e)&&(this.spriteWillLoop=e),J(i)&&(this.spriteTrack=i),J(s)&&(this.spriteForward=s),J(n)&&(this.spriteCurrentFrame=n),this.spriteLastFrameChange=Date.now(),this.spriteIsRunning=!0},t.haltSprite=function(t,e,i,s,n){J(t)&&(this.spriteFrameDuration=t),J(e)&&(this.spriteWillLoop=e),J(i)&&(this.spriteTrack=i),J(s)&&(this.spriteForward=s),J(n)&&(this.spriteCurrentFrame=n),this.spriteIsRunning=!1},t}O.SpriteAsset=SpriteAsset;const Pattern=function(t={}){return this.makeName(t.name),this.register(),this.set(this.defs),this.set(t),this};let Bn=Pattern.prototype=Object.create(Object.prototype);Bn.type="Pattern",Bn.lib="styles",Bn.isArtefact=!1,Bn.isAsset=!1,Bn=ee(Bn),Bn=Qe(Bn),Bn=In(Bn);Bn.defs=_(Bn.defs,{}),Bn.packetObjects=Q(Bn.packetObjects,["asset"]),Bn.finalizePacketOut=function(t,e){if(Array.isArray(e.patternMatrix))t.patternMatrix=e.patternMatrix;else{let e=this.patternMatrix;e&&(t.patternMatrix=[e.a,e.b,e.c,e.d,e.e,e.f])}return t},Bn.kill=function(){let{name:t,asset:e,removeAssetOnKill:i}=this;return U(e)&&e.unsubscribe(this),Object.entries(h).forEach(([e,i])=>{let s=i.state;if(s){let e=s.fillStyle,i=s.strokeStyle;U(e)&&e.name===t&&(s.fillStyle=s.defs.fillStyle),U(i)&&i.name===t&&(s.strokeStyle=s.defs.strokeStyle)}}),i&&(i.substring?e.kill(!0):e.kill()),this.deregister(),this},Bn.get=function(t){let e=this.source;if(0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!e){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t];return void 0!==i?(e=this[t],void 0!==e?e:i):undef}}return Cn.indexOf(t)>=0||Le.indexOf(t)>=0?e[t.substring(6)]:void 0},Bn.set=function(t={}){if(Object.keys(t).length){let e,i=this.setters,s=this.defs,n=this.source;Object.entries(t).forEach(([t,r])=>{0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!n?t&&"name"!==t&&null!=r&&(e=i[t],e?e.call(this,r):void 0!==s[t]&&(this[t]=r)):(Dn.indexOf(t)>=0||$e.indexOf(t)>=0)&&(n[t.substring(6)]=r)},this)}return this},Bn.getData=function(t,e){return this.dirtyAsset&&this.cleanAsset(),this.asset.checkSource(this.sourceNaturalWidth,this.sourceNaturalHeight),this.buildStyle(e)};O.Pattern=Pattern;const FontAttributes=function(t={}){return this.makeName(t.name),this.set(this.defs),this.set(t),this};let Ln=FontAttributes.prototype=Object.create(Object.prototype);Ln.type="FontAttributes",Ln.lib="fontattribute",Ln=ee(Ln);Ln.defs=_(Ln.defs,{style:"normal",variant:"normal",weight:"normal",stretch:"normal",sizeValue:12,sizeMetric:"px",family:"sans-serif"});let $n=Ln.getters,jn=Ln.setters;Ln.deltaSetters;$n.size=function(){return this.sizeValue?`${this.sizeValue}${this.sizeMetric}`:this.sizeMetric},jn.size=function(t){if(J(t)){let e,i,s,n=0,r="medium";t.indexOf("xx-small")>=0?r="xx-small":t.indexOf("x-small")>=0?r="x-small":t.indexOf("smaller")>=0?r="smaller":t.indexOf("small")>=0?r="small":t.indexOf("medium")>=0?r="medium":t.indexOf("xxx-large")>=0?r="xxx-large":t.indexOf("xx-large")>=0?r="xx-large":t.indexOf("x-large")>=0?r="x-large":t.indexOf("larger")>=0?r="larger":t.indexOf("large")>=0?r="large":(n=12,r="px");let o=t.match(/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i);Array.isArray(o)?([e,i,s]=o,i&&s&&"."!=i&&(n=i,r=s)):(o=t.match(/\/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i),Array.isArray(o)&&([e,i,s]=o,i&&s&&"."!=i&&(n=i,r=s))),n!==this.sizeValue&&(this.sizeValue=n,this.dirtyFont=!0),r!==this.sizeMetric&&(this.sizeMetric=r,this.dirtyFont=!0)}},jn.sizeValue=function(t){J(t)&&t!==this.sizeValue&&(this.sizeValue=t,this.dirtyFont=!0)},jn.sizeMetric=function(t){J(t)&&t!==this.sizeMetric&&(this.sizeMetric=t,this.dirtyFont=!0)},jn.font=function(t){J(t)&&(jn.style.call(this,t),jn.variant.call(this,t),jn.weight.call(this,t),jn.stretch.call(this,t),jn.size.call(this,t),jn.family.call(this,t))},jn.style=function(t){if(J(t)){let e="normal";e=t.indexOf("italic")>=0?"italic":e,e=t.indexOf("oblique")>=0?"oblique":e,e!==this.style&&(this.style=e,this.dirtyFont=!0)}},jn.variant=function(t){if(J(t)){let e="normal";e=t.indexOf("small-caps")>=0?"small-caps":e,e!==this.variant&&(this.variant=e,this.dirtyFont=!0)}},jn.weight=function(t){if(J(t)){let e="normal";t.toFixed?e=t:(e=t.indexOf("bold")>=0?"bold":e,e=t.indexOf("lighter")>=0?"lighter":e,e=t.indexOf("bolder")>=0?"bolder":e,e=t.indexOf(" 100 ")>=0?"100":e,e=t.indexOf(" 200 ")>=0?"200":e,e=t.indexOf(" 300 ")>=0?"300":e,e=t.indexOf(" 400 ")>=0?"400":e,e=t.indexOf(" 500 ")>=0?"500":e,e=t.indexOf(" 600 ")>=0?"600":e,e=t.indexOf(" 700 ")>=0?"700":e,e=t.indexOf(" 800 ")>=0?"800":e,e=t.indexOf(" 900 ")>=0?"900":e,e=/^\d00$/.test(t)?t:e),e!==this.weight&&(this.weight=e,this.dirtyFont=!0)}},jn.stretch=function(t){if(J(t)){let e="normal";e=t.indexOf("semi-condensed")>=0?"semi-condensed":e,e=t.indexOf("condensed")>=0?"condensed":e,e=t.indexOf("extra-condensed")>=0?"extra-condensed":e,e=t.indexOf("ultra-condensed")>=0?"ultra-condensed":e,e=t.indexOf("semi-expanded")>=0?"semi-expanded":e,e=t.indexOf("expanded")>=0?"expanded":e,e=t.indexOf("extra-expanded")>=0?"extra-expanded":e,e=t.indexOf("ultra-expanded")>=0?"ultra-expanded":e,e!==this.stretch&&(this.stretch=e,this.dirtyFont=!0)}},Ln.rfsTestArray1=["italic","oblique","small-caps","normal","bold","lighter","bolder","ultra-condensed","extra-condensed","semi-condensed","condensed","ultra-expanded","extra-expanded","semi-expanded","expanded","xx-small","x-small","small","medium","xxx-large","xx-large","x-large","large"],Ln.rfsTestArray2=["0","1","2","3","4","5","6","7","8","9"],jn.family=function(t){if(J(t)){let e="sans-serif",i=t.split(" "),s=i.length;1===s&&(e=t);let n=0,r=!0;for(;r;)if(n===s)r=!1;else{let t=i[n];t.length?this.rfsTestArray1.indexOf(t)>=0||this.rfsTestArray2.indexOf(t[0])>=0?n++:r=!1:n++}n0&&t!==this.scale&&(this.scale=t,this.dirtyFont=!0),e&&e.toFixed&&e>0&&e!==this.lineHeight&&(this.lineHeight=e,this.dirtyFont=!0);let s=this.host&&this.host.type&&"Cell"===this.host.type?this.host.name:"";i&&i.type&&"Cell"===i.type&&i.name!==s&&(this.host=i,this.dirtyFont=!0)},Ln.calculateSize=function(){if(this.host){let t,e,i,s,{scale:n,lineHeight:r,host:o,sizeValue:a,sizeMetric:l}=this;if(o.getComputedFontSizes())[t,e,i,s]=o.getComputedFontSizes();else if(["in","cm","mm","Q","pc","pt","px"].indexOf(l)<0)return this.dirtyFont=!0,"12px";isNaN(a)&&(a=12);let h=t;switch(l){case"rem":h=e*a;break;case"em":h=t*a;break;case"rlh":h=e*r*a;break;case"lh":h=t*r*a;break;case"ex":h=t/2*a;break;case"cap":case"ch":case"ic":h=t*a;break;case"%":h=t/100*a;break;case"vw":h=i/100*a;break;case"vh":h=s/100*a;break;case"vmax":h=Math.max(i,s)/100*a;break;case"vmin":h=Math.min(i,s)/100*a;break;case"vi":h=i/100*a;break;case"vb":h=s/100*a;break;case"in":h=96*a;break;case"cm":h=37.8*a;break;case"mm":h=3.78*a;break;case"Q":h=.95*a;break;case"pc":h=16*a;break;case"pt":h=1.33*a;break;case"px":h=a;break;case"xx-small":h=.6*t;break;case"x-small":h=.75*t;break;case"smaller":h=.8*t;break;case"small":h=.89*t;break;case"xxx-large":h=3*t;break;case"xx-large":h=2*t;break;case"x-large":h=1.5*t;break;case"larger":h=1.3*t;break;case"large":h=1.2*t}return h*n+"px"}return"12px"},Ln.buildFont=function(){this.dirtyFont=!1;let t="";"normal"!==this.style&&(t+=this.style+" "),"normal"!==this.variant&&(t+=this.variant+" "),"normal"!==this.weight&&(t+=this.weight+" "),"normal"!==this.stretch&&(t+=this.stretch+" "),t+=this.calculateSize()+" ",t+=""+this.family;let e=ni();return e.engine.font=t,t=e.engine.font,ri(e),this.temperedFontString=t,t},Ln.update=function(t){return t&&this.set(t),this.getFontString()};O.FontAttributes=FontAttributes;const Nn=document.createElement("div");Nn.style.padding=0,Nn.style.border=0,Nn.style.margin=0,Nn.style.height="auto",Nn.style.lineHeight=1,Nn.style.boxSizing="border-box",Nn.innerHTML="|/}ÁÅþ§¶¿∑ƒ⌈⌊qwertyd0123456789QWERTY",Nn.setAttribute("aria-hidden","true"),is.appendChild(Nn);const Xn=document.createElement("textarea"),Yn=(t,e)=>(t=parseFloat(t),V(t)||(t=0),V(e)||(e=0),parseFloat(t.toFixed(e))),zn=(t,e)=>(t=parseFloat(t),V(t)||(t=0),V(e)||(e=0),Math.abs(parseFloat(t.toFixed(e)))),Phrase=function(t={}){return this.fontAttributes=function(t){return new FontAttributes(t)}(t),delete t.font,delete t.style,delete t.variant,delete t.weight,delete t.stretch,delete t.size,delete t.sizeValue,delete t.sizeMetric,delete t.family,this.entityInit(t),this.sectionStyles=[],this.sectionClasses={DEFAULTS:{defaults:!0},BOLD:{weight:"bold"},ITALIC:{style:"italic"},"SMALL-CAPS":{variant:"small-caps"},HIGHLIGHT:{highlight:!0},UNDERLINE:{underline:!0},OVERLINE:{overline:!0},"/BOLD":{weight:"normal"},"/ITALIC":{style:"normal"},"/SMALL-CAPS":{variant:"normal"},"/HIGHLIGHT":{highlight:!1},"/UNDERLINE":{underline:!1},"/OVERLINE":{overline:!1}},this.dirtyDimensions=!0,this.dirtyText=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this};let Gn=Phrase.prototype=Object.create(Object.prototype);Gn.type="Phrase",Gn.lib="entity",Gn.isArtefact=!0,Gn.isAsset=!1,Gn=ee(Gn),Gn=ps(Gn);Gn.defs=_(Gn.defs,{text:"",width:"auto",exposeText:!0,lineHeight:1.15,letterSpacing:0,justify:"left",sectionClassMarker:"§",sectionClasses:null,overlinePosition:-.1,overlineStyle:"rgb(250,0,0)",underlinePosition:.6,underlineStyle:"rgb(250,0,0)",highlightStyle:"rgba(250,218,94,0.4)",boundingBoxColor:"rgba(0,0,0,0.5)",showBoundingBox:!1,textPath:"",textPathPosition:0,textPathLoop:!0,addTextPathRoll:!0,textPathDirection:"ltr",treatWordAsGlyph:!1}),Gn.packetExclusions=Q(Gn.packetExclusions,["textPositions","textLines","textLineWidths","textLineWords","textGlyphs","textGlyphWidths","fontAttributes"]),Gn.finalizePacketOut=function(t,e){let i=JSON.parse(this.state.saveAsPacket(e))[3];t=_(t,i);let s=JSON.parse(this.fontAttributes.saveAsPacket(e))[3];return delete s.name,t=_(t,s),t=this.handlePacketAnchor(t,e)},Gn.factoryKill=function(){this.exposedTextHold&&this.exposedTextHold.remove()};let Wn=Gn.getters,Vn=Gn.setters,Un=Gn.deltaSetters;Vn.handleX=function(t){null!=t&&(this.handle[0]=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0)},Vn.handleY=function(t){null!=t&&(this.handle[1]=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0)},Vn.handle=function(t,e){this.setCoordinateHelper("handle",t,e),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},Un.handleX=function(t){let e=this.handle;e[0]=addStrings(e[0],t),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},Un.handleY=function(t){let e=this.handle;e[1]=addStrings(e[1],t),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},Un.handle=function(t,e){this.setDeltaCoordinateHelper("handle",t,e),this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},Wn.text=function(){return this.currentText||this.text||""},Vn.text=function(t){var e;this.text=(e=t).substring?e:e.toString,this.dirtyText=!0,this.dirtyPathObject=!0,this.dirtyDimensions=!0},Gn.permittedJustifications=["left","right","center","full"],Vn.justify=function(t){this.permittedJustifications.indexOf(t)>=0&&(this.justify=t),this.dirtyText=!0,this.dirtyPathObject=!0},Vn.width=function(t){this.dimensions[0]=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyPathObject=!0,this.dirtyText=!0},Un.width=function(t){let e=this.dimensions;e[0]=addStrings(e[0],t),this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyPathObject=!0,this.dirtyText=!0},Vn.scale=function(t){this.scale=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this.dirtyScale=!0},Un.scale=function(t){this.scale+=t,this.dirtyDimensions=!0,this.dirtyHandle=!0,this.dirtyFont=!0,this.dirtyPathObject=!0,this.dirtyScale=!0},Vn.lineHeight=function(t){this.lineHeight=zn(t,3),this.lineHeight<.5&&(this.lineHeight=.5),this.dirtyPathObject=!0,this.dirtyText=!0},Un.lineHeight=function(t){this.lineHeight+=Yn(t,3),this.lineHeight<.5&&(this.lineHeight=.5),this.dirtyPathObject=!0,this.dirtyText=!0},Vn.letterSpacing=function(t){this.letterSpacing=zn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},Un.letterSpacing=function(t){this.letterSpacing+=Yn(t,3),this.letterSpacing<0&&(this.letterSpacing=0),this.dirtyPathObject=!0,this.dirtyText=!0},Vn.overlinePosition=function(t){this.overlinePosition=Yn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},Un.overlinePosition=function(t){this.overlinePosition+=Yn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},Vn.underlinePosition=function(t){this.underlinePosition=Yn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},Un.underlinePosition=function(t){this.underlinePosition+=Yn(t,3),this.dirtyPathObject=!0,this.dirtyText=!0},Vn.textPath=function(t){this.textPath=t,this.dirtyHandle=!0,this.dirtyText=!0,this.dirtyPathObject=!0},Vn.textPathPosition=function(t){this.textPathLoop?(t<0&&(t=Math.abs(t)),t>1&&(t%=1),this.textPathPosition=parseFloat(t.toFixed(6))):this.textPathPosition=t},Un.textPathPosition=function(t){let e=this.textPathPosition+t;this.textPathLoop?(e<0&&(e+=1),e>1&&(e%=1),this.textPathPosition=parseFloat(e.toFixed(6))):this.textPathPosition=e},Wn.font=function(){return this.fontAttributes.get("font")},Vn.font=function(t){this.fontAttributes.set({font:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.style=function(){return this.fontAttributes.get("style")},Vn.style=function(t){this.fontAttributes.set({style:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.variant=function(){return this.fontAttributes.get("variant")},Vn.variant=function(t){this.fontAttributes.set({variant:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.weight=function(){return this.fontAttributes.get("weight")},Vn.weight=function(t){this.fontAttributes.set({weight:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.stretch=function(){return this.fontAttributes.get("stretch")},Vn.stretch=function(t){this.fontAttributes.set({stretch:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.size=function(){return this.fontAttributes.get("size")},Vn.size=function(t){this.fontAttributes.set({size:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.sizeValue=function(){return this.fontAttributes.get("sizeValue")},Vn.sizeValue=function(t){this.fontAttributes.set({sizeValue:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Un.sizeValue=function(t){this.fontAttributes.deltaSet({sizeValue:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.sizeMetric=function(){return this.fontAttributes.get("sizeMetric")},Vn.sizeMetric=function(t){this.fontAttributes.set({sizeMetric:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Wn.family=function(){return this.fontAttributes.get("family")},Vn.family=function(t){this.fontAttributes.set({family:t}),this.dirtyFont=!0,this.dirtyPathObject=!0},Gn.cleanDimensionsAdditionalActions=function(){if(this.fontAttributes.dirtyFont=!0,this.fontAttributes.updateMetadata(this.scale,this.lineHeight,this.getHost()),"auto"===this.dimensions[0]){this.buildText();let t=ni(),e=t.engine;e.font=this.fontAttributes.getFontString(),this.currentDimensions[0]=Math.ceil(e.measureText(this.currentText).width/this.scale),ri(t)}this.textLines?this.currentDimensions[1]=Math.ceil(this.textHeight*this.textLines.length*this.lineHeight/this.scale):this.dirtyDimensions=!0},Gn.setSectionStyles=function(t){let e,i,s,n=new RegExp(this.sectionClassMarker),r=t.split(n),o=this.sectionStyles,a=this.sectionClasses,l="";return o.length=0,r.forEach(t=>{e=a[t],e?(i=l.length,s=o[i],s?Object.assign(s,e):o[i]=Object.assign({},e)):J(t)&&(l+=t)}),l},Gn.addSectionClass=function(t,e){return tt(t,e)&&t.substring&&U(e)&&(this.sectionClasses[t]=e),this.dirtyText=!0,this.dirtyPathObject=!0,this},Gn.removeSectionClass=function(t){return delete this.sectionClasses[t],this.dirtyText=!0,this.dirtyPathObject=!0,this},Gn.getTextPath=function(){let t=this.textPath;return t&&t.substring&&(t=this.textPath=i[this.textPath],"Shape"===t.type&&t.useAsPath?t.pathed.push(this.name):t=this.path=!1),t},Gn.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.dirtyFont&&this.fontAttributes&&(this.dirtyFont=!1,this.dirtyText=!0,this.dirtyMimicDimensions=!0,this.dirtyPositionSubscribers=!0),this.dirtyText&&this.buildText(),this.dirtyHandle&&this.cleanHandle();let t=this.pathObject=new Path2D,e=this.currentHandle,i=this.currentDimensions,s=this.currentScale,n=-e[0]*s,r=-e[1]*s,o=i[0]*s,a=i[1]*s;this.boxStartValues=[n,r],t.rect(n,r,o,a)}},Gn.buildText=function(){this.dirtyText=!1;let t=this.convertTextEntityCharacters(this.text);if(t=this.setSectionStyles(t),this.currentText=t,isNaN(this.currentDimensions[0]))this.dirtyText=!0;else if(this.calculateTextPositions(t),this.exposeText){if(!this.exposedTextHold){let t=document.createElement("div");t.id=this.name+"-text-hold",t.setAttribute("aria-live","polite"),this.exposedTextHold=t,this.exposedTextHoldAttached=!1}this.exposedTextHold.textContent=t,this.exposedTextHoldAttached||this.currentHost&&this.currentHost.controller&&this.currentHost.controller.textHold&&(this.currentHost.controller.textHold.appendChild(this.exposedTextHold),this.exposedTextHoldAttached=!0)}},Gn.convertTextEntityCharacters=function(t){let e=t.trim();return e=e.replace(/[\s\uFEFF\xA0]+/g," "),Xn.innerHTML=e,Xn.value},Gn.calculateTextPositions=function(t){const e=function(t){if(!H)return T.dirtyPathObject=!0,T.dirtyText=!0,"black";if(t.substring){let e=!1;if(k.indexOf(t)>=0?e=b[t]:l.indexOf(t)>=0&&(e=a[t]),e)return e}return t};let i,s,n,r,o,h,c,u,d,f,p,m,g,y,S,O,P,v,w,x,A,C,D,R,E=ni(),F=E.engine,T=this,H=!(!this.group||!this.group.getHost)&&this.group.getHost(),M=[],I=[],B=[],L=[],$=[],j=[],N=[],X=this.getTextPath(),Y=this.fontAttributes,z=Y.clone({}),G=this.sectionStyles,W=this.state,V={},U=[],q=this.currentScale,_=this.currentDimensions,Z=_[0]*q,Q=this.treatWordAsGlyph,K=this.lineHeight,tt=this.justify;Y.updateMetadata(q,K,H),z.updateMetadata(q,K,H);let et=Y.getFontString(),it=e(W.fillStyle),st=e(W.strokeStyle),nt=this.letterSpacing*q,rt=et,ot=it,at=st,lt=nt,ht=(!!this.highlightStyle&&e(this.highlightStyle),!1),ct=(!!this.underlineStyle&&e(this.underlineStyle),this.underlinePosition,!1),ut=(!!this.overlineStyle&&e(this.overlineStyle),this.overlinePosition,!1),dt=0;for(i=Q?t.split(" "):t.split(""),U.push(rt),f=0,p=i.length;f{Nn.style.font=t,r=Nn.clientHeight,V[t]=r}),dt=Math.max(...Object.values(V)),A=x=o=h=0,f=0,p=$.length;fP&&!n[0]&&(j[f]-=S-P));for(f=0,p=$.length;f=Z&&ot+e,0),B.push(S),x-=S,o=h+1),f+1===p&&(x===A?(y=t,I.push(y),L.push(Q?y.split(" ").length-1:y.split(" ").length),B.push(A)):(y=i.slice(o).join(""),I.push(y),S=Q?y.split(" ").length-1:y.split(" ").length,L.push(S),S=M.slice(o).reduce((t,e)=>t+e,0),B.push(S))),J(O[3])&&(ht=O[3]),J(O[4])&&(ct=O[4]),J(O[5])&&(ut=O[5]),O[3]=ht,O[4]=ct,O[5]=ut;if(q<=0&&(q=1),_[1]=Math.ceil(dt*I.length*K/q),this.cleanHandle(),this.dirtyHandle=!1,C=this.currentHandle,D=-C[0]*q,R=-C[1]*q,!X)if("full"===tt)for(c=0,u=R,f=0,p=B.length;f1?(Z-B[f])/(L[f]-1):0,m=0,g=I[f].length;m1||n<0)&&(n=n>.5?n-1:n+1),t[10]=n<=1&&n>=0&&r.getPathPositionData(n,f),t[9]=s,h?c+=s/o:c-=s/o,d&&(c>1||c<0)&&(c=c>.5?c-1:c+1)}},Gn.preStamper=function(t,e,i,s){const n=function(e){return e.getData?e.getData(i,t):e};let[r,o,a,l,h,c,...u]=s;if(r&&(e.font=r),l||h||c){let t=i.highlightStyle,s=i.textHeight,r=i.underlineStyle,o=i.underlinePosition,a=i.overlineStyle,d=i.overlinePosition;e.save(),l&&(e.fillStyle=n(t),e.fillRect(u[1],u[2],u[3],s)),h&&(e.strokeStyle=n(r),e.strokeRect(u[1],u[2]+s*o,u[3],1)),c&&(e.strokeStyle=n(a),e.strokeRect(u[1],u[2]+s*d,u[3],1)),e.restore()}return o&&(e.strokeStyle=n(o)),a&&(e.fillStyle=n(a)),u},Gn.stamper={draw:function(t,e,i){t.strokeText(...i)},fill:function(t,e,i){t.fillText(...i)},drawAndFill:function(t,e,i){t.strokeText(...i),e.currentHost.clearShadow(),t.fillText(...i),e.currentHost.restoreShadow(e)},fillAndDraw:function(t,e,i){t.strokeText(...i),e.currentHost.clearShadow(),t.fillText(...i),t.strokeText(...i),e.currentHost.restoreShadow(e)},drawThenFill:function(t,e,i){t.strokeText(...i),t.fillText(...i)},fillThenDraw:function(t,e,i){t.fillText(...i),t.strokeText(...i)},clear:function(t,e,i){let s=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fillText(...i),t.globalCompositeOperation=s}},Gn.drawBoundingBox=function(t){t.save(),t.strokeStyle=this.boundingBoxColor,t.lineWidth=1,t.globalCompositeOperation="source-over",t.globalAlpha=1,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=0,t.stroke(this.pathObject),t.restore()},Gn.performRotation=function(t){let e=this.currentHost;if(e){let[i,s]=this.currentStampPosition;e.rotateDestination(t,i,s,this)}};O.Phrase=Phrase;const Picture=function(t={}){return this.copyStart=He(),this.currentCopyStart=He(),this.copyDimensions=He(),this.currentCopyDimensions=He(),this.copyArray=[],this.pasteArray=[],this.entityInit(t),t.copyStart||(t.copyStartX||(this.copyStart[0]=0),t.copyStartY||(this.copyStart[1]=0)),t.copyDimensions||(t.copyWidth||(this.copyDimensions[0]=1),t.copyHeight||(this.copyDimensions[1]=1)),this.imageSubscribers=[],this.dirtyCopyStart=!0,this.dirtyCopyDimensions=!0,this.dirtyImage=!0,this};let qn=Picture.prototype=Object.create(Object.prototype);qn.type="Picture",qn.lib="entity",qn.isArtefact=!0,qn.isAsset=!1,qn=ee(qn),qn=ps(qn),qn=In(qn);qn.defs=_(qn.defs,{copyStart:null,copyDimensions:null,checkHitIgnoreTransparency:!1}),qn.packetCoordinates=Q(qn.packetCoordinates,["copyStart","copyDimensions"]),qn.packetObjects=Q(qn.packetObjects,["asset"]),qn.factoryKill=function(t=!1){let{asset:e,removeAssetOnKill:i}=this;U(e)&&e.unsubscribe(this),i&&(i.substring?e.kill(!0):e.kill())};let _n=qn.getters,Zn=qn.setters,Qn=qn.deltaSetters;_n.copyStartX=function(){return this.currentCopyStart[0]},_n.copyStartY=function(){return this.currentCopyStart[1]},Zn.copyStartX=function(t){null!=t&&(this.copyStart[0]=t,this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0)},Zn.copyStartY=function(t){null!=t&&(this.copyStart[1]=t,this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0)},Zn.copyStart=function(t,e){this.setCoordinateHelper("copyStart",t,e),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},Qn.copyStartX=function(t){let e=this.copyStart;e[0]=H(e[0],t),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},Qn.copyStartY=function(t){let e=this.copyStart;e[1]=H(e[1],t),this.dirtyCopyStart=!0},Qn.copyStart=function(t,e){this.setDeltaCoordinateHelper("copyStart",t,e),this.dirtyCopyStart=!0,this.dirtyImageSubscribers=!0},_n.copyWidth=function(){return this.currentCopyDimensions[0]},_n.copyHeight=function(){return this.currentCopyDimensions[1]},Zn.copyWidth=function(t){null!=t&&(this.copyDimensions[0]=t,this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0)},Zn.copyHeight=function(t){null!=t&&(this.copyDimensions[1]=t,this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0)},Zn.copyDimensions=function(t,e){this.setCoordinateHelper("copyDimensions",t,e),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},Qn.copyWidth=function(t){let e=this.copyDimensions;e[0]=H(e[0],t),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},Qn.copyHeight=function(t){let e=this.copyDimensions;e[1]=H(e[1],t),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},Qn.copyDimensions=function(t,e){this.setDeltaCoordinateHelper("copyDimensions",t,e),this.dirtyCopyDimensions=!0,this.dirtyImageSubscribers=!0},Zn.checkHitIgnoreTransparency=function(t){this.checkHitIgnoreTransparency=t,t&&(this.stashOutput=!0)},qn.get=function(t){let e=this.source;if(0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!e){let e=this.getters[t];if(e)return e.call(this);{let e,i=this.defs[t],s=this.state;return void 0!==i?(e=this[t],void 0!==e?e:i):(i=s.defs[t],void 0!==i?(e=s[t],void 0!==e?e:i):undef)}}return Cn.indexOf(t)>=0||Le.indexOf(t)>=0?e[t.substring(6)]:void 0},qn.set=function(t={}){if(Object.keys(t).length){let e,i,s=this.setters,n=this.defs,r=this.state,o=this.source,a=r?r.setters:{},l=r?r.defs:{};Object.entries(t).forEach(([t,h])=>{0!==t.indexOf("video_")&&0!==t.indexOf("image_")||!o?t&&"name"!==t&&null!=h&&(e=s[t],i=!1,e||(e=a[t],i=!0),e?e.call(i?this.state:this,h):void 0!==n[t]?this[t]=h:void 0!==l[t]&&(r[t]=h)):(Dn.indexOf(t)>=0||$e.indexOf(t)>=0)&&(o[t.substring(6)]=h)},this)}return this},qn.updateImageSubscribers=function(){this.dirtyImageSubscribers=!1,this.imageSubscribers.length&&this.imageSubscribers.forEach(t=>{let e=i[t];e&&(e.dirtyInput=!0)})},qn.imageSubscribe=function(t){t&&t.substring&&Q(this.imageSubscribers,t)},qn.imageUnsubscribe=function(t){t&&t.substring&&K(this.imageSubscribers,t)},qn.cleanImage=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(tt(t,e)){this.dirtyImage=!1;let i=this.currentCopyStart,s=i[0],n=i[1],r=this.currentCopyDimensions,o=r[0],a=r[1];s+o>t&&(i[0]=t-o),n+a>e&&(i[1]=e-a);let l=this.copyArray;l.length=0,l.push(i[0],i[1],o,a)}},qn.cleanCopyStart=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(tt(t,e)){this.dirtyCopyStart=!1,this.cleanPosition(this.currentCopyStart,this.copyStart,[t,e]);let i=this.currentCopyStart,s=i[0],n=i[1];(s<0||s>t)&&(i[0]=s<0?0:t-1),(n<0||n>e)&&(i[1]=n<0?0:e-1),this.dirtyImage=!0}},qn.cleanCopyDimensions=function(){let t=this.sourceNaturalWidth,e=this.sourceNaturalHeight;if(tt(t,e)){this.dirtyCopyDimensions=!1;let i=this.copyDimensions,s=this.currentCopyDimensions,n=i[0],r=i[1];n.substring?s[0]=parseFloat(n)/100*t:s[0]=n,r.substring?s[1]=parseFloat(r)/100*e:s[1]=r;let o=s[0],a=s[1];(o<=0||o>t)&&(s[0]=o<=0?1:t),(a<=0||a>e)&&(s[1]=a<=0?1:e),this.dirtyImage=!0}},qn.prepareStamp=function(){this.dirtyAsset&&this.cleanAsset(),this.asset&&("Sprite"===this.asset.type?this.checkSpriteFrame(this):this.asset.checkSource?this.asset.checkSource(this.sourceNaturalWidth,this.sourceNaturalHeight):this.dirtyAsset=!0),(this.dirtyDimensions||this.dirtyHandle||this.dirtyScale)&&(this.dirtyPaste=!0),(this.dirtyScale||this.dirtyDimensions||this.dirtyStart||this.dirtyOffset||this.dirtyHandle)&&(this.dirtyPathObject=!0),this.dirtyScale&&this.cleanScale(),this.dirtyDimensions&&this.cleanDimensions(),this.dirtyLock&&this.cleanLock(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyHandle&&this.cleanHandle(),this.dirtyRotation&&this.cleanRotation(),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtyStampHandlePositions&&this.cleanStampHandlePositions(),this.dirtyCopyStart&&this.cleanCopyStart(),this.dirtyCopyDimensions&&this.cleanCopyDimensions(),this.dirtyImage&&this.cleanImage(),this.dirtyPaste&&this.preparePasteObject(),this.dirtyPathObject&&this.cleanPathObject(),this.dirtyPositionSubscribers&&this.updatePositionSubscribers(),this.dirtyImageSubscribers&&this.updateImageSubscribers()},qn.preparePasteObject=function(){this.dirtyPaste=!1;let t=this.currentStampHandlePosition,e=this.currentDimensions,i=this.currentScale,s=-t[0]*i,n=-t[1]*i,r=e[0]*i,o=e[1]*i,a=this.pasteArray;a.length=0,a.push(s,n,r,o),this.dirtyPathObject=!0},qn.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){this.pasteArray||this.preparePasteObject(),(this.pathObject=new Path2D).rect(...this.pasteArray)}},qn.draw=function(t){t.stroke(this.pathObject)},qn.fill=function(t){let[e,i,s,n]=this.copyArray;this.source&&s&&n&&t.drawImage(this.source,...this.copyArray,...this.pasteArray)},qn.drawAndFill=function(t){t.stroke(this.pathObject);let[e,i,s,n]=this.copyArray;this.source&&s&&n&&(this.currentHost.clearShadow(),t.drawImage(this.source,...this.copyArray,...this.pasteArray))},qn.fillAndDraw=function(t){t.stroke(this.pathObject);let[e,i,s,n]=this.copyArray;this.source&&s&&n&&(this.currentHost.clearShadow(),t.drawImage(this.source,...this.copyArray,...this.pasteArray)),t.stroke(this.pathObject)},qn.drawThenFill=function(t){t.stroke(this.pathObject);let[e,i,s,n]=this.copyArray;this.source&&s&&n&&t.drawImage(this.source,...this.copyArray,...this.pasteArray)},qn.fillThenDraw=function(t){let[e,i,s,n]=this.copyArray;this.source&&s&&n&&t.drawImage(this.source,...this.copyArray,...this.pasteArray),t.stroke(this.pathObject)},qn.checkHitReturn=function(t,e,i){if(this.checkHitIgnoreTransparency&&i&&i.engine){let[s,n,r,o]=this.copyArray,[a,l,h,c]=this.pasteArray,[u,d]=this.currentStampPosition,f=4*((e-d)*h+(t-u))+3;return!!i.engine.getImageData(s,n,r,o).data[f]&&{x:t,y:e,artefact:this}}return{x:t,y:e,artefact:this}};O.Picture=Picture;const Polygon=function(t={}){return this.shapeInit(t),this};let Kn=Polygon.prototype=Object.create(Object.prototype);Kn.type="Polygon",Kn.lib="entity",Kn.isArtefact=!0,Kn.isAsset=!1,Kn=ee(Kn),Kn=Os(Kn);Kn.defs=_(Kn.defs,{sides:0,radius:0});let Jn=Kn.setters,tr=Kn.deltaSetters;Jn.sides=function(t){this.sides=t,this.updateDirty()},tr.sides=function(t){this.sides+=t,this.updateDirty()},Jn.radius=function(t){this.radius=t,this.updateDirty()},tr.radius=function(t){this.radius+=t,this.updateDirty()},Kn.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makePolygonPath(),this.pathDefinition=t},Kn.makePolygonPath=function(){let t,e,i,s=this.radius,n=this.sides,r=360/n,o="",a=[],l=0,h=fi({x:0,y:-s});for(let t=0;t{if("pins"===e){let e=[];t.pins.forEach(t=>{U(t)?e.push(t.name):Array.isArray(t)?e.push([].concat(t)):e.push(t)}),t.pins=e}}),t};let ir=er.getters,sr=er.setters,nr=er.deltaSetters;ir.pins=function(t){return J(t)?this.getPinAt(t):this.currentPins.concat()},sr.pins=function(t){if(J(t)){let e=this.pins;if(Array.isArray(t))e.forEach((t,e)=>this.removePinAt(e)),e.length=0,e.push(...t),this.updateDirty();else if(U(t)&&J(t.index)){let i=e[t.index];Array.isArray(i)&&(J(t.x)&&(i[0]=t.x),J(t.y)&&(i[1]=t.y),this.updateDirty())}}},nr.pins=function(t){if(J(t)){let e=this.pins;if(U(t)&&J(t.index)){let i=e[t.index];Array.isArray(i)&&(J(t.x)&&(i[0]=addStrings(i[0],t.x)),J(t.y)&&(i[1]=addStrings(i[1],t.y)),this.updateDirty())}}},sr.tension=function(t){t.toFixed&&(this.tension=t,this.updateDirty())},nr.tension=function(t){t.toFixed&&(this.tension+=t,this.updateDirty())},sr.closed=function(t){this.closed=t,this.updateDirty()},sr.mapToPins=function(t){this.mapToPins=t,this.updateDirty()},sr.flipUpend=function(t){this.flipUpend=t,this.updateDirty()},sr.flipReverse=function(t){this.flipReverse=t,this.updateDirty()},sr.useAsPath=function(t){this.useAsPath=t,this.updateDirty()},sr.pivot=function(t){if(Y(t)&&!t)this.pivot=null,"pivot"===this.lockTo[0]&&(this.lockTo[0]="start"),"pivot"===this.lockTo[1]&&(this.lockTo[1]="start"),this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0;else{let e=this.pivot,s=t.substring?i[t]:t,n=this.name;s&&s.name&&(e&&e.name!==s.name&&K(e.pivoted,n),Q(s.pivoted,n),this.pivot=s,this.dirtyStampPositions=!0,this.dirtyStampHandlePositions=!0)}this.updateDirty()},er.updateDirty=function(){this.dirtySpecies=!0,this.dirtyPathObject=!0,this.dirtyPins=!0},er.getPinAt=function(t){let e=Math.floor(t);if(this.useAsPath){let t=this.getPathPositionData(this.unitPartials[e]);return[t.x,t.y]}{let t,i,s=this.currentPins,n=s[e],[r,o,a,l]=this.localBox,[h,c]=n,[u,d]=s[0],[f,p]=this.localOffset,[m,g]=this.currentStampPosition;return this.mapToPins?(t=h-u+r,i=c-u+o):(t=h-f,i=c-p),[m+t,g+i]}},er.updatePinAt=function(t,e){if(tt(t,e)){e=Math.floor(e);let i=this.pins;if(e=0){let s=i[e];U(s)&&s.pivoted&&K(s.pivoted,this.name),i[e]=t,this.updateDirty()}}},er.removePinAt=function(t){t=Math.floor(t);let e=this.pins;if(t=0){let i=e[t];U(i)&&i.pivoted&&K(i.pivoted,this.name),e[t]=null,this.updateDirty()}},er.prepareStamp=function(){this.dirtyHost&&(this.dirtyHost=!1),this.useParticlesAsPins&&(this.dirtyPins=!0),(this.dirtyPins||this.dirtyLock)&&(this.dirtySpecies=!0),(this.dirtyScale||this.dirtySpecies||this.dirtyDimensions||this.dirtyStart||this.dirtyHandle)&&(this.dirtyPathObject=!0,(this.dirtyScale||this.dirtySpecies)&&(this.pathCalculatedOnce=!1)),(this.isBeingDragged||this.lockTo.indexOf("mouse")>=0||this.lockTo.indexOf("particle")>=0)&&(this.dirtyStampPositions=!0),this.dirtyScale&&this.cleanScale(),this.dirtyStart&&this.cleanStart(),this.dirtyOffset&&this.cleanOffset(),this.dirtyRotation&&this.cleanRotation(),this.dirtyStampPositions&&this.cleanStampPositions(),this.dirtySpecies&&this.cleanSpecies(),this.dirtyPathObject&&(this.cleanPathObject(),this.updatePathSubscribers()),this.dirtyPositionSubscribers&&this.updatePositionSubscribers()},er.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makePolylinePath(),this.pathDefinition=t},er.getPathParts=function(t,e,i,s,n,r,o){let a=Math.sqrt,l=Math.pow,h=a(l(i-t,2)+l(s-e,2)),c=a(l(n-i,2)+l(r-s,2)),u=o*h/(h+c),d=o*c/(h+c);return[i-u*(n-t),s-u*(r-e),i,s,i+d*(n-t),s+d*(r-e)]},er.buildLine=function(t,e,i){let s="m0,0l";for(let n=2;n2&&(t=i[r],e=i[r+1],n=0);return s},er.cleanCoordinate=function(t,e){return t.toFixed?t:"left"===t||"top"===t?0:"right"===t||"bottom"===t?e:"center"===t?e/2:parseFloat(t)/100*e},er.cleanPinsArray=function(){this.dirtyPins=!1;let t=this.pins,e=this.currentPins;if(e.length=0,this.useParticlesAsPins)t.forEach((i,s)=>{let n;i&&i.substring?(n=d[i],n&&(t[s]=n)):n=i;let r=!(!n||!n.position)&&n.position;r&&e.push([r.x,r.y])}),e.length||(this.dirtyPins=!0);else{let s,n,r,o=1,a=1,l=this.getHost(),h=this.cleanCoordinate;l&&(r=l.currentDimensions,r&&([o,a]=r)),t.forEach((r,l)=>{let c;if(r&&r.substring?(c=i[r],t[l]=c):c=r,c)if(Array.isArray(c))[s,n]=c,e.push([h(s,o),h(n,a)]);else if(U(c)&&c.currentStart){let t=this.name;c.pivoted.indexOf(t)<0&&Q(c.pivoted,t),e.push([...c.currentStampPosition])}})}if(e.length){let t=e[0][0],i=e[0][1];e.forEach(e=>{e[0]{let e=i[t];e&&(e.dirtyStart=!0)})};O.Polyline=Polyline;const Quadratic=function(t={}){return this.control=He(),this.currentControl=He(),this.controlLockTo="coord",this.curveInit(t),this.shapeInit(t),this.dirtyControl=!0,this};let rr=Quadratic.prototype=Object.create(Object.prototype);rr.type="Quadratic",rr.lib="entity",rr.isArtefact=!0,rr.isAsset=!1,rr=ee(rr),rr=Os(rr),rr=vs(rr);rr.defs=_(rr.defs,{control:null,controlPivot:"",controlPivotCorner:"",addControlPivotHandle:!1,addControlPivotOffset:!1,controlPath:"",controlPathPosition:0,addControlPathHandle:!1,addControlPathOffset:!0,controlParticle:"",controlLockTo:""}),rr.packetExclusions=Q(rr.packetExclusions,[]),rr.packetExclusionsByRegex=Q(rr.packetExclusionsByRegex,[]),rr.packetCoordinates=Q(rr.packetCoordinates,["control"]),rr.packetObjects=Q(rr.packetObjects,["controlPivot","controlPath"]),rr.packetFunctions=Q(rr.packetFunctions,[]);rr.getters;let or=rr.setters,ar=rr.deltaSetters;or.controlPivot=function(t){this.setControlHelper(t,"controlPivot","control"),this.updateDirty(),this.dirtyControl=!0},or.controlParticle=function(t){this.setControlHelper(t,"controlParticle","control"),this.updateDirty(),this.dirtyControl=!0},or.controlPath=function(t){this.setControlHelper(t,"controlPath","control"),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},or.controlPathPosition=function(t){this.controlPathPosition=t,this.dirtyControl=!0,this.currentControlPathData=!1},ar.controlPathPosition=function(t){this.controlPathPosition+=t,this.dirtyControl=!0,this.currentControlPathData=!1},or.controlX=function(t){null!=t&&(this.control[0]=t,this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1)},or.controlY=function(t){null!=t&&(this.control[1]=t,this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1)},or.control=function(t,e){this.setCoordinateHelper("control",t,e),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},ar.controlX=function(t){let e=this.control;e[0]=H(e[0],t),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},ar.controlY=function(t){let e=this.control;e[1]=H(e[1],t),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},ar.control=function(t,e){this.setDeltaCoordinateHelper("control",t,e),this.updateDirty(),this.dirtyControl=!0,this.currentControlPathData=!1},or.controlLockTo=function(t){this.controlLockTo=t,this.updateDirty(),this.dirtyControlLock=!0,this.currentControlPathData=!1},rr.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeQuadraticPath(),this.pathDefinition=t},rr.makeQuadraticPath=function(){let[t,e]=this.currentStampPosition,[i,s]=this.currentControl,[n,r]=this.currentEnd;return`m0,0q${(i-t).toFixed(2)},${(s-e).toFixed(2)} ${(n-t).toFixed(2)},${(r-e).toFixed(2)}`},rr.cleanDimensions=function(){this.dirtyDimensions=!1,this.dirtyHandle=!0,this.dirtyOffset=!0,this.dirtyStart=!0,this.dirtyControl=!0,this.dirtyEnd=!0},rr.preparePinsForStamp=function(){let t=this.endPivot,e=this.endPath,i=this.controlPivot,s=this.controlPath;this.dirtyPins.forEach(n=>{(i&&i.name===n||s&&s.name===n)&&(this.dirtyControl=!0,this.controlLockTo.includes("path")&&(this.currentControlPathData=!1)),(t&&t.name===n||e&&e.name===n)&&(this.dirtyEnd=!0,this.endLockTo.includes("path")&&(this.currentEndPathData=!1))}),this.dirtyPins.length=0};O.Quadratic=Quadratic;const RadialGradient=function(t={}){return this.stylesInit(t),this};let lr=RadialGradient.prototype=Object.create(Object.prototype);lr.type="RadialGradient",lr.lib="styles",lr.isArtefact=!1,lr.isAsset=!1,lr=ee(lr),lr=Ks(lr);lr.defs=_(lr.defs,{startRadius:0,endRadius:0}),lr.packetObjects=Q(lr.packetObjects,["palette"]);let hr=lr.getters,cr=lr.setters,ur=lr.deltaSetters;hr.startRadius=function(t){return this.currentStartRadius},hr.endRadius=function(t){return this.currentEndRadius},cr.startRadius=function(t){this.startRadius=t,this.dirtyStyle=!0},cr.endRadius=function(t){this.endRadius=t,this.dirtyStyle=!0},ur.startRadius=function(t){this.startRadius=H(this.startRadius,t),this.dirtyStyle=!0},ur.endRadius=function(t){this.endRadius=H(this.endRadius,t),this.dirtyStyle=!0},lr.cleanRadius=function(t){const e=(t,e)=>{if(V(t))return t;switch(t){case"top":case"left":return 0;case"bottom":case"right":return e;case"center":return e/2;default:return t=parseFloat(t),V(t)?t/100*e:0}};this.currentStartRadius=t?e(this.startRadius,t):this.defs.startRadius,this.currentEndRadius=t?e(this.endRadius,t):this.defs.endRadius},lr.buildStyle=function(t={}){if(t){let e=t.engine;if(e){let t=e.createRadialGradient(...this.gradientArgs);return this.addStopsToGradient(t,this.paletteStart,this.paletteEnd,this.cyclePalette)}}return"rgba(0,0,0,0)"},lr.updateGradientArgs=function(t,e){let i=this.gradientArgs,s=this.currentStart,n=this.currentEnd,r=this.currentStartRadius,o=this.currentEndRadius,a=s[0]+t,l=s[1]+e,h=n[0]+t,c=n[1]+e;a===h&&l===c&&r===o&&o++,i.length=0,i.push(a,l,r,h,c,o)};O.RadialGradient=RadialGradient;const Rectangle=function(t={}){return this.shapeInit(t),this.currentRectangleWidth=1,this.currentRectangleHeight=1,this};let dr=Rectangle.prototype=Object.create(Object.prototype);dr.type="Rectangle",dr.lib="entity",dr.isArtefact=!0,dr.isAsset=!1,dr=ee(dr),dr=Os(dr);dr.defs=_(dr.defs,{rectangleWidth:10,rectangleHeight:10,radiusTLX:0,radiusTLY:0,radiusTRX:0,radiusTRY:0,radiusBRX:0,radiusBRY:0,radiusBLX:0,radiusBLY:0,offshootA:.55,offshootB:0});let fr=dr.setters,pr=dr.deltaSetters;fr.radius=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX","radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},fr.radiusX=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX"])},fr.radiusY=function(t){this.setRectHelper(t,["radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},fr.radiusT=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY","radiusTRX","radiusTRY"])},fr.radiusB=function(t){this.setRectHelper(t,["radiusBRX","radiusBRY","radiusBLX","radiusBLY"])},fr.radiusL=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY","radiusBLX","radiusBLY"])},fr.radiusR=function(t){this.setRectHelper(t,["radiusTRX","radiusTRY","radiusBRX","radiusBRY"])},fr.radiusTX=function(t){this.setRectHelper(t,["radiusTLX","radiusTRX"])},fr.radiusBX=function(t){this.setRectHelper(t,["radiusBRX","radiusBLX"])},fr.radiusLX=function(t){this.setRectHelper(t,["radiusTLX","radiusBLX"])},fr.radiusRX=function(t){this.setRectHelper(t,["radiusTRX","radiusBRX"])},fr.radiusTY=function(t){this.setRectHelper(t,["radiusTLY","radiusTRY"])},fr.radiusBY=function(t){this.setRectHelper(t,["radiusBRY","radiusBLY"])},fr.radiusLY=function(t){this.setRectHelper(t,["radiusTLY","radiusBLY"])},fr.radiusRY=function(t){this.setRectHelper(t,["radiusTRY","radiusBRY"])},fr.radiusTL=function(t){this.setRectHelper(t,["radiusTLX","radiusTLY"])},fr.radiusTR=function(t){this.setRectHelper(t,["radiusTRX","radiusTRY"])},fr.radiusBL=function(t){this.setRectHelper(t,["radiusBLX","radiusBLY"])},fr.radiusBR=function(t){this.setRectHelper(t,["radiusBRX","radiusBRY"])},fr.radiusTLX=function(t){this.setRectHelper(t,["radiusTLX"])},fr.radiusTLY=function(t){this.setRectHelper(t,["radiusTLY"])},fr.radiusTRX=function(t){this.setRectHelper(t,["radiusTRX"])},fr.radiusTRY=function(t){this.setRectHelper(t,["radiusTRY"])},fr.radiusBRX=function(t){this.setRectHelper(t,["radiusBRX"])},fr.radiusBRY=function(t){this.setRectHelper(t,["radiusBRY"])},fr.radiusBLX=function(t){this.setRectHelper(t,["radiusBLX"])},fr.radiusBLY=function(t){this.setRectHelper(t,["radiusBLY"])},pr.radius=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX","radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},pr.radiusX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX","radiusBRX","radiusBLX","radiusX"])},pr.radiusY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusTRY","radiusBRY","radiusBLY","radiusY"])},pr.radiusT=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY","radiusTRX","radiusTRY"])},pr.radiusB=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBRY","radiusBLX","radiusBLY"])},pr.radiusL=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY","radiusBLX","radiusBLY"])},pr.radiusR=function(t){this.deltaRectHelper(t,["radiusTRX","radiusTRY","radiusBRX","radiusBRY"])},pr.radiusTX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTRX"])},pr.radiusBX=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBLX"])},pr.radiusLX=function(t){this.deltaRectHelper(t,["radiusTLX","radiusBLX"])},pr.radiusRX=function(t){this.deltaRectHelper(t,["radiusTRX","radiusBRX"])},pr.radiusTY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusTRY"])},pr.radiusBY=function(t){this.deltaRectHelper(t,["radiusBRY","radiusBLY"])},pr.radiusLY=function(t){this.deltaRectHelper(t,["radiusTLY","radiusBLY"])},pr.radiusRY=function(t){this.deltaRectHelper(t,["radiusTRY","radiusBRY"])},pr.radiusTL=function(t){this.deltaRectHelper(t,["radiusTLX","radiusTLY"])},pr.radiusTR=function(t){this.deltaRectHelper(t,["radiusTRX","radiusTRY"])},pr.radiusBL=function(t){this.deltaRectHelper(t,["radiusBLX","radiusBLY"])},pr.radiusBR=function(t){this.deltaRectHelper(t,["radiusBRX","radiusBRY"])},pr.radiusTLX=function(t){this.deltaRectHelper(t,["radiusTLX"])},pr.radiusTLY=function(t){this.deltaRectHelper(t,["radiusTLY"])},pr.radiusTRX=function(t){this.deltaRectHelper(t,["radiusTRX"])},pr.radiusTRY=function(t){this.deltaRectHelper(t,["radiusTRY"])},pr.radiusBRX=function(t){this.deltaRectHelper(t,["radiusBRX"])},pr.radiusBRY=function(t){this.deltaRectHelper(t,["radiusBRY"])},pr.radiusBLX=function(t){this.deltaRectHelper(t,["radiusBLX"])},pr.radiusBLY=function(t){this.deltaRectHelper(t,["radiusBLY"])},fr.offshootA=function(t){this.offshootA=t,this.updateDirty()},fr.offshootB=function(t){this.offshootB=t,this.updateDirty()},pr.offshootA=function(t){t.toFixed&&(this.offshootA+=t,this.updateDirty())},pr.offshootB=function(t){t.toFixed&&(this.offshootB+=t,this.updateDirty())},fr.rectangleWidth=function(t){null!=t&&(this.rectangleWidth=t,this.dirtyDimensions=!0)},fr.rectangleHeight=function(t){null!=t&&(this.rectangleHeight=t,this.dirtyDimensions=!0)},pr.rectangleWidth=function(t){this.rectangleWidth=H(this.rectangleWidth,t),this.dirtyDimensions=!0},pr.rectangleHeight=function(t){this.rectangleHeight=H(this.rectangleHeight,t),this.dirtyDimensions=!0},dr.setRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=t},this)},dr.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=H(this[e],t)},this)},dr.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeRectanglePath(),this.pathDefinition=t},dr.cleanDimensions=function(){let t=this.getHost();if(t){let e=t.currentDimensions?t.currentDimensions:[t.w,t.h],i=this.rectangleWidth,s=this.rectangleHeight,n=this.currentRectangleWidth||1,r=this.currentRectangleHeight||1;i.substring&&(i=parseFloat(i)/100*e[0]),s.substring&&(s=parseFloat(s)/100*e[1]);let o,a=this.mimic;a&&a.name&&this.useMimicDimensions&&(o=a.currentDimensions),o?(this.currentRectangleWidth=this.addOwnDimensionsToMimic?o[0]+i:o[0],this.currentRectangleHeight=this.addOwnDimensionsToMimic?o[1]+s:o[1]):(this.currentRectangleWidth=i,this.currentRectangleHeight=s),this.currentDimensions[0]=this.currentRectangleWidth,this.currentDimensions[1]=this.currentRectangleHeight,this.dirtyStart=!0,this.dirtyHandle=!0,this.dirtyOffset=!0,n===this.currentRectangleWidth&&r===this.currentRectangleHeight||(this.dirtyPositionSubscribers=!0),this.mimicked&&this.mimicked.length&&(this.dirtyMimicDimensions=!0)}else this.dirtyDimensions=!0},dr.makeRectanglePath=function(){this.dirtyDimensions&&this.cleanDimensions();let t=this.currentRectangleWidth,e=this.currentRectangleHeight,i=this.offshootA,s=this.offshootB,n=this.radiusTLX,r=this.radiusTLY,o=this.radiusTRX,a=this.radiusTRY,l=this.radiusBRX,h=this.radiusBRY,c=this.radiusBLX,u=this.radiusBLY;(n.substring||r.substring||o.substring||a.substring||l.substring||h.substring||c.substring||u.substring)&&(n=n.substring?parseFloat(n)/100*t:n,r=r.substring?parseFloat(r)/100*e:r,o=o.substring?parseFloat(o)/100*t:o,a=a.substring?parseFloat(a)/100*e:a,l=l.substring?parseFloat(l)/100*t:l,h=h.substring?parseFloat(h)/100*e:h,c=c.substring?parseFloat(c)/100*t:c,u=u.substring?parseFloat(u)/100*e:u);let d="m0,0";return t-n-o!=0&&(d+="h"+(t-n-o)),o+a!==0&&(d+=`c${o*i},${a*s} ${o-o*s},${a-a*i}, ${o},${a}`),e-a-h!=0&&(d+="v"+(e-a-h)),l+h!==0&&(d+=`c${-l*s},${h*i} ${l*i-l},${h-h*s} ${-l},${h}`),-t+c+l!==0&&(d+="h"+(-t+c+l)),c+u!==0&&(d+=`c${-c*i},${-u*s} ${c*s-c},${u*i-u} ${-c},${-u}`),-e+r+u!==0&&(d+="v"+(-e+r+u)),n+r!==0&&(d+=`c${n*s},${-r*i} ${n-n*i},${r*s-r} ${n},${-r}`),d+="z",d},dr.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};O.Rectangle=Rectangle;const Shape=function(t={}){return this.shapeInit(t),this};let mr=Shape.prototype=Object.create(Object.prototype);mr.type="Shape",mr.lib="entity",mr.isArtefact=!0,mr.isAsset=!1,mr=ee(mr),mr=Os(mr);mr.defs=_(mr.defs,{}),mr.cleanSpecies=function(){this.dirtySpecies=!1},mr.cleanStampHandlePositionsAdditionalActions=function(){let t=this.localBox,e=this.currentStampHandlePosition;e[0]+=t[0],e[1]+=t[1]};O.Shape=Shape;const Spiral=function(t={}){return this.shapeInit(t),this};let gr=Spiral.prototype=Object.create(Object.prototype);gr.type="Spiral",gr.lib="entity",gr.isArtefact=!0,gr.isAsset=!1,gr=ee(gr),gr=Os(gr);gr.defs=_(gr.defs,{loops:1,loopIncrement:1,drawFromLoop:0});let yr=gr.setters,br=gr.deltaSetters;yr.loops=function(t){this.loops=t,this.updateDirty()},br.loops=function(t){this.loops+=t,this.updateDirty()},yr.loopIncrement=function(t){this.loopIncrement=t,this.updateDirty()},br.loopIncrement=function(t){this.loopIncrement+=t,this.updateDirty()},yr.drawFromLoop=function(t){this.drawFromLoop=Math.floor(t),this.updateDirty()},br.drawFromLoop=function(t){this.drawFromLoop=Math.floor(this.drawFromLoop+t),this.updateDirty()},gr.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeSpiralPath(),this.pathDefinition=t},gr.firstTurn=[[.043,0,.082,-.035,.088,-.088],[.007,-.057,-.024,-.121,-.088,-.162],[-.07,-.045,-.169,-.054,-.265,-.015],[-.106,.043,-.194,.138,-.235,.265],[-.044,.139,-.026,.3,.058,.442],[.091,.153,.25,.267,.442,.308],[.206,.044,.431,-.001,.619,-.131],[.2,-.139,.34,-.361,.381,-.619]],gr.subsequentTurns=[[0,-.27,-.11,-.52,-.29,-.71],[-.19,-.19,-.44,-.29,-.71,-.29],[-.27,0,-.52,.11,-.71,.29],[-.19,.19,-.29,.44,-.29,.71],[0,.27,.11,.52,.29,.71],[.19,.19,.44,.29,.71,.29],[.27,0,.52,-.11,.71,-.29],[.19,-.19,.29,-.44,.29,-.71]],gr.makeSpiralPath=function(){let t,e,i,s,n,r,o,a,l,h,c,u,d=Math.floor(this.loops),f=this.loopIncrement,p=Math.floor(this.drawFromLoop),m=this.firstTurn,g=this.subsequentTurns,y=[];for(let o=0;o=p&&(b+=`c${t},${e} ${i},${s} ${n},${r}`),[o,a,l,h,c,u]=g[d],y[d]=[t+o*f,e+a*f,i+l*f,s+h*f,n+c*f,r+u*f];return b},gr.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox,n=this.scale;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t/n},${-e/n}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};O.Spiral=Spiral;const Star=function(t={}){return this.shapeInit(t),this};let kr=Star.prototype=Object.create(Object.prototype);kr.type="Star",kr.lib="entity",kr.isArtefact=!0,kr.isAsset=!1,kr=ee(kr),kr=Os(kr);kr.defs=_(kr.defs,{radius1:0,radius2:0,points:0,twist:0});let Sr=kr.setters,Or=kr.deltaSetters;Sr.radius1=function(t){this.radius1=t,this.updateDirty()},Or.radius1=function(t){this.radius1+=t,this.updateDirty()},Sr.radius2=function(t){this.radius2=t,this.updateDirty()},Or.radius2=function(t){this.radius2+=t,this.updateDirty()},Sr.points=function(t){this.points=t,this.updateDirty()},Or.points=function(t){this.points+=t,this.updateDirty()},Sr.twist=function(t){this.twist=t,this.updateDirty()},Or.twist=function(t){this.twist+=t,this.updateDirty()},kr.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeStarPath(),this.pathDefinition=t},kr.makeStarPath=function(){let t,e,i,s,n,r,o,a=this.points,l=this.twist,h=this.radius1,c=this.radius2,u=360/a,d=[],f="";if(h.substring||c.substring){let t=this.getHost();if(t){let[e,i]=t.currentDimensions;h=h.substring?parseFloat(h)/100*e:h,c=c.substring?parseFloat(c)/100*e:c}}let p=fi({x:0,y:-h}),m=fi({x:0,y:-c});for(t=p.x,e=p.y,d.push(t),m.rotate(-u/2),m.rotate(l),o=0;o{this[e]=t},this)},Pr.deltaRectHelper=function(t,e){this.updateDirty(),e.forEach(e=>{this[e]=addStrings(this[e],t)},this)},Pr.cleanSpecies=function(){this.dirtySpecies=!1;let t="M0,0";t=this.makeTetragonPath(),this.pathDefinition=t},Pr.makeTetragonPath=function(){let t,e,i=this.radiusX,s=this.radiusY;if(i.substring||s.substring){let n=this.getHost();if(n){let[r,o]=n.currentDimensions;t=2*(i.substring?parseFloat(i)/100*r:i),e=2*(s.substring?parseFloat(s)/100*o:s)}}else t=2*i,e=2*s;let n=parseFloat((t*this.intersectX).toFixed(2)),r=parseFloat((t-n).toFixed(2)),o=parseFloat((e*this.intersectY).toFixed(2)),a=parseFloat((e-o).toFixed(2)),l="m0,0";return l+=`l${r},${o} ${-r},${a} ${-n},${-a} ${n},${-o}z`,l},Pr.calculateLocalPathAdditionalActions=function(){let[t,e,i,s]=this.localBox;this.pathDefinition=this.pathDefinition.replace("m0,0",`m${-t},${-e}`),this.pathCalculatedOnce=!1,this.calculateLocalPath(this.pathDefinition,!0)};O.Tetragon=Tetragon;const Ticker=function(t={}){return this.makeName(t.name),this.register(),this.subscribers=[],this.subscriberObjects=[],this.set(this.defs),this.set(t),this.cycleCount=0,this.active=!1,this.effectiveDuration=0,this.startTime=0,this.currentTime=0,this.tick=0,this.lastEvent=0,t.subscribers&&this.subscribe(t.subscribers),this.setEffectiveDuration(),this};let xr=Ticker.prototype=Object.create(Object.prototype);xr.type="Ticker",xr.lib="animationtickers",xr.isArtefact=!1,xr.isAsset=!1,xr=ee(xr);xr.defs=_(xr.defs,{order:1,duration:0,subscribers:null,killOnComplete:!1,cycles:1,eventChoke:0,onRun:null,onHalt:null,onReverse:null,onResume:null,onSeekTo:null,onSeekFor:null,onComplete:null,onReset:null}),xr.packetExclusions=Q(xr.packetExclusions,["subscribers"]),xr.packetFunctions=Q(xr.packetFunctions,["onRun","onHalt","onReverse","onResume","onSeekTo","onSeekFor","onComplete","onReset"]),xr.kill=function(){return this.active&&this.halt(),K(Dr,this.name),Rr=!0,this.deregister(),!0},xr.killTweens=function(t=!1){let e,i,s;for(e=0,i=this.subscribers.length;e{t=y[i],t&&e.push(t)})},xr.getSubscriberObjects=function(){return this.subscribers.length&&!this.subscriberObjects.length&&this.repopulateSubscriberObjects(),this.subscriberObjects},xr.sortSubscribers=function(){let t=this.subscribers;if(t.length>1){let e=[].concat(t),i=Math.floor,s=[];e.forEach(t=>{let e=i(t.effectiveTime)||0;s[e]||(s[e]=[]),s[e].push(t)}),this.subscribers=s.reduce((t,e)=>t.concat(e),[])}this.repopulateSubscriberObjects()},xr.updateSubscribers=function(t,e){e=!!J(e)&&e;let i,s,n=this.getSubscriberObjects();if(e)for(i=n.length-1;i>=0;i--)n[i].set(t);else for(i=0,s=n.length;it.reversed=!t.reversed),this},xr.makeTickerUpdateEvent=function(){return new CustomEvent("tickerupdate",{detail:{name:this.name,type:"Ticker",tick:this.tick,reverseTick:this.effectiveDuration-this.tick},bubbles:!0,cancelable:!0})},xr.recalculateEffectiveDuration=function(){let t,e=this.getSubscriberObjects(),i=0;return this.duration?this.setEffectiveDuration():(e.forEach(e=>{t=e.getEndTime(),t>i&&(i=t)}),this.effectiveDuration=i),this},xr.setEffectiveDuration=function(){let t;return this.duration&&(t=M(this.duration),"%"===t[0]?(this.duration=0,this.recalculateEffectiveDuration()):this.effectiveDuration=t[1]),this},xr.fn=function(t){let e=Fr();t=!!J(t)&&t;let i,s,n,r,o,a,l,h,c=this.active,u=this.startTime,d=this.cycles,f=this.cycleCount,p=this.effectiveDuration,m=this.eventChoke;if(c&&u&&(!d||f=p?(h=this.tick=0,this.startTime=this.currentTime,e.tick=p,e.reverseTick=0,e.willLoop=!0,d&&(f++,this.cycleCount=f)):(e.tick=h,e.reverseTick=p-h),e.next=!0):h>=p?(e.tick=p,e.reverseTick=0,c=this.active=!1,d&&(f++,this.cycleCount=f)):(e.tick=h,e.reverseTick=p-h,e.next=!0),n=this.getSubscriberObjects(),t)for(i=n.length-1;i>=0;i--)n[i].update(e);else for(i=0,s=n.length;i=d&&this.killTweens(!0)}Tr(e)},xr.run=function(){return this.active||(this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!1}),this.active=!0,Q(Dr,this.name),Rr=!0,"function"==typeof this.onRun&&this.onRun()),this},xr.isRunning=function(){return this.active},xr.reset=function(){return this.active&&this.halt(),this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!1}),this.active=!0,this.fn(!0),this.active=!1,"function"==typeof this.onReset&&this.onReset(),this},xr.complete=function(){return this.active&&this.halt(),this.startTime=this.currentTime=Date.now(),this.cycleCount=0,this.updateSubscribers({reversed:!0}),this.active=!0,this.fn(),this.active=!1,"function"==typeof this.onComplete&&this.onComplete(),this},xr.reverse=function(t=!1){let e;return t=et(t,!1),this.active&&this.halt(),e=this.currentTime-this.startTime,this.startTime=this.currentTime-(this.effectiveDuration-e),this.changeSubscriberDirection(),this.active=!0,this.fn(),this.active=!1,"function"==typeof this.onReverse&&this.onReverse(),t&&this.resume(),this},xr.halt=function(){return this.active=!1,Q(Dr,this.name),Rr=!0,"function"==typeof this.onHalt&&this.onHalt(),this},xr.resume=function(){let t,e,i;return this.active||(t=Date.now(),e=this.currentTime,i=this.startTime,this.startTime=t-(e-i),this.currentTime=t,this.active=!0,Q(Dr,this.name),Rr=!0,"function"==typeof this.onResume&&this.onResume()),this},xr.seekTo=function(t,e=!1){let i=!1;return t=et(t,0),this.active&&this.halt(),this.cycles&&this.cycleCount>=this.cycles&&(this.cycleCount=this.cycles-1),t=this.cycles&&(this.cycleCount=this.cycles-1),this.startTime-=t,t<0&&(i=!0),this.active=!0,this.fn(i),this.active=!1,"function"==typeof this.onSeekFor&&this.onSeekFor(),e&&this.resume(),this};let Dr=[],Rr=!0;se({name:"coreTickersAnimation",order:0,fn:function(){return new Promise(t=>{if(Rr){Rr=!1;let t=[].concat(Dr),i=Math.floor,s=[];t.forEach(t=>{let n=e[t];if(J(n)){let t=i(n.order)||0;s[t]||(s[t]=[]),s[t].push(n.name)}}),Dr=s.reduce((t,e)=>t.concat(e),[])}Dr.forEach(t=>{let i=e[t];i&&i.fn&&i.fn()}),t(!0)})}});const Er=[],Fr=function(){return Er.length||Er.push({tick:0,reverseTick:0,willLoop:!1,next:!1}),Er.shift()},Tr=function(t){t&&(t.tick=0,t.reverseTick=0,t.willLoop=!1,t.next=!1,Er.push(t))},Hr=function(t){return new Ticker(t)};O.Ticker=Ticker;const Tracer=function(t={}){return this.makeName(t.name),this.register(),this.initializePositions(),this.set(this.defs),this.onEnter=B,this.onLeave=B,this.onDown=B,this.onUp=B,this.stampAction=B,this.trace=Ns(t),t.group||(t.group=Gi),this.set(t),this.purge&&this.purgeArtefact(this.purge),this};let Mr=Tracer.prototype=Object.create(Object.prototype);Mr.type="Tracer",Mr.lib="entity",Mr.isArtefact=!0,Mr.isAsset=!1,Mr=ee(Mr),Mr=ps(Mr);Mr.defs=_(Mr.defs,{artefact:null,historyLength:1,hitRadius:10,showHitRadius:!1,hitRadiusColor:"#000000"}),Mr.packetExclusions=Q(Mr.packetExclusions,[]),Mr.packetExclusionsByRegex=Q(Mr.packetExclusionsByRegex,[]),Mr.packetCoordinates=Q(Mr.packetCoordinates,[]),Mr.packetObjects=Q(Mr.packetObjects,["artefact","particle"]),Mr.packetFunctions=Q(Mr.packetFunctions,["stampAction"]),Mr.finalizePacketOut=function(t,e){return t},Mr.postCloneAction=function(t,e){return t.trace=Ns({name:t.name,historyLength:e.historyLength||this.historyLength||1}),t},Mr.factoryKill=function(t){t&&this.artefact.kill(),this.trace.kill()};Mr.getters;let Ir=Mr.setters;Mr.deltaSetters;Ir.stampAction=function(t){W(t)&&(this.stampAction=t)},Ir.artefact=function(t){let e;t.substring?e=i[t]:U(t)&&t.isArtefact&&(e=t),e&&(this.artefact=e)},Mr.regularStampSynchronousActions=function(){let{artefact:t,trace:e,stampAction:i,showHitRadius:s,hitRadius:n,hitRadiusColor:r,currentStampPosition:o}=this,a=this.currentHost;if(e.set({position:o}),e.manageHistory(0,a),i.call(this,t,e,a),s){let t=a.engine;t.save(),t.lineWidth=1,t.strokeStyle=r,t.setTransform(1,0,0,1,0,0),t.beginPath(),t.arc(o[0],o[1],n,0,2*Math.PI),t.stroke(),t.restore()}},Mr.checkHit=function(t=[],e){if(this.noUserInteraction)return!1;let i,s,n=Array.isArray(t)?t:[t],r=this.currentStampPosition,o=!1;if(n.some(t=>{if(Array.isArray(t))i=t[0],s=t[1];else{if(!tt(t,t.x,t.y))return!1;i=t.x,s=t.y}if(!i.toFixed||!s.toFixed||isNaN(i)||isNaN(s))return!1;let e=fi(r).vectorSubtract(t);return e.getMagnitude()t.name)),Array.isArray(this.definitions)&&(t.definitions=this.definitions.map(t=>{let e={};if(e.attribute=t.attribute,e.start=t.start,e.end=t.end,t.engine&&t.engine.substring)e.engine=t.engine.substring;else if(J(t.engine)&&null!==t.engine){let i=this.stringifyFunction(t.engine);i&&(e.engine=i,e.engineIsFunction=!0)}return e})),t},Br.postCloneAction=function(t,i){if(i.useNewTicker){let s=e[this.ticker];J(i.cycles)?t.cycles=i.cycles:t.cycles=s?s.cycles:1;let n=e[t.ticker];n.cycles=t.cycles,J(i.duration)&&(t.duration=i.duration,t.calculateEffectiveDuration(),n&&n.recalculateEffectiveDuration())}return Array.isArray(t.definitions)&&t.definitions.forEach((t,e)=>{t.engineIsFunction&&(t.engine=this.definitions[e].engine)}),t};let Lr=Br.getters,$r=Br.setters;Lr.definitions=function(){return[].concat(this.definitions)},$r.definitions=function(t){this.definitions=[].concat(t),this.setDefinitionsValues()},$r.commenceAction=function(t){this.commenceAction=t,"function"!=typeof this.commenceAction&&(this.commenceAction=B)},$r.completeAction=function(t){this.completeAction=t,"function"!=typeof this.completeAction&&(this.completeAction=B)},Br.set=function(t={}){if(Object.keys(t).length){let e,i,s,n,r=this.setters,o=Object.keys(t),a=this.defs,l=!!J(t.ticker)&&this.ticker;for(i=0,s=o.length;ie?r=1:ni&&(r=1)),a?r&&r==this.status||(this.status=r,this.doSimpleUpdate(t),t.next||(this.status=l?-1:1)):r!=this.status&&(this.status=r,this.doSimpleUpdate(t),t.next||(this.status=l?-1:1)),t.willLoop&&(this.reverseOnCycleEnd?this.reversed=!l:this.status=-1)},Br.doSimpleUpdate=function(t={}){let e,i,s,n,r,o,a,l,h,c,u,d,f,p,m=this.effectiveTime,g=this.engineActions,y=this.effectiveDuration,b=this.status,k=this.definitions,S=this.targets,O=this.action,P=this.setObj||{},v=Math.round;for(e=this.reversed?t.reverseTick-m:t.tick-m,o=y&&!b?e/y:b>0?1:0,i=0,s=k.length;i(t=parseFloat(t),V(t)||(t=0),V(e)||(e=0),parseFloat(t.toFixed(e))),Wheel=function(t={}){return it(t.dimensions,t.width,t.height,t.radius)||(t.radius=5),this.entityInit(t),this};let Nr=Wheel.prototype=Object.create(Object.prototype);Nr.type="Wheel",Nr.lib="entity",Nr.isArtefact=!0,Nr.isAsset=!1,Nr=ee(Nr),Nr=ps(Nr);Nr.defs=_(Nr.defs,{radius:5,startAngle:0,endAngle:360,clockwise:!0,includeCenter:!1,closed:!0});Nr.getters;let Xr=Nr.setters,Yr=Nr.deltaSetters;Xr.width=function(t){if(null!=t){let e=this.dimensions;e[0]=e[1]=t,this.dimensionsHelper()}},Yr.width=function(t){let e=this.dimensions;e[0]=e[1]=addStrings(e[0],t),this.dimensionsHelper()},Xr.height=function(t){if(null!=t){let e=this.dimensions;e[0]=e[1]=t,this.dimensionsHelper()}},Yr.height=function(t){let e=this.dimensions;e[0]=e[1]=addStrings(e[0],t),this.dimensionsHelper()},Xr.dimensions=function(t,e){this.setCoordinateHelper("dimensions",t,e),this.dimensionsHelper()},Yr.dimensions=function(t,e){this.setDeltaCoordinateHelper("dimensions",t,e),this.dimensionsHelper()},Xr.radius=function(t){this.radius=t,this.radiusHelper()},Yr.radius=function(t){this.radius=addStrings(this.radius,t),this.radiusHelper()},Xr.startAngle=function(t){this.startAngle=jr(t,4),this.dirtyPathObject=!0},Yr.startAngle=function(t){this.startAngle+=jr(t,4),this.dirtyPathObject=!0},Xr.endAngle=function(t){this.endAngle=jr(t,4),this.dirtyPathObject=!0},Yr.endAngle=function(t){this.endAngle+=jr(t,4),this.dirtyPathObject=!0},Xr.closed=function(t){J(t)&&(this.closed=!!t,this.dirtyPathObject=!0)},Xr.includeCenter=function(t){J(t)&&(this.includeCenter=!!t,this.dirtyPathObject=!0)},Xr.clockwise=function(t){J(t)&&(this.clockwise=!!t,this.dirtyPathObject=!0)},Nr.dimensionsHelper=function(){let t=this.dimensions[0];t.substring?this.radius=parseFloat(t)/2+"%":this.radius=t/2,this.dirtyDimensions=!0},Nr.radiusHelper=function(){let t=this.radius,e=this.dimensions;t.substring?e[0]=e[1]=2*parseFloat(t)+"%":e[0]=e[1]=2*t,this.dirtyDimensions=!0},Nr.cleanDimensionsAdditionalActions=function(){let t=this.radius,e=this.currentDimensions,i=t.substring?parseFloat(t)/100*e[0]:t;e[0]!==2*i?(e[1]=e[0],this.currentRadius=e[0]/2):this.currentRadius=i},Nr.cleanPathObject=function(){if(this.dirtyPathObject=!1,!this.noPathUpdates||!this.pathObject){let t=this.pathObject=new Path2D,e=this.currentStampHandlePosition,i=this.currentScale,s=this.currentRadius*i,n=s-e[0]*i,r=s-e[1]*i,o=this.startAngle*P,a=this.endAngle*P;t.arc(n,r,s,o,a,!this.clockwise),this.includeCenter?(t.lineTo(n,r),t.closePath()):this.closed&&t.closePath()}};O.Wheel=Wheel;const World=function(t={}){this.makeName(t.name),this.register(),this.set(this.defs);let e=t.keytypes||{};return e.gravity||(e.gravity="Vector"),t.gravity||(t.gravity=[0,9.81,0]),t.userAttributes&&t.userAttributes.forEach(t=>{this.addAttribute(t),t.type&&(e[t.key]=t.type)}),this.initializeAttributes(e),this.set(t),this};let zr=World.prototype=Object.create(Object.prototype);zr.type="World",zr.lib="world",zr.isArtefact=!1,zr.isAsset=!1,zr=ee(zr);zr.defs=_(zr.defs,{gravity:null,tickMultiplier:1,keytypes:null}),zr.kill=function(){return this.deregister(),!0};let Gr=zr.getters,Wr=zr.setters,Vr=zr.deltaSetters;Wr.gravityX=function(t){this.gravity&&J(t)&&this.gravity.setX(t)},Wr.gravityY=function(t){this.gravity&&J(t)&&this.gravity.setY(t)},Wr.gravityZ=function(t){this.gravity&&J(t)&&this.gravity.setZ(t)},Wr.gravity=function(t){this.gravity&&J(t)&&this.gravity.set(t)},zr.addAttribute=function(t={}){let{key:e,defaultValue:i,setter:s,deltaSetter:n,getter:r}=t;return e&&e.substring&&(this.defs[e]=J(i)?i:null,this[e]=J(i)?i:null,W(s)&&(Wr[e]=s),W(n)&&(Vr[e]=n),W(r)&&(Gr[e]=r)),this},zr.removeAttribute=function(t){return t&&t.substring&&(delete this.defs[t],delete this[t],delete Gr[t],delete Wr[t],delete Vr[t]),this},zr.initializeAttributes=function(t){for(let[e,i]of Object.entries(t))switch(i){case"Quaternion":this[e]=Si();break;case"Vector":this[e]=mi();break;case"Coordinate":this[e]=He()}};O.World=World;window.scrawlEnvironmentTouchSupported=!!("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch),window.scrawlEnvironmentOffscreenCanvasSupported="OffscreenCanvas"in window,document.querySelectorAll("[data-stack]").forEach(t=>Ni(t)),function(){let t;document.querySelectorAll("canvas").forEach((e,i)=>{t=Yi(e),i||Wi(t)})}(),T(),be(),ce(),oe=!0,ge();var Ur={library:x,startCoreAnimationLoop:T,stopCoreAnimationLoop:function(){A=!1},currentCorePosition:le,startCoreListeners:ge,stopCoreListeners:function(){re=!1,oe=!1,me.halt(),ye("removeEventListener")},observeAndUpdate:function(t={}){if(!tt(t.event,t.origin,t.updates))return!1;let e=t.target.substring&&t.targetLibrarySection?x[t.targetLibrarySection][t.target]:t.target;if(!e)return!1;let i=t.event,s=t.origin,n=t.useNativeListener?Yt:Lt,r=t.useNativeListener?zt:$t,o=B;t.preventDefault&&(o=t=>{t.preventDefault(),t.returnValue=!1});let a=function(i){o(i);let s=!(!i||!i.target)&&i.target.id;if(s){let n=t.updates[s];if(n){let t,s=n[0],r=n[1],o=i.target.value,a=!0;switch(r){case"float":t=parseFloat(o);break;case"int":t=parseInt(o,10);break;case"round":t=Math.round(o);break;case"roundDown":t=Math.floor(o);break;case"roundUp":t=Math.ceil(o);break;case"raw":t=o;break;case"string":t=""+o;break;case"boolean":t=!!J(o)&&(o.substring?"true"===o.toLowerCase()||"false"!==o.toLowerCase()&&!!parseFloat(o):!!o);break;default:r.substring?t=`${parseFloat(o)}${r}`:a=!1}a&&("Group"===e.type?e.setArtefacts({[s]:t}):e.set({[s]:t}))}}};return n(i,a,s),function(){r(i,a,s)}},makeDragZone:function(t={}){let{zone:e,coordinateSource:i,collisionGroup:s,startOn:n,endOn:r,updateOnStart:o,updateOnEnd:a,exposeCurrentArtefact:l}=t;if(!e)return new Error("dragZone constructor - no zone supplied");if(e.substring&&(e=artefact[e]),!e||["Canvas","Stack"].indexOf(e.type)<0)return new Error("dragZone constructor - zone object is not a Stack or Canvas wrapper");let h=e.domElement;if(!h)return new Error("dragZone constructor - zone does not contain a target DOM element");if(s?s.substring&&(s=u[s]):s="Canvas"===e.type?u[e.base.name]:u[e.name],!s||"Group"!==s.type)return new Error("dragZone constructor - unable to recover collisionGroup group");if(i?i.here?i=i.here:tt(i.x,i.y)||(i=!1):i="Canvas"===e.type?e.base.here:e.here,!i)return new Error("dragZone constructor - unable to discover a usable coordinateSource object");Array.isArray(n)||(n=["down"]),Array.isArray(r)||(r=["up"]);let c=!1;U(o)&&(o=function(){c.artefact.set(t.updateOnStart)}),W(o)||(o=B),U(a)&&(a=function(){c.artefact.set(t.updateOnEnd)}),W(a)||(a=B),Y(l)||(l=!1);const d=function(t){t.cancelable&&(t.preventDefault(),t.returnValue=!1)},f=function(t){d(t);let e=t.type;"touchstart"!==e&&"touchcancel"!==e||de(t),c=s.getArtefactAt(i),c&&(c.artefact.pickupArtefact(i),o())};let p=function(t){d(t),c&&(c.artefact.dropArtefact(),a(),c=!1)};const m=function(){$t(n,f,h),$t(r,p,h)};return Lt(n,f,h),Lt(r,p,h),l?function(t=!1){if(!t)return c;m()}:m},makeComponent:function(t){let e=!!G(t.domElement)&&t.domElement,s=U(t.animationHooks)?t.animationHooks:{},n=U(t.canvasSpecs)?t.canvasSpecs:{},r=U(t.observerSpecs)?t.observerSpecs:{},o=!Y(t.includeCanvas)||t.includeCanvas;return e&&e.id&&i[e.id]?as(e,n,s,r):ls(e,n,s,r,o)},addStack:function(t={}){let e,s,n,r,o,a,l="absolute";return e=t.element&&t.element.substring?document.querySelector(t.element):G(t.element)?t.element:document.createElement("div"),t.host&&t.host.substring?(s=document.querySelector(t.host),s||(s=document.body)):s=G(t.host)?t.host:J(e.parentElement)?e.parentElement:document.body,J(t.width)&&(e.style.width=t.width.toFixed?t.width+"px":t.width),J(t.height)&&(e.style.height=t.height.toFixed?t.height+"px":t.height),a=t.name||e.id||e.getAttribute("name")||"",a||(a=N()),e.id=a,e.setAttribute("data-stack","data-stack"),s&&null!=s.getAttribute("data-stack")?(n=i[s.id],o=n?n.name:"root"):o="root",e.setAttribute("data-group",o),"root"===o&&(l="relative"),e.parentElement&&s.id===e.parentElement.id||s.appendChild(e),r=Ii({name:a,domElement:e,group:o,host:o,position:l,setInitialDimensions:!0}),Xi(e,a),Array.from(e.childNodes).forEach(t=>{t.id&&Bi.indexOf(t.id)>=0&&K(Bi,t.id)}),delete t.name,delete t.element,delete t.host,delete t.width,delete t.height,r.set(t),ji=!0,r},getStack:function(t){let e,i=document.querySelector(t);return i&&(e=Ni(i)),e},addCanvas:function(t={}){let e=document.createElement("canvas"),s=t.name?t.name:N(),n=t.host,r="root",o=t.width||300,a=t.height||150,l="relative";if(n.substring){let t=i[n];!t&&n?(n=document.querySelector(n),n&&(r=n.id)):n=t}n?"Stack"===n.type?(r=n.name,l="absolute",n=n.domElement):G(n)||(n=document.body):n=document.body,e.id=s,e.setAttribute("data-group",r),e.width=o,e.height=a,e.style.position=l,n.appendChild(e);let h=Ci({name:s,domElement:e,group:r,host:r,position:l,setInitialDimensions:!1,setAsCurrentCanvas:!J(t.setAsCurrentCanvas)||t.setAsCurrentCanvas,trackHere:J(t.trackHere)?t.trackHere:"subscribe"});return delete t.group,delete t.host,delete t.name,delete t.element,delete t.position,delete t.setInitialDimensions,delete t.setAsCurrentCanvas,delete t.trackHere,h.set(t),h},getCanvas:function(t){let e=document.querySelector(t),i=!1;return e&&(i=Yi(e)),Wi(i),i},setCurrentCanvas:Wi,clear:Vi,compile:Ui,show:qi,render:function(...t){return _i(t),Zi("render")},addListener:Lt,removeListener:$t,addNativeListener:Yt,removeNativeListener:zt,makeAnimationObserver:Bt,reducedMotionActions:_t,setReduceMotionAction:t=>Ut=t,setNoPreferenceMotionAction:t=>qt=t,colorSchemeActions:te,setColorSchemeDarkAction:t=>Kt=t,setColorSchemeLightAction:t=>Jt=t,makeAction:function(t){return new Action(t)},makeAnimation:se,makeBezier:function(t={}){return t.species="bezier",new Bezier(t)},makeBlock:function(t){return new Block(t)},makeCog:function(t={}){return t.species="cog",new Cog(t)},makeColor:Ts,requestCoordinate:Fe,releaseCoordinate:Te,makeEmitter:function(t){return new Emitter(t)},makeFilter:function(t){return new Filter(t)},makeForce:_s,makeGradient:function(t){return new Gradient(t)},makeGrid:function(t){return new Grid(t)},makeGroup:ci,importImage:je,importDomImage:Ne,createImageFromCell:function(t,e=!1){let i=t.substring?a[t]||o[t]:t;"Canvas"===i.type&&(i=i.base),"Cell"===i.type&&(i.stashOutput=!0,e&&(i.stashOutputAsAsset=!0))},createImageFromGroup:function(t,e=!1){let i;t&&!t.substring?"Group"===t.type?i=t:"Cell"===t.type?i=u[t.name]:"Canvas"===t.type&&(i=u[t.base.name]):t&&t.substring&&(i=u[t]),i&&(i.stashOutput=!0,e&&(i.stashOutputAsAsset=!0))},createImageFromEntity:function(t,e=!1){let s=t.substring?i[t]:t;s.isArtefact&&(s.stashOutput=!0,e&&(s.stashOutputAsAsset=!0))},makeLine:function(t={}){return t.species="line",new Line(t)},makeLoom:function(t){return new Loom(t)},makeMesh:function(t){return new hn(t)},makeNet:function(t){return new Net(t)},makeNoise:function(t){return new kn(t)},makeOval:function(t={}){return t.species="oval",new Oval(t)},makePattern:function(t){return new Pattern(t)},makePhrase:function(t){return new Phrase(t)},makePicture:function(t){return new Picture(t)},makePolygon:function(t={}){return t.species="polygon",new Polygon(t)},makePolyline:function(t={}){return t.species="polyline",new Polyline(t)},makeQuadratic:function(t={}){return t.species="quadratic",new Quadratic(t)},requestQuaternion:bi,releaseQuaternion:ki,makeRadialGradient:function(t){return new RadialGradient(t)},makeRectangle:function(t={}){return t.species="rectangle",new Rectangle(t)},makeRender:rs,makeShape:function(t){return new Shape(t)},makeSpiral:function(t={}){return t.species="spiral",new Spiral(t)},makeSpring:mn,importSprite:Hn,makeStar:function(t={}){return t.species="star",new Star(t)},makeTetragon:function(t={}){return t.species="tetragon",new Tetragon(t)},makeTicker:Hr,makeTracer:function(t){return new Tracer(t)},makeTween:function(t){return new Tween(t)},requestVector:fi,releaseVector:pi,importDomVideo:function(t){let e=/.*\/(.*?)\./;document.querySelectorAll(t).forEach(t=>{let i;if("VIDEO"===t.tagName.toUpperCase()){if(t.id||t.name)i=t.id||t.name;else{let s=e.exec(t.src);i=s&&s[1]?s[1]:""}let s=En({name:i,source:t});t.readyState<=2&&(t.oncanplay=()=>{s.set({source:t})})}})},importVideo:Rn,importMediaStream:function(t={}){let e={};e.audio=!J(t.audio)||t.audio,e.video={};let i=e.video.width={};t.minWidth&&(i.min=t.minWidth),t.maxWidth&&(i.max=t.maxWidth),i.ideal=t.width?t.width:1280;let s=e.video.height={};t.minHeight&&(s.min=t.minHeight),t.maxHeight&&(s.max=t.maxHeight),s.ideal=t.height?t.height:720,t.facing&&(e.video.facingMode=t.facing);let n=t.name||N(),r=document.createElement("video"),o=En({name:n,source:r});return new Promise((t,i)=>{navigator&&navigator.mediaDevices?navigator.mediaDevices.getUserMedia(e).then(e=>{let i,s=e.getVideoTracks();Array.isArray(s)&&s[0]&&(i=s[0].getConstraints()),r.id=o.name,i&&(r.width=i.width,r.height=i.height),r.srcObject=e,r.onloadedmetadata=function(t){r.play()},t(o)}).catch(e=>{console.log(e.message),t(o)}):i("Navigator.mediaDevices object not found")})},makeWheel:function(t){return new Wheel(t)},makeWorld:function(t){return new World(t)}};export default Ur; diff --git a/package.json b/package.json index f17eed751..367f34e99 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scrawl-canvas", - "version": "8.3.4", - "description": "Version 8.3.4 - 6 Jan 2021", + "version": "8.4.0", + "description": "Version 8.4.0 - 2 Feb 2021", "main": "min/scrawl.js", "scripts": { "build": "rollup -c && docco ./demo/**/*.js && docco ./demo/*.js && docco ./source/**/*.js && docco ./source/scrawl.js" diff --git a/source/core/library.js b/source/core/library.js index bad869d9f..d633faee1 100644 --- a/source/core/library.js +++ b/source/core/library.js @@ -9,7 +9,7 @@ // Current version -const version = '8.3.4'; +const version = '8.4.0'; // Objects created using the __makeAnchor__ factory diff --git a/source/core/userInteraction.js b/source/core/userInteraction.js index b1dc43942..c8e9a7715 100644 --- a/source/core/userInteraction.js +++ b/source/core/userInteraction.js @@ -18,7 +18,8 @@ const uiSubscribedElements = []; // Local boolean flags. let trackMouse = false, - mouseChanged = false; + mouseChanged = false, + viewportChanged = false; // `Exported object` (to modules and the scrawl object). The __currentCorePosition__ object holds the __global__ mouse cursor position, alongside browser view dimensions and scroll position @@ -43,6 +44,7 @@ const resizeAction = function (e) { currentCorePosition.w = w; currentCorePosition.h = h; mouseChanged = true; + viewportChanged = true; } }; @@ -209,6 +211,18 @@ const updateUiSubscribedElement = function (art) { } }; +const updatePhraseEntitys = function () { + + for (const [name, ent] of Object.entries(library.entity)) { + + if (ent.type === 'Phrase') { + + ent.dirtyDimensions = true; + ent.dirtyFont = true; + } + } +}; + // Internal functions that get triggered when setting a DOM-based artefact's `trackHere` attribute. They add/remove an event listener to the artefact's domElement. const addLocalMouseMoveListener = function (wrapper) { @@ -261,6 +275,12 @@ const coreListenersTracker = makeAnimation({ updateUiSubscribedElements(); } + if (viewportChanged) { + + viewportChanged = false; + updatePhraseEntitys(); + } + resolve(true); }); }, diff --git a/source/core/utilities.js b/source/core/utilities.js index 5c632da90..92ef19305 100644 --- a/source/core/utilities.js +++ b/source/core/utilities.js @@ -104,6 +104,7 @@ const correctForZero = (item) => { // __λ functions__ helps us avoid errors when invoking a function attribute settable by the coder const λnull = () => {}; +const λfirstArg = function (a) { return a; }; const λthis = function () { return this; }; const λpromise = () => Promise.resolve(true); @@ -130,6 +131,10 @@ const generateUniqueString = () => { return performance.now().toString(36) + Math.random().toString(36).substr(2); }; +// __interpolate__ clamp a value between a maximum and minimum value +const interpolate = function (val, min, max) { + return min + val * (max - min); +}; // __isa_boolean__ checks to make sure the argument is a boolean const isa_boolean = item => (typeof item === 'boolean') ? true : false; @@ -297,16 +302,416 @@ const xtGet = (...args) => args.find(item => typeof item != 'undefined'); const xto = (...args) => (args.find(item => typeof item != 'undefined')) ? true : false; +// Seedable random numbers +// The following code has been lifted in its entirety from https://github.com/skratchdot/random-seed + +/* + * random-seed + * https://github.com/skratchdot/random-seed + * + * This code was originally written by Steve Gibson and can be found here: + * + * https://www.grc.com/otg/uheprng.htm + * + * It was slightly modified for use in node, to pass jshint, and a few additional + * helper functions were added. + * + * Copyright (c) 2013 skratchdot + * Dual Licensed under the MIT license and the original GRC copyright/license + * included below. + */ +/* ============================================================================ + Gibson Research Corporation + UHEPRNG - Ultra High Entropy Pseudo-Random Number Generator + ============================================================================ + LICENSE AND COPYRIGHT: THIS CODE IS HEREBY RELEASED INTO THE PUBLIC DOMAIN + Gibson Research Corporation releases and disclaims ALL RIGHTS AND TITLE IN + THIS CODE OR ANY DERIVATIVES. Anyone may be freely use it for any purpose. + ============================================================================ + This is GRC's cryptographically strong PRNG (pseudo-random number generator) + for JavaScript. It is driven by 1536 bits of entropy, stored in an array of + 48, 32-bit JavaScript variables. Since many applications of this generator, + including ours with the "Off The Grid" Latin Square generator, may require + the deteriministic re-generation of a sequence of PRNs, this PRNG's initial + entropic state can be read and written as a static whole, and incrementally + evolved by pouring new source entropy into the generator's internal state. + ---------------------------------------------------------------------------- + ENDLESS THANKS are due Johannes Baagoe for his careful development of highly + robust JavaScript implementations of JS PRNGs. This work was based upon his + JavaScript "Alea" PRNG which is based upon the extremely robust Multiply- + With-Carry (MWC) PRNG invented by George Marsaglia. MWC Algorithm References: + http://www.GRC.com/otg/Marsaglia_PRNGs.pdf + http://www.GRC.com/otg/Marsaglia_MWC_Generators.pdf + ---------------------------------------------------------------------------- + The quality of this algorithm's pseudo-random numbers have been verified by + multiple independent researchers. It handily passes the fermilab.ch tests as + well as the "diehard" and "dieharder" test suites. For individuals wishing + to further verify the quality of this algorithm's pseudo-random numbers, a + 256-megabyte file of this algorithm's output may be downloaded from GRC.com, + and a Microsoft Windows scripting host (WSH) version of this algorithm may be + downloaded and run from the Windows command prompt to generate unique files + of any size: + The Fermilab "ENT" tests: http://fourmilab.ch/random/ + The 256-megabyte sample PRN file at GRC: https://www.GRC.com/otg/uheprng.bin + The Windows scripting host version: https://www.GRC.com/otg/wsh-uheprng.js + ---------------------------------------------------------------------------- + Qualifying MWC multipliers are: 187884, 686118, 898134, 1104375, 1250205, + 1460910 and 1768863. (We use the largest one that's < 2^21) + ============================================================================ */ + + +/* ============================================================================ +This is based upon Johannes Baagoe's carefully designed and efficient hash +function for use with JavaScript. It has a proven "avalanche" effect such +that every bit of the input affects every bit of the output 50% of the time, +which is good. See: http://baagoe.com/en/RandomMusings/hash/avalanche.xhtml +============================================================================ +*/ +var Mash = function () { + var n = 0xefc8249d; + var mash = function (data) { + if (data) { + data = data.toString(); + for (var i = 0; i < data.length; i++) { + n += data.charCodeAt(i); + var h = 0.02519603282416938 * n; + n = h >>> 0; + h -= n; + h *= n; + n = h >>> 0; + h -= n; + n += h * 0x100000000; // 2^32 + } + return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 + } else { + n = 0xefc8249d; + } + }; + return mash; +}; + +var uheprng = function (seed) { + return (function () { + var o = 48; // set the 'order' number of ENTROPY-holding 32-bit values + var c = 1; // init the 'carry' used by the multiply-with-carry (MWC) algorithm + var p = o; // init the 'phase' (max-1) of the intermediate variable pointer + var s = new Array(o); // declare our intermediate variables array + var i; // general purpose local + var j; // general purpose local + var k = 0; // general purpose local + + // when our "uheprng" is initially invoked our PRNG state is initialized from the + // browser's own local PRNG. This is okay since although its generator might not + // be wonderful, it's useful for establishing large startup entropy for our usage. + var mash = new Mash(); // get a pointer to our high-performance "Mash" hash + + // fill the array with initial mash hash values + for (i = 0; i < o; i++) { + s[i] = mash(Math.random()); + } + + // this PRIVATE (internal access only) function is the heart of the multiply-with-carry + // (MWC) PRNG algorithm. When called it returns a pseudo-random number in the form of a + // 32-bit JavaScript fraction (0.0 to <1.0) it is a PRIVATE function used by the default + // [0-1] return function, and by the random 'string(n)' function which returns 'n' + // characters from 33 to 126. + var rawprng = function () { + if (++p >= o) { + p = 0; + } + var t = 1768863 * s[p] + c * 2.3283064365386963e-10; // 2^-32 + return s[p] = t - (c = t | 0); + }; + + // this EXPORTED function is the default function returned by this library. + // The values returned are integers in the range from 0 to range-1. We first + // obtain two 32-bit fractions (from rawprng) to synthesize a single high + // resolution 53-bit prng (0 to <1), then we multiply this by the caller's + // "range" param and take the "floor" to return a equally probable integer. + var random = function (range) { + return Math.floor(range * (rawprng() + (rawprng() * 0x200000 | 0) * 1.1102230246251565e-16)); // 2^-53 + }; + + // this EXPORTED function 'string(n)' returns a pseudo-random string of + // 'n' printable characters ranging from chr(33) to chr(126) inclusive. + random.string = function (count) { + var i; + var s = ''; + for (i = 0; i < count; i++) { + s += String.fromCharCode(33 + random(94)); + } + return s; + }; + + // this PRIVATE "hash" function is used to evolve the generator's internal + // entropy state. It is also called by the EXPORTED addEntropy() function + // which is used to pour entropy into the PRNG. + var hash = function () { + var args = Array.prototype.slice.call(arguments); + for (i = 0; i < args.length; i++) { + for (j = 0; j < o; j++) { + s[j] -= mash(args[i]); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED "clean string" function removes leading and trailing spaces and non-printing + // control characters, including any embedded carriage-return (CR) and line-feed (LF) characters, + // from any string it is handed. this is also used by the 'hashstring' function (below) to help + // users always obtain the same EFFECTIVE uheprng seeding key. + random.cleanString = function (inStr) { + inStr = inStr.replace(/(^\s*)|(\s*$)/gi, ''); // remove any/all leading spaces + inStr = inStr.replace(/[\x00-\x1F]/gi, ''); // remove any/all control characters + inStr = inStr.replace(/\n /, '\n'); // remove any/all trailing spaces + return inStr; // return the cleaned up result + }; + + // this EXPORTED "hash string" function hashes the provided character string after first removing + // any leading or trailing spaces and ignoring any embedded carriage returns (CR) or Line Feeds (LF) + random.hashString = function (inStr) { + inStr = random.cleanString(inStr); + mash(inStr); // use the string to evolve the 'mash' state + for (i = 0; i < inStr.length; i++) { // scan through the characters in our string + k = inStr.charCodeAt(i); // get the character code at the location + for (j = 0; j < o; j++) { // "mash" it into the UHEPRNG state + s[j] -= mash(k); + if (s[j] < 0) { + s[j] += 1; + } + } + } + }; + + // this EXPORTED function allows you to seed the random generator. + random.seed = function (seed) { + if (typeof seed === 'undefined' || seed === null) { + seed = Math.random(); + } + if (typeof seed !== 'string') { + seed = stringify(seed, function (key, value) { + if (typeof value === 'function') { + return (value).toString(); + } + return value; + }); + } + random.initState(); + random.hashString(seed); + }; + + // this handy exported function is used to add entropy to our uheprng at any time + random.addEntropy = function ( /* accept zero or more arguments */ ) { + var args = []; + for (i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + hash((k++) + (new Date().getTime()) + args.join('') + Math.random()); + }; + + // if we want to provide a deterministic startup context for our PRNG, + // but without directly setting the internal state variables, this allows + // us to initialize the mash hash and PRNG's internal state before providing + // some hashing input + random.initState = function () { + mash(); // pass a null arg to force mash hash to init + for (i = 0; i < o; i++) { + s[i] = mash(' '); // fill the array with initial mash hash values + } + c = 1; // init our multiply-with-carry carry + p = o; // init our phase + }; + + // we use this (optional) exported function to signal the JavaScript interpreter + // that we're finished using the "Mash" hash function so that it can free up the + // local "instance variables" is will have been maintaining. It's not strictly + // necessary, of course, but it's good JavaScript citizenship. + random.done = function () { + mash = null; + }; + + // if we called "uheprng" with a seed value, then execute random.seed() before returning + if (typeof seed !== 'undefined') { + random.seed(seed); + } + + // Returns a random integer between 0 (inclusive) and range (exclusive) + random.range = function (range) { + return random(range); + }; + + // Returns a random float between 0 (inclusive) and 1 (exclusive) + random.random = function () { + return random(Number.MAX_VALUE - 1) / Number.MAX_VALUE; + }; + + // Returns a random float between min (inclusive) and max (exclusive) + random.floatBetween = function (min, max) { + return random.random() * (max - min) + min; + }; + + // Returns a random integer between min (inclusive) and max (inclusive) + random.intBetween = function (min, max) { + return Math.floor(random.random() * (max - min + 1)) + min; + }; + + // when our main outer "uheprng" function is called, after setting up our + // initial variables and entropic state, we return an "instance pointer" + // to the internal anonymous function which can then be used to access + // the uheprng's various exported functions. As with the ".done" function + // above, we should set the returned value to 'null' once we're finished + // using any of these functions. + return random; + }()); +}; + +// ... And this code comes from https://github.com/moll/json-stringify-safe +function stringify(obj, replacer, spaces, cycleReplacer) { + return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) +} + +function serializer(replacer, cycleReplacer) { + var stack = [], keys = [] + + if (cycleReplacer == null) cycleReplacer = function(key, value) { + if (stack[0] === value) return "[Circular ~]" + return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" + } + + return function(key, value) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this) + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) + } + else stack.push(value) + + return replacer == null ? value : replacer.call(this, key, value) + } +} + +const seededRandomNumberGenerator = function (seed) { + + return new uheprng(seed); +}; + + +// ##### Easing functions +// The following easing variations come from the [easings.net](https://easings.net/) web page +// + Note: the naming convention for easing is different in Scrawl-canvas. Easing out implies a speeding up, while easing in implies a slowing down. Think of a train easing into a station, and then easing out of it again as it continues its journey. +const easeOutSine = function (t) { return 1 - Math.cos((t * Math.PI) / 2) }; +const easeInSine = function (t) { return Math.sin((t * Math.PI) / 2) }; +const easeOutInSine = function (t) { return -(Math.cos(Math.PI * t) - 1) / 2 }; + +const easeOutQuad = function (t) { return t * t }; +const easeInQuad = function (t) { return 1 - ((1 - t) * (1 - t)) }; +const easeOutInQuad = function(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 }; + +const easeOutCubic = function(t) { return t * t * t }; +const easeInCubic = function(t) { return 1 - Math.pow(1 - t, 3) }; +const easeOutInCubic = function(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2 }; + +const easeOutQuart = function(t) { return t * t * t * t }; +const easeInQuart = function(t) { return 1 - Math.pow(1 - t, 4) }; +const easeOutInQuart = function(t) { return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2 }; + +const easeOutQuint = function(t) { return t * t * t * t * t }; +const easeInQuint = function(t) { return 1 - Math.pow(1 - t, 5) }; +const easeOutInQuint = function(t) { return t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2 }; + +const easeOutExpo = function(t) { return t === 0 ? 0 : Math.pow(2, 10 * t - 10) }; +const easeInExpo = function(t) { return t === 1 ? 1 : 1 - Math.pow(2, -10 * t) }; +const easeOutInExpo = function(t) { + if (t === 0 || t === 1) return t; + return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2; +}; + +const easeOutCirc = function(t) { return 1 - Math.sqrt(1 - Math.pow(t, 2)) }; +const easeInCirc = function(t) { return Math.sqrt(1 - Math.pow(t - 1, 2)) }; +const easeOutInCirc = function(t) { return t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2 }; + +const easeOutBack = function(t) { return (2.70158 * t * t * t) - (1.70158 * t * t) }; +const easeInBack = function(t) { return 1 + (2.70158 * Math.pow(t - 1, 3)) + (1.70158 * Math.pow(t - 1, 2)) }; +const easeOutInBack = function(t) { + let c1 = 1.70158, c2 = c1 * 1.525; + return t < 0.5 ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2; +}; + +const easeOutElastic = function(t) { + const c4 = (2 * Math.PI) / 3; + if (t === 0 || t === 1) return t; + return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4); +}; + +const easeInElastic = function(t) { + const c4 = (2 * Math.PI) / 3; + if (t === 0 || t === 1) return t; + return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1; +}; + +const easeOutInElastic = function(t) { + const c5 = (2 * Math.PI) / 4.5; + if (t === 0 || t === 1) return t; + return t < 0.5 ? + -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 : + (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1; +}; + +const easeOutBounce = function(t) { + t = 1 - t; + const n1 = 7.5625, d1 = 2.75; + if (t < 1 / d1) return 1 - (n1 * t * t); + if (t < 2 / d1) return 1 - (n1 * (t -= 1.5 / d1) * t + 0.75); + if (t < 2.5 / d1) return 1 - (n1 * (t -= 2.25 / d1) * t + 0.9375); + return 1 - (n1 * (t -= 2.625 / d1) * t + 0.984375); +}; + +const easeInBounce = function(t) { + const n1 = 7.5625, d1 = 2.75; + if (t < 1 / d1) return n1 * t * t; + if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75; + if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375; + return n1 * (t -= 2.625 / d1) * t + 0.984375; +}; + +const easeOutInBounce = function(t) { + const n1 = 7.5625, d1 = 2.75; + let res; + if (t < 0.5) { + t = 1 - 2 * t; + if (t < 1 / d1) res = n1 * t * t; + else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75; + else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375; + else res = n1 * (t -= 2.625 / d1) * t + 0.984375; + return (1 - res) / 2; + } + else { + t = 2 * t - 1; + if (t < 1 / d1) res = n1 * t * t; + else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75; + else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375; + else res = n1 * (t -= 2.625 / d1) * t + 0.984375; + return (1 + res) / 2; + } +}; + + + // #### Exports export { addStrings, convertTime, correctForZero, λnull, + λfirstArg, λthis, λpromise, generateUuid, generateUniqueString, + interpolate, isa_boolean, isa_canvas, isa_dom, @@ -319,8 +724,41 @@ export { mergeOver, pushUnique, removeItem, + seededRandomNumberGenerator, xt, xta, xtGet, xto, + + easeOutSine, + easeInSine, + easeOutInSine, + easeOutQuad, + easeInQuad, + easeOutInQuad, + easeOutCubic, + easeInCubic, + easeOutInCubic, + easeOutQuart, + easeInQuart, + easeOutInQuart, + easeOutQuint, + easeInQuint, + easeOutInQuint, + easeOutExpo, + easeInExpo, + easeOutInExpo, + easeOutCirc, + easeInCirc, + easeOutInCirc, + easeOutBack, + easeInBack, + easeOutInBack, + easeOutElastic, + easeInElastic, + easeOutInElastic, + easeOutBounce, + easeInBounce, + easeOutInBounce, }; + diff --git a/source/factory/cell.js b/source/factory/cell.js index d2ecd7c9f..2f4619ee3 100644 --- a/source/factory/cell.js +++ b/source/factory/cell.js @@ -157,7 +157,7 @@ P = filterMix(P); // + Attributes defined in the [anchor mixin](../mixin/anchor.html): __anchor__. // + Attributes defined in the [filter mixin](../mixin/filter.html): __filters, isStencil__. // + Attributes defined in the [cascade mixin](../mixin/cascade.html): __groups__. -// + Attributes defined in the [pattern mixin](../mixin/pattern.html): __repeat__. +// + Attributes defined in the [pattern mixin](../mixin/pattern.html): __repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF__. // + Attributes defined in the [asset mixin](../mixin/asset.html): __source, subscribers__. let defaultAttributes = { @@ -766,6 +766,9 @@ P.setEngineActions = { }; // The following functions are used as part of entity object `stamp` functionality - specifically for those with a __method__ whose appearance is affected by shadows, and for the `clear` method +// + Scrawl-canvas, for the most part, avoids using engine.save() and engine.restore() functionality, instead preferring to keep track of engine state in State objects. +// + When clearing and restoring shadows - a frequent operation given Scrawl-canvas functionality around stamping methods - both the Cell's state object and the Canvas context engine need to be updated with the necessary data +// + The same reasoning holds when setting up the context engine to 'clear' an entity from the canvas display instead of stamping it onto the canvas // `clearShadow` P.clearShadow = function () { @@ -821,6 +824,21 @@ P.restoreEngine = function () { return this; }; +// `getComputedFontSizes` - internal function - the Cell wrapper gets passed by Phrase entitys to its fontAttributes object, which then invokes it when calculating font sizes +P.getComputedFontSizes = function () { + + let host = this.getHost(); + + if (host && host.domElement) { + + let em = window.getComputedStyle(host.domElement), + rem = window.getComputedStyle(document.documentElement); + + return [parseFloat(em.fontSize), parseFloat(rem.fontSize), window.innerWidth, window.innerHeight]; + } + return false; +} + // #### Display cycle functionality // This functionality is entirely Promise-based, and triggered by the Cell's Canvas wrapper controller @@ -1099,6 +1117,9 @@ P.applyFilters = function () { image = engine.getImageData(0, 0, self.currentDimensions[0], self.currentDimensions[1]); worker = requestFilterWorker(); + // NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET'S IMAGEDATA AT THIS POINT + self.preprocessFilters(self.currentFilters); + actionFilterWorker(worker, { image: image, filters: self.currentFilters diff --git a/source/factory/color.js b/source/factory/color.js index b975bf709..cf651306f 100644 --- a/source/factory/color.js +++ b/source/factory/color.js @@ -21,7 +21,7 @@ // #### Imports import { constructors, entity } from '../core/library.js'; -import { mergeOver, xt, xtGet, isa_obj } from '../core/utilities.js'; +import { mergeOver, xt, xtGet, isa_obj, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js'; import baseMix from '../mixin/base.js'; @@ -98,6 +98,9 @@ let defaultAttributes = { // The __autoUpdate__ Boolean flag switches on color animation autoUpdate: false, +// The __easing__ attribute affects the `getRangeColor` function, applying an easing function to those requests. + easing: 'linear', + // ##### Non-retained argument attributes (for factory, clone, set functions) // __random__ - the factory function, and the clone function, can ask the Color object to set its initial channel values randomly by including this attribute in the argument object; if the attribute resolves to true, random color functionality is invoked to set the r, g and b channel attributes to appropriately random values. @@ -338,7 +341,7 @@ P.generateRandomColor = function (items = {}) { return this; }; -// `getRangeColor` - function which generates a color in the rtange between the minimum and maximum colors. +// `getRangeColor` - function which generates a color in the range between the minimum and maximum colors. // + when the argument is `0` the minimum color is returned; values below 0 are rounded up to 0 // + when the argument is `1` the maximum color is returned; values above 1 are rounded down to 1 // + values between `0` and `1` will return a blended color between the minimum and maximum colors @@ -349,7 +352,103 @@ P.getRangeColor = function (item) { let floor = Math.floor; - let {rMin, gMin, bMin, aMin, rMax, gMax, bMax, aMax} = this; + let {rMin, gMin, bMin, aMin, rMax, gMax, bMax, aMax, easing} = this; + + if (easing !== 'linear') { + + switch (easing) { + case 'easeOutSine' : + item = easeOutSine(item); + break; + case 'easeInSine' : + item = easeInSine(item); + break; + case 'easeOutInSine' : + item = easeOutInSine(item); + break; + case 'easeOutQuad' : + item = easeOutQuad(item); + break; + case 'easeInQuad' : + item = easeInQuad(item); + break; + case 'easeOutInQuad' : + item = easeOutInQuad(item); + break; + case 'easeOutCubic' : + item = easeOutCubic(item); + break; + case 'easeInCubic' : + item = easeInCubic(item); + break; + case 'easeOutInCubic' : + item = easeOutInCubic(item); + break; + case 'easeOutQuart' : + item = easeOutQuart(item); + break; + case 'easeInQuart' : + item = easeInQuart(item); + break; + case 'easeOutInQuart' : + item = easeOutInQuart(item); + break; + case 'easeOutQuint' : + item = easeOutQuint(item); + break; + case 'easeInQuint' : + item = easeInQuint(item); + break; + case 'easeOutInQuint' : + item = easeOutInQuint(item); + break; + case 'easeOutExpo' : + item = easeOutExpo(item); + break; + case 'easeInExpo' : + item = easeInExpo(item); + break; + case 'easeOutInExpo' : + item = easeOutInExpo(item); + break; + case 'easeOutCirc' : + item = easeOutCirc(item); + break; + case 'easeInCirc' : + item = easeInCirc(item); + break; + case 'easeOutInCirc' : + item = easeOutInCirc(item); + break; + case 'easeOutBack' : + item = easeOutBack(item); + break; + case 'easeInBack' : + item = easeInBack(item); + break; + case 'easeOutInBack' : + item = easeOutInBack(item); + break; + case 'easeOutElastic' : + item = easeOutElastic(item); + break; + case 'easeInElastic' : + item = easeInElastic(item); + break; + case 'easeOutInElastic' : + item = easeOutInElastic(item); + break; + case 'easeOutBounce' : + item = easeOutBounce(item); + break; + case 'easeInBounce' : + item = easeInBounce(item); + break; + case 'easeOutInBounce' : + item = easeOutInBounce(item); + break; + } + } if (item > 1) item = 1; else if (item < 0) item = 0; @@ -491,15 +590,15 @@ P.convert = function (items) { if (/%/.test(items)) { - r = round((temp[0] / 100) * 255); - g = round((temp[1] / 100) * 255); - b = round((temp[2] / 100) * 255); + r = round((parseFloat(temp[0]) / 100) * 255); + g = round((parseFloat(temp[1]) / 100) * 255); + b = round((parseFloat(temp[2]) / 100) * 255); } else { - r = round(temp[0]); - g = round(temp[1]); - b = round(temp[2]); + r = round(parseFloat(temp[0])); + g = round(parseFloat(temp[1])); + b = round(parseFloat(temp[2])); } } else if (/rgba\(/.test(items)) { @@ -508,31 +607,24 @@ P.convert = function (items) { if (/%/.test(items)) { - r = round((temp[0] / 100) * 255); - g = round((temp[1] / 100) * 255); - b = round((temp[2] / 100) * 255); - a = temp[3] / 100; + r = round((parseFloat(temp[0]) / 100) * 255); + g = round((parseFloat(temp[1]) / 100) * 255); + b = round((parseFloat(temp[2]) / 100) * 255); + a = parseFloat(temp[3]) / 100; } else { - r = round(temp[0]); - g = round(temp[1]); - b = round(temp[2]); + r = round(parseFloat(temp[0])); + g = round(parseFloat(temp[1])); + b = round(parseFloat(temp[2])); a = temp[3]; } } else if (/hsl\(/.test(items) || /hsla\(/.test(items)) { - // the spec explanation can be found here https://developer.mozilla.org/en-US/docs/Web/CSS/color_value - // - // see http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ for one way we can approach converting hsl values to rgb - // - // currently, knock down to transparent black - r = 0; - g = 0; - b = 0; - a = 0; + temp = items.match(/([0-9.]+\b)/g); + this.setFromHSL(parseFloat(temp[0]), parseFloat(temp[1]), parseFloat(temp[2]), parseFloat(temp[3])); } else if (items === 'transparent') { @@ -566,6 +658,148 @@ P.convert = function (items) { }; +P.getHSLfromRGB = function (dr, dg, db) { + + let minColor = Math.min(dr, dg, db), + maxColor = Math.max(dr, dg, db); + + let lum = (minColor + maxColor) / 2; + + let sat = 0; + + if (minColor !== maxColor) { + + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } + + let hue = 0; + + if (maxColor === dr) hue = (dg - db) / (maxColor - minColor); + else if (maxColor === dg) hue = 2 + ((db - dr) / (maxColor - minColor)); + else hue = 4 + ((dr - dg) / (maxColor - minColor)); + + hue *= 60; + + if (hue < 0) hue += 360; + + return [hue, sat, lum]; +}; + +P.getHSL = function () { + + let {r, g, b} = this; + + let minColor = Math.min(r, g, b), + maxColor = Math.max(r, g, b); + + let lum = (minColor + maxColor) / 2; + + let sat = 0; + + if (minColor !== maxColor) { + + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } + + let hue = 0; + + if (maxColor === r) hue = (g - b) / (maxColor - minColor); + else if (maxColor === g) hue = 2 + ((b - r) / (maxColor - minColor)); + else hue = 4 + ((r - g) / (maxColor - minColor)); + + hue *= 60; + + if (hue < 0) hue += 360; + + return [hue, sat, lum]; +}; + +P.getRGBfromHSL = function (h, s, l, a) { + + if (!s) { + + let gray = Math.floor(l * 255); + return [gray, gray, gray]; + } + + let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s), + tempLum2 = (2 * l) - tempLum1; + + const calculator = function (t, l1, l2) { + + if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t); + if (t * 2 < 1) return l1; + if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666)); + return l2; + }; + + h /= 360; + + let tr = h + 0.333, + tg = h, + tb = h - 0.333; + + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; + + let r = calculator(tr, tempLum1, tempLum2) * 255, + g = calculator(tg, tempLum1, tempLum2) * 255, + b = calculator(tb, tempLum1, tempLum2) * 255; + + if (null == a) return [r, g, b]; + + return [r, g, b, a * 255]; +}; + +P.setFromHSL = function (h, s, l, a) { + + if (!s) { + + let gray = Math.floor(l * 255); + return [gray, gray, gray]; + } + + let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s), + tempLum2 = (2 * l) - tempLum1; + + const calculator = function (t, l1, l2) { + + if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t); + if (t * 2 < 1) return l1; + if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666)); + return l2; + }; + + h /= 360; + + let tr = h + 0.333, + tg = h, + tb = h - 0.333; + + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; + + this.r = calculator(tr, tempLum1, tempLum2) * 255, + this.g = calculator(tg, tempLum1, tempLum2) * 255, + this.b = calculator(tb, tempLum1, tempLum2) * 255; + + if (null != a) { + + this.a = a * 255; + } +}; + + // Color keywords harvested from https://developer.mozilla.org/en/docs/Web/CSS/color_value P.colorLibrary = { aliceblue: 'f0f8ff', diff --git a/source/factory/filter.js b/source/factory/filter.js index 14bd4e424..7fd3a6d96 100644 --- a/source/factory/filter.js +++ b/source/factory/filter.js @@ -1,54 +1,122 @@ // # 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. +// 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. // -// Scrawl-canvas uses a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) to generate filter outputs - defined in the [filter web worker](../worker/filter.html). It supports a number of common filter algorithms: -// + `grayscale` - desaturates the image -// + `sepia` - desaturates the image, then 'antiques' it by adding back some yellow tone -// + `invert` - turns white into black, and similar across the spectrum -// + `red` - suppresses the image's green and blue channels -// + `green` - suppresses the image's red and blue channels -// + `blue` - suppresses the image's red and green channels -// + `notred` - suppresses the image's red channel -// + `notgreen` - suppresses the image's green channel -// + `notblue` - suppresses the image's blue channel -// + `cyan` - averages the image's blue and green channels, and suppresses the red channel -// + `magenta` - averages the image's red and blue channels, and suppresses the green channel -// + `yellow` - averages the image's red and green channels, and suppresses the blue channel -// + `brightness` - multiplies the red, green and blue channel values by a value supplied in the `filter.level` attribute -// + `saturation` - multiplies the red, green and blue channel values by a value supplied in the `filter.level` attribute, then normalizes the result -// + `threshold` - desaturates each pixel then tests it against `filter.level` value; those pixels below the level are set to the `filter.lowRGB` values while the rest are set to the `filter.highRGB` values -// + `channels` - multiply each pixel's channel values by the values set in the `filter.RGB` attributes -// + `channelstep` - divide, floor, and then multiply each pixel's channel values by the values set in the `filter.RGB` attributes -// + `tint` - a more fine-grained form of the channels filter -// + `chroma` - (the green-screen effect) - will evaluate each pixel against a range array; pixels that fall within the range are set to transparent -// + `pixelate` - create tiles - whose dimensions and positions are determined by values set in the filter `tileWidth`, `tileHeight`, `offsetX` and `offsetY` attributes - across the image and then average the pixels in each tile to a single color -// + `blur` - creates a blurred image. Note: can be slow across larger images! The degree of the blur - which does not follow conventional algorithms such as gaussian - is determined by the filter attribute values for `radius` (number), `passes` (number) and `shrink` (boolean) -// + `matrix` - apply a 3x3 matrix transform to each of the image's pixels -// + `matrix5` - apply a 5x5 matrix transform to each of the image's pixels +// Scrawl-canvas uses a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) to generate filter outputs - defined in the [filter web worker](../worker/filter.html). The web worker 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. // -// Scrawl-canvas can also use __user-defined filters__. -// -// Filters use the __base__ mixin, thus they come equipped with packet functionality, alongside clone and kill functions. -// -// Note that [CSS-mediated filters](https://developer.mozilla.org/en-US/docs/Web/CSS/filter) - `url()`, `blur()`, `brightness()`, `contrast()`, `drop-shadow()`, `grayscale()`, `hue-rotate()`, `invert()`, `opacity()`, `saturate()`, `sepia()` - can also be applied to DOM elements wrapped into Scrawl-canvas objects (Stack, Element, Canvas) in the normal way. Browsers will apply CSS filters as the final operation in their paint routines. +// #### Web worker filter 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 zero. 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` - Set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the "red", "green", "blue" and "alpha" arguments. Setting these values to 0 disables the action for that channel. Object attributes: action, lineIn, lineOut, opacity, red, green, blue, alpha +// +// `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` - 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. 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` - 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 +// +// `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 +// +// `displace` - Shift pixels around the image, based on the values supplied in a displacement process-image. 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 +// +// `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 +// +// `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 +// +// `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, floored 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'. The output is a form of posterization. Object attributes: action, lineIn, lineOut, opacity, red, green, blue +// +// `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 +// +// `tint-channels` - Has similarities to the SVG 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 +// +// `user-defined-legacy` - Previous to Scrawl-canvas version 8.4.0, filters could be defined with an argument which passed a function string to the filter 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 // -// TODO: we've had to move all the code from the [filter web worker](../worker/filter.html) into a new, [comment-free module](../worker/filter-stringed.html) file because tools like [CreateReactApp](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app) - which uses [Webpack](https://webpack.js.org/) as its bundler of choice - breaks when we `yarn add scrawl-canvas` to a project. -// + The root of the issue is that [Babel](https://babeljs.io/) currently breaks when it encounters the `import.meta` attribute. -// + Babel do supply a plugin which is supposed to address this issue: [babel-plugin-syntax-import-meta](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-import-meta). But trying to add this to a Webpack configuration - particularly as implemented by create-react-app - is, at best, a nightmare. - +// ``` +// // 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 // + [Component-004](../../demo/component-004.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 } from '../core/library.js'; -import { mergeOver, removeItem } from '../core/utilities.js'; +import { constructors, cell, group, entity, asset } from '../core/library.js'; +import { mergeOver, removeItem, λnull } from '../core/utilities.js'; import baseMix from '../mixin/base.js'; @@ -58,7 +126,9 @@ const Filter = function (items = {}) { this.makeName(items.name); this.register(); - this.set(this.defs); + + this.actions = []; + this.set(items); return this; }; @@ -78,132 +148,136 @@ 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 which are collected together in the __actions__ array. Each action object is processed sequentially by the web worker to produce the final output for that filter. + actions: null, -// All filters need to set out their __method__. For preset methods, a method string (eg 'grayscale', 'sepia') is sufficient. Bespoke methods require a function + // 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: 'areaAlpha', 'binary', 'blend', 'blue', 'blur', 'brightness', 'channelLevels', 'channels', 'channelstep', 'channelsToAlpha', 'chroma', 'chromakey', 'clampChannels', 'compose', 'cyan', 'displace', 'edgeDetect', 'emboss', 'flood', 'gray', 'grayscale', 'green', 'image', 'invert', 'magenta', 'matrix', 'matrix5', 'notblue', 'notgreen', 'notred', 'offset', 'offsetChannels', 'pixelate', 'red', 'saturation', 'sepia', 'sharpen', 'threshold', 'tint', 'userDefined', 'yellow', method: '', - -// The following methods require no further attributes: -// + `grayscale`, `sepia`, `invert` -// + `red`, `green`, `blue` -// + `notred`, `notgreen`, `notblue` -// + `cyan`, `magenta`, `yellow` - - -// The following methods require the __level__ attribute: -// -// + `brightness`, `saturation`, `threshold` - level: 0, - - - -// The `threshhold` filter will default to setting (desaturated) pixels below a given level (0 - 255) to black, and those above the level to white. These colours can be changed by using the __low__ and __high__ channel attributes - lowRed: 0, - lowGreen: 0, - lowBlue: 0, - highRed: 255, - highGreen: 255, - highBlue: 255, - - -// The `channels` and `channelstep` methods make use of the __red__, __green__ and __blue__ attributes - red: 0, - green: 0, + // ##### How filters process data + // The Scrawl-canvas filters web worker 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 filters web worker, 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 web worker 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 web worker, which Scrawl-canvas then uses to stamp the entity/Group/Cell into the display canvas. + 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-020](../../demo/filters-020.html) - Parameters for: clampChannels filter + alpha: 255, + angle: 0, + areaAlphaLevels: null, + asset: '', + blend: 'normal', blue: 0, - - -// The `tint` method uses nine attributes - redInRed: 0, - redInGreen: 0, - redInBlue: 0, - greenInRed: 0, - greenInGreen: 0, - greenInBlue: 0, - blueInRed: 0, - blueInGreen: 0, blueInBlue: 0, - - -// The `pixelate` method requires tile dimensions and, optionally, offset coordinates which should not exceed the tile dimensions + blueInGreen: 0, + blueInRed: 0, + channelX: 'red', + channelY: 'green', + clamp: 0, + compose: 'source-over', + copyHeight: 1, + copyWidth: 1, + copyX: 0, + copyY: 0, + excludeAlpha: true, + excludeBlue: false, + excludeGreen: false, + excludeRed: false, + green: 0, + greenInBlue: 0, + greenInGreen: 0, + greenInRed: 0, + gutterHeight: 1, + gutterWidth: 1, + height: 1, + highBlue: 255, + highGreen: 255, + highRed: 255, + includeAlpha: false, + includeBlue: true, + includeGreen: true, + includeRed: true, + keepOnlyChangedAreas: false, + level: 0, + lowBlue: 0, + lowGreen: 0, + lowRed: 0, + offsetAlphaX: 0, + offsetAlphaY: 0, + offsetBlueX: 0, + offsetBlueY: 0, + offsetGreenX: 0, + offsetGreenY: 0, + offsetRedX: 0, + offsetRedY: 0, offsetX: 0, offsetY: 0, - tileWidth: 1, - tileHeight: 1, - - -// The `blur` method uses the following attributes: -// + the __radius__ of the blur effect, in pixels -// + the __passes__ attribute (1+) determines how many times the blur filter will iterate -// + the __shrinkingRadius__ flag reduces the radius by approx 70% on each successive pass -// + when __includeAlpha__ flag is true, filter will include the alpha channel - note this may make the edges of the entity translucent -// + because the blur filter works on a 2-pass basis, we can restrict its operation to the vertical and horizontal directions by setting the `processVertical` and `processHorizontal` flags appropriately - radius: 1, + opaqueAt: 1, passes: 1, - shrinkingRadius: false, - includeAlpha: false, - processVertical: true, + postProcessResults: true, processHorizontal: true, - - -// The `matrix` method requires a weights attribute - an array of 9 numbers (also known as a __kernel__) in the following format: -// ``` -// weights: [ -// topLeftWeight, -// topCenterWeight, -// topRightWeight, -// middleLeftWeight, -// homePixelWeight, -// middleRightWeight, -// bottomLeftWeight, -// bottomCenterWeight, -// bottomRightWeight, -// ] -// ``` -// ... where the top row is the row above the home pixel, etc -// -// The method also makes use of the __includeAlpha__ attribute. - -// The `matrix5` method is the same as the matrix method except that its weights array should contain 25 elements, to cover all the positions (from top-left corner) in a 5x5 grid -// -// Some common kernels include: -// + Identity - matrix3: [0,0,0,0,1,0,0,0,0]; matrix5: [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0] -// + Simple blur - matrix3: [0.1,0.1,0.1,0.1,0.2,0.1,0.1,0.1,0.1] -// + Gaussian blur - matrix3: [0,0.14,0,0.14,0.44,0.14,0,0.13,0] -// + Edge detect (example) - matrix3: [1,0,-1,0,0,0,-1,0,1] -// + Edge emboss - matrix3: [-2,-1,0,-1,1,1,0,1,2] -// + Edge enhance - matrix3: [0,0,0,-1,1,0,0,0,0] -// + Laplacian edge - matrix3: [0,-1,0,-1,4,-1,0,-1,0] -// + Laplacian edge + diagonals - matrix3: [-1,-1,-1,-1,8,-1,-1,-1,-1] -// + Laplacian of Gaussian - matrix5: [0,0,-1,0,0,0,-1,-2,-1,0,-1,-2,16,-2,-1,0,-1,-2,-1,0,0,0,-1,0,0] -// + Sharpen - matrix3: [0,-1,0,-1,5,-1,0,-1,0] -// + Unsharp mask - matrix5: [1,4,6,4,1,4,16,24,16,4,6,24,-476,24,6,4,16,24,16,4,1,4,6,4,1] - weights: null, - -// The __ranges__ attribute - used by the `chroma` method - needs to be an array of arrays with the following format: -// ``` -// [[minRed, minGreen, minBlue, maxRed, maxGreen, maxBlue], etc] -// ``` -// ... multiple ranges can be defined - for instance to key out the lightest and darkest hues: -// ``` -// ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]] -// ``` + processVertical: true, + radius: 1, ranges: null, - - -// The `user-defined` filter should be set as a String value of the function's contents (the bits between the { curly braces }) on the __userDefined__ attribute. The function can take no arguments, and can only use variables defined above (or the __udVariableN__ attributes below). The function can also use __self__ variables supplied by the web worker - see the worker/filter.js for more information - userDefined: '', - udVariable0: '', - udVariable1: '', - udVariable2: '', - udVariable3: '', - udVariable4: '', - udVariable5: '', - udVariable6: '', - udVariable7: '', - udVariable8: '', - udVariable9: '', + red: 0, + redInBlue: 0, + redInGreen: 0, + redInRed: 0, + scaleX: 1, + scaleY: 1, + smoothing: 0, + step: 1, + strength: 1, + tileHeight: 1, + tileWidth: 1, + tolerance: 0, + transparentAt: 0, + transparentEdges: false, + useNaturalGrayscale: false, + weights: null, + width: 1, }; P.defs = mergeOver(P.defs, defaultAttributes); @@ -251,137 +325,840 @@ P.kill = function () { // #### Get, Set, deltaSet -// No additional functionality required +let S = P.setters, + D = P.deltaSetters; -// #### Prototype functions -// No additional prototype functions defined +// `set` - Overwrites mixin/base.js function +P.set = function (items = {}) { + if (Object.keys(items).length) { -// #### Filter webworker pool -// Because starting a web worker is an expensive operation, and a number of different filters may be required to render displays across a number of different <canvas> elements in a web page, Scrawl-canvas operates a pool of web workers to perform work as-and-when required. -const filterPool = []; + let setters = this.setters, + defs = this.defs, + predefined; -// `Exported function` __requestFilterWorker__ -const requestFilterWorker = function () { + Object.entries(items).forEach(([key, value]) => { - if (!filterPool.length) filterPool.push(buildFilterWorker()); + if (key && key !== 'name' && value != null) { - return filterPool.shift(); + predefined = setters[key]; + + if (predefined) predefined.call(this, value); + else if (typeof defs[key] !== 'undefined') this[key] = value; + } + }, this); + } + if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this); + + return this; }; -// `Exported function` __releaseFilterWorker__ - to avoid memory leaks, ___all requested filter workers MUST be released back to the filter pool as soon as their work has completed___. -const releaseFilterWorker = function (f) { - filterPool.push(f); +// `setDelta` - Overwrites mixin/base.js function +P.setDelta = function (items = {}) { + + if (Object.keys(items).length) { + + let setters = this.deltaSetters, + defs = this.defs, + predefined; + + Object.entries(items).forEach(([key, value]) => { + + 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); + } + }, this); + } + if (this.method && setActionsArray[this.method]) setActionsArray[this.method](this); + + return this; }; -// #### IMPORTANT! -// + Almost all modern browsers support import.meta.url -// + Toolchain bundlers generally DO NOT SUPPORT import.meta.url -// + The workaround is to gather all web worker code into a single file, string it, and build an url from that string. Yes, it is ugly. No, there is no viable production-ready alternative to the following. +// #### 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. // -// For use in a website that does not use webpack, rollup, parcel, etc in their toolchain -// + Comment out all the lines marked 'BUNDLED SITE' -// + Uncomment the lines marked 'MODERN SITE' +// 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 coder a function string to pass on to the web worker, will no longer function in Scrawl-canvas v8.4.0. // -// By default, Scrawl-canvas is distributed in a bundler-safe form +// 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, -// BUNDLED SITE -import { filterUrl } from '../worker/filter-stringed.js'; +// // __Threshhold__ filter +// }).clone({ +// name: 'my-duotone-filter', +// method: 'threshold', +// level: 127, +// lowRed: 100, +// lowGreen: 0, +// lowBlue: 0, +// highRed: 220, +// highGreen: 60, +// highBlue: 60, +// }); +// ``` -// __buildFilterWorker__ - create a new filter web worker -const buildFilterWorker = function () { +// `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], + }]; + }, + +// __binary__ (new in v8.4.0) - set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the "red", "green", "blue" and "alpha" arguments. Setting these values to 0 disables the action for that channel + binary: function (f) { + f.actions = [{ + action: 'binary', + 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 : 0, + }]; + }, + +// __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 + 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, + 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 + 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], + }]; + }, + +// __thiskey__ - + 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, + }]; + }, + +// __thiskey__ - + channelstep: function (f) { + f.actions = [{ + action: 'step-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, + }]; + }, + +// __thiskey__ (new in v8.4.0) - + 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, + }]; + }, + +// __thiskey__ - + chroma: function (f) { + f.actions = [{ + action: 'chroma', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + ranges: (f.ranges != null) ? f.ranges : [], + }]; + }, + +// __thiskey__ (new in v8.4.0) - + chromakey: function (f) { + 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: (f.red != null) ? f.red : 0, + green: (f.green != null) ? f.green : 255, + blue: (f.blue != null) ? f.blue : 0, + transparentAt: (f.transparentAt != null) ? f.transparentAt : 0, + opaqueAt: (f.opaqueAt != null) ? f.opaqueAt : 1, + }]; + }, + +// __thiskey__ (new in v8.4.0) - + clampChannels: function (f) { + 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: (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, + }]; + }, + +// __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 != null) ? f.lineOut : '', + lineMix: (f.lineMix != null) ? f.lineMix : '', + compose: (f.compose != null) ? f.compose : 'source-over', + offsetX: (f.offsetX != null) ? f.offsetX : 0, + offsetY: (f.offsetY != null) ? f.offsetY : 0, + opacity: (f.opacity != null) ? f.opacity : 1, + }]; + }, + +// __cyan__ - removes red channel color from the image, and averages the remaining channel colors + cyan: 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, + includeGreen: true, + includeBlue: true, + excludeRed: true, + }]; + }, + +// __displace__ (new in v8.4.0) - moves pixels around the image, based on the color channel values supplied by a displacement map image + displace: function (f) { + f.actions = [{ + action: 'displace', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + lineMix: (f.lineMix != null) ? f.lineMix : '', + opacity: (f.opacity != null) ? f.opacity : 1, + channelX: (f.channelX != null) ? f.channelX : 'red', + channelY: (f.channelY != null) ? f.channelY : 'green', + offsetX: (f.offsetX != null) ? f.offsetX : 0, + offsetY: (f.offsetY != null) ? f.offsetY : 0, + scaleX: (f.scaleX != null) ? f.scaleX : 1, + scaleY: (f.scaleY != null) ? f.scaleY : 1, + transparentEdges: (f.transparentEdges != null) ? f.transparentEdges : false, + }]; + }, + +// __edgeDetect__ (new in v8.4.0) - applies a preset 3x3 edge-detect matrix to the image + edgeDetect: function (f) { + f.actions = [{ + action: 'matrix', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + width: 3, + height: 3, + offsetX: 1, + offsetY: 1, + includeRed: true, + includeGreen: true, + includeBlue: true, + includeAlpha: false, + weights: [0,1,0,1,-4,1,0,1,0], + }]; + }, + +// __emboss__ (new in v8.4.0) - outputs a black-gray-red emboss effect + emboss: function (f) { + const actions = []; + if (f.useNaturalGrayscale) { + actions.push({ + action: 'grayscale', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: 'emboss-work', + }); + } + else { + actions.push({ + action: 'average-channels', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: 'emboss-work', + includeRed: true, + includeGreen: true, + includeBlue: true, + }); + } + if (f.clamp) { + actions.push({ + action: 'clamp-channels', + lineIn: 'emboss-work', + lineOut: 'emboss-work', + lowRed: 0 + f.clamp, + lowGreen: 0 + f.clamp, + lowBlue: 0 + f.clamp, + highRed: 255 - f.clamp, + highGreen: 255 - f.clamp, + highBlue: 255 - f.clamp, + }); + } + if (f.smoothing) { + actions.push({ + action: 'blur', + lineIn: 'emboss-work', + lineOut: 'emboss-work', + radius: f.smoothing, + passes: 2, + }); + } + actions.push({ + action: 'emboss', + lineIn: 'emboss-work', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + angle: (f.angle != null) ? f.angle : 0, + strength: (f.strength != null) ? f.strength : 1, + tolerance: (f.tolerance != null) ? f.tolerance : 0, + keepOnlyChangedAreas: (f.keepOnlyChangedAreas != null) ? f.keepOnlyChangedAreas : false, + postProcessResults: (f.postProcessResults != null) ? f.postProcessResults : true, + }); + f.actions = actions; + }, + +// __flood__ (new in v8.4.0) - creates a uniform sheet of the required color, which can then be used by other filter actions + flood: function (f) { + f.actions = [{ + action: 'flood', + 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, + }]; + }, + +// __gray__ (new in v8.4.0) - averages the three color channel colors + gray: 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, + includeRed: true, + includeGreen: true, + includeBlue: true, + }]; + }, + +// __grayscale__ - produces a more realistic black-and-white photograph effect + grayscale: function (f) { + f.actions = [{ + action: 'grayscale', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + }]; + }, + +// __green__ - removes red and blue channel color from the image + green: 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, + excludeBlue: true, + }]; + }, + +// __image__ (new in v8.4.0) - load an image into the web worker, where it can then be used by other filter actions - useful for effects such as watermarking an image + image: function (f) { + + f.actions = [{ + action: 'process-image', + lineOut: (f.lineOut != null) ? f.lineOut : '', + asset: (f.asset != null) ? f.asset : '', + width: (f.width != null) ? f.width : 1, + height: (f.height != null) ? f.height : 1, + copyWidth: (f.copyWidth != null) ? f.copyWidth : 1, + copyHeight: (f.copyHeight != null) ? f.copyHeight : 1, + copyX: (f.copyX != null) ? f.copyX : 0, + copyY: (f.copyY != null) ? f.copyY : 0, + }]; + }, + +// __invert__ - inverts the colors in the image, producing an effect similar to a photograph negative + invert: function (f) { + f.actions = [{ + action: 'invert-channels', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + includeRed: true, + includeGreen: true, + includeBlue: true, + }]; + }, + +// __magenta__ - removes green channel color from the image, and averages the remaining channel colors + magenta: 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, + includeRed: true, + includeBlue: true, + excludeGreen: true, + }]; + }, + +// __matrix__ - applies a 3x3 convolution matrix, kernel or mask operation to the image + matrix: function (f) { + f.actions = [{ + action: 'matrix', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + width: 3, + height: 3, + offsetX: 1, + offsetY: 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, + weights: (f.weights != null) ? f.weights : [0,0,0,0,1,0,0,0,0], + }]; + }, + +// __matrix5__ - applies a 5x5 convolution matrix, kernel or mask operation to the image + matrix5: function (f) { + f.actions = [{ + action: 'matrix', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + width: 5, + height: 5, + offsetX: 2, + offsetY: 2, + 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, + weights: (f.weights != null) ? f.weights : [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0], + }]; + }, + +// __notblue__ - zeroes the blue channel values (not the same effect as the yellow filter) + notblue: function (f) { + f.actions = [{ + action: 'set-channel-to-level', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + includeBlue: true, + level: 0, + }]; + }, + +// __notgreen__ - zeroes the green channel values (not the same effect as the magenta filter) + notgreen: function (f) { + f.actions = [{ + action: 'set-channel-to-level', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + includeGreen: true, + level: 0, + }]; + }, + +// __notred__ - zeroes the red channel values (not the same effect as the cyan filter) + notred: function (f) { + f.actions = [{ + action: 'set-channel-to-level', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + includeRed: true, + level: 0, + }]; + }, + +// __offset__ (new in v8.4.0) - moves the image in its entirety by the given offset + offset: function (f) { + f.actions = [{ + action: 'offset', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + offsetRedX: (f.offsetX != null) ? f.offsetX : 0, + offsetRedY: (f.offsetY != null) ? f.offsetY : 0, + offsetGreenX: (f.offsetX != null) ? f.offsetX : 0, + offsetGreenY: (f.offsetY != null) ? f.offsetY : 0, + offsetBlueX: (f.offsetX != null) ? f.offsetX : 0, + offsetBlueY: (f.offsetY != null) ? f.offsetY : 0, + offsetAlphaX: (f.offsetX != null) ? f.offsetX : 0, + offsetAlphaY: (f.offsetY != null) ? f.offsetY : 0, + }]; + }, + +// __offsetChannels__ (new in v8.4.0) - moves each channel by an offset set for that channel. Can create a crude stereoscopic output + offsetChannels: function (f) { + f.actions = [{ + action: 'offset', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + offsetRedX: (f.offsetRedX != null) ? f.offsetRedX : 0, + offsetRedY: (f.offsetRedY != null) ? f.offsetRedY : 0, + offsetGreenX: (f.offsetGreenX != null) ? f.offsetGreenX : 0, + offsetGreenY: (f.offsetGreenY != null) ? f.offsetGreenY : 0, + offsetBlueX: (f.offsetBlueX != null) ? f.offsetBlueX : 0, + offsetBlueY: (f.offsetBlueY != null) ? f.offsetBlueY : 0, + offsetAlphaX: (f.offsetAlphaX != null) ? f.offsetAlphaX : 0, + offsetAlphaY: (f.offsetAlphaY != null) ? f.offsetAlphaY : 0, + }]; + }, + +// __pixelate__ - averages the colors in a block to produce a series of obscuring tiles + pixelate: function (f) { + f.actions = [{ + action: 'pixelate', + 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, + 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, + }]; + }, + +// __red__ - removes blue and green channel color from the image + red: 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, + excludeGreen: true, + excludeBlue: true, + }]; + }, + +// __saturation__ - alters the saturation level of the image + saturation: 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, + saturation: true, + }]; + }, + +// __sepia__ - recalculates the values of each color channel (a tint action) to create a more 'antique' version of the image + sepia: function (f) { + f.actions = [{ + action: 'tint-channels', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + redInRed: 0.393, + redInGreen: 0.349, + redInBlue: 0.272, + greenInRed: 0.769, + greenInGreen: 0.686, + greenInBlue: 0.534, + blueInRed: 0.189, + blueInGreen: 0.168, + blueInBlue: 0.131, + }]; + }, + +// __sharpen__ (new in v8.4.0) - applies a preset 3x3 sharpen matrix to the image + sharpen: function (f) { + f.actions = [{ + action: 'matrix', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + width: 3, + height: 3, + offsetX: 1, + offsetY: 1, + includeRed: true, + includeGreen: true, + includeBlue: true, + includeAlpha: false, + weights: [0,-1,0,-1,5,-1,0,-1,0], + }]; + }, + +// __threshold__ - creates a duotone effect - 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. + threshold: 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; + + let low = (f.low != null) ? f.low : [lowRed, lowGreen, lowBlue], + high = (f.high != null) ? f.high : [highRed, highGreen, highBlue]; + + 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, + low: low, + high: high, + }]; + }, + +// __tint__ - 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. + tint: function (f) { + f.actions = [{ + action: 'tint-channels', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + redInRed: (f.redInRed != null) ? f.redInRed : 1, + redInGreen: (f.redInGreen != null) ? f.redInGreen : 0, + redInBlue: (f.redInBlue != null) ? f.redInBlue : 0, + greenInRed: (f.greenInRed != null) ? f.greenInRed : 0, + greenInGreen: (f.greenInGreen != null) ? f.greenInGreen : 1, + greenInBlue: (f.greenInBlue != null) ? f.greenInBlue : 0, + blueInRed: (f.blueInRed != null) ? f.blueInRed : 0, + blueInGreen: (f.blueInGreen != null) ? f.blueInGreen : 0, + blueInBlue: (f.blueInBlue != null) ? f.blueInBlue : 1, + }]; + }, + +// __userDefined__ - DEPRECATED - this filter method no longer has any effect, returning an unchanged image + userDefined: function (f) { + f.actions = [{ + action: 'user-defined-legacy', + lineIn: (f.lineIn != null) ? f.lineIn : '', + lineOut: (f.lineOut != null) ? f.lineOut : '', + opacity: (f.opacity != null) ? f.opacity : 1, + }]; + }, + +// __yellow__ - removes blue channel color from the image, and averages the remaining channel colors + yellow: 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, + includeRed: true, + includeGreen: true, + excludeBlue: true, + }]; + }, +}; - // MODERN SITE - // let path = import.meta.url.slice(0, -('factory/filter.js'.length)); - // MODERN SITE - // let filterUrl = (window.scrawlEnvironmentOffscreenCanvasSupported) ? +// #### Prototype functions +// No additional prototype functions defined - // MODERN SITE - // `${path}worker/filter_canvas.js` : - // MODERN SITE - // `${path}worker/filter.js`; +// #### Filter webworker pool +// Because starting a web worker is an expensive operation, and a number of different filters may be required to render displays across a number of different <canvas> elements in a web page, Scrawl-canvas operates a pool of web workers to perform work as-and-when required. +// + These workers (generally there's only one) are not exposed to developers using the scrawl object, thus only get called internally - // BUNDLED SITE - return new Worker(filterUrl); +// START PRODUCTION CODE +const filterPool = []; +import { filterUrl } from '../worker/filter-string.js'; + +const requestFilterWorker = function () { + if (!filterPool.length) filterPool.push(new Worker(filterUrl)); + return filterPool.shift(); }; +const releaseFilterWorker = function (f) { + filterPool.push(f); +}; -// `Exported function` __actionFilterWorker__ - send a task to the filter web worker, and retrieve the resulting image. This function returns a Promise. const actionFilterWorker = function (worker, items) { - return new Promise((resolve, reject) => { - worker.onmessage = (e) => { - if (e && e.data && e.data.image) resolve(e.data.image); else resolve(false); }; - worker.onerror = (e) => { - console.log('error', e.lineno, e.message); resolve(false); }; + worker.postMessage(items); + }); +}; +// END PRODUCTION CODE + + +// START DEV CODE +/* +const filterPool = []; + +const requestFilterWorker = function () { + if (!filterPool.length) filterPool.push(buildFilterWorker()); + return filterPool.shift(); +}; + +const buildFilterWorker = function () { + let path = import.meta.url.slice(0, -('factory/filter.js'.length)); + let filterUrl = `${path}worker/filter.js`; + return new Worker(filterUrl); +}; +const releaseFilterWorker = function (f) { + filterPool.push(f); +}; + +const actionFilterWorker = function (worker, items) { + return new Promise((resolve, reject) => { + worker.onmessage = (e) => { + if (e && e.data && e.data.image) resolve(e.data.image); + else resolve(false); + }; + worker.onerror = (e) => { + console.log('error', e.lineno, e.message); + resolve(false); + }; worker.postMessage(items); }); }; +*/ +// END DEV CODE // #### Factory -// ``` -// scrawl.makeFilter({ -// -// name: 'my-grayscale-filter', -// method: 'grayscale', -// -// }).clone({ -// -// name: 'my-sepia-filter', -// method: 'sepia', -// }); -// -// scrawl.makeFilter({ -// -// name: 'my-chroma-filter', -// method: 'chroma', -// ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]], -// }); -// -// scrawl.makeFilter({ -// -// name: 'venetian-blinds-filter', -// method: 'userDefined', -// -// level: 9, -// -// userDefined: ` -// let i, iz, j, jz, -// level = filter.level || 6, -// halfLevel = level / 2, -// yw, transparent, pos; -// -// for (i = localY, iz = localY + localHeight; i < iz; i++) { -// -// transparent = (i % level > halfLevel) ? true : false; -// -// if (transparent) { -// -// yw = (i * iWidth) + 3; -// -// for (j = localX, jz = localX + localWidth; j < jz; j ++) { -// -// pos = yw + (j * 4); -// data[pos] = 0; -// } -// } -// }`, -// }); -// ``` const makeFilter = function (items) { return new Filter(items); diff --git a/source/factory/fontAttributes.js b/source/factory/fontAttributes.js index 185dd010a..df972aad1 100644 --- a/source/factory/fontAttributes.js +++ b/source/factory/fontAttributes.js @@ -48,101 +48,78 @@ P = baseMix(P); // + Attributes defined in the [base mixin](../mixin/base.html): __name__. let defaultAttributes = { -// __font-style__ -// -// _values:_ -// + `normal`, `italic`, `oblique` -// -// CANVAS CONTEXT ENGINE - does not handle `oblique` slope values +// __font__ - pseudo-attribute String which gets broken down into its component parts. +// + Once the breakdown completes the font string gets reassembled and then passed through a <canvas> element's context engine to determine the actual font String that will be used by the engine. This value gets stored in the __temperedFontString__ attribute. + +// _font-style_ - saved in the __style__ String attribute - acceptable values are: `normal`, `italic` and `oblique`. Note that browser handling of oblique (sloped rather than explicitly italic) fonts by their respective canvas context engines is, at best, idiosyncratic. +// + To explicitly use an oblique font design (often called 'slanted' or 'sloped'), reference the font face directly in the `family` or font strings. style: 'normal', -// __font-variant__ - the standard indicates that canvas context engine should only recognise `normal` and `small-caps` values -// -// CANVAS CONTEXT ENGINE - complies with the standard, thus Scrawl-canvas ignores all other possible values. Do not use them in font strings: -// + `font-variant-caps` -// + `font-variant-numeric` -// + `font-variant-ligatures` -// + `font-variant-east-asian` -// + `font-variant-alternates` -// -// +// _font-variant_ - saved in the __variant__ String attribute - the standard indicates that canvas context engine should only recognise `normal` and `small-caps` values. Do not use other possibilities (_font-variant-caps, font-variant-numeric, font-variant-ligatures, font-variant-east-asian, font-variant-alternates_) in font strings; scrawl-canvas will remove and/or ignore them when it parses the font string. variant: 'normal', -// __font-weight__ -// -// _Values:_ -// + `normal`, `bold`, `lighter`, `bolder`; or -// + a number (between 1 and 1000, usually in 100 steps between 100 and 900) -// -// (`normal` translates to 400; `bold` translates to 700) -// -// CANVAS CONTEXT ENGINE - does seem to recognise both keyword and (x00) number values, but the interpretation of these values can be somewhat erratic. `normal` and `bold` keywords are generally respected and actioned as expected. +// _font-weight_ - saved in the __weight__ String attribute - acceptable values are: `normal`, `bold`, `lighter`, `bolder`; or a number (100. 200, 300, ... 900). Bold is generally the equivalent of 700, and normal is 400; lighter/bolder are values relative to the <canvas> element's computed font weight. Note that browser handling of font weight requirements by their respective canvas context engines is not entirely standards compliant - for instance Safari browsers will generally ignore weight assertions in font strings. +// + To explicitly use a light, bold or heavy font design, reference the font face directly in the `family` or font strings. weight: 'normal', -// __font-stretch__ -// -// _Values:_ -// + `normal` (default) -// + `semi-condensed`, `condensed`, `extra-condensed`, `ultra-condensed` -// + `semi-expanded`, `expanded`, `extra-expanded`, `ultra-expanded` -// -// We ignore number% values (permitted values are 50% - 200%) because the context engine only accepts a single font string and the syntax requirements for that font string are that "font-stretch may only be a single keyword value" -// -// CANVAS CONTEXT ENGINE - doesn't seem to recognise font-stretch values (for Garamond), but doesn't choke on their presence either +// __font-stretch__ - saved in the __stretch__ String attribute acceptable values are: `normal`, `semi-condensed`, `condensed`, `extra-condensed`, `ultra-condensed`, `semi-expanded`, `expanded`, `extra-expanded`, `ultra-expanded`. Browser support for these values by the <canvas> element's context engine is, for the most part, non-existant. +// + To explicitly use a condensed or stretched font design, reference the font face directly in the `family` or font strings. stretch: 'normal', -// __font-size__ -// -// Standard says: _"with the 'font-size' component converted to CSS pixels"_ -// + TODO: hoping this means that canvas font will do this for us, rather than having to convert in code - if not, extract it by sticking an interim css style against the internal <div> to get computed value? -// -// Values can be: -// -// _Absolute or relative string values:_ -// + `xx-small`, `x-small`, `small`, `medium`, `large`, `x-large`, `xx-large` -// + `smaller`, `larger` +// _font-size_ - broken into two parts and saved in the __sizeValue__ Number and __sizeMetric__ String, each of which can be set separately. The W3C HTML Canvas 2D Context Recommendation states: _"with the 'font-size' component converted to CSS pixels"_. However, Scrawl-canvas makes every effort to respect and interpret non-px-based font-size requests. +// + Note that these pixel values are representative of the <canvas> element's intrinsic drawing area dimensions, not of any such measure modified by CSS dimension rules (width, height, scale, skew, etc) applied to the canvas. // -// _Length values:_ -// + `1.2em`, `1.2ch`, `1.2ex`, `1.2rem` -// + (experimental!) `1.2cap`, `1.2ic`, `1.2lh`, `1.2rlh` -// + `1.2vh`, `1.2vw`, `1.2vmin`, `1.2vmax` -// + (experimental!) `1.2vi`, `1.2vb` -// + `1.2px`, `1.2cm`, `1.2mm`, `1.2in`, `1.2pc`, `1.2pt` -// + (experimental!) `1.2Q` +// Length values relative to some intrinsic propertly of the font - Scrawl-canvas will not attempt to interpret the following, instead treating them as some fixed proportion of the <canvas> element's computed font size: +// + `1em` is the <canvas> element's computed font size +// + `1rem` is the document root (<html>) element's computed font size +// + `1lh` is the <canvas> element's computed font size multiplied by the entity's line height value +// + `1rlh` is the document root (<html>) element's computed font size multiplied by the entity's line height value +// + `1ex` is ≈ '0.5em' +// + `1cap`, `1ch`, `1ic` are all ≈ '1em' // -// Note that only the following have wide support; these are the only metrics this code tests for: `em`, `ch`, `ex`, `rem`, `vh`, `vw`, `vmin`, `vmax`, `px`, `cm`, `mm`, `in`, `pc`, `pt` +// Length percentage values, with calculation based on the <canvas> element's computed font size: +// + `120%` = '1.2em' // -// _Percent values:_ -// + `1.2%` +// Non-numerical values (in the Fonts standards, 'absolute-size' and 'relative-size' keywords) will calculate a size based on the <canvas> element's computed font size: +// + `xx-small` = 60% of the computed font size +// + `x-small` = 75% of the computed font size +// + `small` = 89% of the computed font size +// + `medium` = 100% of the computed font size +// + `large` = 120% of the computed font size +// + `x-large` = 150% of the computed font size +// + `xx-large` = 200% of the computed font size +// + `xxx-large` = 300% of the computed font size +// + `smaller` = 80% of the computed font size +// + `larger` = 130% of the computed font size // -// (Percent values clash with font-stretch % values - assume any number followed by a % is a font-size value) -// -// GOTCHA NOTE 1: font-size is never a number; it must always have a metric. Tweens should be able to handle this requirement with no issues. -// -// GOTCHA NOTE 2: the canvas context engine refuses to handle line heights appended to the font size value (eg: `12px/1.2`) and expects all line height values to = `normal`. Scrawl-canvas handles line height for multiline phrases using an alternative mechanism. Thus including a `/lineheight` value in a font string may cause `Phrase.set` functionality to fail in unexpected ways. +// Length values relative to the browser's viewport (window) dimensions: +// + `1vw` and `1vh` represent 1% of the viewport's width and height, respectively +// + `1vmax` - is 1% of the larger viewport dimension +// + `1vmin` - is 1% of the smaller viewport dimension +// + Scrawl-canvas treats `1vi` as ≈ 1vw, and `1vb` as ≈ 1vh +// +// Absolute length values convert as follow: +// + `1in` (inch) = 96px +// + `1cm` (centimeter) = 37.80px +// + `1mm` (millimeter) = 3.78px +// + `1Q` (quarter mm) = 0.95px +// + `1pc` (pica) = 16px +// + `1pt` (point) = 1.33px +// + `1px` (pixel) = 1px +// +// Note: we break down the `size` attribute into two components: __sizeValue__ and __sizeMetric__. Line height values are ignored and, when present in a font string, may break the code! sizeValue: 12, sizeMetric: 'px', -// __font-family__ -// + Always comes at the end of the string. -// + More than one can be included, with each separated by commas -// + Be aware that string may often include quotes around font families with spaces in their names. -// -// Generic font names have been extended - values include: -// + `serif`, `sans-serif`, `monospace`, `cursive`, `fantasy`, `system-ui`, `math`, `emoji`, `fangsong` -// -// GOTCHA NOTE: current functionality tests the supplied string with the expectation that the font families will be preceded by a font size metric value. To set the fontFamily value direct, put a font size metric at the start of the string - % will do - followed by a space and then the font family string: -// ``` -// phraseInstance.set({ -// font: '% "Gill Sans", sans-serif' -// }) -// ``` +// __font-family__ - any part of the font string that comes after the above declarations. +// + It is generally a good idea to include one of the preset defaults - `serif`, `sans-serif`, `monospace`, `cursive`, `fantasy`, `system-ui`, `math`, `emoji`, `fangsong` - at the end of the font or family string, to act as a fallback default as other fonts load. +// + Scrawl-canvas will attempt to redraw the display when fonts complete their (asynchronous) upload, but this may fail and need to be triggered manually. family: 'sans-serif', }; @@ -182,57 +159,82 @@ S.size = function (item) { size = 0, metric = 'medium'; - // Canvas context engine (Chrome on MacBook Pro) interprets this as 9px Garamond if (item.indexOf('xx-small') >= 0) metric = 'xx-small'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 10px Garamond else if (item.indexOf('x-small') >= 0) metric = 'x-small'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 8.33px Garamond else if (item.indexOf('smaller') >= 0) metric = 'smaller'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 13px Garamond else if (item.indexOf('small') >= 0) metric = 'small'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 32px Garamond + else if (item.indexOf('medium') >= 0) metric = 'medium'; + else if (item.indexOf('xxx-large') >= 0) metric = 'xxx-large'; else if (item.indexOf('xx-large') >= 0) metric = 'xx-large'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 24px Garamond else if (item.indexOf('x-large') >= 0) metric = 'x-large'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 12px Garamond else if (item.indexOf('larger') >= 0) metric = 'larger'; - - // Canvas context engine (Chrome on MacBook Pro) interprets this as 18px Garamond else if (item.indexOf('large') >= 0) metric = 'large'; - // Canvas context engine (Chrome on MacBook Pro) interprets this as 16px Garamond - else if (item.indexOf('medium') >= 0) metric = 'medium'; else { size = 12; metric = 'px' } - // For when the size has stuff before it in the string (which can, sadly, include numbers) - if (/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(item)) { - - res = item.match(/.* (\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i); - size = (res[1] !== '.') ? parseFloat(res[1]) : 12; - metric = res[2]; + let full, val, suffix; + + let r = item.match(/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i); + + if (Array.isArray(r)) { + + [full, val, suffix] = r; + + if (val && suffix && val != '.') { + + size = val; + metric = suffix; + } } - // For when the size starts the string - else if (/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i.test(item)) { - - res = item.match(/^(\d+\.\d+|\d+|\.\d+)(%|em|ch|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?/i); - size = (res[1] !== '.') ? parseFloat(res[1]) : 12; - metric = res[2]; + else { + + r = item.match(/\/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i); + + if (Array.isArray(r)) { + + [full, val, suffix] = r; + + if (val && suffix && val != '.') { + + size = val; + metric = suffix; + } + } } + if (size !== this.sizeValue) { - this.sizeValue = size; - this.sizeMetric = metric; + this.sizeValue = size; + this.dirtyFont = true; + } + if (metric !== this.sizeMetric) { + + this.sizeMetric = metric; + this.dirtyFont = true; + } } }; +S.sizeValue = function (item) { + + if (xt(item) && item !== this.sizeValue) { + + this.sizeValue = item; + this.dirtyFont = true; + } +} + +S.sizeMetric = function (item) { + + if (xt(item) && item !== this.sizeMetric) { + + this.sizeMetric = item; + this.dirtyFont = true; + } +} + // __font__ - pseudo-attribute which calls various functions to break down the font String into its constituent parts S.font = function (item) { @@ -250,34 +252,45 @@ S.font = function (item) { // __style__ S.style = function (item) { - let v = 'normal'; - if (xt(item)) { + let v = 'normal'; + v = (item.indexOf('italic') >= 0) ? 'italic' : v; v = (item.indexOf('oblique') >= 0) ? 'oblique' : v; - } - this.style = v; + if (v !== this.style) { + + this.style = v; + this.dirtyFont = true; + } + } }; // __variant__ S.variant = function (item) { - let v = 'normal'; + if (xt(item)) { + + let v = 'normal'; + + v = (item.indexOf('small-caps') >= 0) ? 'small-caps' : v; - v = (item.indexOf('small-caps') >= 0) ? 'small-caps' : v; + if (v !== this.variant) { - this.variant = v; + this.variant = v; + this.dirtyFont = true; + } + } }; // __weight__ S.weight = function (item) { - let v = 'normal'; - if (xt(item)) { + let v = 'normal'; + // Handling direct entry of numbers if (item.toFixed) v = item; else { @@ -300,48 +313,271 @@ S.weight = function (item) { // Also need to capture instances where a number value has been directly set with no other font attributes around it v = (/^\d00$/.test(item)) ? item : v; } - } - this.weight = v; + if (v !== this.weight) { + + this.weight = v; + this.dirtyFont = true; + } + } }; // __stretch__ S.stretch = function (item) { - let v = 'normal'; - if (xt(item)) { + let v = 'normal'; + v = (item.indexOf('semi-condensed') >= 0) ? 'semi-condensed' : v; v = (item.indexOf('condensed') >= 0) ? 'condensed' : v; v = (item.indexOf('extra-condensed') >= 0) ? 'extra-condensed' : v; v = (item.indexOf('ultra-condensed') >= 0) ? 'ultra-condensed' : v; - v = (item.indexOf('semi-condensed') >= 0) ? 'semi-condensed' : v; - v = (item.indexOf('condensed') >= 0) ? 'condensed' : v; - v = (item.indexOf('extra-condensed') >= 0) ? 'extra-condensed' : v; - v = (item.indexOf('ultra-condensed') >= 0) ? 'ultra-condensed' : v; - } + v = (item.indexOf('semi-expanded') >= 0) ? 'semi-expanded' : v; + v = (item.indexOf('expanded') >= 0) ? 'expanded' : v; + v = (item.indexOf('extra-expanded') >= 0) ? 'extra-expanded' : v; + v = (item.indexOf('ultra-expanded') >= 0) ? 'ultra-expanded' : v; + + if (v !== this.stretch) { - this.stretch = v; + this.stretch = v; + this.dirtyFont = true; + } + } }; // __family__ +P.rfsTestArray1 = ['italic','oblique','small-caps','normal','bold','lighter','bolder','ultra-condensed','extra-condensed','semi-condensed','condensed','ultra-expanded','extra-expanded','semi-expanded','expanded','xx-small','x-small','small','medium','xxx-large','xx-large','x-large','large']; +P.rfsTestArray2 = ['0','1','2','3','4','5','6','7','8','9']; S.family = function (item) { if (xt(item)) { - let guess = item.match(/( xx-small| x-small| small| medium| large| x-large| xx-large| smaller| larger|\d%|\dem|\dch|\dex|\drem|\dvh|\dvw|\dvmin|\dvmax|\dpx|\dcm|\dmm|\din|\dpc|\dpt) (.*)$/i); + let v = 'sans-serif'; + + let itemArray = item.split(' '), + len = itemArray.length; + + if (len === 1) v = item; + + let counter = 0, + flag = true; + + while (flag) { + + if (counter === len) flag = false; + else { + + let el = itemArray[counter]; + + if (!el.length) counter++; + else if (this.rfsTestArray1.indexOf(el) >= 0) counter++; + else if (this.rfsTestArray2.indexOf(el[0]) >= 0) counter++; + else flag = false; + } + } + if (counter < len) v = itemArray.slice(counter).join(' '); + + if (v !== this.family) { - this.family = (guess && guess[2]) ? guess[2] : item; + this.family = v; + this.dirtyFont = true; + } } }; // #### Prototype functions +P.getFontString = function() { + + if (!this.dirtyFont && this.temperedFontString) return this.temperedFontString; + else return this.buildFont(); +} + +P.updateMetadata = function (scale, lineHeight, host) { + + if (scale && scale.toFixed && scale > 0 && scale !== this.scale) { + + this.scale = scale; + this.dirtyFont = true; + } + + if (lineHeight && lineHeight.toFixed && lineHeight > 0 && lineHeight !== this.lineHeight) { + + this.lineHeight = lineHeight; + this.dirtyFont = true; + } + + let currentHost = (this.host && this.host.type && this.host.type === 'Cell') ? this.host.name : ''; + if (host && host.type && host.type === 'Cell' && host.name !== currentHost) { + + this.host = host; + this.dirtyFont = true; + } +}; + +P.calculateSize = function () { + + if (this.host) { + + let {scale, lineHeight, host, sizeValue, sizeMetric} = this; + + let gcfs = host.getComputedFontSizes(), + parentSize, rootSize, viewportWidth, viewportHeight; + + if (!gcfs) { + + if (['in', 'cm', 'mm', 'Q', 'pc', 'pt', 'px'].indexOf(sizeMetric) < 0) { + + this.dirtyFont = true; + return '12px'; + } + } + else { + + [parentSize, rootSize, viewportWidth, viewportHeight] = host.getComputedFontSizes(); + } + + if (isNaN(sizeValue)) sizeValue = 12; + + let res = parentSize; + + switch (sizeMetric) { + + case 'rem' : + res = rootSize * sizeValue; + break; + + case 'em' : + res = parentSize * sizeValue; + break; + + case 'rlh' : + res = (rootSize * lineHeight) * sizeValue; + break; + + case 'lh' : + res = (parentSize * lineHeight) * sizeValue; + break; + + case 'ex' : + res = (parentSize / 2) * sizeValue; + break; + + case 'cap' : + res = parentSize * sizeValue; + break; + + case 'ch' : + res = parentSize * sizeValue; + break; + + case 'ic' : + res = parentSize * sizeValue; + break; + + case '%' : + res = (parentSize / 100) * sizeValue; + break; + + case 'vw' : + res = (viewportWidth / 100) * sizeValue; + break; + + case 'vh' : + res = (viewportHeight / 100) * sizeValue; + break; + + case 'vmax' : + res = (Math.max(viewportWidth, viewportHeight) / 100) * sizeValue; + break; + + case 'vmin' : + res = (Math.min(viewportWidth, viewportHeight) / 100) * sizeValue; + break; + + case 'vi' : + res = (viewportWidth / 100) * sizeValue; + break; + + case 'vb' : + res = (viewportHeight / 100) * sizeValue; + break; + + case 'in' : + res = 96 * sizeValue; + break; + + case 'cm' : + res = 37.8 * sizeValue; + break; + + case 'mm' : + res = 3.78 * sizeValue; + break; + + case 'Q' : + res = 0.95 * sizeValue; + break; + + case 'pc' : + res = 16 * sizeValue; + break; + + case 'pt' : + res = 1.33 * sizeValue; + break; + + case 'px' : + res = sizeValue; + break; + + case 'xx-small' : + res = 0.6 * parentSize; + break; + + case 'x-small' : + res = 0.75 * parentSize; + break; + + case 'smaller' : + res = 0.8 * parentSize; + break; + + case 'small' : + res = 0.89 * parentSize; + break; + + case 'xxx-large' : + res = 3 * parentSize; + break; + + case 'xx-large' : + res = 2 * parentSize; + break; + + case 'x-large' : + res = 1.5 * parentSize; + break; + + case 'larger' : + res = 1.3 * parentSize; + break; + + case 'large' : + res = 1.2 * parentSize; + break; + } + return `${res * scale}px`; + } + return '12px'; +} // `buildFont` - internal function // + Takes into account a scaling factor -P.buildFont = function (scale = 1) { +P.buildFont = function () { + + this.dirtyFont = false; let font = '' @@ -349,27 +585,27 @@ P.buildFont = function (scale = 1) { if (this.variant !== 'normal') font += `${this.variant} `; if (this.weight !== 'normal') font += `${this.weight} `; if (this.stretch !== 'normal') font += `${this.stretch} `; - if (this.sizeValue) font += `${this.sizeValue * scale}${this.sizeMetric} `; - else font += `${this.sizeMetric} `; + + font += `${this.calculateSize()} `; font += `${this.family}`; // Temper the font string. Submit it to a canvas context engine to see what it makes of it let myCell = requestCell(); - myCell.engine.font = font; font = myCell.engine.font; - releaseCell(myCell); + + this.temperedFontString = font; + return font; }; // `update` - `sets` items, then calls `buildFont` -P.update = function (scale = 1, items) { +P.update = function (items) { if (items) this.set(items); - - return this.buildFont(scale); + return this.getFontString(); }; diff --git a/source/factory/group.js b/source/factory/group.js index d7433552c..8830541f9 100644 --- a/source/factory/group.js +++ b/source/factory/group.js @@ -22,7 +22,7 @@ // #### Imports -import { constructors, cell, artefact, group, entity } from '../core/library.js'; +import { constructors, cell, artefact, group, entity, asset } from '../core/library.js'; import { mergeOver, pushUnique, removeItem } from '../core/utilities.js'; import { scrawlCanvasHold } from '../core/document.js'; @@ -519,6 +519,9 @@ P.applyFilters = function (myCell) { let myimage = filterEngine.getImageData(0, 0, filterElement.width, filterElement.height), worker = requestFilterWorker(); + // NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET'S IMAGEDATA AT THIS POINT + self.preprocessFilters(self.currentFilters); + actionFilterWorker(worker, { image: myimage, filters: self.currentFilters, diff --git a/source/factory/loom.js b/source/factory/loom.js index 59cd1463c..bb99e635d 100644 --- a/source/factory/loom.js +++ b/source/factory/loom.js @@ -1,7 +1,7 @@ // # Loom factory // A Loom offers functionality to render an image onto a <canvas> element, where the image is not a rectangle - it can have curved borders. It can also offer the illusion of flat 3D images in the canvas, giving them perspective. // -// Loom entitys are ___composite entitys___ - an wentity that relies on other entitys for its basic functionality. +// Loom entitys are ___composite entitys___ - an entity that relies on other entitys for its basic functionality. // + Every Loom object requires two (or one) path-enabled [Shape](./shape.html) entitys to act as its left and right tracks. // + A Loom entity also requires a [Picture](./picture.html) entity to act as its image source. // + Looms can use CSS color Strings for their strokeStyle values, alongside __Gradient__, __RadialGradient__, __Color__ and __Pattern__ objects. diff --git a/source/factory/mesh.js b/source/factory/mesh.js new file mode 100644 index 000000000..e76062c9a --- /dev/null +++ b/source/factory/mesh.js @@ -0,0 +1,1239 @@ +// # Mesh factory +// The Scrawl-canvas Mesh entity applies a Net particle system to an image, allowing the image to be deformed by dragging particles around the canvas. This is a similar concept to [Photoshop warp meshes](https://helpx.adobe.com/uk/photoshop/using/warp-images-shapes-paths.html) and (more distantly) [the Gimp's cage tool](https://docs.gimp.org/2.10/en/gimp-tool-cage.html). +// +// Mesh entitys are ___composite entitys___ - an entity that relies on other entitys for its basic functionality. +// + Every Mesh object requires a [Net](./net.html) entity create the grid that it uses for transforming its image. +// + A Mesh entity also requires a [Picture](./picture.html) entity to act as its image source. +// + Meshes can (in theory) use CSS color Strings for their strokeStyle values, alongside __Gradient__, __RadialGradient__, __Color__ and __Pattern__ objects. +// + They can (in theory) use __Anchor__ objects for user navigation. +// + They can (in theory) be rendered to the canvas by including them in a __Cell__ object's __Group__. +// + They can (in theory) be __animated__ directly, or using delta animation, or act as the target for __Tween__ animations. +// + Meshes can (in theory) be cloned, and killed. +// +// ___Note that this is experimental technology!___ +// + The Mesh entity code base shares many similarities to that of the Loom entity; some of the code has been copied over from that file directly. +// + Current code does not use [position](./mixin/position.html) or [entity](./mixin/entity.html) mixins, meaning much of the code here has been copied over from those mixins (DRY issue). +// + TODO: packet management, clone and kill functionality not yet tested. Much of the other functionality also lacks tests. + + +// #### Demos: +// + [Particles-008](../../demo/particles-008.html) - Net entity: generation and basic functionality, including Spring objects +// + [Particles-016](../../demo/particles-016.html) - Mesh entitys + + +// #### Imports +import { constructors, artefact } from '../core/library.js'; +import { currentGroup } from '../core/document.js'; +import { mergeOver, mergeDiscard, pushUnique, λnull, λthis, xta } from '../core/utilities.js'; + +import { makeState } from '../factory/state.js'; +import { requestCell, releaseCell } from '../factory/cell.js'; + +import baseMix from '../mixin/base.js'; +import anchorMix from '../mixin/anchor.js'; + + +// #### Mesh constructor +const Mesh = function (items = {}) { + + this.makeName(items.name); + this.register(); + + this.set(this.defs); + + this.state = makeState(); + + if (!items.group) items.group = currentGroup; + + this.onEnter = λnull; + this.onLeave = λnull; + this.onDown = λnull; + this.onUp = λnull; + + this.delta = {}; + + this.set(items); + + this.fromPathData = []; + this.toPathData = []; + + this.watchFromPath = null; + this.watchIndex = -1; + this.engineInstructions = []; + this.engineDeltaLengths = []; + + return this; +}; + + +// #### Mesh prototype +let P = Mesh.prototype = Object.create(Object.prototype); +P.type = 'Mesh'; +P.lib = 'entity'; +P.isArtefact = true; +P.isAsset = false; + + +// #### Mixins +P = baseMix(P); +P = anchorMix(P); + + +// #### Mesh attributes +// + Attributes defined in the [base mixin](../mixin/base.html): __name__. +// + Attributes defined in the [anchor mixin](../mixin/anchor.html): __anchor__. +let defaultAttributes = { + +// __net__ - A Mesh entity requires a Net entity, set to generate a weak or strong net, to supply Particle objects to act as its mapping coordinates. + net: null, + +// __isHorizontalCopy__ - Boolean flag - Copying the source image to the output happens, by default, by rows - which effectively means the struts are on the left-hand and right-hand edges of the image. +// + To change this to columns (which sets the struts to the top and bottom edges of the image) set the attribute to `false` + isHorizontalCopy: true, + +// __source__ - The Picture entity source for this Mesh. For initialization and/or `set`, we can supply either the Picture entity itself, or its name-String value. +// + The content image displayed by the Mesh entity are set in the Picture entity, not the Mesh, and can be any artefact supported by the Picture (image, video, sprite, or a Cell artefact). +// + Note that any ___filters should be applied to the Picture entity___; Mesh entitys do not support filter functionality but will apply a Picture's filters to the source image as-and-where appropriate. + source: null, + +// __sourceIsVideoOrSprite__ - Boolean flag - If the Picture entity is hosting a video or sprite asset, we need to update the input on every frame. +// + It's easier to tell the Mesh entity to do this using a flag, rather than get the Picture entity to update all its Mesh subscribers on every display cycle. +// + For Pictures using image assets the flag must be set to `false` (the default); setting the flag to `true` will significantly degrade display and animation performance. + sourceIsVideoOrSprite: false, + +// The current Frame drawing process often leads to [moiré interference patterns](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern) appearing in the resulting image. Scrawl-canvas uses a resize trick to blur out these patterns. +// +// __interferenceLoops__ (positive integer Number), __interferenceFactor__ (positive float Number) - The interferenceFactor attribute sets the resizing ratio; while he interferenceLoops attribute sets the number of times the image gets resized. +// + If inteference patterns still appear in the final image, tweak these values to see if a better output can be achieved. + interferenceLoops: 2, + interferenceFactor: 1.03, + +// The Mesh entity does not use the [position](./mixin/position.html) or [entity](./mixin/entity.html) mixins (used by most other entitys) as its positioning is entirely dependent on the position, rotation, scale etc of its constituent Shape path entity struts. +// +// It does, however, use these attributes (alongside their setters and getters): __visibility__, __order__, __delta__, __host__, __group__, __anchor__, __collides__. + visibility: true, + order: 0, + delta: null, + host: null, + group: null, + anchor: null, + +// __noCanvasEngineUpdates__ - Boolean flag - Canvas engine updates are required for the Mesh's border - strokeStyle and line styling; if a Mesh is to be drawn without a border, then setting this flag to `true` may help improve rendering efficiency. + noCanvasEngineUpdates: false, + + +// __noDeltaUpdates__ - Boolean flag - Mesh entitys support delta animation - achieved by updating the `...path` attributes by appropriate (and small!) values. If the Mesh is not going to be animated by delta values, setting the flag to `true` may help improve rendering efficiency. + noDeltaUpdates: false, + + +// __onEnter__, __onLeave__, __onDown__, __onUp__ - Mesh entitys support ___collision detection___, reporting a hit when a test coordinate falls within the Mesh's output image. As a result, Looms can also accept and act on the four __on__ functions - see [entity event listener functions](../mixin/entity.html#section-11) for more details. + onEnter: null, + onLeave: null, + onDown: null, + onUp: null, + + +// __noUserInteraction__ - Boolean flag - To switch off collision detection for a Mesh entity - which might help improve rendering efficiency - set the flag to `true`. + noUserInteraction: false, + + +// [Anchor objects](./anchor.html) can be assigned to Mesh entitys, meaning the following attributes are supported: +// + anchorDescription +// + anchorType +// + anchorTarget +// + anchorRel +// + anchorReferrerPolicy +// + anchorPing +// + anchorHreflang +// + anchorHref +// + anchorDownload +// +// And the anchor attributes can also be supplied as a key:value object assigned to the __anchor__ attribute: +// ``` +// anchor: { +// description +// download +// href +// hreflang +// ping +// referrerpolicy +// rel: +// target: +// anchorType +// clickAction: +// } +// ``` +// +// __method__ - All normal Scrawl-canvas entity stamping methods are supported. + method: 'fill', + + +// Mesh entitys support appropriate styling attributes, mainly for their stroke styles (used with the `draw`, `drawAndFill`, `fillAndDraw`, `drawThenFill` and `fillThenDraw` stamping methods). +// + These ___state___ attributes are stored directly on the object, rather than in a separate [State](./state.html) object. +// +// The following attributes are thus supported: +// +// Alpha and Composite operations will be applied to both the Mesh entity's border (the Shape entitys, with connecting lines between their paths' start and end points) and fill (the image displayed between the Mesh's struts) +// + __globalAlpha__ +// + __globalCompositeOperation__ +// +// All line attributes are supported +// + __lineWidth__ +// + __lineCap__ +// + __lineJoin__ +// + __lineDash__ +// + __lineDashOffset__ +// + __miterLimit__ +// +// The Mesh entity's strokeStyle can be any style supported by Scrawl-canvas - color strings, gradient objects, and pattern objects +// + __strokeStyle__ +// +// The shadow attributes will only be applied to the stroke (border), not to the Mesh's fill (image) +// + __shadowOffsetX__ +// + __shadowOffsetY__ +// + __shadowBlur__ +// + __shadowColor__ +}; +P.defs = mergeOver(P.defs, defaultAttributes); + + +// #### Packet management +P.packetExclusions = pushUnique(P.packetExclusions, ['pathObject', 'state']); +P.packetExclusionsByRegex = pushUnique(P.packetExclusionsByRegex, ['^(local|dirty|current)', 'Subscriber$']); +P.packetCoordinates = pushUnique(P.packetCoordinates, []); +P.packetObjects = pushUnique(P.packetObjects, ['group', 'net', 'source']); +P.packetFunctions = pushUnique(P.packetFunctions, ['onEnter', 'onLeave', 'onDown', 'onUp']); + +P.processPacketOut = function (key, value, includes) { + + let result = true; + + if(includes.indexOf(key) < 0 && value === this.defs[key]) result = false; + + return result; +}; + +P.finalizePacketOut = function (copy, items) { + + let stateCopy = JSON.parse(this.state.saveAsPacket(items))[3]; + copy = mergeOver(copy, stateCopy); + + copy = this.handlePacketAnchor(copy, items); + + return copy; +}; + +P.handlePacketAnchor = function (copy, items) { + + if (this.anchor) { + + let a = JSON.parse(this.anchor.saveAsPacket(items))[3]; + copy.anchor = a; + } + return copy; +} + + +// #### Clone management +// TODO - this functionality is currently disabled, need to enable it and make it work properly +P.clone = λthis; + + +// #### Kill management +// No additional kill functionality required + + +// #### Get, Set, deltaSet +let G = P.getters, + S = P.setters, + D = P.deltaSetters; + +// __get__ - copied over from the entity mixin +P.get = function (item) { + + let getter = this.getters[item]; + + if (getter) return getter.call(this); + + else { + + let def = this.defs[item], + state = this.state, + val; + + if (typeof def != 'undefined') { + + val = this[item]; + return (typeof val != 'undefined') ? val : def; + } + + def = state.defs[item]; + + if (typeof def != 'undefined') { + + val = state[item]; + return (typeof val != 'undefined') ? val : def; + } + return undef; + } +}; + +// __set__ - copied over from the entity mixin. +P.set = function (items = {}) { + + if (Object.keys(items).length) { + + let setters = this.setters, + defs = this.defs, + state = this.state, + stateSetters = (state) ? state.setters : {}, + stateDefs = (state) ? state.defs : {}, + predefined, stateFlag; + + Object.entries(items).forEach(([key, value]) => { + + if (key && key !== 'name' && value != null) { + + predefined = setters[key]; + stateFlag = false; + + if (!predefined) { + + predefined = stateSetters[key]; + stateFlag = true; + } + + if (predefined) predefined.call(stateFlag ? this.state : this, value); + else if (typeof defs[key] !== 'undefined') this[key] = value; + else if (typeof stateDefs[key] !== 'undefined') state[key] = value; + } + }, this); + } + return this; +}; + +// __setDelta__ - copied over from the entity mixin. +P.setDelta = function (items = {}) { + + if (Object.keys(items).length) { + + let setters = this.deltaSetters, + defs = this.defs, + state = this.state, + stateSetters = (state) ? state.deltaSetters : {}, + stateDefs = (state) ? state.defs : {}, + predefined, stateFlag; + + Object.entries(items).forEach(([key, value]) => { + + if (key && key !== 'name' && value != null) { + + predefined = setters[key]; + stateFlag = false; + + if (!predefined) { + + predefined = stateSetters[key]; + stateFlag = true; + } + + if (predefined) predefined.call(stateFlag ? this.state : this, value); + else if (typeof defs[key] !== 'undefined') this[key] = addStrings(this[key], value); + else if (typeof stateDefs[key] !== 'undefined') state[key] = addStrings(state[key], value); + } + }, this); + } + return this; +}; + +// __host__, __getHost__ - copied over from the position mixin. +S.host = function (item) { + + if (item) { + + let host = artefact[item]; + + if (host && host.here) this.host = host.name; + else this.host = item; + } + else this.host = ''; +}; + +// __group__ - copied over from the position mixin. +G.group = function () { + + return (this.group) ? this.group.name : ''; +}; +S.group = function (item) { + + let g; + + if (item) { + + if (this.group && this.group.type === 'Group') this.group.removeArtefacts(this.name); + + if (item.substring) { + + g = group[item]; + + if (g) this.group = g; + else this.group = item; + } + else this.group = item; + } + + if (this.group && this.group.type === 'Group') this.group.addArtefacts(this.name); +}; + +// __getHere__ - returns current core position. +P.getHere = function () { + + return currentCorePosition; +}; + +// __delta__ - copied over from the position mixin. +S.delta = function (items = {}) { + + if (items) this.delta = mergeDiscard(this.delta, items); +}; + + +// __net__ +S.net = function (item) { + + if (item) { + + item = (item.substring) ? artefact[item] : item; + + if (item && item.type === 'Net') { + + this.net = item; + this.dirtyStart = true; + } + } +}; + +// __source__ +S.source = function (item) { + + item = (item.substring) ? artefact[item] : item; + + if (item && item.type === 'Picture') { + + let src = this.source; + + if (src && src.type === 'Picture') src.imageUnsubscribe(this.name); + + this.source = item; + item.imageSubscribe(this.name); + this.dirtyInput = true; + } +}; + +// __isHorizontalCopy__ +S.isHorizontalCopy = function (item) { + + this.isHorizontalCopy = (item) ? true : false; + this.dirtyPathData = true; +}; + + +// #### Prototype functions + +// `getHost` - copied over from the position mixin. +P.getHost = function () { + + if (this.currentHost) return this.currentHost; + else if (this.host) { + + let host = artefact[this.host]; + + if (host) { + + this.currentHost = host; + this.dirtyHost = true; + return this.currentHost; + } + } + return currentCorePosition; +}; + +// `updateByDelta`, `reverseByDelta` - copied over from the position mixin. +P.updateByDelta = function () { + + this.setDelta(this.delta); + + return this; +}; + +P.reverseByDelta = function () { + + let temp = {}; + + Object.entries(this.delta).forEach(([key, val]) => { + + if (val.substring) val = -(parseFloat(val)) + '%'; + else val = -val; + + temp[key] = val; + }); + + this.setDelta(temp); + + return this; +}; + +// `setDeltaValues` - copied over from the position mixin. +P.setDeltaValues = function (items = {}) { + + let delta = this.delta, + oldVal, action; + + Object.entries(items).forEach(([key, requirement]) => { + + if (xt(delta[key])) { + + action = requirement; + + oldVal = delta[key]; + + switch (action) { + + case 'reverse' : + if (oldVal.toFixed) delta[key] = -oldVal; + // TODO: reverse String% (and em, etc) values + break; + + case 'zero' : + if (oldVal.toFixed) delta[key] = 0; + // TODO: zero String% (and em, etc) values + break; + + case 'add' : + break; + + case 'subtract' : + break; + + case 'multiply' : + break; + + case 'divide' : + break; + } + } + }) + return this; +}; + +// Invalidate mid-init functionality +P.midInitActions = λnull; + +// Invalidating sensor functionality +P.cleanCollisionData = function () { + + return [0, []]; +}; +P.getSensors = function () { + + return []; +}; + +// #### Display cycle functionality + +// `prepareStamp` - function called as part of the Display cycle `compile` step. +// + This function is called before we get into the entity stamp promise cascade (thus it's a synchronous function). This is where we need to check whether we need to recalculate the path data which we'll use later to build the Mesh entity's output image. +// + We only need to recalculate the path data on the initial render, and afterwards when the __dirtyPathData__ flag has been set. +// + If we perform the recalculation, then we need to make sure to set the __dirtyOutput__ flag, which will trigger the output image build. +P.prepareStamp = function() { + + this.badNet = true; + this.dirtyParticles = false; + + let {net, particlePositions} = this; + + if (net && net.particleStore && net.particleStore.length > 3) { + + let {rows, columns, particleStore} = net; + + if (rows && columns) { + + this.badNet = false; + this.rows = rows; + this.columns = columns; + + if (!particlePositions) particlePositions = []; + + // Sanity check + // + We will recalculate stuff if any of the net particles have moved since the last check. This is most simply done by constructing a string of all current particle position values and comparing it to the previous string. If they are the same, then we can use the stashed image construct, otherwise we build and stash a new image construct + + let checkPositions = []; + + particleStore.forEach(p => { + + let pos = p.position; + let {x, y} = pos; + + checkPositions.push([x, y]); + }); + let checkPositionsString = checkPositions.join(','), + particlePositionsString = particlePositions.join(','); + + if (particlePositionsString !== checkPositionsString) { + + this.particlePositions = checkPositions; + this.dirtyInput = true; + } + + if (this.sourceIsVideoOrSprite) this.dirtyInput = true; + } + } +}; + +// `setSourceDimension` - internal function called by `prepareStamp`. +// + We make the source dimensions a square of the longest row 'path' +// + This way, we can do a horizontal scan, or a vertical scan with no further calculation +// +// This function also: +// + Calculates the bounding box +// + Creates the perimeter path object +// + Stores the (relative) lengths of individual struts in each row +// +// TODO: consider drawing order of squares - is there a way we can predict which squares are going to be behind other squares ... +// + For instance by adding together the particle z values for each square, then filling in the lowest square first? +// + May also be a way of calculating a cull of squares so that we don't need to fill in squares entirely covered by other squares? +P.setSourceDimension = function () { + + if (!this.badNet) { + + const {columns, rows, particlePositions} = this; + + const results = [], + lengths = [], + xPos = [], + yPos = [], + top = [], + left = [], + right = [], + coords = [], + bottom = []; + + let x, xz, y, res, pos, coord, x0, x1, y0, y1, dx, dy, len, l, i, iz, j, jz; + + for (y = 0; y < rows; y++) { + + res = 0; + len = lengths[y] = []; + coord = coords[y] = []; + + for (x = 0, xz = columns - 1; x < xz; x++) { + + pos = (y * columns) + x; + + [x0, y0] = particlePositions[pos]; + [x1, y1] = particlePositions[pos + 1]; + + coord.push([x0, y0, x1, y1]); + + if (x === 0) { + + left.push([x0, y0]); + } + else if (x === xz - 1) { + + right.push([x1, y1]); + } + else if (y === 0) { + + top.push([x0, y0]); + + if (x === xz - 2) top.push([x1, y1]); + } + else if (y === rows - 1) { + + bottom.push([x0, y0]); + + if (x === xz - 2) bottom.push([x1, y1]); + } + + xPos.push(x0, x1); + yPos.push(y0, y1); + + dx = x1 - x0; + dy = y1 - y0; + + l = Math.sqrt((dx * dx) + (dy * dy)); + res += l; + len.push(l); + } + results.push(res); + } + this.sourceDimension = Math.max(...results); + + // Sanity check - the particle system, when it breaks down, can create some massive dimension values! + let host = this.currentHost || this.getHost(); + if (host) { + + let max = Math.max(...host.currentDimensions); + if (this.sourceDimension > max) this.sourceDimension = max; + } + + for (i = 0, iz = lengths.length; i < iz; i++) { + + l = results[i]; + len = lengths[i]; + coord = coords[i]; + + for (j = 0, jz = len.length; j < jz; j++) { + + if (l) len[j] = len[j] / l; + + coord[j].push(len[j]); + } + } + + this.struts = coords; + + let xMin = Math.min(...xPos), + yMin = Math.min(...yPos), + xMax = Math.max(...xPos), + yMax = Math.max(...yPos); + + this.boundingBox = [xMin, yMin, xMax - xMin, yMax - yMin]; + + left.reverse(); + bottom.reverse(); + + let p = `M${top[0][0]},${top[0][1]}L`; + + for (i = 1, iz = top.length; i < iz; i++) { + + [x, y] = top[i]; + p += `${x},${y} `; + } + for (i = 0, iz = right.length; i < iz; i++) { + + [x, y] = right[i]; + p += `${x},${y} `; + } + for (i = 0, iz = bottom.length; i < iz; i++) { + + [x, y] = bottom[i]; + p += `${x},${y} `; + } + for (i = 0, iz = left.length; i < iz; i++) { + + [x, y] = left[i]; + p += `${x},${y} `; + } + p += 'z'; + + this.pathObject = new Path2D(p); + } +}; + +// `simpleStamp` - Simple stamping is entirely synchronous +// + TODO: we may have to disable this functionality for the Mesh entity, if we use a web worker for either the prepareStamp calculations, or to build the output image itself +P.simpleStamp = function (host, changes = {}) { + + if (host && host.type === 'Cell') { + + this.currentHost = host; + + if (changes) { + + this.set(changes); + this.prepareStamp(); + } + + this.regularStampSynchronousActions(); + } +}; + +// `stamp` - All entity stamping, except for simple stamps, goes through this function, which needs to return a Promise which will resolve in due course. +// + While other entitys have to worry about applying filters as part of the stamping process, this is not an issue for Mesh entitys because filters are defined on, and applied to, the source Picture entity, not the Mesh itself +// +// Here we check which dirty flags need actioning, and call a range of different functions to process the work. These flags are: +// + `dirtyInput` - the Picture entity has reported a change in its source, or copy attributes) +P.stamp = function (force = false, host, changes) { + + if (force) { + + if (host && host.type === 'Cell') this.currentHost = host; + + if (changes) { + + this.set(changes); + this.prepareStamp(); + } + return this.regularStamp(); + } + + if (this.visibility) { + + let self = this, + dirtyInput = this.dirtyInput; + + if (dirtyInput) { + + return new Promise((resolve, reject) => { + + self.cleanInput() + .catch(err => { + + // We don't need to completely reject if output is not clean + // + It should be enough to bale out of the stamp functionality and hope it resolves during the next RAF iteration + console.log(`${self.name} - cleanInput Error: source has a zero dimension`); + resolve(false); + }) + .then(res => { + + self.sourceImageData = res; + return self.cleanOutput(); + }) + .then(res => { + + self.output = res; + return self.regularStamp(); + }) + .then(res => { + + resolve(true); + }) + .catch(err => { + + reject(err); + }); + }) + } + else return this.regularStamp(); + } + else return Promise.resolve(false); +}; + +// #### Clean functions + +// `cleanInput` - internal function called by `stamp` +P.cleanInput = function () { + + let self = this; + + return new Promise((resolve, reject) => { + + self.dirtyInput = false; + + self.setSourceDimension(); + + let sourceDimension = self.sourceDimension; + + if (!sourceDimension) { + + self.dirtyInput = true; + reject(); + } + + let cell = requestCell(), + engine = cell.engine, + canvas = cell.element; + + canvas.width = sourceDimension; + canvas.height = sourceDimension; + engine.setTransform(1, 0, 0, 1, 0, 0); + + self.source.stamp(true, cell, { + startX: 0, + startY: 0, + handleX: 0, + handleY: 0, + offsetX: 0, + offsetY: 0, + roll: 0, + scale: 1, + + width: sourceDimension, + height: sourceDimension, + + method: 'fill', + }) + .then(res => { + + let sourceImageData = engine.getImageData(0, 0, sourceDimension, sourceDimension); + + releaseCell(cell); + resolve(sourceImageData); + }) + .catch(err => { + + releaseCell(cell); + reject(err); + }); + }); +}; + +// `cleanOutput` - internal function called by `stamp` +// + If you're not a fan of big functions, please look away now. +P.cleanOutput = function () { + + let self = this; + + return new Promise((resolve, reject) => { + + const halfPi = Math.PI / 2; + + self.dirtyOutput = false; + + let {sourceDimension, sourceImageData, columns, rows, struts, boundingBox} = self; + + sourceDimension = Math.ceil(sourceDimension); + + if (sourceImageData && rows - 1 > 0) { + + let [startX, startY, outputWidth, outputHeight] = boundingBox; + + outputWidth += startX; + outputHeight += startY; + + const inputCell = requestCell(), + inputEngine = inputCell.engine, + inputCanvas = inputCell.element; + + inputCanvas.width = sourceDimension; + inputCanvas.height = sourceDimension; + inputEngine.setTransform(1, 0, 0, 1, 0, 0); + inputEngine.putImageData(sourceImageData, 0, 0); + + const outputCell = requestCell(), + outputEngine = outputCell.engine, + outputCanvas = outputCell.element; + + outputCanvas.width = outputWidth; + outputCanvas.height = outputHeight; + outputEngine.globalAlpha = self.state.globalAlpha; + outputEngine.setTransform(1, 0, 0, 1, 0, 0); + + const inputStrutHeight = parseFloat((sourceDimension / (rows - 1)).toFixed(4)), + inputStrutWidth = parseFloat((sourceDimension / (columns - 1)).toFixed(4)); + + let topStruts, baseStruts, + maxLen, tStep, bStep, iStep, xtStep, ytStep, xbStep, ybStep, tx, ty, bx, by, sx, sy, + xLen, yLen, stripLength, stripAngle, + c, cz, r, rz, i, iz; + + for (r = 0, rz = rows - 1; r < rz; r++) { + + topStruts = struts[r]; + baseStruts = struts[r + 1]; + + for (c = 0, cz = columns - 1; c < cz; c++) { + + let [ltx, lty, rtx, rty, tLen] = topStruts[c]; + let [lbx, lby, rbx, rby, bLen] = baseStruts[c]; + + tLen *= sourceDimension; + bLen *= sourceDimension; + + maxLen = Math.max(tLen, bLen, inputStrutWidth); + + tStep = tLen / maxLen; + bStep = bLen / maxLen; + iStep = inputStrutWidth / maxLen; + + xtStep = (rtx - ltx) / maxLen; + ytStep = (rty - lty) / maxLen; + xbStep = (rbx - lbx) / maxLen; + ybStep = (rby - lby) / maxLen; + + for (i = 0; i < maxLen; i++) { + + tx = ltx + (xtStep * i); + ty = lty + (ytStep * i); + bx = lbx + (xbStep * i); + by = lby + (ybStep * i); + sy = r * inputStrutHeight; + sx = (c * inputStrutWidth) + (iStep * i); + + xLen = tx - bx; + yLen = ty - by; + stripLength = Math.sqrt((xLen * xLen) + (yLen * yLen)); + stripAngle = Math.atan2(yLen, xLen) + halfPi; + + outputEngine.setTransform(1, 0, 0, 1, tx, ty); + outputEngine.rotate(stripAngle); + + // Safari bugfix because we fall foul of of the Safari source-out-of-bounds bug + // + [Stack Overflow question identifying the issue](https://stackoverflow.com/questions/35500999/cropping-with-drawimage-not-working-in-safari) + let testHeight = (sy + inputStrutHeight > sourceDimension) ? sourceDimension - sy : inputStrutHeight; + + outputEngine.drawImage(inputCanvas, sx, sy, 1, testHeight, 0, 0, 1, stripLength); + } + } + } + + let iFactor = self.interferenceFactor, + iLoops = self.interferenceLoops, + + iWidth = Math.ceil(outputWidth * iFactor), + iHeight = Math.ceil(outputHeight * iFactor); + + inputCanvas.width = iWidth; + inputCanvas.height = iHeight; + + outputEngine.setTransform(1, 0, 0, 1, 0, 0); + inputEngine.setTransform(1, 0, 0, 1, 0, 0); + + for (let j = 0; j < iLoops; j++) { + + inputEngine.drawImage(outputCanvas, 0, 0, outputWidth, outputHeight, 0, 0, iWidth, iHeight); + outputEngine.drawImage(inputCanvas, 0, 0, iWidth, iHeight, 0, 0, outputWidth, outputHeight); + } + + let outputData = outputEngine.getImageData(0, 0, outputWidth, outputHeight); + + releaseCell(inputCell); + releaseCell(outputCell); + + self.dirtyTargetImage = true; + + resolve(outputData); + } + else reject(new Error(`${this.name} - cleanOutput Error: source has a zero dimension, or no data`)); + }); +}; + +// `regularStamp` - internal function called by `stamp` +P.regularStamp = function () { + + let self = this; + + return new Promise((resolve, reject) => { + + if (self.currentHost) { + + self.regularStampSynchronousActions(); + resolve(true); + } + reject(new Error(`${self.name} has no current host`)); + }); +}; + +// `regularStampSynchronousActions` - internal function called by `regularStamp` +P.regularStampSynchronousActions = function () { + + let dest = this.currentHost; + + if (dest) { + + let engine = dest.engine; + + if (!this.noCanvasEngineUpdates) dest.setEngine(this); + + this[this.method](engine); + } +}; + + +// ##### Stamp methods +// These 'method' functions stamp the Mesh entity onto the canvas context supplied to them in the `engine` argument. + +// `fill` +P.fill = function (engine) { + + this.doFill(engine); +}; + +// `draw` +P.draw = function (engine) { + + this.doStroke(engine); +}; + +// `drawAndFill` +P.drawAndFill = function (engine) { + + this.doStroke(engine); + this.currentHost.clearShadow(); + this.doFill(engine); +}; + +// `fillAndDraw` +P.fillAndDraw = function (engine) { + + this.doFill(engine); + this.currentHost.clearShadow(); + this.doStroke(engine); +}; + +// `drawThenFill` +P.drawThenFill = function (engine) { + + this.doStroke(engine); + this.doFill(engine); +}; + +// `fillThenDraw` +P.fillThenDraw = function (engine) { + + this.doFill(engine); + this.doStroke(engine); +}; + +// `clear` +P.clear = function (engine) { + + let output = this.output, + canvas = (this.currentHost) ? this.currentHost.element : false, + gco = engine.globalCompositeOperation; + + if (output && canvas) { + + let tempCell = requestCell(), + tempEngine = tempCell.engine, + tempCanvas = tempCell.element; + + let w = canvas.width, + h = canvas.height; + + tempCanvas.width = w; + tempCanvas.height = h; + + tempEngine.putImageData(output, 0, 0); + engine.setTransform(1, 0, 0, 1, 0, 0); + engine.globalCompositeOperation = 'destination-out'; + engine.drawImage(tempCanvas, 0, 0); + engine.globalCompositeOperation = gco; + + releaseCell(tempCell); + } +}; + +// `none` +P.none = λnull; + + +// These __stroke__ and __fill__ functions handle most of the stuff that the method functions require to stamp the Mesh entity onto a canvas cell. + +// `doStroke` +P.doStroke = function (engine) { + + engine.setTransform(1, 0, 0, 1, 0, 0); + engine.stroke(this.pathObject); +}; + +// `doFill` +// + Canvas API's `putImageData` function turns transparent pixels in the output into transparent in the host canvas - which is not what we want +// + Problem solved by putting output into a pool cell, then drawing it from there to the host cell +P.doFill = function (engine) { + + let output = this.output, + canvas = (this.currentHost) ? this.currentHost.element : false; + + if (output && canvas) { + + let tempCell = requestCell(), + tempEngine = tempCell.engine, + tempCanvas = tempCell.element; + + let w = canvas.width, + h = canvas.height; + + tempCanvas.width = w; + tempCanvas.height = h; + + tempEngine.putImageData(output, 0, 0); + engine.setTransform(1, 0, 0, 1, 0, 0); + engine.drawImage(tempCanvas, 0, 0); + + releaseCell(tempCell); + } +}; + + +// #### Collision functionality + +// `checkHit` +// + Overwrites mixin/position.js function +P.checkHit = function (items = [], mycell) { + + if (this.noUserInteraction) return false; + + if (!this.pathObject) return false; + + let tests = (!Array.isArray(items)) ? [items] : items, + poolCellFlag = false; + + if (!mycell) { + + mycell = requestCell(); + poolCellFlag = true; + } + + let engine = mycell.engine, + tx, ty; + + if (tests.some(test => { + + if (Array.isArray(test)) { + + tx = test[0]; + ty = test[1]; + } + else if (xta(test, test.x, test.y)) { + + tx = test.x; + ty = test.y; + } + else return false; + + if (!tx.toFixed || !ty.toFixed || isNaN(tx) || isNaN(ty)) return false; + + return engine.isPointInPath(this.pathObject, tx, ty, this.winding); + + }, this)) { + + let r = { + x: tx, + y: ty, + artefact: this + }; + + if (poolCellFlag) releaseCell(mycell); + + return r; + } + + if (poolCellFlag) releaseCell(mycell); + + return false; +}; + + +// #### Factory +// ``` +// let myMesh = scrawl.makeMesh({ + +// name: 'display-mesh', + +// net: 'test-net', +// source: 'my-flower', + +// lineWidth: 2, +// lineJoin: 'round', +// strokeStyle: 'orange', + +// method: 'fillThenDraw', + +// onEnter: function () { this.set({ lineWidth: 6 }) }, +// onLeave: function () { this.set({ lineWidth: 2 }) }, +// }); +// ``` +const makeMesh = function (items) { + return new Mesh(items); +}; + +constructors.Mesh = Mesh; + + +// #### Exports +export { + makeMesh, +}; diff --git a/source/factory/noise.js b/source/factory/noise.js new file mode 100644 index 000000000..57febd638 --- /dev/null +++ b/source/factory/noise.js @@ -0,0 +1,976 @@ +// # Noise factory +// The purpose of the Noise asset is to give us a resource for generating noisy (semi-regular) maps. These can then be used directly as Picture or Pattern images, or uploaded to the filter worker object as part of a filter that uses displacement map functionality. + + +// #### Current functionality +// At the moment the Noise asset can generate Perlin-type noise, with engines supplied for: +// + Perlin (classic) +// + Perlin (improved) +// + Simplex - the default engine +// + Value +// +// These engines are supported by a number of settable (and thus animatable) attributes, including special functions for smoothing the engine output. Demo [Filters-019](../../demo/filters-019.html) has been set up to allow for experimenting with these attributes +// +// The noise generated will be output to a dedicated offscreen <canvas> element, which is the asset used by Picture entitys, Pattern styles and filters. The output image can be set to three color schemas: +// + __Monochrome__ (black - gray - white) +// + __Gradient__ - mediated by a Scrawl-canvas Color object +// + __Hue__ - where the engine output for each pixel is interpreted as the hue component of an HSL color +// +// (___NOTE:___ Perlin, Simplex and Value noise generator code based on code found in the [canvas-noise GitHub repository](https://github.com/lencinhaus/canvas-noise) written by [lencinhaus](https://github.com/lencinhaus). + + +// #### Possible future functionality +// There's no reason why the Noise asset cannot be extended to output other types of (semi-regular) noise data. For instance: +// + [Nearest neighbour interpolation](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) - for tesselations, particularly with [unstructured grids](https://en.wikipedia.org/wiki/Unstructured_grid) +// + [Linear interpolation](https://en.wikipedia.org/wiki/Linear_interpolation) +// + [Bilinear interpolation](https://en.wikipedia.org/wiki/Bilinear_interpolation) +// + [Bicubic interpolation](https://en.wikipedia.org/wiki/Bicubic_interpolation) - and see also (this blog post)[https://jobtalle.com/cubic_noise.html] +// + Using non-rectangular [grids or meshes](https://en.wikipedia.org/wiki/Types_of_mesh) as a starting point for generating noise. For instance: [sunflower pattern](https://www.sciencemag.org/news/2016/05/sunflowers-show-complex-fibonacci-sequences) + + +// #### Demos: +// + [Canvas-044](../../demo/canvas-044.html) - Building more complex patterns +// + [Filters-019](../../demo/filters-019.html) - Using a Noise asset with a displace filter + + +// #### Imports +import { constructors } from '../core/library.js'; +import { mergeOver, λnull, λthis, λfirstArg, removeItem, seededRandomNumberGenerator, interpolate, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js'; + +import { makeColor } from './color.js'; + +import baseMix from '../mixin/base.js'; +import assetMix from '../mixin/asset.js'; +import patternMix from '../mixin/pattern.js'; + + +// #### Noise constructor +const Noise = function (items = {}) { + + this.makeName(items.name); + this.register(); + + let mycanvas = document.createElement('canvas'); + mycanvas.id = this.name; + this.installElement(mycanvas); + + this.perm = []; + this.permMod8 = []; + this.values = []; + this.grad = []; + + this.subscribers = []; + + this.colorFactory = makeColor({ + name: `${this.name}-color-factory`, + minimumColor: items.gradientStart || 'red', + maximumColor: items.gradientEnd || 'green', + }); + + this.set(this.defs); + this.set(items); + + if (items.subscribe) this.subscribers.push(items.subscribe); + + this.dirtyOutput = true; + + return this; +}; + + +// #### Noise prototype +let P = Noise.prototype = Object.create(Object.prototype); +P.type = 'Noise'; +P.lib = 'asset'; +P.isArtefact = false; +P.isAsset = true; + + +// #### Mixins +// + [base](../mixin/base.html) +// + [asset](../mixin/asset.html) +// + [pattern](../mixin/pattern.html) +P = baseMix(P); +P = assetMix(P); +P = patternMix(P); + + +// #### Noise attributes +// + Attributes defined in the [base mixin](../mixin/base.html): __name__. +// + Attributes defined in the [asset mixin](../mixin/asset.html): __source, subscribers__. +// + Attributes defined in the [pattern mixin](../mixin/pattern.html): __repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF__. +let defaultAttributes = { + + // The offscreen canvas dimensions, within which the noise will be generated, is set using the __width__ and __height__ attributes. These take Number values. + width: 300, + height: 150, + + // __color__ - String value determining how the generated noise will be output on the canvas. Currently recognised values are: `monochrome` (default), `gradient` and `hue` + color: 'monochrome', + + // When the `color` choice has been set to `monochrome` we can clamp the pixel values using the __monochromeStart__ and __monochromeRange__ attributes, both of which take integer Numbers. + // + Accepted monochromeStart values are 0 to 255 + // + Accepted monochromeRange values are -255 to 255 + // + Be aware that the monochromeRange value will be recalculated to make sure calculated pixel values remain in the 0-255 color channel range + monochromeStart: 0, + monochromeRange: 255, + + // When the `color` choice has been set to `gradient` we can control the start and end colors of the gradient using the __gradientStart__ and __gradientEnd__ attributes + gradientStart: '#ff0000', + gradientEnd: '#00ff00', + + // When the `color` choice has been set to `hue` we can control the pixel colors (in terms of their HSL components) using the __hueStart__, __hueRange__, __saturation__ and __luminosity__ attributes: + // + `hueStart` - float Number value in degrees, will be clamped to between 0 and 360 + // + `hueRange` - float Number value in degrees, can be negative as well as positive + // + `saturation` - float Number value, between 0 and 100 + // + `luminosity` - float Number value, between 0 and 100 + hueStart: 0, + hueRange: 120, + saturation: 100, + luminosity: 50, + + // __noiseEngine__ - String - the currently supported noise engines String values are: `perlin`, `improved-perlin`, `simplex`, `value` + noiseEngine: 'simplex', + + // When a noise engine initializes it will create several Arrays of pseudo-random values. The __seed__ attribute is a String used to initialize the pseudo-random number generator, while the __size__ attribute is a Number (often a power of 2 value) which determines the lengths of the Arrays + seed: 'any_random_string_will_do', + size: 256, + + // The __scale__ attribute determines the relative scale of the noise calculation, which affects the noise output. Think of it as a rather idiosyncratic zoom factor + scale: 50, + + // Attributes used when calculating the noise map include: + // + __octaves__ - a positive integer Number - the more octives, the more naturalistic the output - values over 6 are rarely productive + // + __octaveFunction - a String identifying the function to be run at the end of each octave loop. Currently only `none` and `absolute` functions are supported + // + __persistance__ and __lacunarity__ values change at the conclusion of each octave loop; these attributes set their initial values + octaves: 1, + octaveFunction: 'none', + persistence: 0.5, + lacunarity: 2, + + // The __smoothing__ attribute - a String value - identifies the smoothing function that will be applied pixel noise values as they are calculated. There are a wide number of functions available, including: easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce, cosine, hermite, quintic (default) + smoothing: 'quintic', + + // Post-processing the noise map: The __sumFunction__ attribute - a String value - identifies the smoothing function that will be applied to the noise map once the noise calculations complete. + // + Permitted values include: `none`, `sine-x`, `sine-y`, `sine`, `modular` + // + __sineFrequencyCoeff__ - a Number - is used by sine-based sum functions + // + __modularAmplitude__ - a Number - is used by the modular sum function + sumFunction: 'none', + sineFrequencyCoeff: 1, + modularAmplitude: 5, + + +}; +P.defs = mergeOver(P.defs, defaultAttributes); + +delete P.defs.source; +delete P.defs.sourceLoaded; + +// #### Packet management +// This functionality is disabled for Cell objects +P.stringifyFunction = λnull; +P.processPacketOut = λnull; +P.finalizePacketOut = λnull; +P.saveAsPacket = function () { + + return `[${this.name}, ${this.type}, ${this.lib}, {}]` +}; + + +// #### Clone management +P.clone = λthis; + + +// #### Kill management +// No additional kill functionality required + + +// #### Get, Set, deltaSet +let G = P.getters, + S = P.setters, + D = P.deltaSetters; + +// __source__ +S.source = λnull; + +// __subscribers__ - we disable the ability to set the subscribers Array directly. Picture entitys and Pattern styles will manage their subscription to the asset using their subscribe() and unsubscribe() functions. Filters will check for updates every time they run +S.subscribers = λnull; + +S.octaveFunction = function (item) { + + this.octaveFunction = this.octaveFunctions[item] || λfirstArg; + this.dirtyNoise = true; + this.dirtyOutput = true; +}; + +S.sumFunction = function (item) { + + this.sumFunction = this.sumFunctions[item] || λfirstArg; + this.dirtyNoise = true; + this.dirtyOutput = true; +}; + +S.smoothing = function (item) { + + this.smoothing = this.smoothingFunctions[item] || λfirstArg; + this.dirtyNoise = true; + this.dirtyOutput = true; +}; + +S.noiseEngine = function (item) { + + this.noiseEngine = this.noiseEngines[item] || this.noiseEngines['simplex']; + this.dirtyNoise = true; + this.dirtyOutput = true; +}; + +S.octaves = function (item) { + + if (item.toFixed) { + + this.octaves = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.seed = function (item) { + + if (item.substring) { + + this.seed = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +P.supportedColorSchemes = ['monochrome', 'gradient', 'hue']; +S.color = function (item) { + + if (this.supportedColorSchemes.indexOf(item) >= 0) { + + this.color = item; + this.dirtyOutput = true; + } +}; + +S.scale = function (item) { + + if (item.toFixed) { + + this.scale = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.size = function (item) { + + if (item.toFixed) { + + this.size = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.persistence = function (item) { + + if (item.toFixed) { + + this.persistence = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.lacunarity = function (item) { + + if (item.toFixed) { + + this.lacunarity = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.gradientStart = function (item) { + + if (item.substring) { + + this.colorFactory.setMinimumColor(item); + this.dirtyOutput = true; + } +}; + +S.gradientEnd = function (item) { + + if (item.substring) { + + this.colorFactory.setMaximumColor(item); + this.dirtyOutput = true; + } +}; + +S.monochromeStart = function (item) { + + if (item.toFixed && item >= 0) { + + this.monochromeStart = item % 360; + this.dirtyOutput = true; + } +}; + +S.monochromeRange = function (item) { + + if (item.toFixed && item >= -255 && item < 256) { + + this.monochromeRange = Math.floor(item); + this.dirtyOutput = true; + } +}; + +S.hueStart = function (item) { + + if (item.toFixed) { + + this.hueStart = item; + this.dirtyOutput = true; + } +}; + +S.hueRange = function (item) { + + if (item.toFixed) { + + this.hueRange = item; + this.dirtyOutput = true; + } +}; + +S.saturation = function (item) { + + if (item.toFixed && item >= 0 && item <= 100) { + + this.saturation = Math.floor(item); + this.dirtyOutput = true; + } +}; + +S.luminosity = function (item) { + + if (item.toFixed && item >= 0 && item <= 100) { + + this.luminosity = Math.floor(item); + this.dirtyOutput = true; + } +}; + +S.sineFrequencyCoeff = function (item) { + + if (item.toFixed) { + + this.sineFrequencyCoeff = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.modularAmplitude = function (item) { + + if (item.toFixed) { + + this.modularAmplitude = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.width = function (item) { + + if (item.toFixed) { + + this.width = item; + this.sourceNaturalWidth = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + +S.height = function (item) { + + if (item.toFixed) { + + this.height = item; + this.sourceNaturalHeight = item; + this.dirtyNoise = true; + this.dirtyOutput = true; + } +}; + + +// #### Prototype functions +// `installElement` - internal function, used by the constructor +P.installElement = function (element) { + + this.element = element; + this.engine = this.element.getContext('2d'); + + return this; +}; + +// `checkSource` +// + Gets invoked by subscribers (who have a handle to the asset instance object) as part of the display cycle. +// + Noise assets will automatically pass this call onto `notifySubscribers`, where dirty flags get checked and rectified +P.checkSource = function (width, height) { + + this.notifySubscribers(); +}; + +// `getData` function called by Cell objects when calculating required updates to its CanvasRenderingContext2D engine, specifically for an entity's __fillStyle__, __strokeStyle__ and __shadowColor__ attributes. +// + This is the point when we clean Scrawl-canvas assets which have told their subscribers that asset data/attributes have updated +P.getData = function (entity, cell) { + + // this.checkSource(this.width, this.height); + this.notifySubscribers(); + + return this.buildStyle(cell); +}; + +// `notifySubscribers`, `notifySubscriber` - overwrites the functions defined in mixin/asset.js +P.notifySubscribers = function () { + + if (this.dirtyOutput || this.dirtyNoise) this.cleanOutput(); + + this.subscribers.forEach(sub => this.notifySubscriber(sub), this); +}; + +P.notifySubscriber = function (sub) { + + sub.sourceNaturalWidth = this.width; + sub.sourceNaturalHeight = this.height; + sub.sourceLoaded = true; + sub.source = this.element; + sub.dirtyImage = true; + sub.dirtyCopyStart = true; + sub.dirtyCopyDimensions = true; + sub.dirtyImageSubscribers = true; +}; + +// `cleanOutput` - internal function called by the `notifySubscribers` function +P.cleanOutput = function () { + + if (this.dirtyNoise) this.cleanNoise(); + if (this.dirtyOutput) this.paintCanvas(); +}; + +// `cleanNoise` - internal function called by the `cleanOutput` function +P.cleanNoise = function () { + + if (this.dirtyNoise) { + + this.dirtyNoise = false; + + let {noiseEngine, seed, width, height, element, engine, octaves, lacunarity, persistence, scale, octaveFunction, sumFunction} = this; + + if (noiseEngine && noiseEngine.init) { + + // Seed our pseudo-random number generator + this.rndEngine = seededRandomNumberGenerator(seed); + + // Generate the permutations table(s) + this.generatePermutationTable(); + + // Initialize the appropriate noise function + noiseEngine.init.call(this); + + let x, y, o, i, iz, + noiseValues = [], + scaledX, scaledY, + totalNoise, amplitude, frequency; + + // Prepare the noiseValues 2d array + for (y = 0; y < height; y++) { + + noiseValues[y] = []; + + for (x = 0; x < width; x++) { + noiseValues[y][x] = []; + } + } + + // Calculate a relative scale, and setup min/max variables + let relativeScale = Math.pow(width, -scale / 100); + + let max = -1000, + min = 1000; + + // This is the core of the calculation, performed for each cell in the noiseValues 2d array + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + + // We can modify the output by scaling it + // + Note that modifying the canvas dimensions (width, height) can also have a scaling effect + scaledX = x * relativeScale; + scaledY = y * relativeScale; + + // Amplitude and frequency will update once per octave calculation; totalNoise is the sum of all octave results + totalNoise = 0; + amplitude = 1; + frequency = 1; + + // The calculation will be performed at least once + // - For some reason the literature insists on calling these loops "octaves" + for (o = 0; o < octaves; o++) { + + // Call the appropriate getNoiseValue function + // + The result needs to be stored in a variable scoped locally to this loop iteration + let octaveNoise = noiseEngine.getNoiseValue.call(this, scaledX * frequency, scaledY * frequency); + + // Update octave with a post-calculation octaveFunction, if required + octaveNoise = octaveFunction(octaveNoise, scaledX, scaledY, o + 1); + + // Modify result by the current amplitude, and add to the running total + octaveNoise *= amplitude; + totalNoise += octaveNoise; + + // Update the variables that change over multiple octave loops + frequency *= lacunarity; + amplitude *= persistence; + } + // Update the noise value in its array + noiseValues[y][x] = totalNoise; + + // ... and check for max/min spread of the generated values + min = Math.min(min, totalNoise); + max = Math.max(max, totalNoise); + } + } + + // Calculate the span of numbers generated - we need to get all the results in the range 0 to 1 + let noiseSpan = max - min; + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + + scaledX = x * relativeScale; + scaledY = y * relativeScale; + + // Clamp the cell's noise value to between 0 and 1, then update it with the post-calculation sumFunction, if required + let clampedVal = (noiseValues[y][x] - min) / noiseSpan; + noiseValues[y][x] = sumFunction.call(this, clampedVal, x * relativeScale, y * relativeScale); + } + } + // Update the cached noise values arrays + this.noiseValues = noiseValues; + } + else this.dirtyNoise = true; + } +}; + +// `paintCanvas` - internal function called by the `cleanOutput` function +P.paintCanvas = function () { + + if (this.dirtyOutput) { + + this.dirtyOutput = false; + + let {noiseValues, element, engine, width, height, color, colorFactory, monochromeStart, monochromeRange, hueStart, hueRange, saturation, luminosity} = this; + + // Noise values will be calculated in the cleanNoise function, but just in case this function gets invoked directly before the 2d array has been created ... + if (null != noiseValues) { + + // Update the Canvas element's dimensions - this will also clear the canvas display + element.width = width; + element.height = height; + + // Rebuild the display, pixel-by-pixel + switch (color) { + + case 'hue' : + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + + engine.fillStyle = `hsl(${(hueStart + (noiseValues[y][x] * hueRange)) % 360}, ${saturation}%, ${luminosity}%)`; + engine.fillRect(x, y, 1, 1); + } + } + break; + + case 'gradient' : + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + + engine.fillStyle = colorFactory.getRangeColor(noiseValues[y][x]); + engine.fillRect(x, y, 1, 1); + } + } + break; + + // The default color preference is monochrome + default : + + if (monochromeRange > 0) { + + if (monochromeStart + monochromeRange > 255) monochromeRange = 255 - monochromeStart; + } + else if (monochromeRange < 0) { + + if (monochromeStart - monochromeRange < 0) monochromeRange = monochromeStart; + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + + let gray = Math.floor(monochromeStart + (noiseValues[y][x] * monochromeRange)); + + engine.fillStyle = `rgb(${gray}, ${gray}, ${gray})`; + + engine.fillRect(x, y, 1, 1); + } + } + } + } + else this.dirtyOutput = true; + } +}; + +// #### Noise generator functionality + +// Convenience constants +// P.F = 0.5 * (Math.sqrt(3) - 1); +// P.G = (3 - Math.sqrt(3)) / 6; +P.simplexConstantF = 0.5 * (Math.sqrt(3) - 1); +P.simplexConstantG = (3 - Math.sqrt(3)) / 6; +P.simplexConstantDoubleG = ((3 - Math.sqrt(3)) / 6) * 2; +P.perlinGrad = [[1, 1], [-1, 1], [1, -1], [-1, -1], [1, 0], [-1, 0], [0, 1], [0, -1]]; + +// `noiseEngines` - a {key:object} object. Each named object contains two functions: +// + __init__ - invoked to prepare the engine for a bout of calculations - called by the `cleanNoise` function +// + __getNoiseValue__ - a function called on a per-pixel basis, which calculates the noise value for that pixel +P.noiseEngines = { + + 'perlin': { + + init: function () { + + const {grad, size, rndEngine} = this; + + let dist; + + grad.length = 0; + + for(let i = 0; i < size; i++) { + + grad[i] = [(rndEngine.random() * 2) - 1, (rndEngine.random() * 2) - 1]; + dist = Math.sqrt(grad[i][0] * grad[i][0] + grad[i][1] * grad[i][1]); + grad[i][0] /= dist; + grad[i][1] /= dist; + } + }, + + getNoiseValue: function (x, y) { + + const {size, perm, grad, smoothing} = this; + + let a, b, u, v; + + let bx0 = Math.floor(x) % size, + bx1 = (bx0 + 1) % size; + + let rx0 = x - Math.floor(x), + rx1 = rx0 - 1; + + let by0 = Math.floor(y) % size, + by1 = (by0 + 1) % size; + + let ry0 = y - Math.floor(y), + ry1 = ry0 - 1; + + let i = perm[bx0], + j = perm[bx1]; + + let b00 = perm[i + by0], + b10 = perm[j + by0], + b01 = perm[i + by1], + b11 = perm[j + by1]; + + let sx = smoothing(rx0), + sy = smoothing(ry0); + + u = rx0 * grad[b00][0] + ry0 * grad[b00][1]; + v = rx1 * grad[b10][0] + ry0 * grad[b10][1]; + a = interpolate(sx, u, v); + + u = rx0 * grad[b01][0] + ry1 * grad[b01][1]; + v = rx1 * grad[b11][0] + ry1 * grad[b11][1]; + b = interpolate(sx, u, v); + + return 0.5 * (1 + interpolate(sy, a, b)); + }, + }, + + 'improved-perlin': { + + init: λnull, + + getNoiseValue: function (x, y) { + + const {size, perm, permMod8, perlinGrad, smoothing} = this; + + let a, b, u, v; + + let bx0 = Math.floor(x) % size, + bx1 = (bx0 + 1) % size; + + let rx0 = x - Math.floor(x), + rx1 = rx0 - 1; + + let by0 = Math.floor(y) % size, + by1 = (by0 + 1) % size; + + let ry0 = y - Math.floor(y), + ry1 = ry0 - 1; + + let i = perm[bx0], + j = perm[bx1]; + + let b00 = permMod8[i + by0], + b10 = permMod8[j + by0], + b01 = permMod8[i + by1], + b11 = permMod8[j + by1]; + + let sx = smoothing(rx0), + sy = smoothing(ry0); + + u = rx0 * perlinGrad[b00][0] + ry0 * perlinGrad[b00][1]; + v = rx1 * perlinGrad[b10][0] + ry0 * perlinGrad[b10][1]; + a = interpolate(sx, u, v); + + u = rx0 * perlinGrad[b01][0] + ry1 * perlinGrad[b01][1]; + v = rx1 * perlinGrad[b11][0] + ry1 * perlinGrad[b11][1]; + b = interpolate(sx, u, v); + + return 0.5 * (1 + interpolate(sy, a, b)); + } + }, + + 'simplex': { + + init: λnull, + + getNoiseValue: function (x, y) { + + const getCornerNoise = function (cx, cy, gridPos) { + + let calc = 0.5 - (cx * cx) - (cy * cy); + if (calc < 0) return 0; + + let [gx, gy] = perlinGrad[gridPos]; + return calc * calc * ((gx * cx) + (gy * cy)); + }; + + const {simplexConstantF, simplexConstantG, simplexConstantDoubleG, size, perlinGrad, perm, permMod8} = this; + + let summedCoordinates = (x + y) * simplexConstantF, + summedX = Math.floor(x + summedCoordinates), + summedY = Math.floor(y + summedCoordinates), + modifiedSummedCoordinates = (summedX + summedY) * simplexConstantG; + + let cornerX = x - (summedX - modifiedSummedCoordinates), + cornerY = y - (summedY - modifiedSummedCoordinates); + + let remainderX = summedX % size, + remainderY = summedY % size; + + let pos = permMod8[remainderX + perm[remainderY]], + noise = getCornerNoise(cornerX, cornerY, pos); + + pos = permMod8[remainderX + 1 + perm[remainderY + 1]] + noise += getCornerNoise((cornerX - 1 + simplexConstantDoubleG), (cornerY - 1 + simplexConstantDoubleG), pos); + + let unitA = 0, + unitB = 1; + + if (cornerX > cornerY) { + unitA = 1; + unitB = 0; + } + + pos = permMod8[remainderX + unitA + perm[remainderY + unitB]]; + noise += getCornerNoise((cornerX - unitA + simplexConstantG), (cornerY - unitB + simplexConstantG), pos); + + return 0.5 + (35 * noise); + }, + }, + + 'value': { + + init: function () { + + const {values, size, rndEngine} = this; + + values.length = 0; + + for(let i = 0; i < size; i++) { + + values[i] = values[i + size] = rndEngine.random(); + } + }, + + getNoiseValue: function (x, y) { + + const {values, size, perm, smoothing} = this; + + let x0 = Math.floor(x) % size, + y0 = Math.floor(y) % size, + x1 = (x0 + 1) % size, + y1 = (y0 + 1) % size, + vx = x - Math.floor(x), + vy = y - Math.floor(y), + sx = smoothing(vx), + sy = smoothing(vy), + i = perm[x0], + j = perm[x1], + p00 = perm[i + y0], + p10 = perm[j + y0], + p01 = perm[i + y1], + p11 = perm[j + y1], + i1 = interpolate(sx, values[p00], values[p10]), + i2 = interpolate(sx, values[p01], values[p11]); + + return interpolate(sy, i1, i2); + }, + }, +}; + +// `generatePermutationTable` - internal function called by the `cleanNoise` function +// + The permutation tables get recalculated each time the noise data gets cleaned +// + `rndEngine` is a seedable pseudo-random number generator +P.generatePermutationTable = function () { + + const {perm, permMod8, rndEngine, size} = this; + + perm.length = 0; + permMod8.length = 0; + + let i, j, k; + + for(i = 0; i < size; i++) { + + perm[i] = i; + } + + while (--i) { + + j = Math.floor(rndEngine.random() * size); + k = perm[i]; + perm[i] = perm[j]; + perm[j] = k; + } + + for(i = 0; i < size; i++) { + + perm[i + size] = perm[i]; + permMod8[i] = permMod8[i + size] = perm[i] % 8; + } +}; + +// `octaveFunctions` - a {key:functions} object holding functions used to modify octave loop results +// + calling signature: `octaveFunction(octave, scaledX, scaledY, o + 1)` +P.octaveFunctions = { + + none: λfirstArg, + absolute: function (octave) { return Math.abs((octave * 2) - 1) }, +}; + +// `sumFunctions` - a {key:functions} object holding functions used to modify noise values after their calculation has completed (post-processing) +// + calling signature: `sumFunction.call(this, clampedVal, x * relativeScale, y * relativeScale)` +P.sumFunctions = { + + none: λfirstArg, + + 'sine-x': function (v, sx, sy) { return 0.5 + (Math.sin((sx * this.sineFrequencyCoeff) + v) / 2) }, + 'sine-y': function (v, sx, sy) { return 0.5 + (Math.sin((sy * this.sineFrequencyCoeff) + v) / 2) }, + sine: function (v, sx, sy) { return 0.5 + (Math.sin((sx * this.sineFrequencyCoeff) + v) / 4) + (Math.sin((sy * this.sineFrequencyCoeff) + v) / 4) }, + + modular: function(v) { + let g = v * this.modularAmplitude; + return g - Math.floor(g); + }, +}; + +// `smoothingFunctions` - a {key:function} object containing various ___fade functions___ which can be used to smooth calculated coordinate values so that they will ease towards integral values. +// + Used by the "perlin_classic", "perlin_improved" and "value" getNoiseValue functions; the "simplex" getNoiseValue function does away with the need for a smoothing operation. +// + calling signature: `smoothing(value)` +P.smoothingFunctions = { + + // __none__ - effectively linear - no smoothing gets performed + none: λfirstArg, + + easeOutSine, + easeInSine, + easeOutInSine, + easeOutQuad, + easeInQuad, + easeOutInQuad, + easeOutCubic, + easeInCubic, + easeOutInCubic, + easeOutQuart, + easeInQuart, + easeOutInQuart, + easeOutQuint, + easeInQuint, + easeOutInQuint, + easeOutExpo, + easeInExpo, + easeOutInExpo, + easeOutCirc, + easeInCirc, + easeOutInCirc, + easeOutBack, + easeInBack, + easeOutInBack, + easeOutElastic, + easeInElastic, + easeOutInElastic, + easeOutBounce, + easeInBounce, + easeOutInBounce, + + // __cosine__ - a cosine-based interpolator + cosine: function(t) { return .5 * (1 + Math.cos((1 - t) * Math.PI)) }, + + // __hermite__ - a cubic Hermite interpolator + hermite: function(t) { return t * t * (-t * 2 + 3) }, + + // __quintic__ - the original ease function used by Perlin + quintic: function(t) { return t * t * t * (t * (t * 6 - 15) + 10) }, +}; + + +// #### Factory +// ``` +// scrawl.makeNoise({ +// name: 'my-noise-generator', +// width: 50, +// height: 50, +// octaves: 5, +// scale: 2, +// noiseFunction: 'simplex', +// }); +// ``` +const makeNoise = function (items) { + return new Noise(items); +}; + +constructors.Noise = Noise; + + +// #### Exports +export { + makeNoise, +}; diff --git a/source/factory/pattern.js b/source/factory/pattern.js index 684c9b41f..d5e77a6d1 100644 --- a/source/factory/pattern.js +++ b/source/factory/pattern.js @@ -53,7 +53,7 @@ P = assetConsumerMix(P); // #### Pattern attributes // + Attributes defined in the [base mixin](../mixin/base.html): __name__. -// + Attributes defined in the [pattern mixin](../mixin/pattern.html): __repeat__. +// + Attributes defined in the [pattern mixin](../mixin/pattern.html): __repeat, patternMatrix, matrixA, matrixB, matrixC, matrixD, matrixE, matrixF__. // + Attributes defined in the [assetConsumer mixin](../mixin/assetConsumer.html): __asset, spriteTrack, imageSource, spriteSource, videoSource, source__. let defaultAttributes = {}; P.defs = mergeOver(P.defs, defaultAttributes); diff --git a/source/factory/phrase.js b/source/factory/phrase.js index 3d867457a..c28d84803 100644 --- a/source/factory/phrase.js +++ b/source/factory/phrase.js @@ -571,7 +571,7 @@ S.font = function (item) { this.dirtyPathObject = true; }; -// __style__ - CSS `font-style` String - `normal`, `italic` +// __style__ - CSS `font-style` String G.style = function () { return this.fontAttributes.get('style'); @@ -584,7 +584,7 @@ S.style = function (item) { this.dirtyPathObject = true; }; -// __variant__ - CSS `font-variant` String - `normal`, `small-caps` +// __variant__ - CSS `font-variant` String G.variant = function () { return this.fontAttributes.get('variant'); @@ -597,7 +597,7 @@ S.variant = function (item) { this.dirtyPathObject = true; }; -// __weight__ - CSS `font-weight` String - `normal`, `bold`, `lighter`, `bolder`, or an integer Number (between `1` and `1000`) +// __weight__ - CSS `font-weight` String G.weight = function () { return this.fontAttributes.get('weight'); @@ -610,7 +610,7 @@ S.weight = function (item) { this.dirtyPathObject = true; }; -// __stretch__ - CSS `font-stretch` String - `normal` +// __stretch__ - CSS `font-stretch` String G.stretch = function () { return this.fontAttributes.get('stretch'); @@ -623,11 +623,7 @@ S.stretch = function (item) { this.dirtyPathObject = true; }; -// __size__ - CSS `font-size` String: -// + `xx-small`, `x-small`, `small`, `medium`, `large`, `x-large`, `xx-large` -// + `smaller`, `larger` -// + `120%` -// + `1.2rem`, `12px`, etc - Scrawl-canvas will work with the following metrics: `em`, `ch`, `ex`, `rem`, `vh`, `vw`, `vmin`, `vmax`, `px`, `cm`, `mm`, `in`, `pc`, `pt` +// __size__ - CSS `font-size` String G.size = function () { return this.fontAttributes.get('size'); @@ -689,8 +685,12 @@ S.family = function (item) { // #### Prototype functions +// `cleanDimensionsAdditionalActions` - local overwrite P.cleanDimensionsAdditionalActions = function () { + this.fontAttributes.dirtyFont = true; + this.fontAttributes.updateMetadata(this.scale, this.lineHeight, this.getHost()); + if (this.dimensions[0] === 'auto') { this.buildText(); @@ -698,9 +698,9 @@ P.cleanDimensionsAdditionalActions = function () { let myCell = requestCell(), engine = myCell.engine; - engine.font = this.fontAttributes.buildFont(); + engine.font = this.fontAttributes.getFontString(); - this.currentDimensions[0] = Math.ceil(engine.measureText(this.currentText).width); + this.currentDimensions[0] = Math.ceil(engine.measureText(this.currentText).width / this.scale); releaseCell(myCell); } @@ -794,7 +794,6 @@ P.cleanPathObject = function () { if (this.dirtyFont && this.fontAttributes) { this.dirtyFont = false; - this.fontAttributes.buildFont(this.scale); this.dirtyText = true; this.dirtyMimicDimensions = true; this.dirtyPositionSubscribers = true; @@ -942,7 +941,10 @@ P.calculateTextPositions = function (mytext) { justify = this.justify, handle, handleX, handleY; - let defaultFont = fontAttributes.update(scale), + fontAttributes.updateMetadata(scale, lineHeight, host); + glyphAttributes.updateMetadata(scale, lineHeight, host); + + let defaultFont = fontAttributes.getFontString(), defaultFillStyle = makeStyle(state.fillStyle), defaultStrokeStyle = makeStyle(state.strokeStyle), defaultSpace = this.letterSpacing * scale, @@ -1010,7 +1012,7 @@ P.calculateTextPositions = function (mytext) { } if (gStyle.defaults) { - currentFont = glyphAttributes.update(scale, fontAttributes); + currentFont = glyphAttributes.update(fontAttributes); currentStrokeStyle = defaultStrokeStyle; currentFillStyle = defaultFillStyle; currentSpace = defaultSpace; @@ -1062,7 +1064,7 @@ P.calculateTextPositions = function (mytext) { if (i !== 0 && (gStyle.variant || gStyle.weight || gStyle.style || gStyle.stretch || gStyle.size || gStyle.sizeValue || gStyle.sizeMetric || gStyle.family || gStyle.font)) { - item = glyphAttributes.update(scale, gStyle); + item = glyphAttributes.update(gStyle); if (item !== currentFont) { currentFont = item; @@ -1369,7 +1371,7 @@ P.regularStampSynchronousActions = function () { if (!this.noCanvasEngineUpdates) dest.setEngine(this); - pos = this.textPositions; + pos = this.textPositions || []; for (i = 0, iz = pos.length; i < iz; i++) { diff --git a/source/factory/picture.js b/source/factory/picture.js index c0949878f..89a21894b 100644 --- a/source/factory/picture.js +++ b/source/factory/picture.js @@ -599,7 +599,8 @@ P.draw = function (engine) { // `fill` P.fill = function (engine) { - if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); }; // `drawAndFill` @@ -607,7 +608,8 @@ P.drawAndFill = function (engine) { engine.stroke(this.pathObject); - if (this.source) { + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) { this.currentHost.clearShadow(); engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); @@ -619,11 +621,13 @@ P.fillAndDraw = function (engine) { engine.stroke(this.pathObject); - if (this.source) { + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) { this.currentHost.clearShadow(); engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); } + engine.stroke(this.pathObject); }; @@ -631,13 +635,17 @@ P.fillAndDraw = function (engine) { P.drawThenFill = function (engine) { engine.stroke(this.pathObject); - if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); + + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); }; // `fillThenDraw` P.fillThenDraw = function (engine) { - if (this.source) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); + let [x, y, w, h] = this.copyArray; + if (this.source && w && h) engine.drawImage(this.source, ...this.copyArray, ...this.pasteArray); + engine.stroke(this.pathObject); }; diff --git a/source/factory/tween.js b/source/factory/tween.js index b1e5be85c..d5c7e6798 100644 --- a/source/factory/tween.js +++ b/source/factory/tween.js @@ -38,7 +38,7 @@ // #### Imports import { constructors, animationtickers, radian } from '../core/library.js'; -import { mergeOver, pushUnique, xt, xtGet, xto, convertTime, λnull } from '../core/utilities.js'; +import { mergeOver, pushUnique, xt, xtGet, xto, convertTime, λnull, easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce } from '../core/utilities.js'; import { makeTicker } from './ticker.js'; @@ -591,7 +591,159 @@ P.engineActions = { linear: function (start, change, position) { return start + (position * change); - } + }, + +// The following easing functions have been taken from the [easings.net](https://easings.net/) web page: __easeOutSine, easeInSine, easeOutInSine, easeOutQuad, easeInQuad, easeOutInQuad, easeOutCubic, easeInCubic, easeOutInCubic, easeOutQuart, easeInQuart, easeOutInQuart, easeOutQuint, easeInQuint, easeOutInQuint, easeOutExpo, easeInExpo, easeOutInExpo, easeOutCirc, easeInCirc, easeOutInCirc, easeOutBack, easeInBack, easeOutInBack, easeOutElastic, easeInElastic, easeOutInElastic, easeOutBounce, easeInBounce, easeOutInBounce__ + + easeOutSine: function (start, change, position) { + + return start + (change * easeOutSine(position)); + }, + + easeInSine: function (start, change, position) { + + return start + (change * easeInSine(position)); + }, + + easeOutInSine: function (start, change, position) { + + return start + (change * easeOutInSine(position)); + }, + + easeOutQuad: function (start, change, position) { + + return start + (change * easeOutQuad(position)); + }, + + easeInQuad: function (start, change, position) { + + return start + (change * easeInQuad(position)); + }, + + easeOutInQuad: function (start, change, position) { + + return start + (change * easeOutInQuad(position)); + }, + + easeOutCubic: function (start, change, position) { + + return start + (change * easeOutCubic(position)); + }, + + easeInCubic: function (start, change, position) { + + return start + (change * easeInCubic(position)); + }, + + easeOutInCubic: function (start, change, position) { + + return start + (change * easeOutInCubic(position)); + }, + + easeOutQuart: function (start, change, position) { + + return start + (change * easeOutQuart(position)); + }, + + easeInQuart: function (start, change, position) { + + return start + (change * easeInQuart(position)); + }, + + easeOutInQuart: function (start, change, position) { + + return start + (change * easeOutInQuart(position)); + }, + + easeOutQuint: function (start, change, position) { + + return start + (change * easeOutQuint(position)); + }, + + easeInQuint: function (start, change, position) { + + return start + (change * easeInQuint(position)); + }, + + easeOutInQuint: function (start, change, position) { + + return start + (change * easeOutInQuint(position)); + }, + + easeOutExpo: function (start, change, position) { + + return start + (change * easeOutExpo(position)); + }, + + easeInExpo: function (start, change, position) { + + return start + (change * easeInExpo(position)); + }, + + easeOutInExpo: function (start, change, position) { + + return start + (change * easeOutInExpo(position)); + }, + + easeOutCirc: function (start, change, position) { + + return start + (change * easeOutCirc(position)); + }, + + easeInCirc: function (start, change, position) { + + return start + (change * easeInCirc(position)); + }, + + easeOutInCirc: function (start, change, position) { + + return start + (change * easeOutInCirc(position)); + }, + + easeOutBack: function (start, change, position) { + + return start + (change * easeOutBack(position)); + }, + + easeInBack: function (start, change, position) { + + return start + (change * easeInBack(position)); + }, + + easeOutInBack: function (start, change, position) { + + return start + (change * easeOutInBack(position)); + }, + + easeOutElastic: function (start, change, position) { + + return start + (change * easeOutElastic(position)); + }, + + easeInElastic: function (start, change, position) { + + return start + (change * easeInElastic(position)); + }, + + easeOutInElastic: function (start, change, position) { + + return start + (change * easeOutInElastic(position)); + }, + + easeOutBounce: function (start, change, position) { + + return start + (change * easeOutBounce(position)); + }, + + easeInBounce: function (start, change, position) { + + return start + (change * easeInBounce(position)); + }, + + easeOutInBounce: function (start, change, position) { + + return start + (change * easeOutInBounce(position)); + }, }; // `setDefinitionsValues` - convert `start` and `end` values into float Numbers. diff --git a/source/mixin/asset.js b/source/mixin/asset.js index 55abdb322..8366226d6 100644 --- a/source/mixin/asset.js +++ b/source/mixin/asset.js @@ -161,10 +161,11 @@ export default function (P = {}) { } }; -// `notifySubscribers` - Subscriber notification in the asset factories will happen when something changes with the image. Changes vary across the different types of asset: +// `notifySubscribers`, `notifySubscriber` - Subscriber notification in the asset factories will happen when something changes with the image. Changes vary across the different types of asset: // + __imageAsset__ - needs to update its subscribers when an image completes loading - or, for <img> sources with srcset (and sizes) attributes, when the image completes a reload of its source data. // + __spriteAsset__ - will also update its subscribers each time it moves to a new sprite image frame, if the sprite is being animated // + __videoAsset__ - will update its subscribers for every RAF tick while the video is playing, or if the video is halted and seeks to a different time in the video play stream. +// + __Noise__ - will update its subscribers when any of its attributes changes (Note: factory/noise.js overwrites these functions). // // All notifications are push; the notification is achieved by setting various attributes and flags in each subscriber. P.notifySubscribers = function () { diff --git a/source/mixin/entity.js b/source/mixin/entity.js index 1b0dc3216..cb83ef11b 100644 --- a/source/mixin/entity.js +++ b/source/mixin/entity.js @@ -13,6 +13,7 @@ // #### Imports import { λnull, mergeOver, pushUnique, xt, addStrings, isa_obj } from '../core/utilities.js'; import { currentGroup, scrawlCanvasHold } from '../core/document.js'; +import { asset } from '../core/library.js'; import { makeState } from '../factory/state.js'; import { requestCell, releaseCell } from '../factory/cell.js'; @@ -530,6 +531,9 @@ export default function (P = {}) { if (worker) releaseFilterWorker(worker); currentEngine.save(); + + currentEngine.globalAlpha = (self.state && self.state.globalAlpha) ? self.state.globalAlpha : 1; + currentEngine.globalCompositeOperation = (self.state && self.state.globalCompositeOperation) ? self.state.globalCompositeOperation : 'source-over'; currentEngine.setTransform(1, 0, 0, 1, 0, 0); @@ -622,6 +626,9 @@ export default function (P = {}) { myimage = filterEngine.getImageData(0, 0, w, h); worker = requestFilterWorker(); + // NEED TO POPULATE IMAGE FILTER ACTION OBJECTS WITH THEIR ASSET'S IMAGEDATA AT THIS POINT + self.preprocessFilters(self.currentFilters); + // Pass control over to the web worker actionFilterWorker(worker, { image: myimage, diff --git a/source/mixin/filter.js b/source/mixin/filter.js index fa6b8af0a..f9ba0e3a2 100644 --- a/source/mixin/filter.js +++ b/source/mixin/filter.js @@ -8,8 +8,9 @@ // #### Imports -import { filter } from '../core/library.js'; +import { filter, asset } from '../core/library.js'; import { mergeOver, pushUnique, removeItem } from '../core/utilities.js'; +import { requestCell, releaseCell } from '../factory/cell.js'; // #### Export function @@ -20,14 +21,13 @@ export default function (P = {}) { // All factories using the filter mixin will add these attributes to their objects let defaultAttributes = { - // __filters__ - An array of filter object String names. If only one filter is to be applied, then it is enough to use the String name of that filter object - Scrawl-canvas will make sure it gets added to the Array. // + To add/remove new filters to the filters array, use the `addFilters` and `removeFilters` functions. Note that the `set` function will replace all the existing filters in the array with the new filters. To remove all existing filters from the array, use the `clearFilters` function -// + Multiple filters can be batch-applied to an entity, group of entitys, or an entire cell in one operation. Filters are applied in the order that they appear in in the filters array. +// + Multiple filters will be batch-applied to an entity, group of entitys, or an entire cell in one operation. Filters are applied in the order that they appear in in the filters array. +// + ___Be aware that the "filters" (plural) attribute is different to the CSS/SVG "filter" (singular) attribute___ - details about how Scrawl-canvas uses CSS/SVG filter Strings to produce filtering effects (at the entity and Cell levels only) are investigated in the Filter Demos 051 to 055. CSS/SVG filter Strings can be applied in addition to Scrawl-canvas filters Array objects, and will be applied after them. filters: null, - -// __isStencil__ - Use the entity as a stencil. +// __isStencil__ - Use the entity as a stencil. When this flag is set filter effects will be applied to the background imagery covered by the entity (or Group of entitys, or Cell), the results of which will replace the entity/Group/Cell in the final display. isStencil: false, }; P.defs = mergeOver(P.defs, defaultAttributes); @@ -37,7 +37,7 @@ export default function (P = {}) { let S = P.setters; -// `filters` - Replaces the existing filters array with a new filters array. If a string name is supplied, will add that name to the existing filters array +// `filters` - ___Dangerous action!__ - replaces the existing filters Array with a new filters Array. If a string name is supplied, will add that name to the existing filters array S.filters = function (item) { if (!Array.isArray(this.filters)) this.filters = []; @@ -47,13 +47,15 @@ export default function (P = {}) { if (Array.isArray(item)) { this.filters = item; + this.dirtyFilters = true; this.dirtyImageSubscribers = true; } else if (item.substring) { - pushUnique(this.filters, item); + pushUnique(this.filters, item); + this.dirtyFilters = true; this.dirtyImageSubscribers = true; } @@ -78,7 +80,6 @@ export default function (P = {}) { // #### Prototype functions - // `cleanFilters` - Internal housekeeping P.cleanFilters = function () { @@ -94,29 +95,31 @@ export default function (P = {}) { myfilters.forEach(name => { myobj = filter[name]; - order = floor(myobj.order) || 0; - if (!buckets[order]) buckets[order] = []; + if (myobj) { + + order = floor(myobj.order) || 0; - buckets[order].push(myobj); + if (!buckets[order]) buckets[order] = []; + + buckets[order].push(myobj); + } }); this.currentFilters = buckets.reduce((a, v) => a.concat(v), []); }; -// `addFilters` - Add one or more filter name strings to the filters array. Filter name strings can be supplied as comma-separated arguments to the function +// `addFilters`, `removeFilters` - Add or remove one or more filter name strings to/from the filters array. Filter name strings can be supplied as comma-separated arguments to the function P.addFilters = function (...args) { if (!Array.isArray(this.filters)) this.filters = []; args.forEach(item => { - if (this.name, 'addFilters', item) { + if (item && item.type === 'Filter') item = item.name; + pushUnique(this.filters, item); - if (item.substring) pushUnique(this.filters, item); - else if (item.type === 'Filter') pushUnique(this.filters, item.name); - } }, this); this.dirtyFilters = true; @@ -125,19 +128,15 @@ export default function (P = {}) { return this; }; - -// `removeFilters` - Remove one or more filter name strings from the filters array. Filter name strings can be supplied as comma-separated arguments to the function P.removeFilters = function (...args) { if (!Array.isArray(this.filters)) this.filters = []; args.forEach(item => { - if (item) { + if (item && item.type === 'Filter') item = item.name; + removeItem(this.filters, item); - if (item.substring) removeItem(this.filters, item); - else if (item.type === 'Filter') removeItem(this.filters, item.name); - } }, this); this.dirtyFilters = true; @@ -146,7 +145,6 @@ export default function (P = {}) { return this; }; - // `clearFilters` - Clears the filters array P.clearFilters = function () { @@ -160,6 +158,113 @@ export default function (P = {}) { return this; }; +// `preprocessFilters` - internal function called as part of the Display cycle. The __process-image__ filter action loads a Scrawl-canvas asset into the filters web worker, where it can be used as a lineIn or lineMix argument for other filter actions. + P.preprocessFilters = function (filters) { + + filters.forEach(filter => { + + filter.actions.forEach(obj => { + + if (obj.action == 'process-image') { + + let flag = true; + + let img = asset[obj.asset]; + + if (img) { + + if (img.type === 'Noise') { + + img.checkSource(); + } + + let width = img.sourceNaturalWidth || img.sourceNaturalDimensions[0] || img.currentDimensions[0], + height = img.sourceNaturalHeight || img.sourceNaturalDimensions[1] || img.currentDimensions[1]; + + if (width && height) { + + flag = false; + + let copyX = obj.copyX || 0, + copyY = obj.copyY || 0, + copyWidth = obj.copyWidth || 1, + copyHeight = obj.copyHeight || 1, + destWidth = obj.width || 1, + destHeight = obj.height || 1; + + if (copyX.substring) copyX = (parseFloat(copyX) / 100) * width; + if (copyY.substring) copyY = (parseFloat(copyY) / 100) * height; + if (copyWidth.substring) copyWidth = (parseFloat(copyWidth) / 100) * width; + if (copyHeight.substring) copyHeight = (parseFloat(copyHeight) / 100) * height; + + copyX = Math.abs(copyX); + copyY = Math.abs(copyY); + copyWidth = Math.abs(copyWidth); + copyHeight = Math.abs(copyHeight); + + if (copyX > width) { + copyX = width - 2; + copyWidth = 1; + } + + if (copyY > height) { + copyY = height - 2; + copyHeight = 1; + } + + if (copyWidth > width) { + copyWidth = width - 1; + copyX = 0; + } + + if (copyHeight > height) { + copyHeight = height - 1; + copyY = 0; + } + + + if (copyX + copyWidth > width) { + copyX = width - copyWidth - 1; + } + + if (copyY + copyHeight > height) { + copyY = height - copyHeight - 1; + } + + let cell = requestCell(), + engine = cell.engine, + canvas = cell.element; + + canvas.width = destWidth; + canvas.height = destHeight; + + engine.setTransform(1, 0, 0, 1, 0, 0); + engine.globalCompositeOperation = 'source-over'; + engine.globalAlpha = 1; + + let src = img.source || img.element; + + engine.drawImage(src, copyX, copyY, copyWidth, copyHeight, 0, 0, destWidth, destHeight); + + obj.assetData = engine.getImageData(0, 0, destWidth, destHeight); + + releaseCell(cell); + } + } + + if (flag) { + + obj.assetData = { + width: 1, + height: 1, + data: [0, 0, 0, 0], + } + } + } + }); + }); + }; + // Return the prototype return P; }; diff --git a/source/mixin/pattern.js b/source/mixin/pattern.js index 81110a024..185bce634 100644 --- a/source/mixin/pattern.js +++ b/source/mixin/pattern.js @@ -110,7 +110,7 @@ export default function (P = {}) { repeat = this.repeat, engine = mycell.engine; - if (this.type === 'Cell') { + if (this.type === 'Cell' || this.type === 'Noise') { source = this.element; loaded = true; diff --git a/source/scrawl.js b/source/scrawl.js index b22ce9aee..02c450fe0 100644 --- a/source/scrawl.js +++ b/source/scrawl.js @@ -1,7 +1,6 @@ - // # Scrawl-canvas // -// #### Version 8.3.4 - 6 Jan 2021 +// #### Version 8.4.0 - 2 Feb 2021 // // --------------------------------------------------------------------------------- // The MIT License (MIT) @@ -161,11 +160,21 @@ import { } from './factory/loom.js'; +import { + makeMesh, +} from './factory/mesh.js'; + + import { makeNet, } from './factory/net.js'; +import { + makeNoise, +} from './factory/noise.js'; + + import { makeOval, } from './factory/oval.js'; @@ -416,10 +425,18 @@ export default { makeLoom, + // factory/mesh.js + makeMesh, + + // factory/net.js makeNet, + // factory/noise.js + makeNoise, + + // factory/oval.js makeOval, diff --git a/source/worker/filter-string.js b/source/worker/filter-string.js new file mode 100644 index 000000000..4c8afe75e --- /dev/null +++ b/source/worker/filter-string.js @@ -0,0 +1,16 @@ +// Compressed using https://javascript-minifier.com/ + +const filterCode = function () { + return 'let packet,packetFiltersArray,source,work,cache,actions,workstore={},workstoreLastAccessed={};const createResultObject=function(e){return{r:new Uint8ClampedArray(e),g:new Uint8ClampedArray(e),b:new Uint8ClampedArray(e),a:new Uint8ClampedArray(e)}},unknit=function(e){let t=e.data,l=Math.floor(t.length/4);source=createResultObject(l),e.channels=source;let n=source.r,r=source.g,o=source.b,s=source.a,u=(work=createResultObject(l)).r,a=work.g,c=work.b,i=work.a,f=0;for(let e=0,l=t.length;e{workstoreLastAccessed[e]actions.push(...e.actions)),actions.length&&(unknit(cache.source),actions.forEach(e=>theBigActionsObject[e.action]&&theBigActionsObject[e.action](e)),knit()),postMessage(packet)},onerror=function(e){console.log("error"+e.message),postMessage(packet)};const buildImageGrid=function(e){if(e||(e=cache.source),e&&e.width&&e.height){let t=`grid-${e.width}-${e.height}`;if(workstore[t])return workstoreLastAccessed[t]=Date.now(),workstore[t];let l=[],n=0;for(let t=0,r=e.height;t=s&&(e=s-l-1),t+(n=n.toFixed&&!isNaN(n)?n:1)>=a&&(t=a-n-1),e<1&&(e=1),t<1&&(t=1),e+l>=s&&(l=s-e-1),t+n>=a&&(n=a-t-1);let c=e+l,i=t+n;(r=r.toFixed&&!isNaN(r)?r:0)<0&&(r=0),r>=c&&(r=c-1),(o=o.toFixed&&!isNaN(o)?o:0)<0&&(o=0),o>=i&&(o=i-1);let f=`alphatileset-${s}-${a}-${e}-${t}-${l}-${n}-${r}-${o}`;if(workstore[f])return workstoreLastAccessed[f]=Date.now(),workstore[f];let h,g,p,d,b,k,w,R=[];for(d=o-i,b=a;d=0&&k=0&&t=0&&k=0&&t=0&&k=0&&t=0&&k=0&&t=o&&(e=o-1),(t=t.toFixed&&!isNaN(t)?t:1)<1&&(t=1),t>=s&&(t=s-1),(l=l.toFixed&&!isNaN(l)?l:0)<0&&(l=0),l>=e&&(l=e-1),(n=n.toFixed&&!isNaN(n)?n:0)<0&&(n=0),n>=t&&(n=t-1);let u=`imagetileset-${o}-${s}-${e}-${t}-${l}-${n}`;if(workstore[u])return workstoreLastAccessed[u]=Date.now(),workstore[u];let a=[];for(let r=n-t,u=s;r=0&&y=0&&t=0&&o=0&&n=e&&(l=e-1),null==n||n<0?n=0:n>=t&&(n=t-1);let s=o.width,u=o.height,a=`matrix-${s}-${u}-${e}-${t}-${l}-${n}`;if(workstore[a])return workstoreLastAccessed[a]=Date.now(),workstore[a];let c,i,f,h,g,p,d=o.data.length,b=[],k=[];for(f=-n,h=t-n;f=d&&(l-=d),t.push(l)}k.push(t)}return workstore[a]=k,workstoreLastAccessed[a]=Date.now(),k},checkChannelLevelsParameters=function(e){const t=function(e,t=!1){if(e.toFixed)return e<0?[[0,255,0]]:e>255?[[0,255,255]]:isNaN(e)?t?[[0,255,255]]:[[0,255,0]]:[[0,255,e]];if(e.substring&&(e=e.split(",")),Array.isArray(e)){if(!e.length)return e;if(Array.isArray(e[0]))return e;if((e=e.map(e=>parseInt(e,10))).sort((e,t)=>e-t),1==e.length)return[[0,255,e[0]]];let t,l,n=[];for(let r=0,o=e.length;r0){antiRatio=1-l;for(let e=0,t=n.length;e1&&(s-=1),u<0&&(u+=1),u>1&&(u-=1),a<0&&(a+=1),a>1&&(a-=1),[255*o(s,n,r),255*o(u,n,r),255*o(a,n,r)]},theBigActionsObject={"alpha-to-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,includeRed:o,includeGreen:s,includeBlue:u,excludeRed:a,excludeGreen:c,excludeBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=!0),null==s&&(s=!0),null==u&&(u=!0),null==a&&(a=!0),null==c&&(c=!0),null==i&&(i=!0);const{r:h,g:g,b:p,a:d}=t,{r:b,g:k,b:w,a:R}=l;for(let e=0;e{for(let l=0,n=e.length;lo?255:0:i[e],d[e]=s?f[e]>s?255:0:f[e],b[e]=u?h[e]>u?255:0:h[e],k[e]=a?g[e]>a?255:0:g[e];c?processResults(l,work,1-r):processResults(work,l,r)},blend:function(e){let[t,l,n]=getInputAndOutputChannels(e),{opacity:r,blend:o,offsetX:s,offsetY:u,lineOut:a}=(l.r.length,e);null==r&&(r=1),null==o&&(o=""),null==s&&(s=0),null==u&&(u=0);const{r:c,g:i,b:f,a:h}=t,{r:g,g:p,b:d,a:b}=l,{r:k,g:w,b:R,a:O}=n;let[A,y,I,m,M,x]=getInputAndOutputDimensions(e);const B=function(e,t,l){g[t]=l.r[e],p[t]=l.g[e],d[t]=l.b[e],b[t]=l.a[e]},G=function(e,t){let l=e-s,n=t-u,r=-1;return l>=0&&l=0&&n255*(e+t*(1-e));switch(o){case"color-burn":const e=(e,t)=>1==t?255:0==e?0:255*(1-Math.min(1,(1-t)/e));for(let l=0;l0==t?0:1==e?255:255*Math.min(1,t/(1-e));for(let e=0;ee255*Math.abs(e-t);for(let e=0;e255*(e+t-2*t*e);for(let e=0;ee<=.5?e*t*255:255*(t+(e-t*e));for(let e=0;ee>t?e:t;for(let e=0;e255*(e+t);for(let e=0;ee*t*255;for(let e=0;ee>=.5?e*t*255:255*(t+(e-t*e));for(let e=0;e255*(t+(e-t*e));for(let e=0;e{let l=t<=.25?((16*t-12)*t+4)*t:Math.sqrt(t);return e<=.5?255*(t-(1-2*e)*t*(1-t)):255*(t+(2*e-1)*(l-t))};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,u,f)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(s,i,f)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,i,a)};for(let e=0;e{let[s,u,a]=getHSLfromRGB(e,t,l),[c,i,f]=getHSLfromRGB(n,r,o);return getRGBfromHSL(c,u,f)};for(let e=0;et*e+n*l*(1-t);for(let e=0;e=e&&l<=a&&n>=s&&n<=c&&r>=u&&r<=f){t=!0;break}}h[e]=u[e],g[e]=a[e],p[e]=c[e],d[e]=t?0:f[e]}s?processResults(l,work,1-r):processResults(work,l,r)},"clamp-channels":function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lowRed:o,lowGreen:s,lowBlue:u,highRed:a,highGreen:c,highBlue:i,lineOut:f}=e;null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255),null==c&&(c=255),null==i&&(i=255);const h=a-o,g=c-s,p=i-u,{r:d,g:b,b:k,a:w}=t,{r:R,g:O,b:A,a:y}=l;for(let e=0;eg?255:(n-h)/p*255},{r:b,g:k,b:w,a:R}=t,{r:O,g:A,b:y,a:I}=l;for(let e=0;e=0&&l=0&&nt*e*n+n*l*(1-t);for(let t=0;t=0){let t=h[n]/255,l=O[r]/255;g[n]=e(c[n],t,k[r],l),p[n]=e(i[n],t,w[r],l),d[n]=e(f[n],t,R[r],l),b[n]=255*(t*l+l*(1-t))}}break;case"source-in":const r=(e,t,l)=>t*e*l;for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=r(c[l],e,t),p[l]=r(i[l],e,t),d[l]=r(f[l],e,t),b[l]=e*t*255}}break;case"source-out":const s=(e,t,l)=>t*e*(1-l);for(let e=0;e=0&&B(r,l,n)}break;case"destination-atop":const u=(e,t,l,n)=>t*e*(1-n)+n*l*t;for(let e=0;et*e*(1-n)+n*l;for(let e=0;et*e*l;for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=I(k[n],t,e),p[l]=I(w[n],t,e),d[l]=I(R[n],t,e),b[l]=e*t*255}}break;case"destination-out":const m=(e,t,l)=>l*e*(1-t);for(let e=0;e=0){let e=h[l]/255,t=O[n]/255;g[l]=m(k[n],e,t),p[l]=m(w[n],e,t),d[l]=m(R[n],e,t),b[l]=t*(1-e)*255}}break;case"clear":break;case"xor":const M=(e,t,l,n)=>t*e*(1-n)+n*l*(1-t);for(let e=0;et*e+n*l*(1-t);for(let e=0;e=0&&l=0&&n=0){let c,i=Math.floor(l+(127-o[r])/127*u),h=Math.floor(e+(127-s[r])/127*a);f?c=i<0||i>=M||h<0||h>=x?-1:h*M+i:(i<0&&(i=0),i>=M&&(i=M-1),h<0&&(h=0),h>=x&&(h=x-1),c=h*M+i),$(c,n,t)}else $(n,n,t)}h?processResults(l,work,1-r):processResults(work,l,r)},emboss:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,strength:o,angle:s,tolerance:u,keepOnlyChangedAreas:a,postProcessResults:c,lineOut:i}=e;for(null==r&&(r=1),null==o&&(o=1),null==s&&(s=0),null==u&&(u=0),null==a&&(a=!1),null==c&&(c=!1),o=Math.abs(o);s<0;)s+=360;s%=360;let f=Math.floor(s/45),h=s%45/45*o,g=new Array(9);(g=g.fill(0,0,9))[4]=1,0==f?(g[5]=o-h,g[8]=h,g[3]=-g[5],g[0]=-g[8]):1==f?(g[8]=o-h,g[7]=h,g[0]=-g[8],g[1]=-g[7]):2==f?(g[7]=o-h,g[6]=h,g[1]=-g[7],g[2]=-g[6]):3==f?(g[6]=o-h,g[3]=h,g[2]=-g[6],g[5]=-g[3]):4==f?(g[3]=o-h,g[0]=h,g[5]=-g[3],g[8]=-g[0]):5==f?(g[0]=o-h,g[1]=h,g[8]=-g[0],g[7]=-g[1]):6==f?(g[1]=o-h,g[2]=h,g[7]=-g[1],g[6]=-g[2]):(g[2]=o-h,g[5]=h,g[6]=-g[2],g[3]=-g[5]);const{r:p,g:d,b:b,a:k}=t,{r:w,g:R,b:O,a:A}=l;grid=buildMatrixGrid(3,3,1,1);const y=function(e,t){let l=0;for(let n=0,r=t.length;n=p[e]-u&&w[e]<=p[e]+u&&R[e]>=d[e]-u&&R[e]<=d[e]+u&&O[e]>=b[e]-u&&O[e]<=b[e]+u&&(a?A[e]=0:(w[e]=127,R[e]=127,O[e]=127)));i?processResults(l,work,1-r):processResults(work,l,r)},flood:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,red:o,green:s,blue:u,alpha:a,lineOut:c}=(Math.floor,e);null==r&&(r=1),null==o&&(o=0),null==s&&(s=0),null==u&&(u=0),null==a&&(a=255);const{r:i,g:f,b:h,a:g}=l;i.fill(o,0,n-1),f.fill(s,0,n-1),h.fill(u,0,n-1),g.fill(a,0,n-1),c?processResults(l,work,1-r):processResults(work,l,r)},grayscale:function(e){let[t,l]=getInputAndOutputChannels(e),n=t.r.length,{opacity:r,lineOut:o}=e;null==r&&(r=1);const{r:s,g:u,b:a,a:c}=t,{r:i,g:f,b:h,a:g}=l;for(let e=0;e=n&&e<=r)return o}};let[l,n]=getInputAndOutputChannels(e),r=l.r.length,{opacity:o,red:s,green:u,blue:a,alpha:c,lineOut:i}=e;null==o&&(o=1),null==s&&(s=[0]),null==u&&(u=[0]),null==a&&(a=[0]),null==c&&(c=[255]);const{r:f,g:h,b:g,a:p}=l,{r:d,g:b,b:k,a:w}=n;for(let e=0;e=0&&y=0&&I=0&&y=0&&I=0&&m=0&&M=0&&x=0&&B=0&&G=0&&Ct+e[l],0);n=Math.floor(n/l.length);for(let e=0,r=l.length;e{i?t(b,O,e):l(b,O,e),f?t(k,A,e):l(k,A,e),h?t(w,y,e):l(w,y,e),g?t(R,I,e):l(R,I,e)}),p?processResults(r,work,1-o):processResults(work,r,o)},"process-image":function(e){const{assetData:t,lineOut:l}=e;if(l&&l.substring&&l.length&&t&&t.width&&t.height&&t.data){let e=t.data,n=e.length,r=createResultObject(n/4),o=r.r,s=r.g,u=r.b,a=r.a,c=0;for(let t=0;t maxX) ? x : maxX; - maxY = (y > maxY) ? y : maxY; - } - - localX = minX; - localY = minY; - localWidth = maxX - minX; - localHeight = maxY - minY; -}; - -const getTiles = function () { - - let i, iz, j, jz, x, xz, y, yz, startX, startY, pos, - hold = [], - tileWidth = filter.tileWidth || 1, - tileHeight = filter.tileHeight || 1, - offsetX = filter.offsetX, - offsetY = filter.offsetY, - w = image.width, - h = image.height; - - if (Array.isArray(tiles)) tiles.length = 0; - else tiles = []; - - offsetX = (offsetX >= tileWidth) ? tileWidth - 1 : offsetX; - offsetY = (offsetY >= tileHeight) ? tileHeight - 1 : offsetY; - - startX = (offsetX) ? offsetX - tileWidth : 0; - startY = (offsetY) ? offsetY - tileHeight : 0; - - for (j = startY, jz = h + tileHeight; j < jz; j += tileHeight) { - - for (i = startX, iz = w + tileWidth; i < iz; i += tileWidth) { - - hold.length = 0; - - for (y = j, yz = j + tileHeight; y < yz; y++) { - - if (y >= 0 && y < h) { - - for (x = i, xz = i + tileWidth; x < xz; x++) { - - if (x >= 0 && x < w) { - - pos = (y * iWidth) + (x * 4); - - if (data[pos + 3]) hold.push(pos); - } - } - } - } - if (hold.length) tiles.push([].concat(hold)); - } - } -}; - -const average = function (c) { - - let a = 0, - k, kz, - l = c.length; - - if (l) { - - for (k = 0, kz = l; k < kz; k++) { - - a +=c[k]; - } - return a / l; - } - return 0; -}; - -const checkBounds = function (p) { - - let len = data.length; - - if (p < 0) p += len; - if (p >= len) p -= len; - return p; -}; - -const actions = { - - - userDefined: function () {}, - - grayscale: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - data[pos] = data[pos + 1] = data[pos + 2] = gray; - } - }, - - sepia: function () { - - let i, iz, pos, r, g, b; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * 0.393) + (g * 0.769) + (b * 0.189); - data[pos + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168); - data[pos + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131); - } - }, - - invert: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - } - }, - - red: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 1] = 0; - data[pos + 2] = 0; - } - }, - - green: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - data[pos + 2] = 0; - } - }, - - blue: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - data[pos + 1] = 0; - } - }, - - notred: function() { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - } - }, - - notgreen: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 1] = 0; - } - }, - - notblue: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 2] = 0; - } - }, - - cyan: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos + 1] + data[pos + 2]) / 2; - - data[pos] = 0; - data[pos + 1] = gray; - data[pos + 2] = gray; - } - }, - - magenta: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos] + data[pos + 2]) / 2; - - data[pos] = gray; - data[pos + 1] = 0; - data[pos + 2] = gray; - } - }, - - yellow: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos] + data[pos + 1]) / 2; - - data[pos] = gray; - data[pos + 1] = gray; - data[pos + 2] = 0; - } - }, - - brightness: function () { - - let i, iz, pos, - level = filter.level || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] *= level; - - pos++; - data[pos] *= level; - - pos++; - data[pos] *= level; - } - }, - - saturation: function () { - - let i, iz, pos, - level = filter.level || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); - } - }, - - threshold: function () { - - let i, iz, pos, gray, test, - level = filter.level || 0, - lowRed = filter.lowRed, - lowGreen = filter.lowGreen, - lowBlue = filter.lowBlue, - highRed = filter.highRed, - highGreen = filter.highGreen, - highBlue = filter.highBlue; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - test = (gray > level) ? true : false; - - if (test) { - - data[pos] = highRed; - data[pos + 1] = highGreen; - data[pos + 2] = highBlue; - } - else { - - data[pos] = lowRed; - data[pos + 1] = lowGreen; - data[pos + 2] = lowBlue; - } - - } - }, - - channels: function () { - - let i, iz, pos, - red = filter.red || 0, - green = filter.green || 0, - blue = filter.blue || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - data[pos] *= red; - data[pos + 1] *= green; - data[pos + 2] *= blue; - } - }, - - channelstep: function () { - - let i, iz, pos, - red = filter.red || 1, - green = filter.green || 1, - blue = filter.blue || 1, - floor = Math.floor; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = floor(data[pos] / red) * red; - - pos++; - data[pos] = floor(data[pos] / green) * green; - - pos++; - data[pos] = floor(data[pos] / blue) * blue; - } - }, - - tint: function () { - - let i, iz, pos, r, g, b, - redInRed = filter.redInRed || 0, - redInGreen = filter.redInGreen || 0, - redInBlue = filter.redInBlue || 0, - greenInRed = filter.greenInRed || 0, - greenInGreen = filter.greenInGreen || 0, - greenInBlue = filter.greenInBlue || 0, - blueInRed = filter.blueInRed || 0, - blueInGreen = filter.blueInGreen || 0, - blueInBlue = filter.blueInBlue || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * redInRed) + (g * greenInRed) + (b * blueInRed); - data[pos + 1] = (r * redInGreen) + (g * greenInGreen) + (b * blueInGreen); - data[pos + 2] = (r * redInBlue) + (g * greenInBlue) + (b * blueInBlue); - } - }, - - chroma: function () { - - let pos, posA, - ranges = filter.ranges, - range, min, max, val, - i, iz, j, jz, flag; - - for (j = 0, jz = cache.length; j < jz; j++) { - - flag = false; - - for (i = 0, iz = ranges.length; i < iz; i++) { - - posA = cache[j] + 3; - range = ranges[i]; - min = range[2]; - pos = posA - 1; - val = data[pos]; - - if (val >= min) { - - max = range[5]; - - if (val <= max) { - - min = range[1]; - pos--; - val = data[pos]; - - if (val >= min) { - - max = range[4]; - - if (val <= max) { - - min = range[0]; - pos--; - val = data[pos]; - - if (val >= min) { - - max = range[3]; - - if (val <= max) { - flag = true; - break; - } - } - } - } - } - } - } - if (flag) data[posA] = 0; - } - }, - - pixelate: function () { - - let i, iz, j, jz, pos, r, g, b, a, tile, len; - - getTiles(); - - for (i = 0, iz = tiles.length; i < iz; i++) { - - tile = tiles[i]; - r = g = b = a = 0; - len = tile.length; - - if (len) { - - for (j = 0, jz = len; j < jz; j++) { - - pos = tile[j]; - - r += data[pos]; - g += data[pos + 1]; - b += data[pos + 2]; - a += data[pos + 3]; - } - - r /= len; - g /= len; - b /= len; - a /= len; - - for (j = 0, jz = len; j < jz; j++) { - - pos = tile[j]; - - data[pos] = r; - data[pos + 1] = g; - data[pos + 2] = b; - data[pos + 3] = a; - } - } - } - }, - - blur: function () { - - if (data.slice) { - - let radius = filter.radius || 1, - alpha = filter.includeAlpha || false, - shrink = filter.shrinkingRadius || false, - passes = filter.passes || 1, - vertical = filter.processVertical, - horizontal = filter.processHorizontal, - len = data.length, - imageWidth = image.width, - imageHeight = image.height, - tempDataTo, tempDataFrom, - i, iz, index; - - let processPass = function () { - - let j, jz; - - if (vertical) { - - tempDataFrom = tempDataTo.slice(); - - for (j = localX * 4, jz = (localX + localWidth) * 4; j < jz; j++) { - - if (alpha) processColumn(j); - else { - - if (j % 4 !== 3) processColumn(j); - } - } - } - - if (horizontal) { - - tempDataFrom = tempDataTo.slice(); - - for (j = localY, jz = localY + localHeight; j < jz; j++) { - - if (alpha) processRowWithAlpha(j); - else processRowNoAlpha(j); - } - } - }; - - let processColumn = function (col) { - - let pos, avg, val, cagePointer, y, yz, q, dataPointer, - vLead = radius * iWidth, - cage = [], - cageLen; - - for (y = -radius, yz = radius; y < yz; y++) { - - pos = col + (y * iWidth); - pos = checkBounds(pos, len); - cage.push(tempDataFrom[pos]); - } - - tempDataTo[col] = avg = average(cage); - - cageLen = cage.length; - - for (q = 0; q < cageLen; q++) { - - cage[q] /= cageLen; - } - - cagePointer = 0; - - for (y = 1; y < imageHeight; y++) { - - avg -= cage[cagePointer]; - - dataPointer = col + (y * iWidth); - pos = dataPointer + vLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avg += val; - cage[cagePointer] = val; - tempDataTo[dataPointer] = avg; - - cagePointer++; - - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - let processRowWithAlpha = function (row) { - - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - cage = [[], [], [], []], - rowPos = row * iWidth, - hLead = radius * 4, - dataPointer, cagePointer, cageLen; - - q = 0; - - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { - - pos = rowPos + x; - pos = checkBounds(pos, len); - - cage[q].push(tempDataFrom[pos]); - - q++; - if (q === 4) q = 0; - } - - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); - tempDataTo[rowPos + 3] = avg[3] = average(cage[3]); - - cageLen = cage[0].length; - - for (q = 0; q < 4; q++) { - - for (x = 0; x < cageLen; x++) { - - cage[q][x] /= cageLen; - } - } - cagePointer = 0; - - for (x = 1; x < imageWidth; x++) { - - rowPosX = rowPos + (x * 4); - - for (q = 0; q < 4; q++) { - - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; - - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; - } - - cagePointer++; - - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - let processRowNoAlpha = function (row) { - - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - hLead = radius * 4, - cage = [[], [], []], - rowPos = row * iWidth, - dataPointer, cagePointer, cageLen; - - q = 0; - - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { - - if (q < 3) { - - pos = rowPos + x; - pos = checkBounds(pos, len); - cage[q].push(tempDataFrom[pos]); - q++; - } - else q = 0; - } - - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); - - cageLen = cage[0].length; - - for (q = 0; q < 3; q++) { - - cageQ = cage[q]; - - for (x = 0; x < cageLen; x++) { - - cageQ[x] /= cageLen; - } - } - cagePointer = 0; - - for (x = 1; x < imageWidth; x++) { - - rowPosX = rowPos + (x * 4); - - for (q = 0; q < 3; q++) { - - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; - - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; - } - - cagePointer++; - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - tempDataTo = data.slice(); - - for (i = 0; i < passes; i++) { - - processPass(); - - if (shrink) { - - radius = Math.ceil(radius * 0.3); - radius = (radius < 1) ? 1 : radius; - } - } - - for (i = 0, iz = cache.length; i < iz; i++) { - - index = cache[i]; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - if (alpha) { - - index++; - data[index] = tempDataTo[index]; - } - } - } - }, - - matrix: function () { - - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 1, 0, 0, 0, 0], - tempCache = [], - cursor = 0; - - offset[0] = -iWidth - 4; - offset[1] = -iWidth; - offset[2] = -iWidth + 4; - offset[3] = -4; - offset[4] = 0; - offset[5] = 4; - offset[6] = iWidth - 4; - offset[7] = iWidth; - offset[8] = iWidth + 4; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { - - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { - - weight = weights[j]; - sumR += data[pos] * weight; - - pos++; - sumG += data[pos] * weight; - - pos++; - sumB += data[pos] * weight; - - if (alpha) { - - pos++; - sumA += data[pos] * weight; - } - } - } - - tempCache[cursor] = sumR; - cursor++; - - tempCache[cursor] = sumG; - cursor++; - - tempCache[cursor] = sumB; - cursor++; - - if (alpha) { - - tempCache[cursor] = sumA; - cursor++; - } - } - - cursor = 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - if (alpha) { - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - } - } - }, - - matrix5: function () { - - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - tempCache = [], - iWidth2 = iWidth * 2, - cursor = 0; - - offset[0] = -iWidth2 - 8; - offset[1] = -iWidth2 - 4; - offset[2] = -iWidth2; - offset[3] = -iWidth2 + 4; - offset[4] = -iWidth2 + 8; - offset[5] = -iWidth - 8; - offset[6] = -iWidth - 4; - offset[7] = -iWidth; - offset[8] = -iWidth + 4; - offset[9] = -iWidth + 8; - offset[10] = -8; - offset[11] = -4; - offset[12] = 0; - offset[13] = 4; - offset[14] = 8; - offset[15] = iWidth - 8; - offset[16] = iWidth - 4; - offset[17] = iWidth; - offset[18] = iWidth + 4; - offset[19] = iWidth + 8; - offset[20] = iWidth2 - 8; - offset[21] = iWidth2 - 4; - offset[22] = iWidth2; - offset[23] = iWidth2 + 4; - offset[24] = iWidth2 + 8; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { - - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { - - weight = weights[j]; - sumR += data[pos] * weight; - - pos++; - sumG += data[pos] * weight; - - pos++; - sumB += data[pos] * weight; - - if (alpha) { - - pos++; - sumA += data[pos] * weight; - } - } - } - - tempCache[cursor] = sumR; - cursor++; - - tempCache[cursor] = sumG; - cursor++; - - tempCache[cursor] = sumB; - cursor++; - - if (alpha) { - - tempCache[cursor] = sumA; - cursor++; - } - } - - cursor = 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - if (alpha) { - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - } - } - }, -};` -}; - - -const filterUrl = URL.createObjectURL( - - new Blob([ filterCode() ], { type: 'text/javascript' }) -); - - -// #### Exports -export { - filterUrl, -}; diff --git a/source/worker/filter.js b/source/worker/filter.js index 0cfbb8a5b..15ed5a0ed 100644 --- a/source/worker/filter.js +++ b/source/worker/filter.js @@ -1,96 +1,144 @@ -// # Filter worker -// A long-running web worker which, when not in use, gets stored in the filter pool defined in the [filter factory](../factory/filter.html) +// # Scrawl-canvas filters web worker +// This web worker code is called in a just-in-time manner; the code will not be requested from the server until a Scrawl-canvas entity, Group or Cell with a non-zero `filters` attribute is processed during the Display cycle. +// +// All Scrawl-canvas filters-related image manipulation work happens in this worker code. Note that this functionality is entirely separate from the <canvas> element's context engine's native `filter` functionality, which allows us to add CSS/SVG-based filters to the canvas context + +// ##### Road map +// At some point we shall attempt to convert this code into something that can be run as WebAssembly code, possibly using Rust or AssemblyScript. +// +// ##### Development +// Compress this file using https://javascript-minifier.com/ // -// TODO: we've had to move all the code from this module into a new, [comment-free module](./filter-stringed.html) file because tools like [CreateReactApp](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app) - which uses [Webpack](https://webpack.js.org/) as its bundler of choice - breaks when we `yarn add scrawl-canvas` to a project. -// + The root of the issue is that [Babel](https://babeljs.io/) currently breaks when it encounters the `import.meta` attribute. -// + Babel do supply a plugin which is supposed to address this issue: [babel-plugin-syntax-import-meta](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-import-meta). But trying to add this to a Webpack configuration - particularly as implemented by create-react-app - is, at best, a nightmare. +// Copy the results of the compression into the string in worker/filter-string.js // -// __NOTE: any changes to filter code need to be replicated in both files!__ +// ### Common variables +// __packet__ and `packetFiltersArray` will hold data contained in the message sent to the web worker; the packet object will be returned to the main program when processing completes (or errors) in the worker +let packet, packetFiltersArray; +// __source__ - the original image data supplied in the message +let source; -// #### 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 -// + [Component-004](../../demo/component-004.html) - Scrawl-canvas packets; save and load a range of different entitys +// __work__ - a copy of the source image which then gets manipulated by the functions invoked by each action object included in the filter +let work; +// __cache__ - an Object consisting of `key:Object` pairs where the key is the named input of a `process-image` action or the output of any action object. This object is cleared and re-initialized each time the worker receives a new message +let cache; +// __actions__ - the Array of action objects that the worker needs to process - data supplied by the main thread in its message's `packetFiltersArray` attribute. +let actions; -// #### Imports -// None used +// The web worker maintains a semi-permanent storage space - the __workstore__ - for some processing objects that are computationally expensive, for instance grids, matrix reference data objects, etc. The web worker maintains a record of when each of these processing objects was last accessed and will remove objects if they have not been accessed in the last three seconds. +let workstore = {}, + workstoreLastAccessed = {}; -// #### Polyfills -// TypedArray.slice() [polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice) - for blur filter -if (!Uint8Array.prototype.slice) { - Object.defineProperty(Uint8Array.prototype, 'slice', { - value: function (begin, end) { - return new Uint8Array(Array.prototype.slice.call(this, begin, end)); - } - }); -} +// ### Result objects + +// `createResultObject` - to make the following code easier to maintain, the web worker will create result objects for all source image data it receives, and for each action object output. These result objects contain four arrays - one for each color channle, and one for the alpha channel. +const createResultObject = function (len) { + + return { + r: new Uint8ClampedArray(len), + g: new Uint8ClampedArray(len), + b: new Uint8ClampedArray(len), + a: new Uint8ClampedArray(len), + }; +}; + +// `unknit` - called at the start of each new message action chain. Creates and populates the __source__ and __work__ objects from the image data supplied in the message +const unknit = function (iData) { + + let imageData = iData.data; + + let len = Math.floor(imageData.length / 4); + + + source = createResultObject(len); + iData.channels = source; + + let sourceRed = source.r, + sourceGreen = source.g, + sourceBlue = source.b, + sourceAlpha = source.a; + + work = createResultObject(len); + + let workRed = work.r, + workGreen = work.g, + workBlue = work.b, + workAlpha = work.a; + + let counter = 0; + for (let i = 0, iz = imageData.length; i < iz; i += 4) { -// #### Worker-wide variables -// The following variables are defined here so that all filter functions - including user-defined filters - can make use of them: + sourceRed[counter] = imageData[i]; + sourceGreen[counter] = imageData[i + 1]; + sourceBlue[counter] = imageData[i + 2]; + sourceAlpha[counter] = imageData[i + 3]; -// __packet__ - the `e.data` object sent to the web worker from the main code -let packet; + workRed[counter] = imageData[i]; + workGreen[counter] = imageData[i + 1]; + workBlue[counter] = imageData[i + 2]; + workAlpha[counter] = imageData[i + 3]; -// __image__ - the `ImageData` object -let image; + counter++; + } +}; -// __iWidth__ - convenience variable - `image.width * 4` (to cope with moving through the data array by pixel) -let iWidth; +// `knit` - called at the end of each message action chain. Recreates the message image data in the correct format so it can be used by the main thread +const knit = function () { -// __data__ - the `Uint8ClampedArray` data component of the ImageData object -let data; + let imageData = packet.image.data; -// __cache__ - an Array of all the starting (ie: red channel) indexes for pixels whose alpha channel is > 0 (and thus not transparent) - speeds up things for pixel-by-pixel filters that don't need to process transparent pixels. Generated by running the web worker's `getCache` function -let cache; + let workRed = work.r, + workGreen = work.g, + workBlue = work.b, + workAlpha = work.a; -// __tiles__ - the output from running the web worker's `getTiles` function. This effectively divides the image data into a grid of blocks which can be further processed by a filter -let tiles; + let counter = 0; -// __localX__, __localY__, __localWidth__, __localHeight__ - the start coordinates and dimensions of the box enclosing all the non-transparent pixels in the supplied image data array. Used mainly by the blur filter - but any filter (including user-defined filters) that needs to include transparent pixel values in its calculations, but would waste time processing non-visible areas of the image, can make use of these variables -let localX; -let localY; -let localWidth; -let localHeight; + for (let i = 0, iz = imageData.length; i < iz; i += 4) { -// __filters__ - the filter objects Array sent to the web worker from the main code -let filters; + imageData[i] = workRed[counter]; + imageData[i + 1] = workGreen[counter]; + imageData[i + 2] = workBlue[counter]; + imageData[i + 3] = workAlpha[counter]; -// __filter__ - the current filter object being processed by the web worker - holds all the settings required for that filter function, for example `filter.level` for the brightness, saturation and threshold filter functions -let filter; + counter++; + } +}; -// __action__ - internal processing variable -let action; +// ### Messaging functionality +onmessage = function (msg) { + packet = msg.data; + packetFiltersArray = packet.filters; -// #### Messaging and error handling -onmessage = function (e) { + let workstoreKeys = Object.keys(workstore), + workstoreChoke = Date.now() - 3000; - let i, iz; + workstoreKeys.forEach(k => { - packet = e.data; - image = packet.image; - iWidth = image.width * 4; - data = image.data; - filters = packet.filters; + if (workstoreLastAccessed[k] < workstoreChoke) { - getCache(); - getLocal(); + delete workstore[k]; + delete workstoreLastAccessed[k]; + } + }); - for (i = 0, iz = filters.length; i < iz; i++) { + cache = {}; + actions = []; - filter = filters[i]; + cache.source = packet.image; - if (filter.method === 'userDefined' && filter.userDefined) actions.userDefined = new Function(filter.userDefined); + packetFiltersArray.forEach(f => actions.push(...f.actions)); - action = actions[filter.method]; + if (actions.length) { - if (action) action(); + unknit(cache.source); + actions.forEach(a => theBigActionsObject[a.action] && theBigActionsObject[a.action](a)); + knit(); } postMessage(packet); @@ -102,1034 +150,2613 @@ onerror = function (e) { postMessage(packet); }; -// __getCache__ - function that fills the `cache` worker variable with the starting index position of each non-transparent pixel in the current image's Uint8ClampedArray Array -const getCache = function () { - let i, iz; +// ### Functions invoked by a range of different action functions +// +// `buildImageGrid` creates an Array of Arrays which contain the indexes of each pixel in the data channel Arrays +const buildImageGrid = function (data) { - if (Array.isArray(cache)) cache.length = 0; - else cache = []; + if (!data) data = cache.source; - for (i = 0, iz = data.length; i < iz; i += 4) { + if (data && data.width && data.height) { - if (data[i + 3]) cache.push(i); - } -}; + let name = `grid-${data.width}-${data.height}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; + } -// __getLocal__ - function that populates the `localX`, `localY`, `localWidth`, `localHeight` worker variables with relevant values for the current image data -const getLocal = function () { - let i, iz, w, h, minX, minY, maxX, maxY, x, y, val, - floor = Math.floor; + let grid = [], + counter = 0; - w = image.width; - h = image.height; - minX = w; - minY = h; - maxX = 0; - maxY = 0; + for (let y = 0, yz = data.height; y < yz; y++) { - for (i = 0, iz = cache.length; i < iz; i++) { + let row = []; - val = cache[i] / 4; - y = floor(val / w); - x = val % w; - minX = (x < minX) ? x : minX; - minY = (y < minY) ? y : minY; - maxX = (x > maxX) ? x : maxX; - maxY = (y > maxY) ? y : maxY; + for (let x = 0, xz = data.width; x < xz; x++) { + + row.push(counter); + counter++; + } + grid.push(row); + } + workstore[name] = grid; + workstoreLastAccessed[name] = Date.now(); + return grid; } - - localX = minX; - localY = minY; - localWidth = maxX - minX; - localHeight = maxY - minY; + return false; }; -// __getTiles__ - function that populates the `tiles` worker Array with relevant values for the current image data -const getTiles = function () { +// `buildAlphaTileSets` - creates a record of which pixels belong to which tile - used for manipulating alpha channel values. Resulting object will be cached in the store +const buildAlphaTileSets = function (tileWidth, tileHeight, gutterWidth, gutterHeight, offsetX, offsetY, areaAlphaLevels, data) { - let i, iz, j, jz, x, xz, y, yz, startX, startY, pos, - hold = [], - tileWidth = filter.tileWidth || 1, - tileHeight = filter.tileHeight || 1, - offsetX = filter.offsetX, - offsetY = filter.offsetY, - w = image.width, - h = image.height; + if (!data) data = cache.source; - if (Array.isArray(tiles)) tiles.length = 0; - else tiles = []; + if (data && data.width && data.height) { - offsetX = (offsetX >= tileWidth) ? tileWidth - 1 : offsetX; - offsetY = (offsetY >= tileHeight) ? tileHeight - 1 : offsetY; + let iWidth = data.width, + iHeight = data.height; - startX = (offsetX) ? offsetX - tileWidth : 0; - startY = (offsetY) ? offsetY - tileHeight : 0; + tileWidth = (tileWidth.toFixed && !isNaN(tileWidth)) ? tileWidth : 1; + tileHeight = (tileHeight.toFixed && !isNaN(tileHeight)) ? tileHeight : 1; + gutterWidth = (gutterWidth.toFixed && !isNaN(gutterWidth)) ? gutterWidth : 1; + gutterHeight = (gutterHeight.toFixed && !isNaN(gutterHeight)) ? gutterHeight : 1; + offsetX = (offsetX.toFixed && !isNaN(offsetX)) ? offsetX : 0; + offsetY = (offsetY.toFixed && !isNaN(offsetY)) ? offsetY : 0; - for (j = startY, jz = h + tileHeight; j < jz; j += tileHeight) { + if (tileWidth < 1) tileWidth = 1; + if (tileHeight < 1) tileHeight = 1; + if (tileWidth + gutterWidth >= iWidth) tileWidth = iWidth - gutterWidth - 1; + if (tileHeight + gutterHeight >= iHeight) tileHeight = iHeight - gutterHeight - 1; - for (i = startX, iz = w + tileWidth; i < iz; i += tileWidth) { + if (tileWidth < 1) tileWidth = 1; + if (tileHeight < 1) tileHeight = 1; + if (tileWidth + gutterWidth >= iWidth) gutterWidth = iWidth - tileWidth - 1; + if (tileHeight + gutterHeight >= iHeight) gutterHeight = iHeight - tileHeight - 1; - hold.length = 0; - - for (y = j, yz = j + tileHeight; y < yz; y++) { + let aWidth = tileWidth + gutterWidth, + aHeight = tileHeight + gutterHeight; + + if (offsetX < 0) offsetX = 0; + if (offsetX >= aWidth) offsetX = aWidth - 1; + if (offsetY < 0) offsetY = 0; + if (offsetY >= aHeight) offsetY = aHeight - 1; + + let name = `alphatileset-${iWidth}-${iHeight}-${tileWidth}-${tileHeight}-${gutterWidth}-${gutterHeight}-${offsetX}-${offsetY}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; + } + + let tiles = [], + hold, i, iz, j, jz, x, xz, y, yz; - if (y >= 0 && y < h) { + for (j = offsetY - aHeight, jz = iHeight; j < jz; j += aHeight) { - for (x = i, xz = i + tileWidth; x < xz; x++) { + for (i = offsetX - aWidth, iz = iWidth; i < iz; i += aWidth) { + + hold = []; + for (y = j, yz = j + tileHeight; y < yz; y++) { + if (y >= 0 && y < iHeight) { + for (let x = i, xz = i + tileWidth; x < xz; x++) { + if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x); + } + } + } + tiles.push([].concat(hold)); - if (x >= 0 && x < w) { + hold = []; + for (y = j + tileHeight, yz = j + tileHeight + gutterHeight; y < yz; y++) { + if (y >= 0 && y < iHeight) { + for (let x = i, xz = i + tileWidth; x < xz; x++) { + if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x); + } + } + } + tiles.push([].concat(hold)); - pos = (y * iWidth) + (x * 4); + hold = []; + for (y = j, yz = j + tileHeight; y < yz; y++) { + if (y >= 0 && y < iHeight) { + for (let x = i + tileWidth, xz = i + tileWidth + gutterWidth; x < xz; x++) { + if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x); + } + } + } + tiles.push([].concat(hold)); - if (data[pos + 3]) hold.push(pos); + hold = []; + for (y = j + tileHeight, yz = j + tileHeight + gutterHeight; y < yz; y++) { + if (y >= 0 && y < iHeight) { + for (let x = i + tileWidth, xz = i + tileWidth + gutterWidth; x < xz; x++) { + if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x); } } } + tiles.push([].concat(hold)); } - if (hold.length) tiles.push([].concat(hold)); } + workstore[name] = tiles; + workstoreLastAccessed[name] = Date.now(); + return tiles; } + return false; + }; - -// __average__ - Returns the average of values in an array -const average = function (c) { +// `buildImageTileSets` - creates a record of which pixels belong to which tile - used for manipulating color channels values. Resulting object will be cached in the store +const buildImageTileSets = function (tileWidth, tileHeight, offsetX, offsetY, data) { + + if (!data) data = cache.source; - let a = 0, - k, kz, - l = c.length; + if (data && data.width && data.height) { - if (l) { + let iWidth = data.width, + iHeight = data.height; - for (k = 0, kz = l; k < kz; k++) { + tileWidth = (tileWidth.toFixed && !isNaN(tileWidth)) ? tileWidth : 1; + tileHeight = (tileHeight.toFixed && !isNaN(tileHeight)) ? tileHeight : 1; + offsetX = (offsetX.toFixed && !isNaN(offsetX)) ? offsetX : 0; + offsetY = (offsetY.toFixed && !isNaN(offsetY)) ? offsetY : 0; - a +=c[k]; + if (tileWidth < 1) tileWidth = 1; + if (tileWidth >= iWidth) tileWidth = iWidth - 1; + if (tileHeight < 1) tileHeight = 1; + if (tileHeight >= iHeight) tileHeight = iHeight - 1; + if (offsetX < 0) offsetX = 0; + if (offsetX >= tileWidth) offsetX = tileWidth - 1; + if (offsetY < 0) offsetY = 0; + if (offsetY >= tileHeight) offsetY = tileHeight - 1; + + let name = `imagetileset-${iWidth}-${iHeight}-${tileWidth}-${tileHeight}-${offsetX}-${offsetY}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; } - return a / l; - } - return 0; -}; - -// __checkBounds__ - Checks that a position cursor is within the bounds of the data array -const checkBounds = function (p) { + let tiles = []; - let len = data.length; + for (let j = offsetY - tileHeight, jz = iHeight; j < jz; j += tileHeight) { - if (p < 0) p += len; - if (p >= len) p -= len; - return p; -}; + for (let i = offsetX - tileWidth, iz = iWidth; i < iz; i += tileWidth) { + let hold = []; + + for (y = j, yz = j + tileHeight; y < yz; y++) { -// All the pre-defined filter functions are held as attributes to the __actions object__ -const actions = { + if (y >= 0 && y < iHeight) { + for (let x = i, xz = i + tileWidth; x < xz; x++) { -// __userDefined__ - requires a String function in the `filter.userDefined` attribute, which will be generated into a working function by the web worker - such filters can make use of any other filter attributes (for example: `filter.level`) alongside the dedicated userDefined attributes `filter.udVariable0 - filter.udVariable9` - userDefined: function () {}, + if (x >= 0 && x < iWidth) hold.push((y * iWidth) + x); + } + } + } + if (hold.length) tiles.push(hold); + } + } + workstore[name] = tiles; + workstoreLastAccessed[name] = Date.now(); + return tiles; + } + return false; +}; +// `buildHorizontalBlur` - creates an Array of Arrays detailing which pixels contribute to the horizontal part of each pixel's blur calculation. Resulting object will be cached in the store +const buildHorizontalBlur = function (grid, radius) { -// __grayscale__ - desaturates the image - grayscale: function () { + if (!radius || !radius.toFixed || isNaN(radius)) radius = 0; - let i, iz, pos, gray; + let gridHeight = grid.length, + gridWidth = grid[0].length; - for (i = 0, iz = cache.length; i < iz; i++) { + let name = `blur-h-${gridWidth}-${gridHeight}-${radius}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; + } - pos = cache[i]; - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - data[pos] = data[pos + 1] = data[pos + 2] = gray; - } - }, + let horizontalBlur = [], + cell; + for (let y = 0; y < gridHeight; y++) { -// __sepia__ - desaturates the image, then 'antiques' it by adding back some yellow tone - sepia: function () { + for (let x = 0; x < gridWidth; x++) { - let i, iz, pos, r, g, b; + let cellsToProcess = []; - for (i = 0, iz = cache.length; i < iz; i++) { + for (let c = x - radius, cz = x + radius + 1; c < cz; c++) { - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * 0.393) + (g * 0.769) + (b * 0.189); - data[pos + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168); - data[pos + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131); + if (c >= 0 && c < gridWidth) cellsToProcess.push(grid[y][c]); + } + horizontalBlur[(y * gridWidth) + x] = cellsToProcess; } - }, + } + workstore[name] = horizontalBlur; + workstoreLastAccessed[name] = Date.now(); + return horizontalBlur; +}; +// `buildVerticalBlur` - creates an Array of Arrays detailing which pixels contribute to the vertical part of each pixel's blur calculation. Resulting object will be cached in the store +const buildVerticalBlur = function (grid, radius) { -// __invert__ - turns white into black, and similar across the spectrum - invert: function () { + if (!radius || !radius.toFixed || isNaN(radius)) radius = 0; - let i, iz, pos; + let gridHeight = grid.length, + gridWidth = grid[0].length; - for (i = 0, iz = cache.length; i < iz; i++) { + let name = `blur-v-${gridWidth}-${gridHeight}-${radius}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; + } - pos = cache[i]; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - } - }, + let verticalBlur = [], + cell; + for (let x = 0; x < gridWidth; x++) { -// __red__ - suppresses the image's green and blue channels - red: function () { + for (let y = 0; y < gridHeight; y++) { - let i, iz, pos; + let cellsToProcess = []; - for (i = 0, iz = cache.length; i < iz; i++) { + for (let c = y - radius, cz = y + radius + 1; c < cz; c++) { - pos = cache[i]; - data[pos + 1] = 0; - data[pos + 2] = 0; + if (c >= 0 && c < gridHeight) cellsToProcess.push(grid[c][x]); + } + verticalBlur[(y * gridWidth) + x] = cellsToProcess; } - }, + } + workstore[name] = verticalBlur; + workstoreLastAccessed[name] = Date.now(); + return verticalBlur; +}; +// `buildMatrixGrid` - creates an Array of Arrays detailing which pixels contribute to each pixel's matrix calculation. Resulting object will be cached in the store +const buildMatrixGrid = function (mWidth, mHeight, mX, mY, alpha, data) { -// __green__ - suppresses the image's red and blue channels - green: function () { + if (!data) data = cache.source; - let i, iz, pos; + if (mWidth == null || mWidth < 1) mWidth = 1; + if (mHeight == null || mHeight < 1) mHeight = 1; - for (i = 0, iz = cache.length; i < iz; i++) { + if (mX == null || mX < 0) mX = 0; + else if (mX >= mWidth) mX = mWidth - 1; - pos = cache[i]; - data[pos] = 0; - data[pos + 2] = 0; - } - }, + if (mY == null || mY < 0) mY = 0; + else if (mY >= mHeight) mY = mHeight - 1; + let iWidth = data.width, + iHeight = data.height; + + let name = `matrix-${iWidth}-${iHeight}-${mWidth}-${mHeight}-${mX}-${mY}`; + if (workstore[name]) { + workstoreLastAccessed[name] = Date.now(); + return workstore[name]; + } -// __blue__ - suppresses the image's red and green channels - blue: function () { + let dataLength = data.data.length, + x, xz, y, yz, i, iz, + cellsTemplate = [], + grid = []; - let i, iz, pos; + for (y = -mY, yz = mHeight - mY; y < yz; y++) { - for (i = 0, iz = cache.length; i < iz; i++) { + for (x = -mX, xz = mWidth - mX; x < xz; x++) { - pos = cache[i]; - data[pos] = 0; - data[pos + 1] = 0; + cellsTemplate.push((y * iWidth) + x); } - }, + } + for (y = 0; y < iHeight; y++) { -// __notred__ - suppresses the image's red channel - notred: function() { + for (x = 0; x < iWidth; x++) { + + let pos = (y * iWidth) + x; + let cell = []; - let i, iz, pos; + for (i = 0, iz = cellsTemplate.length; i < iz; i++) { - for (i = 0, iz = cache.length; i < iz; i++) { + let val = pos + cellsTemplate[i]; - pos = cache[i]; - data[pos] = 0; - } - }, + if (val < 0) val += dataLength; + else if (val >= dataLength) val -= dataLength; + cell.push(val); + } + grid.push(cell); + } + } + workstore[name] = grid; + workstoreLastAccessed[name] = Date.now(); + return grid; +}; -// __notgreen__ - suppresses the image's green channel - notgreen: function () { +// `checkChannelLevelsParameters` - divide each channel into discrete sequences of pixels +const checkChannelLevelsParameters = function (f) { - let i, iz, pos; + const doCheck = function (v, isHigh = false) { - for (i = 0, iz = cache.length; i < iz; i++) { + if (v.toFixed) { + if (v < 0) return [[0, 255, 0]]; + if (v > 255) return [[0, 255, 255]]; + if (isNaN(v)) return (isHigh) ? [[0, 255, 255]] : [[0, 255, 0]]; + return [[0, 255, v]]; + } - pos = cache[i]; - data[pos + 1] = 0; + if (v.substring) { + v = v.split(','); } - }, + if (Array.isArray(v)) { -// __notblue__ - suppresses the image's blue channel - notblue: function () { + if (!v.length) return v; + if (Array.isArray(v[0])) return v; - let i, iz, pos; + v = v.map(s => parseInt(s, 10)); + v.sort((a, b) => a - b); - for (i = 0, iz = cache.length; i < iz; i++) { + if (v.length == 1) return [[0, 255, v[0]]]; - pos = cache[i]; - data[pos + 2] = 0; - } - }, + let res = [], + starts, ends; + for (let i = 0, iz = v.length; i < iz; i++) { -// __cyan__ - averages the image's blue and green channels, and suppresses the red channel - cyan: function () { + starts = 0; + ends = 255; + if (i != 0) starts = Math.ceil(v[i - 1] + ((v[i] - v[i - 1]) / 2)); + if (i != iz - 1) ends = Math.floor(v[i] + ((v[i + 1] - v[i]) / 2)); - let i, iz, pos, gray; + res.push([starts, ends, v[i]]); + } + return res; + } + return (isHigh) ? [[0, 255, 255]] : [[0, 255, 0]]; + } + f.red = doCheck(f.red); + f.green = doCheck(f.green); + f.blue = doCheck(f.blue); + f.alpha = doCheck(f.alpha, true); +}; - for (i = 0, iz = cache.length; i < iz; i++) { +// `cacheOutput` - insert an action function's output into the worker's cache +const cacheOutput = function (name, obj, caller) { - pos = cache[i]; - - gray = (data[pos + 1] + data[pos + 2]) / 2; - - data[pos] = 0; - data[pos + 1] = gray; - data[pos + 2] = gray; - } - }, + cache[name] = obj; +}; +// `copyOver` - copy the values from one results object to another +const copyOver = function (f, t) { -// __magenta__ - averages the image's red and blue channels, and suppresses the green channel - magenta: function () { + let {r:fromR, g:fromG, b:fromB, a:fromA } = f; + let {r:toR, g:toG, b:toB, a:toA } = t; - let i, iz, pos, gray; + for (let i = 0; i < fromR.length; i++) { - for (i = 0, iz = cache.length; i < iz; i++) { + toR[i] = fromR[i]; + toG[i] = fromG[i]; + toB[i] = fromB[i]; + toA[i] = fromA[i]; + } +}; - pos = cache[i]; +// `getInputAndOutputDimensions` - determine, and return, the dimensions (width, height) for the appropriate results object for the lineIn, lineMix and lineOut values supplied to each action function when it gets invoked +const getInputAndOutputDimensions = function (requirements) { - gray = (data[pos] + data[pos + 2]) / 2; + let data = cache.source, + results = []; - data[pos] = gray; - data[pos + 1] = 0; - data[pos + 2] = gray; - } - }, + if (requirements.lineIn && requirements.lineIn != 'source' && requirements.lineIn != 'source-alpha' && cache[requirements.lineIn]) { + data = cache[requirements.lineIn]; + } + results.push(data.width, data.height); -// __yellow__ - averages the image's red and green channels, and suppresses the blue channel - yellow: function () { + if (requirements.lineOut && cache[requirements.lineOut]) { - let i, iz, pos, gray; + data = cache[requirements.lineOut]; + } + results.push(data.width, data.height); - for (i = 0, iz = cache.length; i < iz; i++) { + data = cache.source; - pos = cache[i]; - - gray = (data[pos] + data[pos + 1]) / 2; - - data[pos] = gray; - data[pos + 1] = gray; - data[pos + 2] = 0; - } - }, + if (requirements.lineMix && requirements.lineMix != 'source' && requirements.lineMix != 'source-alpha' && cache[requirements.lineMix]) { + data = cache[requirements.lineMix]; + } + results.push(data.width, data.height); -// __brightness__ - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute - brightness: function () { + return results; +}; - let i, iz, pos, - level = filter.level || 0; +// `getInputAndOutputChannels` - determine, and return, the appropriate results object for the lineIn, lineMix and lineOut values supplied to each action function when it gets invoked +const getInputAndOutputChannels = function (requirements) { - for (i = 0, iz = cache.length; i < iz; i++) { + let lineIn = work; + let len = lineIn.r.length; + let data = cache.source; - pos = cache[i]; - data[pos] *= level; - - pos++; - data[pos] *= level; - - pos++; - data[pos] *= level; - } - }, + if (requirements.lineIn) { + if (requirements.lineIn == 'source') lineIn = data.channels; + else if (requirements.lineIn == 'source-alpha') { -// __saturation__ - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute, then normalizes the result - saturation: function () { + lineIn = createResultObject(len); - let i, iz, pos, - level = filter.level || 0; + let destAlpha = lineIn.a, + sourceAlpha = data.channels.a; - for (i = 0, iz = cache.length; i < iz; i++) { + for (let i = 0; i < len; i++) { - pos = cache[i]; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); + destAlpha[i] = sourceAlpha[i]; + } } - }, + else if (cache[requirements.lineIn]) { + data = cache[requirements.lineIn]; + lineIn = data.channels; + } + } -// __threshold__ - desaturates each pixel then tests it against filter.level value; those pixels below the level are set to the filter.lowRGB values while the rest are set to the filter.highRGB values - threshold: function () { + let lineMix = false; - let i, iz, pos, gray, test, - level = filter.level || 0, - lowRed = filter.lowRed, - lowGreen = filter.lowGreen, - lowBlue = filter.lowBlue, - highRed = filter.highRed, - highGreen = filter.highGreen, - highBlue = filter.highBlue; + if (requirements.lineMix) { - for (i = 0, iz = cache.length; i < iz; i++) { + if (requirements.lineMix == 'source') lineMix = cache.source.channels; + else if (requirements.lineMix == 'source-alpha') { - pos = cache[i]; - - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - test = (gray > level) ? true : false; - - if (test) { + lineMix = createResultObject(len); - data[pos] = highRed; - data[pos + 1] = highGreen; - data[pos + 2] = highBlue; - } - else { + let destAlpha = lineMix.a, + sourceAlpha = cache.source.channels.a; - data[pos] = lowRed; - data[pos + 1] = lowGreen; - data[pos + 2] = lowBlue; + for (let i = 0; i < len; i++) { + + destAlpha[i] = sourceAlpha[i]; } - } - }, - + else if (cache[requirements.lineMix]) lineMix = cache[requirements.lineMix].channels; + } -// __channels__ - multiply each pixel's channel values by the values set in the filter.RGB attributes - channels: function () { + let lineOut; - let i, iz, pos, - red = filter.red || 0, - green = filter.green || 0, - blue = filter.blue || 0; + if (requirements.lineOut) { - for (i = 0, iz = cache.length; i < iz; i++) { + if (cache[requirements.lineOut]) lineOut = cache[requirements.lineOut].channels; + else { - pos = cache[i]; - - data[pos] *= red; - data[pos + 1] *= green; - data[pos + 2] *= blue; + lineOut = createResultObject(len); + cache[requirements.lineOut] = { + width: data.width, + height: data.height, + channels: lineOut, + }; } - }, - - -// __channelstep__ - divide, floor, and then multiply each pixel's channel values by the values set in the filter.RGB attributes - channelstep: function () { + } + else lineOut = createResultObject(len); - let i, iz, pos, - red = filter.red || 1, - green = filter.green || 1, - blue = filter.blue || 1, - floor = Math.floor; + return [lineIn, lineOut, lineMix]; +}; - for (i = 0, iz = cache.length; i < iz; i++) { +// `processResults` - at the conclusion of each action function, combine the results of the function's manipulations back into the data supplied for manipulation, in line with the value of the action object's `opacity` attribute +const processResults = function (store, incoming, ratio) { - pos = cache[i]; - data[pos] = floor(data[pos] / red) * red; - - pos++; - data[pos] = floor(data[pos] / green) * green; - - pos++; - data[pos] = floor(data[pos] / blue) * blue; - } - }, + let sR = store.r, + sG = store.g, + sB = store.b, + sA = store.a; + let iR = incoming.r, + iG = incoming.g, + iB = incoming.b, + iA = incoming.a; -// __tint__ - a more fine-grained form of the channels filter - tint: function () { + if (ratio === 1) copyOver(incoming, store); + else if (ratio > 0) { - let i, iz, pos, r, g, b, - redInRed = filter.redInRed || 0, - redInGreen = filter.redInGreen || 0, - redInBlue = filter.redInBlue || 0, - greenInRed = filter.greenInRed || 0, - greenInGreen = filter.greenInGreen || 0, - greenInBlue = filter.greenInBlue || 0, - blueInRed = filter.blueInRed || 0, - blueInGreen = filter.blueInGreen || 0, - blueInBlue = filter.blueInBlue || 0; + antiRatio = 1 - ratio; - for (i = 0, iz = cache.length; i < iz; i++) { + for (let i = 0, iz = sR.length; i < iz; i++) { - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * redInRed) + (g * greenInRed) + (b * blueInRed); - data[pos + 1] = (r * redInGreen) + (g * greenInGreen) + (b * blueInGreen); - data[pos + 2] = (r * redInBlue) + (g * greenInBlue) + (b * blueInBlue); + sR[i] = Math.floor((sR[i] * antiRatio) + (iR[i] * ratio)); + sG[i] = Math.floor((sG[i] * antiRatio) + (iG[i] * ratio)); + sB[i] = Math.floor((sB[i] * antiRatio) + (iB[i] * ratio)); + sA[i] = Math.floor((sA[i] * antiRatio) + (iA[i] * ratio)); } - }, + } +}; +// `getHSLfromRGB` - convert an RGB format color into an HSL format color +const getHSLfromRGB = function (dr, dg, db) { -// __chroma__ - will evaluate each pixel against a range array; pixels that fall within the range are set to transparent + let minColor = Math.min(dr, dg, db), + maxColor = Math.max(dr, dg, db); -// The __ranges__ attribute needs to be an array of arrays with the following format: + let lum = (minColor + maxColor) / 2; -// [[minRed, minGreen, minBlue, maxRed, maxGreen, maxBlue], etc] + let sat = 0; -// ... multiple ranges can be defined - for instance to key out the lightest and darkest hues: + if (minColor !== maxColor) { -// ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]] - chroma: function () { + if (lum <= 0.5) sat = (maxColor - minColor) / (maxColor + minColor); + else sat = (maxColor - minColor) / (2 - maxColor - minColor); + } - let pos, posA, - ranges = filter.ranges, - range, min, max, val, - i, iz, j, jz, flag; + let hue = 0; - for (j = 0, jz = cache.length; j < jz; j++) { + if (maxColor === dr) hue = (dg - db) / (maxColor - minColor); + else if (maxColor === dg) hue = 2 + ((db - dr) / (maxColor - minColor)); + else hue = 4 + ((dr - dg) / (maxColor - minColor)); - flag = false; + hue *= 60; - for (i = 0, iz = ranges.length; i < iz; i++) { + if (hue < 0) hue += 360; - posA = cache[j] + 3; - range = ranges[i]; - min = range[2]; - pos = posA - 1; - val = data[pos]; + return [hue, sat, lum]; +}; - if (val >= min) { +// `getRGBfromHSL` - convert an HSL format color into an RGB format color +const getRGBfromHSL = function (h, s, l) { - max = range[5]; + if (!s) { - if (val <= max) { + let gray = Math.floor(l * 255); + return [gray, gray, gray]; + } - min = range[1]; - pos--; - val = data[pos]; + let tempLum1 = (l < 0.5) ? l * (s + 1) : l + s - (l * s), + tempLum2 = (2 * l) - tempLum1; - if (val >= min) { + const calculator = function (t, l1, l2) { - max = range[4]; + if (t * 6 < 1) return l2 + ((l1 - l2) * 6 * t); + if (t * 2 < 1) return l1; + if (t * 2 < 2) return l2 + ((l1 - l2) * 6 * (t * 0.666)); + return l2; + }; - if (val <= max) { + h /= 360; - min = range[0]; - pos--; - val = data[pos]; + let tr = h + 0.333, + tg = h, + tb = h - 0.333; - if (val >= min) { + if (tr < 0) tr += 1; + if (tr > 1) tr -= 1; + if (tg < 0) tg += 1; + if (tg > 1) tg -= 1; + if (tb < 0) tb += 1; + if (tb > 1) tb -= 1; - max = range[3]; + let r = calculator(tr, tempLum1, tempLum2) * 255, + g = calculator(tg, tempLum1, tempLum2) * 255, + b = calculator(tb, tempLum1, tempLum2) * 255; - if (val <= max) { - flag = true; - break; - } - } - } - } - } - } - } - if (flag) data[posA] = 0; - } - }, + return [r, g, b]; +}; -// __pixelate__ - create tiles - whose dimensions and positions are determined by values set in the filter tileWidth, tileHeight, offsetX and offsetY attributes - across the image and then average the pixels in each tile to a single color - pixelate: function () { +// ## Filter action functions +// Each function is held in the `theBigActionsObject` object, for convenience +const theBigActionsObject = { - let i, iz, j, jz, pos, r, g, b, a, tile, len; +// __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. + 'alpha-to-channels': function (requirements) { - getTiles(); + let [input, output] = getInputAndOutputChannels(requirements); - for (i = 0, iz = tiles.length; i < iz; i++) { + let len = input.r.length; - tile = tiles[i]; - r = g = b = a = 0; - len = tile.length; + let {opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue, lineOut} = requirements; - if (len) { + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == excludeRed) excludeRed = true; + if (null == excludeGreen) excludeGreen = true; + if (null == excludeBlue) excludeBlue = true; - for (j = 0, jz = len; j < jz; j++) { + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; - pos = tile[j]; + for (let i = 0; i < len; i++) { - r += data[pos]; - g += data[pos + 1]; - b += data[pos + 2]; - a += data[pos + 3]; - } + outR[i] = (includeRed) ? inA[i] : ((excludeRed) ? 0 : inR[i]); + outG[i] = (includeGreen) ? inA[i] : ((excludeGreen) ? 0 : inG[i]); + outB[i] = (includeBlue) ? inA[i] : ((excludeBlue) ? 0 : inB[i]); + } + outA.fill(255, 0, outA.length - 1); - r /= len; - g /= len; - b /= len; - a /= len; + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, - for (j = 0, jz = len; j < jz; j++) { +// __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 zero. Can be used to create horizontal or vertical bars, or chequerboard effects. + 'area-alpha': function (requirements) { - pos = tile[j]; + let [input, output] = getInputAndOutputChannels(requirements); - data[pos] = r; - data[pos + 1] = g; - data[pos + 2] = b; - data[pos + 3] = a; - } - } - } - }, + let len = input.r.length; + let {opacity, tileWidth, tileHeight, offsetX, offsetY, gutterWidth, gutterHeight, areaAlphaLevels, lineOut} = requirements; -// __blur__ - creates a blurred image. Note: can be slow across larger images! The degree of the blur - which does not follow conventional algorithms such as gaussian - is determined by the filter attribute values for radius (number), passes (number) and shrink (boolean) - blur: function () { + if (null == opacity) opacity = 1; + if (null == tileWidth) tileWidth = 1; + if (null == tileHeight) tileHeight = 1; + if (null == offsetX) offsetX = 0; + if (null == offsetY) offsetY = 0; + if (null == gutterWidth) gutterWidth = 1; + if (null == gutterHeight) gutterHeight = 1; + if (null == areaAlphaLevels) areaAlphaLevels = [255,0,0,0]; - if (data.slice) { + let tiles = buildAlphaTileSets(tileWidth, tileHeight, gutterWidth, gutterHeight, offsetX, offsetY, areaAlphaLevels); - let radius = filter.radius || 1, - alpha = filter.includeAlpha || false, - shrink = filter.shrinkingRadius || false, - passes = filter.passes || 1, - vertical = filter.processVertical, - horizontal = filter.processHorizontal, - len = data.length, - imageWidth = image.width, - imageHeight = image.height, - tempDataTo, tempDataFrom, - i, iz, index; + if (!Array.isArray(areaAlphaLevels)) areaAlphaLevels = [255,0,0,0]; - let processPass = function () { + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; - let j, jz; + for (let i = 0; i < len; i++) { + outR[i] = inR[i]; + outG[i] = inG[i]; + outB[i] = inB[i]; + } + tiles.forEach((t, index) => { - if (vertical) { + for (let j = 0, jz = t.length; j < jz; j++) { - tempDataFrom = tempDataTo.slice(); + if (inA[t[j]]) outA[t[j]] = areaAlphaLevels[index % 4]; + } + }); - for (j = localX * 4, jz = (localX + localWidth) * 4; j < jz; j++) { + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, - if (alpha) processColumn(j); - else { +// __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. + 'average-channels': function (requirements) { - if (j % 4 !== 3) processColumn(j); - } - } - } + let [input, output] = getInputAndOutputChannels(requirements); - if (horizontal) { + let len = input.r.length; - tempDataFrom = tempDataTo.slice(); + let {opacity, includeRed, includeGreen, includeBlue, excludeRed, excludeGreen, excludeBlue, lineOut} = requirements; - for (j = localY, jz = localY + localHeight; j < jz; j++) { + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == excludeRed) excludeRed = false; + if (null == excludeGreen) excludeGreen = false; + if (null == excludeBlue) excludeBlue = false; - if (alpha) processRowWithAlpha(j); - else processRowNoAlpha(j); - } - } - }; + let divisor = 0; + if (includeRed) divisor++; + if (includeGreen) divisor++; + if (includeBlue) divisor++; - let processColumn = function (col) { + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; - let pos, avg, val, cagePointer, y, yz, q, dataPointer, - vLead = radius * iWidth, - cage = [], - cageLen; + for (let i = 0; i < len; i++) { - for (y = -radius, yz = radius; y < yz; y++) { + if (inA[i]) { - pos = col + (y * iWidth); - pos = checkBounds(pos, len); - cage.push(tempDataFrom[pos]); - } + if (divisor) { - tempDataTo[col] = avg = average(cage); + let avg = 0; - cageLen = cage.length; + if (includeRed) avg += inR[i]; + if (includeGreen) avg += inG[i]; + if (includeBlue) avg += inB[i]; - for (q = 0; q < cageLen; q++) { + avg = Math.floor(avg / divisor); - cage[q] /= cageLen; + outR[i] = (excludeRed) ? 0 : avg; + outG[i] = (excludeGreen) ? 0 : avg; + outB[i] = (excludeBlue) ? 0 : avg; + outA[i] = inA[i]; } + else { + + outR[i] = (excludeRed) ? 0 : inR[i]; + outG[i] = (excludeGreen) ? 0 : inG[i]; + outB[i] = (excludeBlue) ? 0 : inB[i]; + outA[i] = inA[i]; + } + } + else { - cagePointer = 0; + outR[i] = inR[i]; + outG[i] = inG[i]; + outB[i] = inB[i]; + outA[i] = inA[i]; + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, - for (y = 1; y < imageHeight; y++) { +// __binary__ - Set the channel to either 0 or 255, depending on whether the channel value is below or above a given level. Level values are set using the "red", "green", "blue" and "alpha" arguments. Setting these values to 0 disables the action for that channel. + 'binary': function (requirements) { - avg -= cage[cagePointer]; + let [input, output] = getInputAndOutputChannels(requirements); - dataPointer = col + (y * iWidth); - pos = dataPointer + vLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; + let len = input.r.length; - avg += val; - cage[cagePointer] = val; - tempDataTo[dataPointer] = avg; + let {opacity, red, green, blue, alpha, lineOut} = requirements; - cagePointer++; + if (null == opacity) opacity = 1; + if (null == red) red = 0; + if (null == green) green = 0; + if (null == blue) blue = 0; + if (null == alpha) alpha = 0; - if (cagePointer === cageLen) cagePointer = 0; - } - }; + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; - let processRowWithAlpha = function (row) { + for (let i = 0; i < len; i++) { - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - cage = [[], [], [], []], - rowPos = row * iWidth, - hLead = radius * 4, - dataPointer, cagePointer, cageLen; + if (red) outR[i] = (inR[i] > red) ? 255 : 0; + else outR[i] = inR[i]; - q = 0; + if (green) outG[i] = (inG[i] > green) ? 255 : 0; + else outG[i] = inG[i]; - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { + if (blue) outB[i] = (inB[i] > blue) ? 255 : 0; + else outB[i] = inB[i]; - pos = rowPos + x; - pos = checkBounds(pos, len); - - cage[q].push(tempDataFrom[pos]); - - q++; - if (q === 4) q = 0; - } + if (alpha) outA[i] = (inA[i] > alpha) ? 255 : 0; + else outA[i] = inA[i]; + } - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); - tempDataTo[rowPos + 3] = avg[3] = average(cage[3]); + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, - cageLen = cage[0].length; +// __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. + 'blend': function (requirements) { - for (q = 0; q < 4; q++) { + let [input, output, mix] = getInputAndOutputChannels(requirements); - for (x = 0; x < cageLen; x++) { + let len = output.r.length; - cage[q][x] /= cageLen; - } - } - cagePointer = 0; + let {opacity, blend, offsetX, offsetY, lineOut} = requirements; - for (x = 1; x < imageWidth; x++) { + if (null == opacity) opacity = 1; + if (null == blend) blend = ''; + if (null == offsetX) offsetX = 0; + if (null == offsetY) offsetY = 0; - rowPosX = rowPos + (x * 4); + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + const {r:mixR, g:mixG, b:mixB, a:mixA} = mix; - for (q = 0; q < 4; q++) { + let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements); - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; + const copyPixel = function (fromPos, toPos, channel) { - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; + outR[toPos] = channel.r[fromPos]; + outG[toPos] = channel.g[fromPos]; + outB[toPos] = channel.b[fromPos]; + outA[toPos] = channel.a[fromPos]; + }; - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; - } + const getLinePositions = function (x, y) { - cagePointer++; + let ix = x, + iy = y, + mx = x - offsetX, + my = y - offsetY; - if (cagePointer === cageLen) cagePointer = 0; - } - }; + let mPos = -1, + iPos = (iy * iWidth) + ix; - let processRowNoAlpha = function (row) { + if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx; - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - hLead = radius * 4, - cage = [[], [], []], - rowPos = row * iWidth, - dataPointer, cagePointer, cageLen; + return [iPos, mPos]; + }; - q = 0; + const getChannelNormals = function (i, m) { - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { + return [ + input.r[i] / 255, + input.g[i] / 255, + input.b[i] / 255, + input.a[i] / 255, + mix.r[m] / 255, + mix.g[m] / 255, + mix.b[m] / 255, + mix.a[m] / 255 + ]; + }; - if (q < 3) { + const alphaCalc = (dinA, dmixA) => (dinA + (dmixA * (1 - dinA))) * 255; - pos = rowPos + x; - pos = checkBounds(pos, len); - cage[q].push(tempDataFrom[pos]); - q++; - } - else q = 0; - } + switch (blend) { - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); + case 'color-burn' : + const colorburnCalc = (din, dmix) => { + if (dmix == 1) return 255; + else if (din == 0) return 0; + return (1 - Math.min(1, ((1 - dmix) / din ))) * 255; + }; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - cageLen = cage[0].length; + let [iPos, mPos] = getLinePositions(x, y); - for (q = 0; q < 3; q++) { + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { - cageQ = cage[q]; - - for (x = 0; x < cageLen; x++) { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - cageQ[x] /= cageLen; + outR[iPos] = colorburnCalc(dinR, dmixR); + outG[iPos] = colorburnCalc(dinG, dmixG); + outB[iPos] = colorburnCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } } } - cagePointer = 0; + break; + + case 'color-dodge' : + const colordodgeCalc = (din, dmix) => { + if (dmix == 0) return 0; + else if (din == 1) return 255; + return Math.min(1, (dmix / (1 - din))) * 255; + }; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { - for (x = 1; x < imageWidth; x++) { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - rowPosX = rowPos + (x * 4); + outR[iPos] = colordodgeCalc(dinR, dmixR); + outG[iPos] = colordodgeCalc(dinG, dmixG); + outB[iPos] = colordodgeCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; - for (q = 0; q < 3; q++) { + case 'darken' : + const darkenCalc = (din, dmix) => (din < dmix) ? din : dmix; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; + let [iPos, mPos] = getLinePositions(x, y); - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; + outR[iPos] = darkenCalc(inR[iPos], mixR[mPos]); + outG[iPos] = darkenCalc(inG[iPos], mixG[mPos]); + outB[iPos] = darkenCalc(inB[iPos], mixB[mPos]); + outA[iPos] = alphaCalc(inA[iPos] / 255, mixA[mPos] / 255); + } } - - cagePointer++; - if (cagePointer === cageLen) cagePointer = 0; } - }; + break; - tempDataTo = data.slice(); + case 'difference' : + const differenceCalc = (din, dmix) => Math.abs(din - dmix) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - for (i = 0; i < passes; i++) { + let [iPos, mPos] = getLinePositions(x, y); - processPass(); - - if (shrink) { + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { - radius = Math.ceil(radius * 0.3); - radius = (radius < 1) ? 1 : radius; - } - } + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - for (i = 0, iz = cache.length; i < iz; i++) { + outR[iPos] = differenceCalc(dinR, dmixR); + outG[iPos] = differenceCalc(dinG, dmixG); + outB[iPos] = differenceCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; - index = cache[i]; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - if (alpha) { + case 'exclusion' : + const exclusionCalc = (din, dmix) => (din + dmix - (2 * dmix * din)) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - index++; - data[index] = tempDataTo[index]; - } - } - } - }, + let [iPos, mPos] = getLinePositions(x, y); + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { -// __matrix__ - apply a 3x3 matrix transform to each of the image's pixels - matrix: function () { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 1, 0, 0, 0, 0], - tempCache = [], - cursor = 0; + outR[iPos] = exclusionCalc(dinR, dmixR); + outG[iPos] = exclusionCalc(dinG, dmixG); + outB[iPos] = exclusionCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; - offset[0] = -iWidth - 4; - offset[1] = -iWidth; - offset[2] = -iWidth + 4; - offset[3] = -4; - offset[4] = 0; - offset[5] = 4; - offset[6] = iWidth - 4; - offset[7] = iWidth; - offset[8] = iWidth + 4; + case 'hard-light' : + const hardlightCalc = (din, dmix) => (din <= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - for (i = 0, iz = cache.length; i < iz; i++) { + let [iPos, mPos] = getLinePositions(x, y); - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { - - weight = weights[j]; - sumR += data[pos] * weight; - - pos++; - sumG += data[pos] * weight; - - pos++; - sumB += data[pos] * weight; - - if (alpha) { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - pos++; - sumA += data[pos] * weight; + outR[iPos] = hardlightCalc(dinR, dmixR); + outG[iPos] = hardlightCalc(dinG, dmixG); + outB[iPos] = hardlightCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } } } - } + break; - tempCache[cursor] = sumR; - cursor++; + case 'lighten' : + const lightenCalc = (din, dmix) => (din > dmix) ? din : dmix; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - tempCache[cursor] = sumG; - cursor++; + let [iPos, mPos] = getLinePositions(x, y); - tempCache[cursor] = sumB; - cursor++; - - if (alpha) { + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { - tempCache[cursor] = sumA; - cursor++; - } - } + outR[iPos] = lightenCalc(inR[iPos], mixR[mPos]); + outG[iPos] = lightenCalc(inG[iPos], mixG[mPos]); + outB[iPos] = lightenCalc(inB[iPos], mixB[mPos]); + outA[iPos] = alphaCalc(inA[iPos] / 255, mixA[mPos] / 255); + } + } + } + break; - cursor = 0; + case 'lighter' : + const lighterCalc = (din, dmix) => (din + dmix) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - for (i = 0, iz = cache.length; i < iz; i++) { + let [iPos, mPos] = getLinePositions(x, y); - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; + outR[iPos] = lighterCalc(dinR, dmixR); + outG[iPos] = lighterCalc(dinG, dmixG); + outB[iPos] = lighterCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; - if (alpha) { + case 'multiply' : + const multiplyCalc = (din, dmix) => din * dmix * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - } - } - }, + let [iPos, mPos] = getLinePositions(x, y); + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { -// __matrix5__ - apply a 5x5 matrix transform to each of the image's pixels - - matrix5: function () { - - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - tempCache = [], - iWidth2 = iWidth * 2, - cursor = 0; - - offset[0] = -iWidth2 - 8; - offset[1] = -iWidth2 - 4; - offset[2] = -iWidth2; - offset[3] = -iWidth2 + 4; - offset[4] = -iWidth2 + 8; - offset[5] = -iWidth - 8; - offset[6] = -iWidth - 4; - offset[7] = -iWidth; - offset[8] = -iWidth + 4; - offset[9] = -iWidth + 8; - offset[10] = -8; - offset[11] = -4; - offset[12] = 0; - offset[13] = 4; - offset[14] = 8; - offset[15] = iWidth - 8; - offset[16] = iWidth - 4; - offset[17] = iWidth; - offset[18] = iWidth + 4; - offset[19] = iWidth + 8; - offset[20] = iWidth2 - 8; - offset[21] = iWidth2 - 4; - offset[22] = iWidth2; - offset[23] = iWidth2 + 4; - offset[24] = iWidth2 + 8; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { + outR[iPos] = multiplyCalc(dinR, dmixR); + outG[iPos] = multiplyCalc(dinG, dmixG); + outB[iPos] = multiplyCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; - weight = weights[j]; - sumR += data[pos] * weight; + case 'overlay' : + const overlayCalc = (din, dmix) => (din >= 0.5) ? (din * dmix) * 255 : (dmix + (din - (dmix * din))) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - pos++; - sumG += data[pos] * weight; + let [iPos, mPos] = getLinePositions(x, y); - pos++; - sumB += data[pos] * weight; + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { - if (alpha) { + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); - pos++; - sumA += data[pos] * weight; + outR[iPos] = overlayCalc(dinR, dmixR); + outG[iPos] = overlayCalc(dinG, dmixG); + outB[iPos] = overlayCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } } } - } + break; - tempCache[cursor] = sumR; - cursor++; + case 'screen' : + const screenCalc = (din, dmix) => (dmix + (din - (dmix * din))) * 255; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { - tempCache[cursor] = sumG; - cursor++; + let [iPos, mPos] = getLinePositions(x, y); - tempCache[cursor] = sumB; - cursor++; + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + outR[iPos] = screenCalc(dinR, dmixR); + outG[iPos] = screenCalc(dinG, dmixG); + outB[iPos] = screenCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + case 'soft-light' : + const softlightCalc = (din, dmix) => { + + let d = (dmix <= 0.25) ? + ((((16 * dmix) - 12) * dmix) + 4) * dmix : + Math.sqrt(dmix); + + if (din <= 0.5) return (dmix - ((1 - (2 * din)) * dmix * (1 - dmix))) * 255; + return (dmix + (((2 * din) - 1) * (d - dmix))) * 255; + }; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + outR[iPos] = softlightCalc(dinR, dmixR); + outG[iPos] = softlightCalc(dinG, dmixG); + outB[iPos] = softlightCalc(dinB, dmixB); + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + case 'color' : + const colorCalc = (iR, iG, iB, mR, mG, mB) => { + + let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB); + let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB); + + return getRGBfromHSL(iH, iS, mL); + } + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + let [cr, cg, cb] = colorCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB); + outR[iPos] = cr; + outG[iPos] = cg; + outB[iPos] = cb; + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + case 'hue' : + const hueCalc = (iR, iG, iB, mR, mG, mB) => { + + let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB); + let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB); + + return getRGBfromHSL(iH, mS, mL); + } + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + let [cr, cg, cb] = hueCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB); + outR[iPos] = cr; + outG[iPos] = cg; + outB[iPos] = cb; + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + case 'luminosity' : + const luminosityCalc = (iR, iG, iB, mR, mG, mB) => { + + let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB); + let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB); + + return getRGBfromHSL(mH, mS, iL); + } + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + let [cr, cg, cb] = luminosityCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB); + outR[iPos] = cr; + outG[iPos] = cg; + outB[iPos] = cb; + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + case 'saturation' : + const saturationCalc = (iR, iG, iB, mR, mG, mB) => { + + let [iH, iS, iL] = getHSLfromRGB(iR, iG, iB); + let [mH, mS, mL] = getHSLfromRGB(mR, mG, mB); + + return getRGBfromHSL(mH, iS, mL); + } + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else if (!mixA[mPos]) copyPixel(iPos, iPos, input); + else { + + let [dinR, dinG, dinB, dinA, dmixR, dmixG, dmixB, dmixA] = getChannelNormals(iPos, mPos); + + let [cr, cg, cb] = saturationCalc(dinR, dinG, dinB, dmixR, dmixG, dmixB); + outR[iPos] = cr; + outG[iPos] = cg; + outB[iPos] = cb; + outA[iPos] = alphaCalc(dinA, dmixA); + } + } + } + break; + + default: + const normalCalc = (Cs, As, Cb, Ab) => (As * Cs) + (Ab * Cb * (1 - As)); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else if (!inA[iPos]) copyPixel(mPos, iPos, mix); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = normalCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = normalCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = normalCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = alphaCalc(dinA, dmixA) + } + } + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'blur': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, radius, passes, processVertical, processHorizontal, includeRed, includeGreen, includeBlue, includeAlpha, step, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == radius) radius = 0; + if (null == passes) passes = 1; + if (null == processVertical) processVertical = true; + if (null == processHorizontal) processHorizontal = true; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == includeAlpha) includeAlpha = false; + if (null == step) step = 1; + + let horizontalBlurGrid, verticalBlurGrid; + + if (processHorizontal || processVertical) { + + let grid = buildImageGrid(); + + if (processHorizontal) horizontalBlurGrid = buildHorizontalBlur(grid, radius); + + if (processVertical) verticalBlurGrid = buildVerticalBlur(grid, radius); + } + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + const getValue = function (flag, gridStore, pos, holdChannel, alpha) { + + if (flag) { + + let h = gridStore[pos]; + + if (h != null) { + + let l = h.length, + counter = 0, + total = 0; + + if (alpha) { + + for (let t = 0; t < l; t += step) { + + if (alpha[h[t]]) { + + total += holdChannel[h[t]]; + counter++; + } + } + return total / counter; + } + for (let t = 0; t < l; t++) { + total += holdChannel[h[t]]; + } + return total / l; + } + } + return holdChannel[pos]; + } + + if (!passes || (!processHorizontal && !processVertical)) copyOver(input, output); + else { + + const hold = createResultObject(len); + const {r:holdR, g:holdG, b:holdB, a:holdA} = hold; + + copyOver(input, hold); + + for (let pass = 0; pass < passes; pass++) { + + if (processHorizontal) { + + for (let k = 0; k < len; k++) { + + if (includeAlpha || holdA[k]) { + + outR[k] = getValue(includeRed, horizontalBlurGrid, k, holdR, holdA); + outG[k] = getValue(includeGreen, horizontalBlurGrid, k, holdG, holdA); + outB[k] = getValue(includeBlue, horizontalBlurGrid, k, holdB, holdA); + outA[k] = getValue(includeAlpha, horizontalBlurGrid, k, holdA, false); + } + } + if (processVertical || pass < passes - 1) copyOver(output, hold); + } + + if (processVertical) { + + for (let k = 0; k < len; k++) { + + if (includeAlpha || holdA[k]) { + + outR[k] = getValue(includeRed, verticalBlurGrid, k, holdR, holdA); + outG[k] = getValue(includeGreen, verticalBlurGrid, k, holdG, holdA); + outB[k] = getValue(includeBlue, verticalBlurGrid, k, holdB, holdA); + outA[k] = getValue(includeAlpha, verticalBlurGrid, k, holdA, false); + } + } + if (pass < passes - 1) copyOver(output, hold); + } + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __channels-to-alpha__ - Calculates an average value from each pixel's included channels and applies that value to the alpha channel. + 'channels-to-alpha': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, includeRed, includeGreen, includeBlue, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + + let divisor = 0; + if (includeRed) divisor++; + if (includeGreen) divisor++; + if (includeBlue) divisor++; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + outR[i] = inR[i]; + outG[i] = inG[i]; + outB[i] = inB[i]; + + if (divisor) { + + let avg = 0; + + if (includeRed) avg += inR[i]; + if (includeGreen) avg += inG[i]; + if (includeBlue) avg += inB[i]; + + avg = Math.floor(avg / divisor); + + outA[i] = avg; + } + else outA[i] = inA[i]; + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'chroma': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, ranges, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == ranges) ranges = []; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let j = 0; j < len; j++) { + + let flag = false; + + let r = inR[j], + g = inG[j], + b = inB[j]; + + for (i = 0, iz = ranges.length; i < iz; i++) { + + let range = ranges[i]; + + let [minR, minG, minB, maxR, maxG, maxB] = ranges[i]; + + if (r >= minR && r <= maxR && g >= minG && g <= maxG && b >= minB && b <= maxB) { + flag = true; + break; + } + + } + outR[j] = inR[j]; + outG[j] = inG[j]; + outB[j] = inB[j]; + outA[j] = (flag) ? 0 : inA[j]; + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __clamp-channels__ - Clamp each color channel to a range set by lowColor and highColor values + 'clamp-channels': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, lowRed, lowGreen, lowBlue, highRed, highGreen, highBlue, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == lowRed) lowRed = 0; + if (null == lowGreen) lowGreen = 0; + if (null == lowBlue) lowBlue = 0; + if (null == highRed) highRed = 255; + if (null == highGreen) highGreen = 255; + if (null == highBlue) highBlue = 255; + + const dR = highRed - lowRed, + dG = highGreen - lowGreen, + dB = highBlue - lowBlue; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + if (inA[i]) { + + let r = inR[i] / 255, + g = inG[i] / 255, + b = inB[i] / 255; + + outR[i] = lowRed + (r * dR); + outG[i] = lowGreen + (g * dG); + outB[i] = lowBlue + (b * dB); + outA[i] = inA[i]; + } + else { + outR[i] = inR[i]; + outG[i] = inG[i]; + outB[i] = inB[i]; + outA[i] = inA[i]; + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __colors-to-alpha__ - 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. + 'colors-to-alpha': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, red, green, blue, opaqueAt, transparentAt, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = 0; + if (null == green) green = 255; + if (null == blue) blue = 0; + if (null == opaqueAt) opaqueAt = 1; + if (null == transparentAt) transparentAt = 0; + + const maxDiff = Math.max(((red + green + blue) / 3), (((255 - red) + (255 - green) + (255 - blue)) / 3)), + transparent = transparentAt * maxDiff, + opaque = opaqueAt * maxDiff, + range = opaque - transparent; + + const getValue = function (r, g, b) { + + let diff = (Math.abs(red - r) + Math.abs(green - g) + Math.abs(blue - b)) / 3; + + if (diff < transparent) return 0; + if (diff > opaque) return 255; + return ((diff - transparent) / range) * 255; + }; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + outR[i] = inR[i]; + outG[i] = inG[i]; + outB[i] = inB[i]; + outA[i] = getValue(inR[i], inG[i], inB[i]); + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'compose': function (requirements) { + + let [input, output, mix] = getInputAndOutputChannels(requirements); + + let len = output.r.length; + + let {opacity, compose, offsetX, offsetY, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == compose) compose = ''; + if (null == offsetX) offsetX = 0; + if (null == offsetY) offsetY = 0; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + const {r:mixR, g:mixG, b:mixB, a:mixA} = mix; + + let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements); + + const copyPixel = function (fromPos, toPos, channel) { + + outR[toPos] = channel.r[fromPos]; + outG[toPos] = channel.g[fromPos]; + outB[toPos] = channel.b[fromPos]; + outA[toPos] = channel.a[fromPos]; + }; + + const getLinePositions = function (x, y) { + + let ix = x, + iy = y, + mx = x - offsetX, + my = y - offsetY; + + let mPos = -1, + iPos = (iy * iWidth) + ix; + + if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx; + + return [iPos, mPos]; + }; + + switch (compose) { + + case 'source-only' : + copyOver(input, output); + break; + + case 'source-atop' : + const sAtopCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * mAlpha) + (mAlpha * mColor * (1 - iAlpha)); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = sAtopCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = sAtopCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = sAtopCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = ((dinA * dmixA) + (dmixA * (1 - dinA))) * 255; + } + } + } + break; + + case 'source-in' : + const sInCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * mAlpha; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = sInCalc(inR[iPos], dinA, dmixA); + outG[iPos] = sInCalc(inG[iPos], dinA, dmixA); + outB[iPos] = sInCalc(inB[iPos], dinA, dmixA); + outA[iPos] = dinA * dmixA * 255; + } + } + } + break; + + case 'source-out' : + const sOutCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * (1 - mAlpha); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = sOutCalc(inR[iPos], dinA, dmixA); + outG[iPos] = sOutCalc(inG[iPos], dinA, dmixA); + outB[iPos] = sOutCalc(inB[iPos], dinA, dmixA); + outA[iPos] = dinA * (1 - dmixA) * 255; + } + } + } + break; + + case 'destination-only' : + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) copyPixel(mPos, iPos, mix); + } + } + break; + + case 'destination-atop' : + const dAtopCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor * iAlpha); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = dAtopCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = dAtopCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = dAtopCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = ((dinA * (1 - dmixA)) + (dmixA * dinA)) * 255; + } + } + } + break; + + case 'destination-over' : + const dOverCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = dOverCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = dOverCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = dOverCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = ((dinA * (1 - dmixA)) + dmixA) * 255; + } + } + } + break; + + case 'destination-in' : + const dInCalc = (iColor, iAlpha, mAlpha) => iAlpha * iColor * mAlpha; + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = dInCalc(mixR[mPos], dmixA, dinA); + outG[iPos] = dInCalc(mixG[mPos], dmixA, dinA); + outB[iPos] = dInCalc(mixB[mPos], dmixA, dinA); + outA[iPos] = dinA * dmixA * 255; + } + } + } + break; + + case 'destination-out' : + const dOutCalc = (mColor, iAlpha, mAlpha) => mAlpha * mColor * (1 - iAlpha); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = dOutCalc(mixR[mPos], dinA, dmixA); + outG[iPos] = dOutCalc(mixG[mPos], dinA, dmixA); + outB[iPos] = dOutCalc(mixB[mPos], dinA, dmixA); + outA[iPos] = dmixA * (1 - dinA) * 255; + } + } + } + break; + + case 'clear' : + break; + + case 'xor' : + const xorCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor * (1 - mAlpha)) + (mAlpha * mColor * (1 - iAlpha)); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = xorCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = xorCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = xorCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = ((dinA * (1 - dmixA)) + (dmixA * (1 - dinA))) * 255; + } + } + } + break; + + default: + const sOverCalc = (iColor, iAlpha, mColor, mAlpha) => (iAlpha * iColor) + (mAlpha * mColor * (1 - iAlpha)); + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos < 0) copyPixel(iPos, iPos, input); + else { + + let dinA = inA[iPos] / 255, + dmixA = mixA[mPos] / 255; + + outR[iPos] = sOverCalc(inR[iPos], dinA, mixR[mPos], dmixA); + outG[iPos] = sOverCalc(inG[iPos], dinA, mixG[mPos], dmixA); + outB[iPos] = sOverCalc(inB[iPos], dinA, mixB[mPos], dmixA); + outA[iPos] = (dinA + (dmixA * (1 - dinA))) * 255; + } + } + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __displace__ - Shift pixels around the image, based on the values supplied in a displacement image + 'displace': function (requirements) { + + let [input, output, mix] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, channelX, channelY, scaleX, scaleY, offsetX, offsetY, transparentEdges, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == channelX) channelX = 'red'; + if (null == channelY) channelY = 'green'; + if (null == scaleX) scaleX = 1; + if (null == scaleY) scaleY = 1; + if (null == offsetX) offsetX = 0; + if (null == offsetY) offsetY = 0; + if (null == transparentEdges) transparentEdges = false; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + const {r:mixR, g:mixG, b:mixB, a:mixA} = mix; + + if (channelX == 'red') channelX = mixR; + else if (channelX == 'green') channelX = mixG; + else if (channelX == 'blue') channelX = mixB; + else channelX = mixA; + + if (channelY == 'red') channelY = mixR; + else if (channelY == 'green') channelY = mixG; + else if (channelY == 'blue') channelY = mixB; + else channelY = mixA; + + let [iWidth, iHeight, oWidth, oHeight, mWidth, mHeight] = getInputAndOutputDimensions(requirements); + + const copyPixel = function (fromPos, toPos, channel) { + + if (fromPos < 0) outA[toPos] = 0; + else { + + outR[toPos] = channel.r[fromPos]; + outG[toPos] = channel.g[fromPos]; + outB[toPos] = channel.b[fromPos]; + outA[toPos] = channel.a[fromPos]; + } + }; + + const getLinePositions = function (x, y) { + + let ix = x, + iy = y, + mx = x + offsetX, + my = y + offsetY; + + let mPos = -1, + iPos = (iy * iWidth) + ix; + + if (mx >= 0 && mx < mWidth && my >= 0 && my < mHeight) mPos = (my * mWidth) + mx; + + return [iPos, mPos]; + }; + + for (let y = 0; y < iHeight; y++) { + for (let x = 0; x < iWidth; x++) { + + let [iPos, mPos] = getLinePositions(x, y); + + if (mPos >= 0) { + + let dx = Math.floor(x + ((127 - channelX[mPos]) / 127) * scaleX); + let dy = Math.floor(y + ((127 - channelY[mPos]) / 127) * scaleY); + let dPos; + + if (!transparentEdges) { + + if (dx < 0) dx = 0; + if (dx >= iWidth) dx = iWidth - 1; + if (dy < 0) dy = 0; + if (dy >= iHeight) dy = iHeight - 1; + + dPos = (dy * iWidth) + dx; + } + else { - if (alpha) { + if (dx < 0 || dx >= iWidth || dy < 0 || dy >= iHeight) dPos = -1; + else dPos = (dy * iWidth) + dx; + } - tempCache[cursor] = sumA; - cursor++; + copyPixel(dPos, iPos, input); + } + else copyPixel(iPos, iPos, input); } } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, - cursor = 0; +// __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 + 'emboss': function (requirements) { - for (i = 0, iz = cache.length; i < iz; i++) { + let [input, output] = getInputAndOutputChannels(requirements); - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; + let len = input.r.length; - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; + let {opacity, strength, angle, tolerance, keepOnlyChangedAreas, postProcessResults, lineOut} = requirements; - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; + if (null == opacity) opacity = 1; + if (null == strength) strength = 1; + if (null == angle) angle = 0; + if (null == tolerance) tolerance = 0; + if (null == keepOnlyChangedAreas) keepOnlyChangedAreas = false; + if (null == postProcessResults) postProcessResults = false; - if (alpha) { - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; + strength = Math.abs(strength); + + while (angle < 0) { + angle += 360; + } + + angle = angle % 360; + + let slices = Math.floor(angle / 45), + remains = ((angle % 45) / 45) * strength, + weights = new Array(9); + + weights = weights.fill(0, 0, 9); + weights[4] = 1; + + if (slices == 0) { + weights[5] = strength - remains; + weights[8] = remains; + weights[3] = -weights[5]; + weights[0] = -weights[8]; + } + else if (slices == 1) { + weights[8] = strength - remains; + weights[7] = remains; + weights[0] = -weights[8]; + weights[1] = -weights[7]; + } + else if (slices == 2) { + weights[7] = strength - remains; + weights[6] = remains; + weights[1] = -weights[7]; + weights[2] = -weights[6]; + } + else if (slices == 3) { + weights[6] = strength - remains; + weights[3] = remains; + weights[2] = -weights[6]; + weights[5] = -weights[3]; + } + else if (slices == 4) { + weights[3] = strength - remains; + weights[0] = remains; + weights[5] = -weights[3]; + weights[8] = -weights[0]; + } + else if (slices == 5) { + weights[0] = strength - remains; + weights[1] = remains; + weights[8] = -weights[0]; + weights[7] = -weights[1]; + } + else if (slices == 6) { + weights[1] = strength - remains; + weights[2] = remains; + weights[7] = -weights[1]; + weights[6] = -weights[2]; + } + else { + weights[2] = strength - remains; + weights[5] = remains; + weights[6] = -weights[2]; + weights[3] = -weights[5]; + } + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + grid = buildMatrixGrid(3, 3, 1, 1, inA); + + const doCalculations = function (inChannel, matrix) { + + let val = 0; + + for (let m = 0, mz = matrix.length; m < mz; m++) { + + if (weights[m]) val += (inChannel[matrix[m]] * weights[m]); + } + return val; + } + + for (let i = 0; i < len; i++) { + + if (inA[i]) { + + outR[i] = doCalculations(inR, grid[i]); + outG[i] = doCalculations(inG, grid[i]); + outB[i] = doCalculations(inB, grid[i]); + outA[i] = inA[i]; + + if (postProcessResults) { + + if (outR[i] >= inR[i] - tolerance && outR[i] <= inR[i] + tolerance && + outG[i] >= inG[i] - tolerance && outG[i] <= inG[i] + tolerance && + outB[i] >= inB[i] - tolerance && outB[i] <= inB[i] + tolerance) { + + if (keepOnlyChangedAreas) outA[i] = 0; + else { + outR[i] = 127; + outG[i] = 127; + outB[i] = 127; + } + } + } + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __flood__ - Set all pixels to the channel values supplied in the "red", "green", "blue" and "alpha" arguments + 'flood': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length, + floor = Math.floor; + + let {opacity, red, green, blue, alpha, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = 0; + if (null == green) green = 0; + if (null == blue) blue = 0; + if (null == alpha) alpha = 255; + + const {r:outR, g:outG, b:outB, a:outA} = output; + + outR.fill(red, 0, len - 1); + outG.fill(green, 0, len - 1); + outB.fill(blue, 0, len - 1); + outA.fill(alpha, 0, len - 1); + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'grayscale': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, lineOut} = requirements; + + if (null == opacity) opacity = 1; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + let gray = Math.floor((0.2126 * inR[i]) + (0.7152 * inG[i]) + (0.0722 * inB[i])); + + outR[i] = gray; + outG[i] = gray; + outB[i] = gray; + outA[i] = inA[i]; + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __invert-channels__ - For each pixel, subtracts its current channel values - when included - from 255. + 'invert-channels': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == includeAlpha) includeAlpha = false; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + outR[i] = (includeRed) ? 255 - inR[i] : inR[i]; + outG[i] = (includeGreen) ? 255 - inG[i] : inG[i]; + outB[i] = (includeBlue) ? 255 - inB[i] : inB[i]; + outA[i] = (includeAlpha) ? 255 - inA[i] : inA[i]; + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'lock-channels-to-levels': function (requirements) { + + checkChannelLevelsParameters(requirements) + + const getValue = function (val, levels) { + + if (!levels.length) return val; + + for (let i = 0, iz = levels.length; i < iz; i++) { + + let [start, end, level] = levels[i]; + if (val >= start && val <= end) return level; + } + }; + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, red, green, blue, alpha, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = [0]; + if (null == green) green = [0]; + if (null == blue) blue = [0]; + if (null == alpha) alpha = [255]; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + outR[i] = getValue(inR[i], red); + outG[i] = getValue(inG[i], green); + outB[i] = getValue(inB[i], blue); + outA[i] = getValue(inA[i], alpha); + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'matrix': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, width, height, offsetX, offsetY, weights, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == includeAlpha) includeAlpha = false; + if (null == width || width < 1) width = 3; + if (null == height || height < 1) height = 3; + if (null == offsetX) offsetX = 1; + if (null == offsetY) offsetY = 1; + if (null == weights) { + weights = [].fill(0, 0, (width * height) - 1); + weights[Math.floor(weights.length / 2) + 1] = 1; + } + + grid = buildMatrixGrid(width, height, offsetX, offsetY, input.a); + + const doCalculations = function (inChannel, matrix) { + + let val = 0; + + for (let m = 0, mz = matrix.length; m < mz; m++) { + + if (weights[m]) val += (inChannel[matrix[m]] * weights[m]); + } + return val; + } + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + if (inA[i]) { + + if (includeRed) outR[i] = doCalculations(inR, grid[i]); + else outR[i] = inR[i]; + + if (includeGreen) outG[i] = doCalculations(inG, grid[i]); + else outG[i] = inG[i]; + + if (includeBlue) outB[i] = doCalculations(inB, grid[i]); + else outB[i] = inB[i]; + + if (includeAlpha) outA[i] = doCalculations(inA, grid[i]); + else outA[i] = inA[i]; + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'modulate-channels': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, red, green, blue, alpha, saturation, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = 1; + if (null == green) green = 1; + if (null == blue) blue = 1; + if (null == alpha) alpha = 1; + if (null == saturation) saturation = false; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + if (saturation) { + + for (let i = 0; i < len; i++) { + outR[i] = 127 + ((inR[i] - 127) * red); + outG[i] = 127 + ((inG[i] - 127) * green); + outB[i] = 127 + ((inB[i] - 127) * blue); + outA[i] = 127 + ((inA[i] - 127) * alpha); + } + } + else { + + for (let i = 0; i < len; i++) { + outR[i] = inR[i] * red; + outG[i] = inG[i] * green; + outB[i] = inB[i] * blue; + outA[i] = inA[i] * alpha; + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __offset__ - Offset the input image in the output image. + 'offset': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let {opacity, offsetRedX, offsetRedY, offsetGreenX, offsetGreenY, offsetBlueX, offsetBlueY, offsetAlphaX, offsetAlphaY, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == offsetRedX) offsetRedX = 0; + if (null == offsetRedY) offsetRedY = 0; + if (null == offsetGreenX) offsetGreenX = 0; + if (null == offsetGreenY) offsetGreenY = 0; + if (null == offsetBlueX) offsetBlueX = 0; + if (null == offsetBlueY) offsetBlueY = 0; + if (null == offsetAlphaX) offsetAlphaX = 0; + if (null == offsetAlphaY) offsetAlphaY = 0; + + let simpleoffset = false; + + if (offsetRedX == offsetGreenX && offsetRedX == offsetBlueX && offsetRedX == offsetAlphaX && offsetRedY == offsetGreenY && offsetRedY == offsetBlueY && offsetRedY == offsetAlphaY) simpleoffset = true; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + let grid = buildImageGrid(), + gWidth = grid[0].length, + gHeight = grid.length, + drx, dry, dgx, dgy, dbx, dby, dax, day, inCell, outCell; + + for (let y = 0; y < gHeight; y++) { + for (let x = 0; x < gWidth; x++) { + + inCell = grid[y][x]; + + if (inA[inCell]) { + + if (simpleoffset) { + + drx = x + offsetRedX; + dry = y + offsetRedY; + + if (drx >= 0 && drx < gWidth && dry >= 0 && dry < gHeight) { + + outCell = grid[dry][drx]; + outR[outCell] = inR[inCell]; + outG[outCell] = inG[inCell]; + outB[outCell] = inB[inCell]; + outA[outCell] = inA[inCell]; + } + } + else { + + drx = x + offsetRedX; + dry = y + offsetRedY; + dgx = x + offsetGreenX; + dgy = y + offsetGreenY; + dbx = x + offsetBlueX; + dby = y + offsetBlueY; + dax = x + offsetAlphaX; + day = y + offsetAlphaY; + + if (drx >= 0 && drx < gWidth && dry >= 0 && dry < gHeight) { + + outCell = grid[dry][drx]; + outR[outCell] = inR[inCell]; + } + + if (dgx >= 0 && dgx < gWidth && dgy >= 0 && dgy < gHeight) { + + outCell = grid[dgy][dgx]; + outG[outCell] = inG[inCell]; + } + + if (dbx >= 0 && dbx < gWidth && dby >= 0 && dby < gHeight) { + + outCell = grid[dby][dbx]; + outB[outCell] = inB[inCell]; + } + + if (dax >= 0 && dax < gWidth && day >= 0 && day < gHeight) { + + outCell = grid[day][dax]; + outA[outCell] = inA[inCell]; + } + } + } + } + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'pixelate': function (requirements) { + + const doCalculations = function (inChannel, outChannel, tile) { + + let avg = tile.reduce((a, v) => a + inChannel[v], 0); + + avg = Math.floor(avg / tile.length); + + for (let i = 0, iz = tile.length; i < iz; i++) { + + outChannel[tile[i]] = avg; + } + } + + const setOutValueToInValue = function (inChannel, outChannel, tile) { + + let cell; + + for (let i = 0, iz = tile.length; i < iz; i++) { + + cell = tile[i]; + outChannel[cell] = inChannel[cell]; + } + }; + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, tileWidth, tileHeight, offsetX, offsetY, includeRed, includeGreen, includeBlue, includeAlpha, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = true; + if (null == includeGreen) includeGreen = true; + if (null == includeBlue) includeBlue = true; + if (null == includeAlpha) includeAlpha = false; + if (null == tileWidth) tileWidth = 1; + if (null == tileHeight) tileHeight = 1; + if (null == offsetX) offsetX = 0; + if (null == offsetY) offsetY = 0; + + const tiles = buildImageTileSets(tileWidth, tileHeight, offsetX, offsetY); + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + tiles.forEach(t => { + if (includeRed) doCalculations(inR, outR, t); + else setOutValueToInValue(inR, outR, t); + + if (includeGreen) doCalculations(inG, outG, t); + else setOutValueToInValue(inG, outG, t); + + if (includeBlue) doCalculations(inB, outB, t); + else setOutValueToInValue(inB, outB, t); + + if (includeAlpha) doCalculations(inA, outA, t); + else setOutValueToInValue(inA, outA, t); + }) + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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.__ - + 'process-image': function (requirements) { + + const {assetData, lineOut} = requirements; + + if (lineOut && lineOut.substring && lineOut.length && assetData && assetData.width && assetData.height && assetData.data) { + + let d = assetData.data; + let len = d.length; + + let res = createResultObject(len / 4); + + let r = res.r, + g = res.g, + b = res.b, + a = res.a; + + let counter = 0; + + for (let i = 0; i < len; i += 4) { + + r[counter] = d[i]; + g[counter] = d[i + 1]; + b[counter] = d[i + 2]; + a[counter] = d[i + 3]; + + counter++; + } + assetData.channels = res; + + cache[lineOut] = assetData; + } + }, + +// __set-channel-to-level__ - Sets the value of each pixel's included channel to the value supplied in the "level" argument. + 'set-channel-to-level': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, includeRed, includeGreen, includeBlue, includeAlpha, level, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == includeRed) includeRed = false; + if (null == includeGreen) includeGreen = false; + if (null == includeBlue) includeBlue = false; + if (null == includeAlpha) includeAlpha = false; + if (null == level) level = 0; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + outR[i] = (includeRed) ? level : inR[i]; + outG[i] = (includeGreen) ? level : inG[i]; + outB[i] = (includeBlue) ? level : inB[i]; + outA[i] = (includeAlpha) ? level : inA[i]; + } + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __step-channels__ - Takes three divisor values - "red", "green", "blue". For each pixel, its color channel values are divided by the corresponding color divisor, floored 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'. The output is a form of posterization. + 'step-channels': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length, + floor = Math.floor; + + let {opacity, red, green, blue, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == red) red = 1; + if (null == green) green = 1; + if (null == blue) blue = 1; + + if (red == null) red = 1; + if (green == null) green = 1; + if (blue == null) blue = 1; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + outR[i] = floor(inR[i] / red) * red; + outG[i] = floor(inG[i] / green) * green; + outB[i] = floor(inB[i] / blue) * blue; + outA[i] = inA[i]; + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'threshold': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, low, high, level, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == low) low = [0,0,0]; + if (null == high) high = [255,255,255]; + if (null == level) level = 128; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + let [lowR, lowG, lowB] = low; + let [highR, highG, highB] = high; + + for (let i = 0; i < len; i++) { + + let gray = Math.floor((0.2126 * inR[i]) + (0.7152 * inG[i]) + (0.0722 * inB[i])); + + if (gray < level) { + + outR[i] = lowR; + outG[i] = lowG; + outB[i] = lowB; } + else { + + outR[i] = highR; + outG[i] = highG; + outB[i] = highB; + } + outA[i] = inA[i]; + } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __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. + 'tint-channels': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let len = input.r.length; + + let {opacity, redInRed, redInGreen, redInBlue, greenInRed, greenInGreen, greenInBlue, blueInRed, blueInGreen, blueInBlue, lineOut} = requirements; + + if (null == opacity) opacity = 1; + if (null == redInRed) redInRed = 1; + if (null == redInGreen) redInGreen = 0; + if (null == redInBlue) redInBlue = 0; + if (null == greenInRed) greenInRed = 0; + if (null == greenInGreen) greenInGreen = 1; + if (null == greenInBlue) greenInBlue = 0; + if (null == blueInRed) blueInRed = 0; + if (null == blueInGreen) blueInGreen = 0; + if (null == blueInBlue) blueInBlue = 1; + + const {r:inR, g:inG, b:inB, a:inA} = input; + const {r:outR, g:outG, b:outB, a:outA} = output; + + for (let i = 0; i < len; i++) { + + let r = inR[i], + g = inG[i], + b = inB[i]; + + outR[i] = Math.floor((r * redInRed) + (g * greenInRed) + (b * blueInRed)); + outG[i] = Math.floor((r * redInGreen) + (g * greenInGreen) + (b * blueInGreen)); + outB[i] = Math.floor((r * redInBlue) + (g * greenInBlue) + (b * blueInBlue)); + outA[i] = inA[i]; } + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); + }, + +// __user-defined-legacy__ - Previous to version 8.4, filters could be defined with an argument which passed a function string to the filter 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. + + 'user-defined-legacy': function (requirements) { + + let [input, output] = getInputAndOutputChannels(requirements); + + let {opacity, lineOut} = requirements; + + if (null == opacity) opacity = 1; + + copyOver(input, output); + + if (lineOut) processResults(output, work, 1 - opacity); + else processResults(work, output, opacity); }, }; diff --git a/source/worker/filter_canvas.js b/source/worker/filter_canvas.js deleted file mode 100644 index ae29ae752..000000000 --- a/source/worker/filter_canvas.js +++ /dev/null @@ -1,1123 +0,0 @@ -// # Filter worker -// A long-running web worker which, when not in use, gets stored in the filter pool defined in the [filter factory](../factory/filter.html) -// -// TODO: at the moment the code in this file replicates exactly the code in the worker/filter.js file. However at some point we will be writing filters that can make use of offscreen canvases ... in this file! - - -// #### Imports -// None used - - -// #### Polyfills -// TypedArray.slice() [polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice) - for blur filter -if (!Uint8Array.prototype.slice) { - Object.defineProperty(Uint8Array.prototype, 'slice', { - value: function (begin, end) { - return new Uint8Array(Array.prototype.slice.call(this, begin, end)); - } - }); -} - - -// #### Worker-wide variables -// The following variables are defined here so that all filter functions - including user-defined filters - can make use of them: - -// __packet__ - the `e.data` object sent to the web worker from the main code -let packet; - -// __image__ - the `ImageData` object -let image; - -// __iWidth__ - convenience variable - `image.width * 4` (to cope with moving through the data array by pixel) -let iWidth; - -// __data__ - the `Uint8ClampedArray` data component of the ImageData object -let data; - -// __cache__ - an Array of all the starting (ie: red channel) indexes for pixels whose alpha channel is > 0 (and thus not transparent) - speeds up things for pixel-by-pixel filters that don't need to process transparent pixels. Generated by running the web worker's `getCache` function -let cache; - -// __tiles__ - the output from running the web worker's `getTiles` function. This effectively divides the image data into a grid of blocks which can be further processed by a filter -let tiles; - -// __localX__, __localY__, __localWidth__, __localHeight__ - the start coordinates and dimensions of the box enclosing all the non-transparent pixels in the supplied image data array. Used mainly by the blur filter - but any filter (including user-defined filters) that needs to include transparent pixel values in its calculations, but would waste time processing non-visible areas of the image, can make use of these variables -let localX; -let localY; -let localWidth; -let localHeight; - -// __filters__ - the filter objects Array sent to the web worker from the main code -let filters; - -// __filter__ - the current filter object being processed by the web worker - holds all the settings required for that filter function, for example `filter.level` for the brightness, saturation and threshold filter functions -let filter; - -// __action__ - internal processing variable -let action; - - -// #### Messaging and error handling -onmessage = function (e) { - - let i, iz; - - packet = e.data; - image = packet.image; - iWidth = image.width * 4; - data = image.data; - filters = packet.filters; - - getCache(); - getLocal(); - - for (i = 0, iz = filters.length; i < iz; i++) { - - filter = filters[i]; - - if (filter.method === 'userDefined' && filter.userDefined) actions.userDefined = new Function(filter.userDefined); - - action = actions[filter.method]; - - if (action) action(); - } - - postMessage(packet); -}; - -onerror = function (e) { - - console.log('error' + e.message); - postMessage(packet); -}; - -// __getCache__ - function that fills the `cache` worker variable with the starting index position of each non-transparent pixel in the current image's Uint8ClampedArray Array -const getCache = function () { - - let i, iz; - - if (Array.isArray(cache)) cache.length = 0; - else cache = []; - - for (i = 0, iz = data.length; i < iz; i += 4) { - - if (data[i + 3]) cache.push(i); - } -}; - -// __getLocal__ - function that populates the `localX`, `localY`, `localWidth`, `localHeight` worker variables with relevant values for the current image data -const getLocal = function () { - - let i, iz, w, h, minX, minY, maxX, maxY, x, y, val, - floor = Math.floor; - - w = image.width; - h = image.height; - minX = w; - minY = h; - maxX = 0; - maxY = 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - val = cache[i] / 4; - y = floor(val / w); - x = val % w; - minX = (x < minX) ? x : minX; - minY = (y < minY) ? y : minY; - maxX = (x > maxX) ? x : maxX; - maxY = (y > maxY) ? y : maxY; - } - - localX = minX; - localY = minY; - localWidth = maxX - minX; - localHeight = maxY - minY; -}; - -// __getTiles__ - function that populates the `tiles` worker Array with relevant values for the current image data -const getTiles = function () { - - let i, iz, j, jz, x, xz, y, yz, startX, startY, pos, - hold = [], - tileWidth = filter.tileWidth || 1, - tileHeight = filter.tileHeight || 1, - offsetX = filter.offsetX, - offsetY = filter.offsetY, - w = image.width, - h = image.height; - - if (Array.isArray(tiles)) tiles.length = 0; - else tiles = []; - - offsetX = (offsetX >= tileWidth) ? tileWidth - 1 : offsetX; - offsetY = (offsetY >= tileHeight) ? tileHeight - 1 : offsetY; - - startX = (offsetX) ? offsetX - tileWidth : 0; - startY = (offsetY) ? offsetY - tileHeight : 0; - - for (j = startY, jz = h + tileHeight; j < jz; j += tileHeight) { - - for (i = startX, iz = w + tileWidth; i < iz; i += tileWidth) { - - hold.length = 0; - - for (y = j, yz = j + tileHeight; y < yz; y++) { - - if (y >= 0 && y < h) { - - for (x = i, xz = i + tileWidth; x < xz; x++) { - - if (x >= 0 && x < w) { - - pos = (y * iWidth) + (x * 4); - - if (data[pos + 3]) hold.push(pos); - } - } - } - } - if (hold.length) tiles.push([].concat(hold)); - } - } -}; - - -// __average__ - Returns the average of values in an array -const average = function (c) { - - let a = 0, - k, kz, - l = c.length; - - if (l) { - - for (k = 0, kz = l; k < kz; k++) { - - a +=c[k]; - } - return a / l; - } - return 0; -}; - - -// __checkBounds__ - Checks that a position cursor is within the bounds of the data array -const checkBounds = function (p) { - - let len = data.length; - - if (p < 0) p += len; - if (p >= len) p -= len; - return p; -}; - - -// All the pre-defined filter functions are held as attributes to the __actions object__ -const actions = { - - -// __userDefined__ - requires a String function in the `filter.userDefined` attribute, which will be generated into a working function by the web worker - such filters can make use of any other filter attributes (for example: `filter.level`) alongside the dedicated userDefined attributes `filter.udVariable0 - filter.udVariable9` - userDefined: function () {}, - - -// __grayscale__ - desaturates the image - grayscale: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - data[pos] = data[pos + 1] = data[pos + 2] = gray; - } - }, - - -// __sepia__ - desaturates the image, then 'antiques' it by adding back some yellow tone - sepia: function () { - - let i, iz, pos, r, g, b; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * 0.393) + (g * 0.769) + (b * 0.189); - data[pos + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168); - data[pos + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131); - } - }, - - -// __invert__ - turns white into black, and similar across the spectrum - invert: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - - pos++; - data[pos] = 255 - data[pos]; - } - }, - - -// __red__ - suppresses the image's green and blue channels - red: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 1] = 0; - data[pos + 2] = 0; - } - }, - - -// __green__ - suppresses the image's red and blue channels - green: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - data[pos + 2] = 0; - } - }, - - -// __blue__ - suppresses the image's red and green channels - blue: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - data[pos + 1] = 0; - } - }, - - -// __notred__ - suppresses the image's red channel - notred: function() { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 0; - } - }, - - -// __notgreen__ - suppresses the image's green channel - notgreen: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 1] = 0; - } - }, - - -// __notblue__ - suppresses the image's blue channel - notblue: function () { - - let i, iz, pos; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos + 2] = 0; - } - }, - - -// __cyan__ - averages the image's blue and green channels, and suppresses the red channel - cyan: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos + 1] + data[pos + 2]) / 2; - - data[pos] = 0; - data[pos + 1] = gray; - data[pos + 2] = gray; - } - }, - - -// __magenta__ - averages the image's red and blue channels, and suppresses the green channel - magenta: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos] + data[pos + 2]) / 2; - - data[pos] = gray; - data[pos + 1] = 0; - data[pos + 2] = gray; - } - }, - - -// __yellow__ - averages the image's red and green channels, and suppresses the blue channel - yellow: function () { - - let i, iz, pos, gray; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (data[pos] + data[pos + 1]) / 2; - - data[pos] = gray; - data[pos + 1] = gray; - data[pos + 2] = 0; - } - }, - - -// __brightness__ - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute - brightness: function () { - - let i, iz, pos, - level = filter.level || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] *= level; - - pos++; - data[pos] *= level; - - pos++; - data[pos] *= level; - } - }, - - -// __saturation__ - multiplies the red, green and blue channel values by a value supplied in the filter.level attribute, then normalizes the result - saturation: function () { - - let i, iz, pos, - level = filter.level || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); - - pos++; - data[pos] = 127 + ((data[pos] - 127) * level); - } - }, - - -// __threshold__ - desaturates each pixel then tests it against filter.level value; those pixels below the level are set to the filter.lowRGB values while the rest are set to the filter.highRGB values - threshold: function () { - - let i, iz, pos, gray, test, - level = filter.level || 0, - lowRed = filter.lowRed, - lowGreen = filter.lowGreen, - lowBlue = filter.lowBlue, - highRed = filter.highRed, - highGreen = filter.highGreen, - highBlue = filter.highBlue; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - gray = (0.2126 * data[pos]) + (0.7152 * data[pos + 1]) + (0.0722 * data[pos + 2]); - test = (gray > level) ? true : false; - - if (test) { - - data[pos] = highRed; - data[pos + 1] = highGreen; - data[pos + 2] = highBlue; - } - else { - - data[pos] = lowRed; - data[pos + 1] = lowGreen; - data[pos + 2] = lowBlue; - } - - } - }, - - -// __channels__ - multiply each pixel's channel values by the values set in the filter.RGB attributes - channels: function () { - - let i, iz, pos, - red = filter.red || 0, - green = filter.green || 0, - blue = filter.blue || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - data[pos] *= red; - data[pos + 1] *= green; - data[pos + 2] *= blue; - } - }, - - -// __channelstep__ - divide, floor, and then multiply each pixel's channel values by the values set in the filter.RGB attributes - channelstep: function () { - - let i, iz, pos, - red = filter.red || 1, - green = filter.green || 1, - blue = filter.blue || 1, - floor = Math.floor; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - data[pos] = floor(data[pos] / red) * red; - - pos++; - data[pos] = floor(data[pos] / green) * green; - - pos++; - data[pos] = floor(data[pos] / blue) * blue; - } - }, - - -// __tint__ - a more fine-grained form of the channels filter - tint: function () { - - let i, iz, pos, r, g, b, - redInRed = filter.redInRed || 0, - redInGreen = filter.redInGreen || 0, - redInBlue = filter.redInBlue || 0, - greenInRed = filter.greenInRed || 0, - greenInGreen = filter.greenInGreen || 0, - greenInBlue = filter.greenInBlue || 0, - blueInRed = filter.blueInRed || 0, - blueInGreen = filter.blueInGreen || 0, - blueInBlue = filter.blueInBlue || 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - pos = cache[i]; - - r = data[pos]; - g = data[pos + 1]; - b = data[pos + 2]; - - data[pos] = (r * redInRed) + (g * greenInRed) + (b * blueInRed); - data[pos + 1] = (r * redInGreen) + (g * greenInGreen) + (b * blueInGreen); - data[pos + 2] = (r * redInBlue) + (g * greenInBlue) + (b * blueInBlue); - } - }, - - -// __chroma__ - will evaluate each pixel against a range array; pixels that fall within the range are set to transparent - -// The __ranges__ attribute needs to be an array of arrays with the following format: - -// [[minRed, minGreen, minBlue, maxRed, maxGreen, maxBlue], etc] - -// ... multiple ranges can be defined - for instance to key out the lightest and darkest hues: - -// ranges: [[0, 0, 0, 80, 80, 80], [180, 180, 180, 255, 255, 255]] - chroma: function () { - - let pos, posA, - ranges = filter.ranges, - range, min, max, val, - i, iz, j, jz, flag; - - for (j = 0, jz = cache.length; j < jz; j++) { - - flag = false; - - for (i = 0, iz = ranges.length; i < iz; i++) { - - posA = cache[j] + 3; - range = ranges[i]; - min = range[2]; - pos = posA - 1; - val = data[pos]; - - if (val >= min) { - - max = range[5]; - - if (val <= max) { - - min = range[1]; - pos--; - val = data[pos]; - - if (val >= min) { - - max = range[4]; - - if (val <= max) { - - min = range[0]; - pos--; - val = data[pos]; - - if (val >= min) { - - max = range[3]; - - if (val <= max) { - flag = true; - break; - } - } - } - } - } - } - } - if (flag) data[posA] = 0; - } - }, - - -// __pixelate__ - create tiles - whose dimensions and positions are determined by values set in the filter tileWidth, tileHeight, offsetX and offsetY attributes - across the image and then average the pixels in each tile to a single color - pixelate: function () { - - let i, iz, j, jz, pos, r, g, b, a, tile, len; - - getTiles(); - - for (i = 0, iz = tiles.length; i < iz; i++) { - - tile = tiles[i]; - r = g = b = a = 0; - len = tile.length; - - if (len) { - - for (j = 0, jz = len; j < jz; j++) { - - pos = tile[j]; - - r += data[pos]; - g += data[pos + 1]; - b += data[pos + 2]; - a += data[pos + 3]; - } - - r /= len; - g /= len; - b /= len; - a /= len; - - for (j = 0, jz = len; j < jz; j++) { - - pos = tile[j]; - - data[pos] = r; - data[pos + 1] = g; - data[pos + 2] = b; - data[pos + 3] = a; - } - } - } - }, - - -// __blur__ - creates a blurred image. Note: can be slow across larger images! The degree of the blur - which does not follow conventional algorithms such as gaussian - is determined by the filter attribute values for radius (number), passes (number) and shrink (boolean) - blur: function () { - - if (data.slice) { - - let radius = filter.radius || 1, - alpha = filter.includeAlpha || false, - shrink = filter.shrinkingRadius || false, - passes = filter.passes || 1, - vertical = filter.processVertical, - horizontal = filter.processHorizontal, - len = data.length, - imageWidth = image.width, - imageHeight = image.height, - tempDataTo, tempDataFrom, - i, iz, index; - - let processPass = function () { - - let j, jz; - - if (vertical) { - - tempDataFrom = tempDataTo.slice(); - - for (j = localX * 4, jz = (localX + localWidth) * 4; j < jz; j++) { - - if (alpha) processColumn(j); - else { - - if (j % 4 !== 3) processColumn(j); - } - } - } - - if (horizontal) { - - tempDataFrom = tempDataTo.slice(); - - for (j = localY, jz = localY + localHeight; j < jz; j++) { - - if (alpha) processRowWithAlpha(j); - else processRowNoAlpha(j); - } - } - }; - - let processColumn = function (col) { - - let pos, avg, val, cagePointer, y, yz, q, dataPointer, - vLead = radius * iWidth, - cage = [], - cageLen; - - for (y = -radius, yz = radius; y < yz; y++) { - - pos = col + (y * iWidth); - pos = checkBounds(pos, len); - cage.push(tempDataFrom[pos]); - } - - tempDataTo[col] = avg = average(cage); - - cageLen = cage.length; - - for (q = 0; q < cageLen; q++) { - - cage[q] /= cageLen; - } - - cagePointer = 0; - - for (y = 1; y < imageHeight; y++) { - - avg -= cage[cagePointer]; - - dataPointer = col + (y * iWidth); - pos = dataPointer + vLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avg += val; - cage[cagePointer] = val; - tempDataTo[dataPointer] = avg; - - cagePointer++; - - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - let processRowWithAlpha = function (row) { - - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - cage = [[], [], [], []], - rowPos = row * iWidth, - hLead = radius * 4, - dataPointer, cagePointer, cageLen; - - q = 0; - - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { - - pos = rowPos + x; - pos = checkBounds(pos, len); - - cage[q].push(tempDataFrom[pos]); - - q++; - if (q === 4) q = 0; - } - - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); - tempDataTo[rowPos + 3] = avg[3] = average(cage[3]); - - cageLen = cage[0].length; - - for (q = 0; q < 4; q++) { - - for (x = 0; x < cageLen; x++) { - - cage[q][x] /= cageLen; - } - } - cagePointer = 0; - - for (x = 1; x < imageWidth; x++) { - - rowPosX = rowPos + (x * 4); - - for (q = 0; q < 4; q++) { - - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; - - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; - } - - cagePointer++; - - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - let processRowNoAlpha = function (row) { - - let pos, val, x, xz, q, avgQ, cageQ, rowPosX, - avg = [], - hLead = radius * 4, - cage = [[], [], []], - rowPos = row * iWidth, - dataPointer, cagePointer, cageLen; - - q = 0; - - for (x = -radius * 4, xz = radius * 4; x < xz; x++) { - - if (q < 3) { - - pos = rowPos + x; - pos = checkBounds(pos, len); - cage[q].push(tempDataFrom[pos]); - q++; - } - else q = 0; - } - - tempDataTo[rowPos] = avg[0] = average(cage[0]); - tempDataTo[rowPos + 1] = avg[1] = average(cage[1]); - tempDataTo[rowPos + 2] = avg[2] = average(cage[2]); - - cageLen = cage[0].length; - - for (q = 0; q < 3; q++) { - - cageQ = cage[q]; - - for (x = 0; x < cageLen; x++) { - - cageQ[x] /= cageLen; - } - } - cagePointer = 0; - - for (x = 1; x < imageWidth; x++) { - - rowPosX = rowPos + (x * 4); - - for (q = 0; q < 3; q++) { - - avgQ = avg[q]; - cageQ = cage[q]; - avgQ -= cageQ[cagePointer]; - - dataPointer = rowPosX + q; - pos = dataPointer + hLead; - pos = checkBounds(pos, len); - val = tempDataFrom[pos] / cageLen; - - avgQ += val; - tempDataTo[dataPointer] = avgQ; - avg[q] = avgQ; - cageQ[cagePointer] = val; - } - - cagePointer++; - if (cagePointer === cageLen) cagePointer = 0; - } - }; - - tempDataTo = data.slice(); - - for (i = 0; i < passes; i++) { - - processPass(); - - if (shrink) { - - radius = Math.ceil(radius * 0.3); - radius = (radius < 1) ? 1 : radius; - } - } - - for (i = 0, iz = cache.length; i < iz; i++) { - - index = cache[i]; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - index++; - data[index] = tempDataTo[index]; - - if (alpha) { - - index++; - data[index] = tempDataTo[index]; - } - } - } - }, - - -// __matrix__ - apply a 3x3 matrix transform to each of the image's pixels - matrix: function () { - - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 1, 0, 0, 0, 0], - tempCache = [], - cursor = 0; - - offset[0] = -iWidth - 4; - offset[1] = -iWidth; - offset[2] = -iWidth + 4; - offset[3] = -4; - offset[4] = 0; - offset[5] = 4; - offset[6] = iWidth - 4; - offset[7] = iWidth; - offset[8] = iWidth + 4; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { - - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { - - weight = weights[j]; - sumR += data[pos] * weight; - - pos++; - sumG += data[pos] * weight; - - pos++; - sumB += data[pos] * weight; - - if (alpha) { - - pos++; - sumA += data[pos] * weight; - } - } - } - - tempCache[cursor] = sumR; - cursor++; - - tempCache[cursor] = sumG; - cursor++; - - tempCache[cursor] = sumB; - cursor++; - - if (alpha) { - - tempCache[cursor] = sumA; - cursor++; - } - } - - cursor = 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - if (alpha) { - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - } - } - }, - - -// __matrix5__ - apply a 5x5 matrix transform to each of the image's pixels - - matrix5: function () { - - let i, iz, j, jz, pos, weight, sumR, sumG, sumB, sumA, homePos, - len = data.length, - alpha = filter.includeAlpha || false, - offset = [], - weights = filter.weights || [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - tempCache = [], - iWidth2 = iWidth * 2, - cursor = 0; - - offset[0] = -iWidth2 - 8; - offset[1] = -iWidth2 - 4; - offset[2] = -iWidth2; - offset[3] = -iWidth2 + 4; - offset[4] = -iWidth2 + 8; - offset[5] = -iWidth - 8; - offset[6] = -iWidth - 4; - offset[7] = -iWidth; - offset[8] = -iWidth + 4; - offset[9] = -iWidth + 8; - offset[10] = -8; - offset[11] = -4; - offset[12] = 0; - offset[13] = 4; - offset[14] = 8; - offset[15] = iWidth - 8; - offset[16] = iWidth - 4; - offset[17] = iWidth; - offset[18] = iWidth + 4; - offset[19] = iWidth + 8; - offset[20] = iWidth2 - 8; - offset[21] = iWidth2 - 4; - offset[22] = iWidth2; - offset[23] = iWidth2 + 4; - offset[24] = iWidth2 + 8; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - sumR = sumG = sumB = sumA = 0; - - for (j = 0, jz = offset.length; j < jz; j++) { - - pos = homePos + offset[j]; - - if (pos >= 0 && pos < len) { - - weight = weights[j]; - sumR += data[pos] * weight; - - pos++; - sumG += data[pos] * weight; - - pos++; - sumB += data[pos] * weight; - - if (alpha) { - - pos++; - sumA += data[pos] * weight; - } - } - } - - tempCache[cursor] = sumR; - cursor++; - - tempCache[cursor] = sumG; - cursor++; - - tempCache[cursor] = sumB; - cursor++; - - if (alpha) { - - tempCache[cursor] = sumA; - cursor++; - } - } - - cursor = 0; - - for (i = 0, iz = cache.length; i < iz; i++) { - - homePos = cache[i]; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - - if (alpha) { - - homePos++; - data[homePos] = tempCache[cursor]; - cursor++; - } - } - }, -};