Parse

File Parse screenshot.js

This runs the server-side parser and regenerates the documentation tree for this source file.

Source

            /*
files:
    image-edge-detection.js
    offscreen.js
categories: screenshot
---

Version two supplies better functionss and clipping tech.


Add extra methods to the `Scene` to capture screenshots of the canvas.

*/
class Screenshot {

    fileFormat = "image/png" // "image/jpeg"
    fileQuality = .9

    constructor(stage) {
        this.stage = stage
    }

    _stashBlob(uriBlob) {
        this.lastStash = uriBlob
    }

    blobURL(blob) {
        /* Given a _blob_ object, creaate an object url (a base 64 representation
        of the blob) */
        return URL.createObjectURL(blob);
    }

    revokeURL(url) {
        /* Revoke an object url from memory. */
        return URL.revokeObjectURL(url)
    }

    toBlob(callback=undefined) {
        /* Call the `canvas.toBlob` function to screenshot the canvas.
        Provide a callback to override the internal onScreenshot method
        capture function

            toBlob(function(blob){})
        */
        callback = callback || this.onScreenshot.bind(this)
        // const url = URL.createObjectURL(blob)
        // Navtive toBlob function call.
        return this.stage.canvas.toBlob((blob) => callback && callback(blob));
    }

    toBlobCropped(crop, callback){
        callback = callback || this.onScreenshot.bind(this)
        let cb = (blob) => callback && callback(blob)
        // let crop = {x: 200, y:300, width: 30, height: 30}
        let canvas = this.stage.canvas
        return asObjectWithCrop.bind(this.stage)(canvas, this.fileFormat, this.fileQuality, crop)
        // return this.stage.canvas.toBlob(cb)
    }

    onScreenshot(blob) {
        this._stashBlob(blob)
    }

    createImgElement() {
        /* generate a new `<img>` with the src as a new blob of the canvas.
        Append to the body to present.
        */
        const newImg = document.createElement("img");
        this.toBlob((blob)=>{
            let url = this.blobURL(blob)
            newImg.onload = () => { URL.revokeObjectURL(url) };
            newImg.src = url;
        });
        return newImg
    }

    downloadImage(name='polypoint-screenshot.png'){
        /* Perform an image _download_ of the canvas, rendered through toBlob
        This will push a new file to the client.
        */
        let anchorClick = (blob) => {
            const anchor = document.createElement('a');
            anchor.download = name; // optional, but you can give the file a name
            anchor.href = this.blobURL(blob);
            anchor.click(); // ✨ magic!
            // remove it to save on memory
            setTimeout(()=> {
                let _stage = this
                _stage.revokeURL(anchor.href)
            }, 500)
        }

        return this.toBlob(anchorClick)
    }


    toBlobDetectedCropped(background=undefined, borderRadius=10, dimensions=undefined, minimumSize=undefined) {
        let stage = this.stage
            , ctx = stage.ctx
            , d = dimensions || stage.dimensions
            , w = d?.width
            , h = d?.height

        const minWidth = Math.max(0, Math.floor(Number(minimumSize?.width) || 0))
        const minHeight = Math.max(0, Math.floor(Number(minimumSize?.height) || 0))

        const fallbackBlob = () => {
            return new Promise((resolve, reject) => {
                this.toBlob((blob) => {
                    if (!blob) {
                        reject(new Error('Screenshot capture failed.'))
                        return
                    }

                    resolve(blob)
                })
            })
        }

        if (!Number.isFinite(w) || !Number.isFinite(h) || w < 1 || h < 1) {
            return fallbackBlob()
        }

        if (typeof detectEdges != 'function') {
            return fallbackBlob()
        }

        const initImageData = ctx.getImageData(0, 0, w, h)
        let edges = detectEdges(initImageData.data, initImageData.width)
        const innerPadding = 10
        const contentWidth = edges.width + (innerPadding * 2)
        const contentHeight = edges.height + (innerPadding * 2)
        const hasMinimumWidth = minWidth > 0
        const hasMinimumHeight = minHeight > 0
        const outputWidth = hasMinimumWidth
            ? Math.max(minWidth, edges.width)
            : contentWidth
        const outputHeight = hasMinimumHeight
            ? Math.max(minHeight, edges.height)
            : contentHeight
        const drawX = Math.max(0, Math.floor((outputWidth - edges.width) * .5))
        const drawY = Math.max(0, Math.floor((outputHeight - edges.height) * .5))
        let offscreen = stage?.offscreen?.create?.({
            width: outputWidth,
            height: outputHeight,
        })

        if (!offscreen) {
            offscreen = document.createElement('canvas')
            offscreen.width = outputWidth
            offscreen.height = outputHeight
        }

        const imageData = ctx.getImageData(
            edges.left, edges.top,
            edges.width, edges.height
        )

        let offCtx = offscreen.getContext('2d')
        if (!offCtx) {
            return Promise.reject(new Error('Screenshot crop context unavailable.'))
        }

        if(background != undefined) {
            // Draw background
            offCtx.fillStyle = background
            offCtx.roundRect(0, 0, offscreen.width, offscreen.height, borderRadius)
            offCtx.fill()

            // Create temp canvas for proper blending
            let tempCanvas = document.createElement('canvas')
            tempCanvas.width = imageData.width
            tempCanvas.height = imageData.height
            let tempCtx = tempCanvas.getContext('2d')
            tempCtx.putImageData(imageData, 0, 0)

            // Draw blended image on top
            offCtx.drawImage(
                tempCanvas,
                0, 0, imageData.width, imageData.height,
                drawX, drawY, imageData.width, imageData.height
            )
        } else {
            offCtx.clearRect(0, 0, offscreen.width, offscreen.height)
            offCtx.putImageData(imageData, drawX, drawY)
        }

        if (typeof offscreen.convertToBlob == 'function') {
            return offscreen.convertToBlob({
                type: this.fileFormat,
                quality: this.fileQuality,
            })
        }

        return new Promise((resolve, reject) => {
            if (typeof offscreen.toBlob != 'function') {
                reject(new Error('Canvas blob conversion unavailable.'))
                return
            }

            offscreen.toBlob((blob) => {
                if (!blob) {
                    reject(new Error('Screenshot capture failed.'))
                    return
                }

                resolve(blob)
            }, this.fileFormat, this.fileQuality)
        })
    }


    downloadCroppedImage(name='polypoint-screenshot.png', background=undefined, borderRadius=10, dimensions=undefined){
        /* grab the placement, and create a new download image with cropping.*/
        this.toBlobDetectedCropped(background, borderRadius, dimensions).then((blob) => {
            const anchor = document.createElement('a')
            anchor.download = name
            anchor.href = this.blobURL(blob)
            anchor.click()
            setTimeout(()=> {
                this.revokeURL(anchor.href)
            }, 500)
        }).catch((error) => {
            console.error('downloadCroppedImage failed:', error)
        })
    }

    downloadCroppedImageAlphaComposite(name='polypoint-screenshot.png',
        background=undefined, borderRadius=10, dimensions=undefined){
        return this.downloadCroppedImage(name, background, borderRadius, dimensions)
    }


    // asDownloadLink(name="polypoint-screenshot.png") {
    //     const linkObjectUrl = document.createElement("a");
    //     linkObjectUrl.download = name
    //     linkObjectUrl.innerHTML = 'Download'
    //     linkObjectUrl.onclick = function(e) {
    //         setTimeout(()=> URL.revokeObjectURL(linkObjectUrl), 1000)
    //         linkObjectUrl.remove()
    //     }

    //     document.body.appendChild(linkObjectUrl);

    //     let cb = (url) => {
    //         linkObjectUrl.href = url
    //     }

    //     this.toBlob(cb);
    // }

}

Polypoint.head.install(Screenshot)


Polypoint.head.lazierProp('Stage',
    function screenshot() {
        console.log('make screenshot', this)
        return new Screenshot(this)
    }
)

asObjectUrl = async function(width, height, callback) {
    let quality = .8
    let canvas = stage.canvas
    let objectUrl = await canvas.toObjectURL("image/jpeg", quality);
    // let dataUrl = canvas.toDataURL("image/webp", quality);

    // set object URLs
    // linkObjectUrl.href = objectUrl;
    let p = asObjectUrl()
    return p.then((d)=>console.log('got data'));
    // return p//.then(console.log);
}

const asObject = async function(mimeType="image/jpeg", quality = 0.85) {
    let promiseFunc = (resolve, reject) => {
        let caller = (blob) => {
            if (!blob) {
                reject("Error creating blob");
                return;
            }

            const blobUrl = URL.createObjectURL(blob);
            resolve(blobUrl);
        };

        this.toBlob(caller, mimeType, quality);
    }

    return new Promise(promiseFunc);
};

const asObjectWithCrop = async function(canvas, mimeType = "image/jpeg", quality = 0.85, crop = null) {
    // const canvas = this;
    const innerPadding = 20;

    const promiseFunc = (resolve, reject) => {
        let targetCanvas = canvas;

        if (crop) {
            const { x, y, width, height } = crop;
            const cropWidth = Math.max(1, Math.round(width));
            const cropHeight = Math.max(1, Math.round(height));
            targetCanvas = document.createElement("canvas");
            targetCanvas.width = cropWidth + (innerPadding * 2);
            targetCanvas.height = cropHeight + (innerPadding * 2);
            const ctx = targetCanvas.getContext("2d");
            ctx.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
            // ctx.putImageData(canvas, 0, 0);
            ctx.drawImage(
                canvas,
                x,
                y,
                cropWidth,
                cropHeight,
                innerPadding,
                innerPadding,
                cropWidth,
                cropHeight,
            );
        }

        targetCanvas.toBlob((blob) => {
            if (!blob) {
                reject("Error creating blob");
                return;
            }

            // const blobUrl = URL.createObjectURL(blob);
            // resolve(blobUrl);

            resolve(blob);
        }, mimeType, quality);
    };

    return new Promise(promiseFunc);
};


// HTMLCanvasElement.prototype.toObjectURL = asObjectWithCrop;
copy