lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
104 lines • 3.94 kB
JavaScript
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