mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { Worker } from "node:worker_threads";
|
|
import type { ImageResizeOptions, ResizedImage } from "./image-resize-core.ts";
|
|
|
|
export type { ImageResizeOptions, ResizedImage } from "./image-resize-core.ts";
|
|
|
|
interface ResizeImageWorkerResponse {
|
|
result?: ResizedImage | null;
|
|
error?: string;
|
|
}
|
|
|
|
function toTransferableBytes(input: Uint8Array): Uint8Array<ArrayBuffer> {
|
|
// Transfer detaches the buffer, so transfer a worker-owned copy and leave the
|
|
// caller's bytes intact.
|
|
return new Uint8Array(input);
|
|
}
|
|
|
|
function isResizeImageWorkerResponse(value: unknown): value is ResizeImageWorkerResponse {
|
|
return value !== null && typeof value === "object";
|
|
}
|
|
|
|
function createResizeWorker(): Worker {
|
|
const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
|
|
const workerUrl = new URL(
|
|
isTypeScriptRuntime ? "./image-resize-worker.ts" : "./image-resize-worker.js",
|
|
import.meta.url,
|
|
);
|
|
return new Worker(workerUrl);
|
|
}
|
|
|
|
async function resizeImageInWorker(
|
|
inputBytes: Uint8Array,
|
|
mimeType: string,
|
|
options?: ImageResizeOptions,
|
|
): Promise<ResizedImage | null> {
|
|
const worker = createResizeWorker();
|
|
try {
|
|
const inputBytesForWorker = toTransferableBytes(inputBytes);
|
|
return await new Promise<ResizedImage | null>((resolve, reject) => {
|
|
let settled = false;
|
|
const settle = (result: ResizedImage | null): void => {
|
|
if (settled) return;
|
|
settled = true;
|
|
resolve(result);
|
|
};
|
|
const fail = (error: Error): void => {
|
|
if (settled) return;
|
|
settled = true;
|
|
reject(error);
|
|
};
|
|
|
|
worker.once("message", (message: unknown) => {
|
|
if (!isResizeImageWorkerResponse(message)) {
|
|
fail(new Error("Invalid image resize worker response"));
|
|
return;
|
|
}
|
|
if (message.error) {
|
|
fail(new Error(message.error));
|
|
return;
|
|
}
|
|
settle(message.result ?? null);
|
|
});
|
|
worker.once("error", fail);
|
|
worker.once("exit", (code) => {
|
|
if (!settled) {
|
|
fail(new Error(`Image resize worker exited with code ${code}`));
|
|
}
|
|
});
|
|
worker.postMessage(
|
|
{
|
|
inputBytes: inputBytesForWorker,
|
|
mimeType,
|
|
options,
|
|
},
|
|
[inputBytesForWorker.buffer],
|
|
);
|
|
});
|
|
} finally {
|
|
void worker.terminate().catch(() => undefined);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resize an image to fit within the specified max dimensions and encoded file size.
|
|
* Runs Photon in a worker thread so WASM decoding, resizing, and encoding do not
|
|
* block the TUI event loop. Worker failures are propagated instead of retried on
|
|
* the main thread.
|
|
*/
|
|
export async function resizeImage(
|
|
inputBytes: Uint8Array,
|
|
mimeType: string,
|
|
options?: ImageResizeOptions,
|
|
): Promise<ResizedImage | null> {
|
|
return resizeImageInWorker(inputBytes, mimeType, options);
|
|
}
|
|
|
|
/**
|
|
* Format a dimension note for resized images.
|
|
* This helps the model understand the coordinate mapping.
|
|
*/
|
|
export function formatDimensionNote(result: ResizedImage): string | undefined {
|
|
if (!result.wasResized) {
|
|
return undefined;
|
|
}
|
|
|
|
const scale = result.originalWidth / result.width;
|
|
return `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;
|
|
}
|