Исправлена блокировка потока при вставке изображений, оптимизирован код и ответственность

This commit is contained in:
RoyceDa
2026-02-19 21:43:27 +02:00
parent a38a331cd1
commit 53535d68e0
7 changed files with 147 additions and 54 deletions

View File

@@ -0,0 +1,39 @@
// ...existing code...
const worker = new Worker(new URL('./image.worker.ts', import.meta.url), { type: 'module' });
type WorkerReq =
| { id: number; type: 'blurhashToBase64Image'; payload: { blurhash: string; width: number; height: number } }
| { id: number; type: 'base64ImageToBlurhash'; payload: { base64Image: string } };
type WorkerRes =
| { id: number; ok: true; data: string }
| { id: number; ok: false; error: string };
let seq = 0;
const pending = new Map<number, (res: WorkerRes) => void>();
worker.onmessage = (e: MessageEvent<WorkerRes>) => {
const res = e.data;
const cb = pending.get(res.id);
if (cb) {
pending.delete(res.id);
cb(res);
}
};
function callWorker(req: Omit<WorkerReq, 'id'>): Promise<string> {
return new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, (res) => (res.ok ? resolve(res.data) : reject(res.error)));
worker.postMessage({ ...req, id });
});
}
export function blurhashToBase64Image(blurhash: string, width: number, height: number): Promise<string> {
return callWorker({ type: 'blurhashToBase64Image', payload: { blurhash, width, height } });
}
export function base64ImageToBlurhash(base64Image: string): Promise<string> {
return callWorker({ type: 'base64ImageToBlurhash', payload: { base64Image } });
}
// ...existing code...

View File

@@ -0,0 +1,71 @@
import { decode, encode } from 'blurhash';
type Req =
| { id: number; type: 'blurhashToBase64Image'; payload: { blurhash: string; width: number; height: number } }
| { id: number; type: 'base64ImageToBlurhash'; payload: { base64Image: string } };
type Res =
| { id: number; ok: true; data: string }
| { id: number; ok: false; error: string };
const toBase64 = async (blurhash: string, width: number, height: number): Promise<string> => {
const pixels = decode(blurhash, width, height);
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('No 2d context');
const imageData = ctx.createImageData(width, height);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/png' });
const buf = new Uint8Array(await blob.arrayBuffer());
let bin = '';
for (let i = 0; i < buf.length; i++) bin += String.fromCharCode(buf[i]);
return `data:image/png;base64,${btoa(bin)}`;
};
const toBlurhash = async (base64Image: string): Promise<string> => {
const src = base64Image?.trim();
if (!src) throw new Error('Empty image data');
const resp = await fetch(src);
const blob = await resp.blob();
if (!blob.size) throw new Error('Image fetch returned empty blob');
const bitmap = await createImageBitmap(blob);
const { width, height } = bitmap;
if (!width || !height) {
bitmap.close();
throw new Error(`Image has invalid size ${width}x${height}`);
}
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext('2d');
if (!ctx) {
bitmap.close();
throw new Error('No 2d context');
}
ctx.drawImage(bitmap, 0, 0, width, height);
bitmap.close();
const imageData = ctx.getImageData(0, 0, width, height);
return encode(imageData.data, imageData.width, imageData.height, 4, 4);
};
self.onmessage = async (e: MessageEvent<Req>) => {
const { id, type, payload } = e.data;
const reply = (res: Res) => (self as unknown as Worker).postMessage(res);
try {
if (type === 'blurhashToBase64Image') {
const data = await toBase64(payload.blurhash, payload.width, payload.height);
reply({ id, ok: true, data });
} else if (type === 'base64ImageToBlurhash') {
const data = await toBlurhash(payload.base64Image);
reply({ id, ok: true, data });
} else {
throw new Error(`Unknown type ${type}`);
}
} catch (err: any) {
reply({ id, ok: false, error: String(err?.message ?? err) });
}
};