• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Styles mixin

    The styles mixin contains most of the code required for the Gradient, RadialGradient an ConicGradient styles factories. It is not used by the other styles objects (Color, Pattern).

    • the start and end positioning attributes are defined here rather than in the factories
    • gradient-type styles manage their color stops in Palette factory objects; that functionality is entirely defined here

    The Canvas API CanvasRenderingContext2D interface defines three types of gradient: the widely supported linear and radial gradients, and the conic which, while widely supported as a CSS feature, is very much an experimental technology for CanvasRenderingContext2D engines.

    • the createLinearGradient method creates a gradient along the line connecting two given coordinates (start and end) which are absolute values (measured in pixels) from the <canvas> elements top-left corner
    • the createRadialGradient method creates a radial gradient using the size and coordinates of two circles.
    • the createConicGradient method creates a conic gradient as a 360° sweep around the gradient’s start coordinate. For browsers which don’t support this gradient out of the box, the method will apply no gradient to the entity.

    Common to linear and radial types of gradient is the idea of a start coordinate and an end coordinate, supplied in pixels.

    • Scrawl-canvas extends this idea so that the coordinates can be supplied as a percentage value (String%) of the host Cell’s dimensions.
    • Furthermore Scrawl-canvas allows each entity that uses a Gradient-type style to indicate whether the reference box should be that of the host Cell, or of the entity itself, through their lockFillStyleToEntity and lockStrokeStyleToEntity attribute flags.
  • §

    Imports

    import { entity, styles, stylesnames } from '../core/library.js';
    
    import { addStrings, isa_obj, mergeDiscard, mergeOver, xt, λnull, Ωempty } from '../helper/utilities.js';
    
    import { makeAnimation } from '../factory/animation.js';
    import { makeCoordinate } from '../untracked-factory/coordinate.js';
    
    import { makePalette } from '../untracked-factory/palette.js';
  • §

    Shared constants

    import { _floor, _isArray, _isFinite, _keys, _values, BLACK, BLANK, BOTTOM, CENTER, END, LEFT, LINEAR, NAME, RGB, RIGHT, START, T_PALETTE, TOP, UNDEF, WHITE } from '../helper/shared-vars.js';
  • §

    Local constants

    const COLORS = 'colors',
        PALETTE_KEYS = ['colors', 'stops'];
  • §

    Create an animation to handle automated delta gradient animation

    makeAnimation({
    
        name: 'SC-core-gradient-delta-animation',
        fn: () => {
    
            stylesnames.forEach(name => {
    
                const style = styles[name];
    
                if (style && style.animateByDelta) style.updateByDelta();
            });
        },
    });
  • §

    Export function

    export default function (P = Ωempty) {
  • §

    Shared attributes

        const defaultAttributes = {
  • §

    start, end - Gradient-type styles use Coordinate factory Arrays to hold details of their start and end coordinates. The following pseudo-attributes can also be used to reference these values:

    • for the start coordinate, startX and startY
    • for the end coordinate, endX and endY

    In all cases, the attribute values can be Numbers, which indicate absolute pixel coordinates, or String% values for coordinates calculated relative to either Cell or entity current dimensions

    • for the x coordinate, the Strings left, center and right are also supported
    • for the y coordinate, the Strings top, center and bottom are also supported
            start: null,
            end: null,
  • §

    palette - Every gradient requires a Palette object containing color stop instructions. Generation of the object is automated and should ot be tampered with. Add and remove colors to the gradient using the updateColor and removeColor functions.

            palette: null,
  • §

    paletteStart, paletteEnd - We don’t need to use the entire palette when building a gradient; we can restrict the palette using these start and end attributes.

            paletteStart: 0,
            paletteEnd: 999,
  • §

    The cyclePalette attribute tells the Palette object how to handle situations where the paletteStart value is greater than the paletteEnd value:

    • when false, we reverse the color stops
    • when true, we keep the normal order of color stops and pass through the 1/0 border
            cyclePalette: false,
  • §

    The animateByDelta attribute, when true, will delta animate the gradient at the start of each Display cycle. When the gradient is used in the mapToGradient`` filter, setting this attribute to false` (default) should speed up the filter

            animateByDelta: false,
  • §

    The delta object is not stored in the defs object; it acts in a similar way to the artefact delta object - though it is restricted to adding delta values to Number and ‘String%’ attributes.

    • Unlike artefacts, where delta animation will be applied to artefacts by default as part of each Display cycle, gradient delta animations need to be explicitly invoked: my_gradient.updateByDelta();

    The colors pseudo-attribute can be used to pass through an array of palette color objects to the Palette object. The data is not retained by the gradient object.

    • A better approach to managing gradient colors is to use the updateColor and removeColor functions
  • §

    The easing pseudo-attribute represents a transformation that will be applied to a copy of the color stops Array - this allows us to create non-linear gradients. Value is passed through to the Palette object

  • §

    The precision pseudo-attribute - value is passed through to the Palette object

  • §

    The colorSpace - String pseudo-attribute defines the color space to be used by the Palette’s Color object for its internal calculations - value is passed through to the Palette object

    • Accepted values from: 'RGB', 'HSL', 'HWB', 'XYZ', 'LAB', 'LCH', 'OKLAB', 'OKLCH' with RGB as the default

    The returnColorAs - String pseudo-attribute defines the type of color String the Palette’s Color object will return - value is passed through to the Palette object

    • Accepted values from: 'RGB', 'HSL', 'HWB', 'LAB', 'LCH', 'OKLAB', 'OKLCH' with RGB as the default
        };
        P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

        P.finalizePacketOut = function (copy, items) {
    
            if (items.colors) copy.colors = items.colors;
            else if (this.palette) copy.colors = this.palette.get(COLORS);
            else copy.colors = [[0, BLACK], [999, WHITE]];
  • §

    TODO: don’t think this will return bespoke easing functions - need to think further on how to accomplish this

            if (items.easing) copy.easing = items.easing;
            else if (this.palette && this.palette.easing) copy.easing = this.palette.easing;
            else copy.easing = LINEAR;
    
            if (xt(items.precision)) copy.precision = items.precision;
            else if (this.palette && xt(this.palette.precision)) copy.precision = this.palette.precision;
            else copy.precision = 25;
    
            if (items.colorSpace) copy.colorSpace = items.colorSpace;
            else if (this.palette) copy.colorSpace = this.palette.getColorSpace();
            else copy.colorSpace = RGB;
    
            if (items.returnColorAs) copy.returnColorAs = items.returnColorAs;
            else if (this.palette) copy.returnColorAs = this.palette.getReturnColorAs();
            else copy.returnColorAs = RGB;
    
            return copy;
        };
  • §

    Clone management

    No additional clone functionality defined here

  • §

    Kill management

        P.kill = function () {
    
            const myname = this.name;
    
            if (this.palette && this.palette.kill) this.palette.kill();
  • §

    Remove style from all entity state objects

            _values(entity).forEach(ent => {
    
                const state = ent.state;
    
                if (state) {
    
                    const fill = state.fillStyle,
                        stroke = state.strokeStyle;
    
                    if (isa_obj(fill) && fill.name === myname) state.fillStyle = state.defs.fillStyle;
                    if (isa_obj(stroke) && stroke.name === myname) state.strokeStyle = state.defs.strokeStyle;
                }
            });
  • §

    Remove style from the Scrawl-canvas library

            this.deregister();
    
            return this;
        };
  • §

    Get, Set, deltaSet

        const G = P.getters,
            S = P.setters,
            D = P.deltaSetters;
  • §

    start, startX, startY

        G.startX = function () {
    
            return this.currentStart[0];
        };
        G.startY = function () {
    
            return this.currentStart[1];
        };
        S.startX = function (coord) {
    
            if (coord != null) {
    
                this.start[0] = coord;
                this.dirtyStart = true;
            }
        };
        S.startY = function (coord) {
    
            if (coord != null) {
    
                this.start[1] = coord;
                this.dirtyStart = true;
            }
        };
        S.start = function (x, y) {
    
            this.setCoordinateHelper(START, x, y);
            this.dirtyStart = true;
        };
        D.startX = function (coord) {
    
            const c = this.start;
            c[0] = addStrings(c[0], coord);
            this.dirtyStart = true;
        };
        D.startY = function (coord) {
    
            const c = this.start;
            c[1] = addStrings(c[1], coord);
            this.dirtyStart = true;
        };
        D.start = function (x, y) {
    
            this.setDeltaCoordinateHelper(START, x, y);
            this.dirtyStart = true;
        };
  • §

    end, endX, endY

        G.endX = function () {
    
            return this.currentEnd[0];
        };
        G.endY = function () {
    
            return this.currentEnd[1];
        };
        S.endX = function (coord) {
    
            if (coord != null) {
    
                this.end[0] = coord;
                this.dirtyEnd = true;
            }
        };
        S.endY = function (coord) {
    
            if (coord != null) {
    
                this.end[1] = coord;
                this.dirtyEnd = true;
            }
        };
        S.end = function (x, y) {
    
            this.setCoordinateHelper(END, x, y);
            this.dirtyEnd = true;
        };
        D.endX = function (coord) {
    
            const c = this.end;
            c[0] = addStrings(c[0], coord);
            this.dirtyEnd = true;
        };
        D.endY = function (coord) {
    
            const c = this.end;
            c[1] = addStrings(c[1], coord);
            this.dirtyEnd = true;
        };
        D.end = function (x, y) {
    
            this.setDeltaCoordinateHelper(END, x, y);
            this.dirtyEnd = true;
        };
  • §

    palette - argument has to be a Palette object

        S.palette = function (item = Ωempty) {
    
            if(item.type === T_PALETTE) {
    
                item.dirtyPalette = true;
                this.palette = item;
            }
        };
  • §

    paletteStart - argument must be a positive integer Number in the range 0 - 999

        S.paletteStart = function (item) {
    
            if (_isFinite(item) && this.palette) {
    
                let p = _floor(item);
    
                if (p < 0 || p > 999) p = (p > 500) ? 999 : 0;
                this.paletteStart = p;
    
                this.palette.updateData();
            }
        };
        D.paletteStart = function (item) {
    
            if (_isFinite(item) && this.palette) {
    
                let p = _floor(this.paletteStart + item);
    
                if (p < 0 || p > 999) {
    
                    if (this.cyclePalette) p = ((p % 1000) + 1000) % 1000;
                    else p = (p > 500) ? 999 : 0;
                }
                this.paletteStart = p;
                this.palette.updateData();
            }
        };
  • §

    paletteEnd - argument must be a positive integer Number in the range 0 - 999

        S.paletteEnd = function (item) {
    
            if (_isFinite(item) && this.palette) {
    
                let p = _floor(item);
    
                if (p < 0 || p > 999) p = (p > 500) ? 999 : 0;
                this.paletteEnd = p;
    
                this.palette.updateData();
            }
        };
    
        D.paletteEnd = function (item) {
    
            if (_isFinite(item) && this.palette) {
    
                let p = _floor(this.paletteEnd + item);
    
                if (p < 0 || p > 999) {
    
                    if (this.cyclePalette) p = ((p % 1000) + 1000) % 1000;
                    else p = (p > 500) ? 999 : 0;
                }
                this.paletteEnd = p;
                this.palette.updateData();
            }
        };
    
        S.cyclePalette = function (item) {
    
            if (this.palette) {
    
                this.cyclePalette = !!item;
                this.palette.updateData();
            }
        };
  • §

    colors - We can pass through an array of palette color arrays to the Palette object by setting it on the gradient-type styles object. Each palette array is in the form [Number, String] where:

    • Number is a positive integer in the range 0-999
    • String is any legitimate CSS color string value
        S.colors = function (item) {
    
            if (_isArray(item) && this.palette) this.palette.set({ colors: item });
        };
  • §

    easing, easingFunction - the easing to be applied to the gradient

    • Can accept a String value identifying an SC pre-defined easing function (default: linear)
    • Can also accept a function accepting a single Number argument (a value between 0-1) and returning an eased Number (again, between 0-1)
        S.easing = function (item) {
    
            if (this.palette) {
    
                this.palette.set({ easing: item });
            }
        };
        S.easingFunction = S.easing;
  • §

    colorSpace, returnColorAs - Pass through a color space String to the Palette object

        S.colorSpace = function (item) {
    
            if (this.palette) this.palette.set({ colorSpace: item });
        };
        S.returnColorAs = function (item) {
    
            if (this.palette) this.palette.set({ returnColorAs: item });
        };
  • §

    precision - Pass through a positive integer Number value between 0 and 50 to the Palette object. If value is 0 (default) no easing will be applied to the gradient; values above 0 apply the easing to the gradient; higher values will give a quicker, but less precise, mapping.

        S.precision = function (item) {
    
            if (this.palette) this.palette.set({ precision: item });
        };
  • §

    delta - Gradient-type styles objects support the delta attribute, and can be delta-animated using its attributes

        S.delta = function (items = Ωempty) {
    
            if (items) this.delta = mergeDiscard(this.delta, items);
        };
  • §

    Prototype functions

  • §

    get - Overwrites function defined in mixin/base.js - takes into account Palette object attributes

        P.get = function (item) {
    
            const getter = this.getters[item],
                palette = this.palette;
    
            if (getter) return getter.call(this);
            else {
    
                let def = this.defs[item],
                    val;
    
                if (typeof def !== UNDEF) {
    
                    val = this[item];
                    return (typeof val !== UNDEF) ? val : def;
                }
    
                if (palette) {
    
                    def = palette.defs[item];
    
                    if (typeof def !== UNDEF) {
    
                        val = palette[item];
                        return (typeof val !== UNDEF) ? val : def;
                    }
                }
            }
            return undefined
        };
  • §

    set - Overwrites function defined in mixin/base.js - takes into account Palette object attributes

        P.set = function (items = Ωempty) {
    
            const keys = _keys(items),
                keysLen = keys.length;
    
            if (keysLen) {
    
                const setters = this.setters,
                    defs = this.defs,
                    palette = this.palette;
    
                let paletteSetters, paletteDefs, predefined, i, key, value;
    
                if (palette) {
    
                    paletteSetters = palette.setters || Ωempty;
                    paletteDefs = palette.defs || Ωempty;
                }
    
                for (i = 0; i < keysLen; i++) {
    
                    key = keys[i];
                    value = items[key];
    
                    if (key && key !== NAME && value != null) {
    
                        if (!PALETTE_KEYS.includes(key)) {
    
                            predefined = setters[key];
    
                            if (predefined) predefined.call(this, value);
                            else if (typeof defs[key] !== UNDEF) this[key] = value;
                        }
                        else {
    
                            predefined = paletteSetters[key];
    
                            if (predefined) predefined.call(palette, value);
                            else if (typeof paletteDefs[key] !== UNDEF) palette[key] = value;
                        }
                    }
                }
                this.dirtyFilterIdentifier = true;
            }
            return this;
        };
  • §

    setDelta - Overwrites function defined in mixin/base.js - takes into account Palette object attributes

        P.setDelta = function (items = Ωempty) {
    
            const keys = _keys(items),
                keysLen = keys.length;
    
            if (keysLen) {
    
                const setters = this.deltaSetters,
                    defs = this.defs,
                    palette = this.palette;
    
                let paletteSetters, paletteDefs, predefined, i, key, value;
    
                if (palette) {
    
                    paletteSetters = palette.deltaSetters || Ωempty;
                    paletteDefs = palette.defs || Ωempty;
                }
    
                for (i = 0; i < keysLen; i++) {
    
                    key = keys[i];
                    value = items[key];
    
                    if (key && key !== NAME && value != null) {
    
                        if (!PALETTE_KEYS.includes(key)) {
    
                            predefined = setters[key];
    
                            if (predefined) predefined.call(this, value);
                            else if (typeof defs[key] !== UNDEF) this[key] = addStrings(this[key], value);
                        }
                        else {
    
                            predefined = paletteSetters[key];
    
                            if (predefined) predefined.call(palette, value);
                            else if (typeof paletteDefs[key] !== UNDEF) palette[key] = addStrings(palette[key], value);
                        }
                    }
                }
                this.dirtyFilterIdentifier = true;
            }
            return this;
        };
  • §

    setCoordinateHelper - internal helper function

        P.setCoordinateHelper = function (label, x, y) {
    
            const c = this[label];
    
            if (_isArray(x)) {
    
                c[0] = x[0];
                c[1] = x[1];
            }
            else {
    
                c[0] = x;
                c[1] = y;
            }
        };
  • §

    setDeltaCoordinateHelper - internal helper function

        P.setDeltaCoordinateHelper = function (label, x, y) {
    
            const c = this[label],
                myX = c[0],
                myY = c[1];
    
            if (_isArray(x)) {
    
                c[0] = addStrings(myX, x[0]);
                c[1] = addStrings(myY, x[1]);
            }
            else {
    
                c[0] = addStrings(myX, x);
                c[1] = addStrings(myY, y);
            }
        };
  • §

    updateByDelta - manually force the gradient-type styles object to update its attributes by the values supplied in its delta attribute

        P.updateByDelta = function () {
    
            this.setDelta(this.delta);
    
            return this;
        };
  • §

    stylesInit - common functionality invoked by gradient-type factory constructors

        P.stylesInit = function (items = Ωempty) {
    
            this.makeName(items.name);
            this.register();
    
            this.gradientArgs = [];
    
            this.start = makeCoordinate();
            this.end = makeCoordinate();
    
            this.currentStart = makeCoordinate();
            this.currentEnd = makeCoordinate();
    
            this.palette = makePalette({
                name: `${this.name}_palette`,
            });
    
            this.delta = {};
    
            this.set(this.defs);
    
            this.set(items);
        };
  • §

    getData - Every styles object (Gradient, RadialGradient, ConicGradient, Pattern, Color, Cell) needs to include a getData function. This is invoked by Cell objects during the Display cycle compile step, when it takes an entity State object and updates its <canvas> element’s context engine to bring it into alignment with requirements.

        P.getData = function (entity, cell) {
  • §

    Step 1: recalculate current start and end points

            this.cleanStyle(entity, cell);
  • §

    Step 2: finalize the coordinates to use for creating the gradient in relation to the current entity’s position and requirements on the canvas

            this.finalizeCoordinates(entity);
  • §

    Step 3: create, populate and return gradient/pattern object

            return this.buildStyle(cell);
        };
  • §

    cleanStyle - internal function invoked as part of the gradient-type object’s getData function. The style has to be cleaned every time it is applied to a Cell’s engine because it can never know which Cell is invoking it, or for which entity it is to be used.

        P.cleanStyle = function (entity = Ωempty, cell = Ωempty) {
    
            let dims, w, h, scale;
    
            if (entity.lockFillStyleToEntity || entity.lockStrokeStyleToEntity) {
    
                dims = entity.currentDimensions;
                scale = entity.currentScale;
    
                w = dims[0] * scale;
                h = dims[1] * scale;
            }
            else {
    
                dims = cell.currentDimensions || [cell.element.width, cell.element.height];
                w = dims[0];
                h = dims[1];
            }
    
            this.cleanPosition(this.currentStart, this.start, [w, h]);
            this.cleanPosition(this.currentEnd, this.end, [w, h]);
            this.cleanRadius(w);
        };
  • §

    cleanPosition - internal function invoked as part of the gradient-type object’s cleanStyle function.

        P.cleanPosition = function (current, source, dimensions) {
    
            let val, dim;
    
            for (let i = 0; i < 2; i++) {
    
                val = source[i];
                dim = dimensions[i];
    
                if (val.toFixed) current[i] = val;
                else if (val === LEFT || val === TOP) current[i] = 0;
                else if (val === RIGHT || val === BOTTOM) current[i] = dim;
                else if (val === CENTER) current[i] = dim / 2;
                else if (!_isFinite(parseFloat(val))) current[i] = 0;
                else current[i] = (parseFloat(val) / 100) * dim;
            }
        };
  • §

    finalizeCoordinates - internal function invoked as part of the gradient-type object’s getData function.

        P.finalizeCoordinates = function (entity = Ωempty) {
    
            const entityStampPosition = entity.currentStampPosition,
                entityStampHandlePosition = entity.currentStampHandlePosition,
                entityScale = entity.currentScale;
    
            let correctX, correctY;
    
            if (entity.lockFillStyleToEntity || entity.lockStrokeStyleToEntity) {
    
                correctX = -(entityStampHandlePosition[0] * entityScale) || 0;
                correctY = -(entityStampHandlePosition[1] * entityScale) || 0;
            }
            else {
    
                correctX = -entityStampPosition[0] || 0;
                correctY = -entityStampPosition[1] || 0;
            }
            this.updateGradientArgs(correctX, correctY);
        };
  • §

    cleanRadius - overwritten by the RadialGradient factory

        P.cleanRadius = λnull;
  • §

    buildStyle - Just in case something went wrong with loading other styles Factory modules, which must overwrite this function, we can return transparent color here

        P.buildStyle = function () {
    
            return BLANK;
        };
  • §

    addStopsToGradient - internal function, called by the buildStyle function (which is overwritten by the various gradient factories)

        P.addStopsToGradient = function (gradient, start, stop, cycle) {
    
            if (this.palette) return this.palette.addStopsToGradient(gradient, start, stop, cycle);
            return gradient;
        };
  • §

    Gradients and color stops

    The Canvas API uses a rather convoluted way to add color data to a CanvasGradient interface object:

    • the object is created first on the <canvas> context engine where it is to be applied, with start and end coordinates,
    • then color stops are individually added to it afterwards.
    • This needs to be done for every gradient applied to a context engine before any fill or stroke operation using that gradient.
    • And only one gradient may be applied to the context engine at any time.

    The specificity of the above requirements - in particular relating to position coordinates - and the inability to update the CanvasGradient beyond adding color stops to it, means that storing these objects for future use is not a useful proposition … especially in a dynamic environment where we want the gradient to move in-step with an entity, or animate its colors in some way.

    Scrawl-canvas overcomes this problem through the use of Palette objects which separate a gradient-type style’s color-stop data from its positioning data. We treat Canvas API CanvasGradient objects as use-once-and-dispose objects, generating them in a just-in-time fashion for each entity’s stamp operation in the Display cycle.

    Palette objects store their color data in a colors attribute object:

    {
        name: "mygradient_palette",
        colors: {
            "0 ": [0, 0, 0, 1],
            "350 ": [255, 0, 0, 1],
            "650 ": [0, 0, 255, 1],
            "999 ": [255, 255, 255, 1],
            "index-label-between-0-and-999 ": [redValue, greenValue, blueValue, alphaValue]
        },
    }
    

    To set the Palette object’s colors object, either when creating the gradient-type style or at some point afterwards, we can use CSS color Strings instead of an array of values for each color. Note that:

    • the color object attribute labels MUST include a space after the String number; and
    • the number itself must be a positive integer in the range 0-999:
    myGradient.set({
    
        colors: {
    
            '0 ': 'black',
            '350 ': 'red',
            '650 ': 'blue',
            '999 ': 'white'
        },
    });
    

    The following convenience functions are supplied to make adding, deleting and managing Palette object color stop data easier than the above:

  • §

    updateColor - add or update a gradient-type style’s Palette object with a color.

    • index - positive integer Number between 0 and 999 inclusive
    • color - CSS color String
        P.updateColor = function (index, color) {
    
            if (this.palette) {
    
                this.dirtyFilterIdentifier = true;
                this.palette.updateColor(index, color);
            }
    
            return this;
        };
  • §

    removeColor - remove a gradient-type style’s Palette object color from a specified index

    • index - positive integer number between 0 and 999 inclusive
        P.removeColor = function (index) {
    
            if (this.palette) {
    
                this.dirtyFilterIdentifier = true;
                this.palette.removeColor(index);
            }
    
            return this;
        };
    }