Release Image Tools 0.1.0
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { processImageOnCanvas, type ProcessingStage } from "./canvas-pipeline";
|
||||
import { ImageToolError, type ImageErrorCode } from "./errors";
|
||||
import { IMAGE_LIMITS } from "./limits";
|
||||
import type { ProcessImageRequest, ProcessImageResult } from "./types";
|
||||
|
||||
let requestSequence = 0;
|
||||
|
||||
export async function processImage(
|
||||
request: ProcessImageRequest,
|
||||
options: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly onStage?: (stage: ProcessingStage) => void;
|
||||
} = {},
|
||||
): Promise<ProcessImageResult> {
|
||||
if (supportsWorkerPipeline()) return processInWorker(request, options);
|
||||
return processImageOnCanvas(request, options);
|
||||
}
|
||||
|
||||
function processInWorker(
|
||||
request: ProcessImageRequest,
|
||||
options: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly onStage?: (stage: ProcessingStage) => void;
|
||||
},
|
||||
): Promise<ProcessImageResult> {
|
||||
const id = ++requestSequence;
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(
|
||||
new URL("../workers/image.worker.ts", import.meta.url),
|
||||
{
|
||||
type: "module",
|
||||
name: `image-tools-${id}`,
|
||||
},
|
||||
);
|
||||
let settled = false;
|
||||
const finish = (callback: () => void) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
window.clearTimeout(timeout);
|
||||
options.signal?.removeEventListener("abort", abort);
|
||||
worker.terminate();
|
||||
callback();
|
||||
};
|
||||
const abort = () => {
|
||||
finish(() =>
|
||||
reject(
|
||||
new ImageToolError("ABORTED", "The image operation was cancelled."),
|
||||
),
|
||||
);
|
||||
};
|
||||
const timeout = window.setTimeout(() => {
|
||||
finish(() =>
|
||||
reject(
|
||||
new ImageToolError(
|
||||
"PROCESSING_TIMEOUT",
|
||||
`Image processing exceeded the ${IMAGE_LIMITS.processingTimeoutMs / 1000}-second safety limit.`,
|
||||
),
|
||||
),
|
||||
);
|
||||
}, IMAGE_LIMITS.processingTimeoutMs);
|
||||
options.signal?.addEventListener("abort", abort, { once: true });
|
||||
worker.onerror = () => {
|
||||
finish(() =>
|
||||
reject(
|
||||
new ImageToolError(
|
||||
"DECODE_FAILED",
|
||||
"The image worker stopped unexpectedly.",
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
worker.onmessage = (
|
||||
event: MessageEvent<
|
||||
| { id: number; type: "progress"; stage: ProcessingStage }
|
||||
| { id: number; type: "result"; result: ProcessImageResult }
|
||||
| { id: number; type: "error"; code: ImageErrorCode; message: string }
|
||||
>,
|
||||
) => {
|
||||
const data = event.data;
|
||||
if (data.id !== id || settled) return;
|
||||
if (data.type === "progress") {
|
||||
options.onStage?.(data.stage);
|
||||
} else if (data.type === "result") {
|
||||
finish(() => resolve(data.result));
|
||||
} else {
|
||||
finish(() => reject(new ImageToolError(data.code, data.message)));
|
||||
}
|
||||
};
|
||||
if (options.signal?.aborted) abort();
|
||||
else worker.postMessage({ id, request });
|
||||
});
|
||||
}
|
||||
|
||||
function supportsWorkerPipeline(): boolean {
|
||||
return (
|
||||
typeof Worker !== "undefined" && typeof OffscreenCanvas !== "undefined"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user