• Jump To … +
    ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-015a.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-019a.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/canvas-048.js ./demo/canvas-049.js ./demo/canvas-050.js ./demo/canvas-051.js ./demo/canvas-052.js ./demo/canvas-053.js ./demo/canvas-054.js ./demo/canvas-055.js ./demo/canvas-056.js ./demo/canvas-057.js ./demo/canvas-058.js ./demo/canvas-059.js ./demo/canvas-060.js ./demo/canvas-061.js ./demo/canvas-062.js ./demo/canvas-063.js ./demo/canvas-064.js ./demo/core-001.js ./demo/delaunator-001.js ./demo/delaunator-002.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-015.js ./demo/dom-016.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-002a.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-021.js ./demo/filters-022.js ./demo/filters-023.js ./demo/filters-024.js ./demo/filters-025.js ./demo/filters-026.js ./demo/filters-027.js ./demo/filters-101.js ./demo/filters-102.js ./demo/filters-103.js ./demo/filters-104.js ./demo/filters-105.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/mediapipe-001.js ./demo/mediapipe-002.js ./demo/mediapipe-003.js ./demo/modules-001.js ./demo/modules-002.js ./demo/modules-003.js ./demo/modules-004.js ./demo/modules-005.js ./demo/packets-001.js ./demo/packets-002.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/rapier-001.js ./demo/snippets-001.js ./demo/snippets-002.js ./demo/snippets-003.js ./demo/snippets-004.js ./demo/snippets-005.js ./demo/snippets-006.js ./demo/temp-000.js ./demo/temp-001.js ./demo/temp-001a.js ./demo/temp-002.js ./demo/temp-003.js ./demo/temp-004.js ./demo/temp-005.js ./demo/temp-006.js ./demo/temp-007.js ./demo/temp-008.js ./demo/temp-009.js ./demo/temp-010.js ./demo/temp-011.js ./demo/temp-012.js ./demo/temp-013.js ./demo/temp-014.js ./demo/temp-015.js ./demo/temp-016.js ./demo/temp-017.js ./demo/temp-018.js ./demo/temp-018a.js ./demo/temp-019.js ./demo/temp-020.js ./demo/temp-021.js ./demo/temp-022.js ./demo/temp-023.js ./demo/temp-024.js ./demo/temp-025.js ./demo/temp-026.js ./demo/temp-027.js ./demo/temp-028.js ./demo/temp-029.js ./demo/temp-030.js ./demo/temp-031.js ./demo/temp-032.js ./demo/temp-033.js ./demo/temp-034.js ./demo/temp-035.js ./demo/temp-036.js ./demo/temp-037.js ./demo/temp-100.js ./demo/temp-101.js ./demo/temp-102.js ./demo/temp-103.js ./demo/temp-104.js ./demo/temp-200.js ./demo/temp-201.js ./demo/temp-301.js ./demo/temp-inkscapeSvgFilters.js ./demo/temp-lottie.js ./demo/tensorflow-001.js ./demo/tensorflow-002.js ./demo/utilities.js
  • §

    Demo Canvas 052

    Create and use a RawAsset object to modify an image asset

  • §

    Run code

    import * as scrawl from '../source/scrawl.js'
    
    import { reportSpeed, addImageDragAndDrop } from './utilities.js';
  • §

    Scene setup

    let canvas = scrawl.library.artefact.mycanvas;
  • §

    Magic numbers

    const dimension = 600;
  • §

    Import image from DOM, and create Picture entity using it

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

    We need a background image to act as the template on which we will draw

    const backgroundImage = scrawl.makePicture({
    
        name: 'background',
        asset: 'iris',
        dimensions: [dimension, dimension],
        copyDimensions: ['100%', '100%'],
        method: 'none',
    });
  • §

    We will use Perlin noise to determine brush stroke length and direction

    let noiseAsset = scrawl.makeNoiseAsset({
    
        name: 'my-noise-generator',
        width: dimension,
        height: dimension,
        noiseEngine: 'improved-perlin',
        scale: 80,
    });
  • §

    We’ll code up the painting effect in a RawAsset, which can then be used by Picture entitys, Pattern styles, and filters

    let impressionistAsset = scrawl.makeRawAsset({
    
        name: 'pretend-van-gogh',
    
        userAttributes: [{
  • §

    lineWidth, lineLengthMultiplier, lineLengthStart, linesToAdd, lineBlend, lineOpacity - some brush attributes that we’ll allow the user to modify in real time.

            key: 'lineWidth', 
            defaultValue: 4,
        },{
            key: 'lineLengthMultiplier', 
            defaultValue: 20,
        },{
            key: 'lineLengthStart', 
            defaultValue: 5,
        },{
            key: 'linesToAdd', 
            defaultValue: 50,
        },{
            key: 'lineBlend', 
            defaultValue: 'source-over',
        },{
            key: 'lineOpacity', 
            defaultValue: 1,
        },{
  • §

    offsetX, offsetY, rotationMultiplier, rotationStart - some additional brush rotation attributes.

            key: 'offsetX', 
            defaultValue: 0,
        },{
            key: 'offsetY', 
            defaultValue: 0,
        },{
            key: 'rotationMultiplier', 
            defaultValue: 90,
        },{
            key: 'rotationStart', 
            defaultValue: 0,
        },{
  • §

    canvasWidth, canvasHeight - make the RawAsset’s dimensions the same as our canvas base Cell’s dimensions

            key: 'canvasWidth', 
            defaultValue: dimension,
        },{
            key: 'canvasHeight', 
            defaultValue: dimension,
        },{
  • §

    background - a handle to our background Picture entity, from which we will be extracting color values

            key: 'background', 
            defaultValue: false,
            setter: function (item) {
                this.background = item;
                this.dirtyBackground = true;
            },
        },{
  • §

    noise - a handle to our Noise asset, from which we will be extracting brushstroke direction and length data

            key: 'noise', 
            defaultValue: false,
            setter: function (item) {
                this.noise = item;
                this.dirtyData = true;
            },
        },{
  • §

    trigger - we update the RawAsset at the start of each Display cycle by setting its trigger attribute.

    • It’s at this point that we fill the RawAsset canvas with the background image, if required
            key: 'trigger', 
            defaultValue: false,
            setter: function (item) {
    
                if (this.dirtyBackground) {
    
                    this.dirtyBackground = false;
  • §

    @ts-expect-error

                    const { element, engine, canvasWidth, canvasHeight, background } = this;
    
                    element.width = canvasWidth;
                    element.height = canvasHeight;
    
                    const { source, copyArray, pasteArray } = background;
    
                    if (source && copyArray && pasteArray ) {
  • §

    Strictly speaking, copyArray and pasteArray are Picture entity internal data structures but that doesn’t stop us using them here.

    • TODO: consider whether the RawAsset object should be using a Cell wrapper rather than a raw <canvas> element
    • if we did it that way, then we would be able to simpleStamp the Picture entity onto the canvas
    • the reason why we’re NOT doing it that way at the moment is to keep RawAsset canvases out of the SC library
                        engine.drawImage(background.source, ...background.copyArray, ...background.pasteArray);
    
                        this.backgroundData = engine.getImageData(0, 0, dimension, dimension);
    
                        this.dirtyData = true;
                    }
                    else this.dirtyBackground = true;
                }
                else this.dirtyData = true;
            },
        }],
  • §

    assetWrapper is the same as this when function is declared with the function keyword

    • We clear the RawAsset’s canvas, then draw the updated Voronoi web onto it
        updateSource: function (assetWrapper) {
    
            const { engine, noise, backgroundData, lineWidth, lineLengthMultiplier, lineLengthStart, linesToAdd, lineBlend, lineOpacity, offsetX, offsetY, rotationMultiplier, rotationStart } = assetWrapper;
    
            if (noise && backgroundData) {
    
                const { data, width, height } = backgroundData;
    
                const { noiseValues } = noise;
    
                if (noiseValues) {
    
                    engine.lineWidth = lineWidth;
                    engine.lineCap = 'round';
                    engine.globalCompositeOperation = lineBlend;
                    engine.globalAlpha = lineOpacity;
    
                    let x, y, pos, len, rx, ry, dx, dy, roll, r, g, b, a;
    
                    const coord = scrawl.requestCoordinate();
    
                    for (let i = 0; i < linesToAdd; i++) {
    
                        x = Math.floor(Math.random() * width);
                        y = Math.floor(Math.random() * height);
    
                        len = (noiseValues[y][x] * lineLengthMultiplier) + lineLengthStart;
    
                        pos = ((y * width) + x) * 4;
    
                        r = data[pos];
                        g = data[++pos];
                        b = data[++pos];
                        a = data[++pos];
    
                        engine.strokeStyle = `rgba(${r},${g},${b},${a/255})`;
    
                        rx = (x + offsetX);
                        if (rx < 0 || rx >= width) {
                            rx = (rx < 0) ? rx + width : rx - width;
                        }
    
                        ry = (y + offsetY);
                        if (ry < 0 || ry >= height) {
                            ry = (ry < 0) ? ry + height : ry - height;
                        }
    
                        roll = (noiseValues[ry][rx] * rotationMultiplier) + rotationStart;
    
                        coord.set(len, 0).rotate(roll);
                        [dx, dy] = coord;
    
                        engine.beginPath();
                        engine.moveTo(x, y);
  • §

    @ts-expect-error

                        engine.lineTo(x + dx, y + dy);
                        engine.stroke();
                    }
                    scrawl.releaseCoordinate(coord);
                }
            }
        },
    });
    
    impressionistAsset.set({
        background: backgroundImage,
        noise: noiseAsset,
    });
    
    scrawl.makePicture({
        name: 'noise-image',
        asset: 'my-noise-generator',
        method: 'none',
    });
    
    const displayImage = scrawl.makePicture({
        name: 'display-image',
        asset: 'pretend-van-gogh',
        dimensions: [dimension, dimension],
        copyDimensions: ['100%', '100%'],
    });
  • §

    Scene animation

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

    const report = reportSpeed('#reportmessage');
  • §

    Create the Display cycle animation

    scrawl.makeRender({
    
        name: 'demo-animation',
        target: canvas,
  • §

    We need to trigger the RawAsset object to update its output at the start of each Display cycle

        commence: () => impressionistAsset.set({ trigger: true }),
        
        afterShow: report,
    });
  • §

    Drag-and-Drop image loading functionality

    addImageDragAndDrop(
        canvas, 
        '#my-image-store', 
        backgroundImage, 
        () => { 
            impressionistAsset.set({
                background: backgroundImage,
            });
        },
    );
  • §

    User interaction

    Setup form observer functionality

    scrawl.observeAndUpdate({
    
        event: ['input', 'change'],
        origin: '.controlItem',
    
        target: impressionistAsset,
    
        useNativeListener: true,
        preventDefault: true,
    
        updates: {
    
            lineBlend: ['lineBlend', 'raw'],
            lineLengthMultiplier: ['lineLengthMultiplier', 'round'],
            lineLengthStart: ['lineLengthStart', 'round'],
            lineWidth: ['lineWidth', 'round'],
            linesToAdd: ['linesToAdd', 'round'],
            lineOpacity: ['lineOpacity', 'float'],
            offsetX: ['offsetX', 'round'],
            offsetY: ['offsetY', 'round'],
            rotationMultiplier: ['rotationMultiplier', 'round'],
            rotationStart: ['rotationStart', 'round'],
        },
    });
    
    scrawl.observeAndUpdate({
    
        event: ['input', 'change'],
        origin: '#noiseScale',
    
        target: noiseAsset,
    
        useNativeListener: true,
        preventDefault: true,
    
        updates: {
    
            noiseScale: ['scale', 'round'],
        },
    });
  • §

    Setup form @ts-expect-error

    document.querySelector('#lineBlend').options.selectedIndex = 0;
  • §

    @ts-expect-error

    document.querySelector('#lineWidth').value = 4;
  • §

    @ts-expect-error

    document.querySelector('#lineLengthMultiplier').value = 20;
  • §

    @ts-expect-error

    document.querySelector('#lineLengthStart').value = 5;
  • §

    @ts-expect-error

    document.querySelector('#linesToAdd').value = 50;
  • §

    @ts-expect-error

    document.querySelector('#lineOpacity').value = 1;
  • §

    @ts-expect-error

    document.querySelector('#noiseScale').value = 80;
  • §

    @ts-expect-error

    document.querySelector('#offsetX').value = 0;
  • §

    @ts-expect-error

    document.querySelector('#offsetY').value = 0;
  • §

    @ts-expect-error

    document.querySelector('#rotationMultiplier').value = 90;
  • §

    @ts-expect-error

    document.querySelector('#rotationStart').value = 0;
  • §

    Video recording and download functionality

    const videoButton = document.querySelector("#my-record-video-button");
    
    let recording = false;
    let myRecorder;
    let recordedChunks;
    
    videoButton.addEventListener("click", () => {
        recording = !recording;
    
        if (recording) {
    
            videoButton.textContent = "Stop recording";
    
            const stream = canvas.domElement.captureStream(25);
    
            myRecorder = new MediaRecorder(stream, {
                mimeType: "video/webm;codecs=vp8"
            });
    
            recordedChunks = [];
    
            myRecorder.ondataavailable = (e) => {
    
                if (e.data.size > 0) recordedChunks.push(e.data);
            };
    
            myRecorder.start();
        } 
        else {
    
            videoButton.textContent = "Record a video";
    
            myRecorder.stop();
    
            setTimeout(() => {
                
                const blob = new Blob(recordedChunks, { type: "video/webm" });
    
                const url = URL.createObjectURL(blob);
                const a = document.createElement("a");
    
                a.href = url;
                a.download = `Scrawl-canvas-art-recording-${Date().slice(4, 24)}.webm`;
                a.click();
    
                URL.revokeObjectURL(url);
            }, 0);
        }
    });
  • §

    Development and testing

    console.log(scrawl.library);