101 lines
2.5 KiB
TypeScript
101 lines
2.5 KiB
TypeScript
import { isWebpSupported } from './environment';
|
|
import { dataUriToBlob, blobToDataUri } from './files';
|
|
import { pause } from './schedulers';
|
|
|
|
const WORKER_INITIALIZATION_TIMEOUT = 2000;
|
|
|
|
let canvas: HTMLCanvasElement;
|
|
let worker: IWebpWorker;
|
|
|
|
export const EMPTY_IMAGE_DATA_URI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk'
|
|
+ 'YAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
|
|
export async function webpToPng(url: string, blob: Blob): Promise<Blob | undefined> {
|
|
initWebpWorker();
|
|
|
|
while (!worker.wasmReady) {
|
|
await pause(WORKER_INITIALIZATION_TIMEOUT);
|
|
}
|
|
|
|
const { result, width, height } = await getDecodePromise(url, blob);
|
|
if (!width || !height) {
|
|
return undefined;
|
|
}
|
|
|
|
return createPng({ result, width, height });
|
|
}
|
|
|
|
export async function webpToPngBase64(key: string, url: string): Promise<string> {
|
|
if (isWebpSupported() || url.substr(0, 15) !== 'data:image/webp') {
|
|
return url;
|
|
}
|
|
|
|
initWebpWorker();
|
|
|
|
const pngBlob = await webpToPng(key, dataUriToBlob(url));
|
|
|
|
if (!pngBlob) {
|
|
throw new Error(`Can't convert webp to png. Url: ${url}`);
|
|
}
|
|
|
|
return blobToDataUri(pngBlob);
|
|
}
|
|
|
|
function initWebpWorker() {
|
|
if (!worker) {
|
|
worker = new Worker(new URL('../lib/webp/webp_wasm.worker.js', import.meta.url)) as IWebpWorker;
|
|
worker.wasmReady = false;
|
|
worker.onmessage = handleLibWebpMessage;
|
|
}
|
|
}
|
|
|
|
function createPng({ result, width, height }: TEncodedImage): Promise<Blob | undefined> {
|
|
if (!canvas) {
|
|
canvas = document.createElement('canvas');
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const img = new ImageData(result, width, height);
|
|
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
const ctx = canvas.getContext('2d')!;
|
|
ctx.putImageData(img, 0, 0);
|
|
|
|
canvas.toBlob((blob) => {
|
|
resolve(blob ?? undefined);
|
|
}, 'image/png', 1);
|
|
});
|
|
}
|
|
|
|
function handleLibWebpMessage(e: MessageEvent) {
|
|
const { id } = e.data;
|
|
switch (e.data.type) {
|
|
case 'initialized': {
|
|
worker.wasmReady = true;
|
|
break;
|
|
}
|
|
|
|
case 'result': {
|
|
if (worker.requests.has(id)) {
|
|
const resolve = worker.requests.get(id)!;
|
|
|
|
worker.requests.delete(id);
|
|
resolve(e.data!);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDecodePromise(url: string, blob: Blob): Promise<TEncodedImage> {
|
|
return new Promise((resolve) => {
|
|
if (!worker.requests) {
|
|
worker.requests = new Map();
|
|
}
|
|
|
|
worker.requests.set(url, resolve);
|
|
worker.postMessage({ id: url, blob });
|
|
});
|
|
}
|