• 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
  • §

    Picture factory

    Picture entitys are image, video or canvas-based rectangles rendered onto a DOM <canvas> element using the Canvas API’s CanvasRenderingContext2D interface - in particular the drawImage method.

  • §

    Imports

    import { artefact, constructors } from '../core/library.js';
    
    import { addStrings, doCreate, isa_obj, mergeOver, pushUnique, removeItem, xta, Ωempty } from '../helper/utilities.js';
    
    import { gettableVideoAssetAtributes, settableVideoAssetAtributes } from '../asset-management/video-asset.js';
    
    import { gettableImageAssetAtributes, settableImageAssetAtributes } from '../asset-management/image-asset.js';
    
    import { makeCoordinate } from '../untracked-factory/coordinate.js';
    
    import baseMix from '../mixin/base.js';
    import entityMix from '../mixin/entity.js';
    import assetConsumerMix from '../mixin/asset-consumer.js';
  • §

    Shared constants

    import { $IMAGE, $VIDEO, _keys, ENTITY, MOUSE, NAME, PARTICLE, STATE_KEYS, T_PICTURE, T_SPRITE, UNDEF } from '../helper/shared-vars.js';
  • §

    Local constants

    const COPY_DIMENSIONS = 'copyDimensions',
        COPY_START = 'copyStart';
  • §

    Picture constructor

    const Picture = function (items = Ωempty) {
    
        this.copyStart = makeCoordinate();
        this.currentCopyStart = makeCoordinate();
    
        this.copyDimensions = makeCoordinate();
        this.currentCopyDimensions = makeCoordinate();
    
        this.copyArray = [];
        this.pasteArray = [];
        this.dirtyPaste = true;
    
        this.source = null;
        this.sourceNaturalWidth = 0;
        this.sourceNaturalHeight = 0;
        this.sourceNaturalDimensions = [];
        this.sourceLoaded = false;
    
        this.entityInit(items);
    
        if (!items.copyStart) {
    
            if (!items.copyStartX) this.copyStart[0] = 0;
            if (!items.copyStartY) this.copyStart[1] = 0;
        }
    
        if (!items.copyDimensions) {
    
            if (!items.copyWidth) this.copyDimensions[0] = 1;
            if (!items.copyHeight) this.copyDimensions[1] = 1;
        }
    
        this.imageSubscribers = [];
    
        this.dirtyCopyStart = true;
        this.dirtyCopyDimensions = true;
        this.dirtyImage = true;
    
        return this;
    };
  • §

    Picture prototype

    const P = Picture.prototype = doCreate();
    P.type = T_PICTURE;
    P.lib = ENTITY;
    P.isArtefact = true;
    P.isAsset = false;
  • §

    Mixins

    baseMix(P);
    entityMix(P);
    assetConsumerMix(P);
  • §

    Picture attributes

    const defaultAttributes = {
  • §

    copyStart - Coordinate array

    • We can use the pseudo-attributes copyStartX and copyStartY to make working with the Coordinate easier.
        copyStart: null,
  • §

    copyDimensions - Coordinate array

    • We can use the pseudo-attributes copyWidth and copyHeight to make working with the Coordinate easier.
        copyDimensions: null,
  • §

    checkHitIgnoreTransparency - Boolean - when set, will check the stashedImage data to return whether a coordinate is hitting the image; otherwise checkHit will use the Picture entity’s dimensions to calculate the hit

        checkHitIgnoreTransparency: false,
  • §

    Additional attributes and pseudo-attributes are defined in the assetConsumer mixin

    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

    P.packetCoordinates = pushUnique(P.packetCoordinates, ['copyStart', 'copyDimensions']);
    P.packetObjects = pushUnique(P.packetObjects, ['asset']);
  • §

    Clone management

    No additional clone functionality required

  • §

    Kill management

    P.factoryKill = function () {
    
        const { asset, removeAssetOnKill } = this;
    
        if (isa_obj(asset)) {
    
            asset.unsubscribe(this);
  • §

    Cascade kill invocation to the asset object, if required

            if (removeAssetOnKill) asset.kill(true);
        }
    };
  • §

    Get, Set, deltaSet

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

    copyStart

    • Including copyStartX, copyStartY
    G.copyStart = function () {
    
        return [].concat(this.currentCopyStart);
    };
    G.copyStartX = function () {
    
        return this.currentCopyStart[0];
    };
    G.copyStartY = function () {
    
        return this.currentCopyStart[1];
    };
    S.copyStartX = function (coord) {
    
        if (coord != null) {
    
            this.copyStart[0] = coord;
            this.dirtyCopyStart = true;
            this.dirtyImageSubscribers = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    S.copyStartY = function (coord) {
    
        if (coord != null) {
    
            this.copyStart[1] = coord;
            this.dirtyCopyStart = true;
            this.dirtyImageSubscribers = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    S.copyStart = function (x, y) {
    
        this.setCoordinateHelper(COPY_START, x, y);
        this.dirtyCopyStart = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyStartX = function (coord) {
    
        const c = this.copyStart;
        c[0] = addStrings(c[0], coord);
        this.dirtyCopyStart = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyStartY = function (coord) {
    
        const c = this.copyStart;
        c[1] = addStrings(c[1], coord);
        this.dirtyCopyStart = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyStart = function (x, y) {
    
        this.setDeltaCoordinateHelper(COPY_START, x, y);
        this.dirtyCopyStart = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
  • §

    copyDimensions

    • Including copyWidth, copyHeight
    G.copyWidth = function () {
    
        return this.currentCopyDimensions[0];
    };
    G.copyHeight = function () {
    
        return this.currentCopyDimensions[1];
    };
    G.copyDimensions = function () {
    
        return [].concat(this.currentCopyDimensions);
    };
    S.copyWidth = function (val) {
    
        if (val != null) {
    
            this.copyDimensions[0] = val;
            this.dirtyCopyDimensions = true;
            this.dirtyImageSubscribers = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    S.copyHeight = function (val) {
    
        if (val != null) {
    
            this.copyDimensions[1] = val;
            this.dirtyCopyDimensions = true;
            this.dirtyImageSubscribers = true;
            this.dirtyFilterIdentifier = true;
        }
    };
    S.copyDimensions = function (w, h) {
    
        this.setCoordinateHelper(COPY_DIMENSIONS, w, h);
        this.dirtyCopyDimensions = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyWidth = function (val) {
    
        const c = this.copyDimensions;
        c[0] = addStrings(c[0], val);
        this.dirtyCopyDimensions = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyHeight = function (val) {
    
        const c = this.copyDimensions;
        c[1] = addStrings(c[1], val);
        this.dirtyCopyDimensions = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
    D.copyDimensions = function (w, h) {
    
        this.setDeltaCoordinateHelper(COPY_DIMENSIONS, w, h);
        this.dirtyCopyDimensions = true;
        this.dirtyImageSubscribers = true;
        this.dirtyFilterIdentifier = true;
    };
  • §

    Picture get and set (but not deltaSet) functions need to take into account their current source, whose attributes can be retrieved/amended directly on the Picture object

  • §

    get

    P.get = function (item) {
    
        const source = this.source;
    
        if ((item.indexOf($VIDEO) === 0 || item.indexOf($IMAGE) === 0) && source) {
    
            if (gettableVideoAssetAtributes.includes(item)) return source[item.substring(6)];
            else if (gettableImageAssetAtributes.includes(item)) return source[item.substring(6)];
        }
    
        else {
    
            const getter = this.getters[item];
    
            if (getter) return getter.call(this);
    
            else {
    
                const state = this.state;
    
                let def = this.defs[item],
                    val;
    
                if (typeof def !== UNDEF) {
    
                    val = this[item];
                    return (typeof val !== UNDEF) ? val : def;
                }
    
                def = state.defs[item];
    
                if (typeof def !== UNDEF) {
    
                    val = state[item];
                    return (typeof val !== UNDEF) ? val : def;
                }
                return undefined;
            }
        }
    };
  • §

    set

    P.set = function (items = Ωempty) {
    
        const keys = _keys(items),
            keysLen = keys.length;
    
        if (keysLen) {
    
            const setters = this.setters,
                defs = this.defs,
                source = this.source,
                state = this.state;
    
            const stateSetters = (state) ? state.setters : Ωempty;
            const stateDefs = (state) ? state.defs : Ωempty;
    
            let fn, i, key, value;
    
            for (i = 0; i < keysLen; i++) {
    
                key = keys[i];
                value = items[key];
    
                if ((key.indexOf($VIDEO) === 0 || key.indexOf($IMAGE) === 0) && source) {
    
                    if (settableVideoAssetAtributes.includes(key)) source[key.substring(6)] = value
                    else if (settableImageAssetAtributes.includes(key)) source[key.substring(6)] = value
                }
    
                else if (key && key !== NAME && value != null) {
    
                    if (!STATE_KEYS.includes(key)) {
    
                        fn = setters[key];
    
                        if (fn) fn.call(this, value);
                        else if (typeof defs[key] !== UNDEF) this[key] = value;
                    }
                    else {
    
                        fn = stateSetters[key];
    
                        if (fn) fn.call(state, value);
                        else if (typeof stateDefs[key] !== UNDEF) state[key] = value;
                    }
                }
            }
        }
        return this;
    };
  • §

    Subscriber management

  • §

    updateImageSubscribers

    P.updateImageSubscribers = function () {
    
        this.dirtyImageSubscribers = false;
    
        if (this.imageSubscribers.length) {
    
            this.imageSubscribers.forEach(name => {
    
                const instance = artefact[name];
    
                if (instance) instance.dirtyInput = true;
            });
        }
    };
  • §

    imageSubscribe

    P.imageSubscribe = function (name) {
    
        if (name && name.substring) pushUnique(this.imageSubscribers, name);
    };
  • §

    imageUnsubscribe

    P.imageUnsubscribe = function (name) {
    
        if (name && name.substring) removeItem(this.imageSubscribers, name);
    };
  • §

    Display cycle functionality

  • §

    cleanImage

    P.cleanImage = function () {
    
        const natWidth = this.sourceNaturalWidth,
            natHeight = this.sourceNaturalHeight;
    
        if (xta(natWidth, natHeight) && natWidth > 0 && natHeight > 0) {
    
            this.dirtyImage = false;
    
            const start = this.currentCopyStart,
                x = start[0],
                y = start[1];
    
            const dims = this.currentCopyDimensions,
                w = dims[0],
                h = dims[1];
    
            if (x + w > natWidth) start[0] = natWidth - w;
            if (y + h > natHeight) start[1] = natHeight - h;
    
            const copyArray = this.copyArray;
    
            copyArray.length = 0;
            copyArray.push(~~start[0], ~~start[1], ~~w, ~~h);
        }
    };
  • §

    cleanCopyStart

    P.cleanCopyStart = function () {
    
        const width = this.sourceNaturalWidth,
            height = this.sourceNaturalHeight;
    
        if (xta(width, height) && width > 0 && height > 0) {
    
            this.dirtyCopyStart = false;
    
            this.cleanPosition(this.currentCopyStart, this.copyStart, [width, height]);
    
            const current = this.currentCopyStart,
                x = current[0],
                y = current[1];
    
            if (x < 0 || x > width) {
    
                if (x < 0) current[0] = 0;
                else current[0] = width - 1;
            }
    
            if (y < 0 || y > height) {
    
                if (y < 0) current[1] = 0;
                else current[1] = height - 1;
            }
            this.dirtyImage = true;
        }
    };
  • §

    cleanCopyDimensions

    P.cleanCopyDimensions = function () {
    
        const natWidth = this.sourceNaturalWidth,
            natHeight = this.sourceNaturalHeight;
    
        if (xta(natWidth, natHeight) && natWidth > 0 && natHeight > 0) {
    
            this.dirtyCopyDimensions = false;
    
            const dims = this.copyDimensions,
                currentDims = this.currentCopyDimensions,
                width = dims[0],
                height = dims[1];
    
            if (width.substring) currentDims[0] = (parseFloat(width) / 100) * natWidth;
            else currentDims[0] = width;
    
            if (height.substring) currentDims[1] = (parseFloat(height) / 100) * natHeight;
            else currentDims[1] = height;
    
            const currentWidth = currentDims[0],
                currentHeight = currentDims[1];
    
            if (currentWidth <= 0 || currentWidth > natWidth) {
    
                if (currentWidth <= 0) currentDims[0] = 1;
                else currentDims[0] = natWidth;
            }
    
            if (currentHeight <= 0 || currentHeight > natHeight) {
    
                if (currentHeight <= 0) currentDims[1] = 1;
                else currentDims[1] = natHeight;
            }
            this.dirtyImage = true;
        }
    };
  • §

    prepareStamp

    P.prepareStamp = function() {
  • §

    The asset itself will update the Picture entity object when changes occur, by setting the entity’s dirtyAsset flag

        if (this.dirtyAsset) this.cleanAsset();
  • §

    Not content with the dirty flag, the entity now interrogates its asset via its checkSource to trigger it to directly rewrite key information if it has changed - particularly dimensional data

        if (this.asset) {
    
            if (this.asset.type === T_SPRITE) this.checkSpriteFrame(this);
            else {
    
                if (this.asset.checkSource) this.asset.checkSource(this.sourceNaturalWidth, this.sourceNaturalHeight);
                else this.dirtyAsset = true;
            }
        }
  • §

    See the entity mixin function for details on the following checks and actions

        if (this.dirtyDimensions || this.dirtyHandle || this.dirtyScale) this.dirtyPaste = true;
    
        if (this.dirtyScale || this.dirtyDimensions || this.dirtyStart || this.dirtyOffset || this.dirtyHandle) this.dirtyPathObject = true;
    
        if (this.dirtyScale) this.cleanScale();
        if (this.dirtyDimensions) this.cleanDimensions();
        if (this.dirtyLock) this.cleanLock();
        if (this.dirtyStart) this.cleanStart();
        if (this.dirtyOffset) this.cleanOffset();
        if (this.dirtyHandle) this.cleanHandle();
        if (this.dirtyRotation) this.cleanRotation();
    
        if (this.isBeingDragged || this.lockTo.includes(MOUSE) || this.lockTo.includes(PARTICLE)) {
    
            this.dirtyStampPositions = true;
            this.dirtyStampHandlePositions = true;
        }
    
        if (this.dirtyStampPositions) this.cleanStampPositions();
        if (this.dirtyStampHandlePositions) this.cleanStampHandlePositions();
    
        if (this.dirtyCopyStart) this.cleanCopyStart();
        if (this.dirtyCopyDimensions) this.cleanCopyDimensions();
        if (this.dirtyImage) this.cleanImage();
        if (this.dirtyPaste) this.preparePasteObject();
    
        if (this.dirtyPathObject) this.cleanPathObject();
  • §

    Update artefacts subscribed to this artefact (using it as their pivot or mimic source), if required

        if (this.dirtyPositionSubscribers) this.updatePositionSubscribers();
  • §

    Specifically for Loom entitys

        if (this.dirtyImageSubscribers) this.updateImageSubscribers();
  • §

    prepareStampTabsHelper is defined in the mixin/hidden-dom-elements.js file - handles updates to anchor and button objects

        this.prepareStampTabsHelper();
    };
  • §

    preparePasteObject - internal function

    • the pasteArray is a convenience Array containing start coordinate and dimensions data, which we can quickly add to the render engine’s drawImage function (which gets called many times)
    P.preparePasteObject = function () {
    
        this.dirtyPaste = false;
    
        const handle = this.currentStampHandlePosition,
            dims = this.currentDimensions,
            scale = this.currentScale;
    
        const x = -handle[0] * scale,
            y = -handle[1] * scale,
            w = dims[0] * scale,
            h = dims[1] * scale;
    
        const pasteArray = this.pasteArray;
    
        pasteArray.length = 0;
        pasteArray.push(~~x, ~~y, ~~w, ~~h);
    
        this.dirtyPathObject = true;
    };
  • §

    cleanPathObject - internal function

    • For Picture entitys, the pathObject is a rectangle
    P.cleanPathObject = function () {
    
        this.dirtyPathObject = false;
    
        if (!this.noPathUpdates || !this.pathObject) {
    
            if (!this.pasteArray || this.pasteArray.length !== 4) this.preparePasteObject();
    
            if (this.pasteArray.length !== 4) this.dirtyPathObject = true;
            else {
    
                const p = this.pathObject = new Path2D();
    
                p.rect(...this.pasteArray);
            }
        }
    };
  • §
    Stamp methods
  • §

    draw

    P.draw = function (engine) {
    
        engine.stroke(this.pathObject);
    };
  • §

    fill

    P.fill = function (engine) {
    
        const [x, y, w, h] = this.copyArray;
        if (this.source && w && h) engine.drawImage(this.source, x, y, w, h, ...this.pasteArray);
    };
  • §

    drawAndFill

    P.drawAndFill = function (engine) {
    
        const [x, y, w, h] = this.copyArray;
        const [_x, _y, _w, _h] = this.pasteArray;
    
        if (this.source && w && h) {
    
            engine.stroke(this.pathObject);
            engine.drawImage(this.source, x, y, w, h, _x, _y, _w, _h);
    
            this.currentHost.clearShadow();
    
            engine.stroke(this.pathObject);
            engine.drawImage(this.source, x, y, w, h, _x, _y, _w, _h);
        }
    };
  • §

    fillAndDraw

    P.fillAndDraw = function (engine) {
    
        const [x, y, w, h] = this.copyArray;
        const [_x, _y, _w, _h] = this.pasteArray;
    
        if (this.source && w && h) {
    
            engine.drawImage(this.source, x, y, w, h, _x, _y, _w, _h);
            engine.stroke(this.pathObject);
    
            this.currentHost.clearShadow();
    
            engine.drawImage(this.source, x, y, w, h, _x, _y, _w, _h);
            engine.stroke(this.pathObject);
        }
    
        engine.stroke(this.pathObject);
    };
  • §

    drawThenFill

    P.drawThenFill = function (engine) {
    
        const [x, y, w, h] = this.copyArray;
        if (this.source && w && h) {
    
            engine.stroke(this.pathObject);
            engine.drawImage(this.source, x, y, w, h, ...this.pasteArray);
        }
    };
  • §

    fillThenDraw

    P.fillThenDraw = function (engine) {
    
        const [x, y, w, h] = this.copyArray;
        if (this.source && w && h) {
    
            engine.drawImage(this.source, x, y, w, h, ...this.pasteArray);
            engine.stroke(this.pathObject);
        }
    };
  • §

    checkHitReturn - overwrites mixin/position.js function

    P.checkHitReturn = function (x, y) {
    
        if (this.checkHitIgnoreTransparency) {
    
            const img = this.stashedImageData;
    
            if (img) {
    
                const index = (((y * img.width) + x) * 4) + 3;
    
                if (img.data[index]) {
    
                    return {
                        x: x,
                        y: y,
                        artefact: this
                    };
                }
            }
            return false;
        }
        else {
    
            return {
                x: x,
                y: y,
                artefact: this
            };
        }
    };
  • §

    Factory

    scrawl.importDomImage('.flowers');
    
    scrawl.makePicture({
    
        name: 'myFlower',
        asset: 'iris',
    
        width: 200,
        height: 200,
    
        startX: 300,
        startY: 200,
        handleX: 100,
        handleY: 100,
    
        copyWidth: 200,
        copyHeight: 200,
        copyStartX: 100,
        copyStartY: 100,
    
        lineWidth: 10,
        strokeStyle: 'gold',
    
        order: 1,
        method: 'drawAndFill',
    
    }).clone({
    
        name: 'myFactory',
        imageSource: 'img/canalFactory-800.png',
    
        width: 600,
        height: 400,
    
        startX: 0,
        startY: 0,
        handleX: 0,
        handleY: 0,
    
        copyWidth: 600,
        copyHeight: 400,
        copyStartX: 150,
        copyStartY: 0,
    
        order: 0,
        method: 'fill',
    });
    
    export const makePicture = function (items) {
    
        if (!items) return false;
        return new Picture(items);
    };
    
    constructors.Picture = Picture;