/** * A decorator that binds values to their class instance. * @example * class C { * @bind * foo () { * return this; * } * } * let f = new C().foo; * f() instanceof C; // true */ export function bind(target: any, propertyKey: string, descriptor: PropertyDescriptor) { return { // the first time the prototype property is accessed for an instance, // define an instance property pointing to the bound function. // This effectively "caches" the bound prototype method as an instance property. get() { const bound = descriptor.value.bind(this); Object.defineProperty(this, propertyKey, { value: bound, }); return bound; }, }; } /** Creates a function ref that assigns its value to a given property of an object. * @example * // element is stored as `this.foo` when rendered. *
*/ export function linkRef(obj: any, name: string) { const refName = `$$ref_${name}`; let ref = obj[refName]; if (!ref) { ref = obj[refName] = (c: T) => { obj[name] = c; }; } return ref; } /** * Turns a given `ImageBitmap` into `ImageData`. */ export async function bitmapToImageData(bitmap: ImageBitmap): Promise { // Make canvas same size as image // TODO: Move this off-thread if possible with `OffscreenCanvas` or iFrames? const canvas = document.createElement('canvas'); canvas.width = bitmap.width; canvas.height = bitmap.height; // Draw image onto canvas const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Could not create canvas context'); } ctx.drawImage(bitmap, 0, 0); return ctx.getImageData(0, 0, bitmap.width, bitmap.height); } export async function imageDataToBitmap(data: ImageData): Promise { // Make canvas same size as image // TODO: Move this off-thread if possible with `OffscreenCanvas` or iFrames? const canvas = document.createElement('canvas'); canvas.width = data.width; canvas.height = data.height; // Draw image onto canvas const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Could not create canvas context'); } ctx.putImageData(data, 0, 0); return createImageBitmap(canvas); } /** Replace the contents of a canvas with the given bitmap */ export function drawBitmapToCanvas(canvas: HTMLCanvasElement, bitmap: ImageBitmap) { const ctx = canvas.getContext('2d'); if (!ctx) throw Error('Canvas not initialized'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(bitmap, 0, 0); } export async function canvasEncode(data: ImageData, type: string, quality?: number) { const canvas = document.createElement('canvas'); canvas.width = data.width; canvas.height = data.height; const ctx = canvas.getContext('2d'); if (!ctx) throw Error('Canvas not initialized'); ctx.putImageData(data, 0, 0); const blob = await new Promise(r => canvas.toBlob(r, type, quality)); if (!blob) throw Error('Encoding failed'); return blob; } export function canDecodeImage(data: string): Promise { return new Promise((resolve) => { const img = document.createElement('img'); img.src = data; img.onload = _ => resolve(true); img.onerror = _ => resolve(false); }); } export function blobToArrayBuffer(blob: Blob): Promise { return new Promise((resolve) => { const fileReader = new FileReader(); fileReader.addEventListener('load', () => { resolve(fileReader.result); }); fileReader.readAsArrayBuffer(blob); }); } const magicNumberToMimeType = new Map([ [/^%PDF-/, 'application/pdf'], [/^GIF87a/, 'image/gif'], [/^GIF89a/, 'image/gif'], [/^\x89PNG\x0D\x0A\x1A\x0A/, 'image/png'], [/^\xFF\xD8\xFF/, 'image/jpeg'], [/^BM/, 'image/bmp'], [/^I I/, 'image/tiff'], [/^II*/, 'image/tiff'], [/^MM\x00*/, 'image/tiff'], [/^RIFF....WEBPVP8 /, 'image/webp'], ]); export async function sniffMimeType(blob: Blob): Promise { const firstChunk = await blobToArrayBuffer(blob.slice(0, 16)); const firstChunkString = Array.from(new Uint8Array(firstChunk)) .map(v => String.fromCodePoint(v)) .join(''); for (const [detector, mimeType] of magicNumberToMimeType) { if (detector.test(firstChunkString)) { return mimeType; } } return ''; } export function createImageBitmapPolyfill(blob: Blob): Promise { return createImageBitmap(blob); }