UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

104 lines 3.94 kB
import { AsyncImageBitmap } from './AsyncImageBitmap.js'; import { urlToBackingMediaSource } from './BackingMediaSource.js'; export class EffectImageBitmap extends AsyncImageBitmap { constructor(backingMedia, options) { var _a, _b; super(); this.backingMedia = backingMedia; this.innerBitmap = null; this.ctx = null; this.lastSrc = null; this.waiting = false; this._presentationHash = -1; this.tint = (_a = options === null || options === void 0 ? void 0 : options.tint) !== null && _a !== void 0 ? _a : 0xFFFFFF; this.resolution = (_b = options === null || options === void 0 ? void 0 : options.resolution) !== null && _b !== void 0 ? _b : 1; // TODO should this be moved to an update-based approach? if (backingMedia.complete) { this.evaluateEffect(); } else { backingMedia.addEventListener('load', () => { this.evaluateEffect(); }); } } static fromURL(url, options) { const [image, type] = urlToBackingMediaSource(url); if (type !== 0 /* BackingMediaSourceType.HTMLImageElement */) { throw new Error('Unsupported BackingMediaSource type'); } return new EffectImageBitmap(image, options); } get width() { return this.backingMedia.width; } get height() { return this.backingMedia.height; } getScratchCtx(width, height) { if (this.ctx) { const canvas = this.ctx.canvas; if (canvas.width !== width) { canvas.width = width; } if (canvas.height !== height) { canvas.height = height; } this.ctx.globalCompositeOperation = 'source-over'; } else { const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Could not create 2D offscreen context'); } this.ctx = ctx; } return this.ctx; } handleBitmapPromise(curSrc, promise) { this.waiting = true; promise.then((bitmap) => { this._presentationHash++; this.innerBitmap = bitmap; }).catch((err) => { console.error(err); }).finally(() => { this.lastSrc = curSrc; this.waiting = false; }); } evaluateEffect() { const curSrc = this.backingMedia.src; if (this.waiting || curSrc === this.lastSrc || !this.backingMedia.complete) { return; } // special case if there is no tint; just convert image to bitmap if (this.tint === 0xFFFFFF) { this.handleBitmapPromise(curSrc, createImageBitmap(this.backingMedia)); return; } // get context with target size const width = Math.max(1, Math.round(this.backingMedia.width * this.resolution)); const height = Math.max(1, Math.round(this.backingMedia.height * this.resolution)); const ctx = this.getScratchCtx(width, height); // tint image // source: https://stackoverflow.com/a/44558286 ctx.drawImage(this.backingMedia, 0, 0, width, height); ctx.fillStyle = `#${this.tint.toString(16).padStart(6, '0')}`; ctx.globalCompositeOperation = 'multiply'; ctx.fillRect(0, 0, width, height); ctx.globalCompositeOperation = 'destination-in'; ctx.drawImage(this.backingMedia, 0, 0, width, height); // convert to bitmap this.handleBitmapPromise(curSrc, createImageBitmap(ctx.canvas, 0, 0, width, height)); } get bitmap() { this.evaluateEffect(); return this.innerBitmap; } get presentationHash() { return this._presentationHash; } } //# sourceMappingURL=EffectImageBitmap.js.map