openclaw / src /media /image-processor.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3144483 verified
Raw History Blame Contribute Delete
4.33 kB
import {
RastermillError,
RastermillUnavailableError,
type ImageInput,
type Rastermill,
} from "rastermill";
import { runtimeProcessEntrypoints } from "../infra/runtime-process-entrypoints.js";
import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js";
import { WorkerTaskPool } from "../infra/worker-task-pool.js";
import {
createLocalImageProcessor,
MAX_IMAGE_INPUT_PIXELS,
type ImageProcessorPixelLimits,
} from "./image-processor-config.js";
import type {
ImageProcessorOperation,
ImageProcessorReply,
ImageProcessorRequest,
} from "./image-processor.types.js";
const pool = new WorkerTaskPool<ImageProcessorRequest, ImageProcessorReply>({
workerUrl: resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.imageProcessor),
// Each Photon instance retains a WASM heap; serialize transforms rather than multiply decodes.
maxWorkers: 1,
sharedCompute: true,
});
async function runImageTask(
input: ImageInput,
operation: ImageProcessorOperation,
signal?: AbortSignal,
limits?: ImageProcessorPixelLimits,
): Promise<ImageProcessorReply> {
const reply = await pool.run(
() => ({
...operation,
...(limits ? { limits } : {}),
// The caller may reuse its Buffer. Transfer a dedicated copy only after admission.
input: Uint8Array.from(input instanceof ArrayBuffer ? new Uint8Array(input) : input),
}),
{
timeoutMs: 180_000,
signal,
inputBytes: input.byteLength,
transferList: (request) => [request.input.buffer],
},
);
// Structured cloning preserves Error messages but drops Rastermill's public error codes.
if (reply.kind === "failed" && reply.code && !reply.unavailable) {
reply.error = new RastermillError(reply.code, reply.error.message, { cause: reply.error });
}
return reply;
}
/** Keep cheap probes local and move in-process image computation off the caller's event loop. */
export function createImageProcessor(): Rastermill {
return createImageProcessorWithPixelLimits({
inputPixels: MAX_IMAGE_INPUT_PIXELS,
outputPixels: MAX_IMAGE_INPUT_PIXELS,
});
}
/** Internal operation-specific admission uses the same worker and native fallback owners. */
export function createImageProcessorWithPixelLimits(params: ImageProcessorPixelLimits): Rastermill {
const limits = { inputPixels: params.inputPixels, outputPixels: params.outputPixels };
const local = createLocalImageProcessor("auto", limits);
const workerLimits =
limits.inputPixels === MAX_IMAGE_INPUT_PIXELS && limits.outputPixels === MAX_IMAGE_INPUT_PIXELS
? undefined
: limits;
return {
probe: (input) => local.probe(input),
transparency: async (input) => {
const reply = await runImageTask(input, { kind: "transparency" }, undefined, workerLimits);
if (reply.kind === "failed") {
throw reply.unavailable
? new RastermillUnavailableError("transparency", reply.error.message, [reply.error])
: reply.error;
}
if (reply.kind !== "transparency") {
throw new Error("Unexpected image worker result");
}
return reply.value;
},
encode: async (input, options) => {
const { signal, ...workerOptions } = options ?? {};
const reply = await runImageTask(
input,
{ kind: "encode", options: workerOptions },
signal,
workerLimits,
);
signal?.throwIfAborted();
if (reply.kind === "failed") {
if (!reply.unavailable) {
throw reply.error;
}
// Preserve Rastermill's native-codec and alpha policy when its internal backend declines.
return local.encode(input, options);
}
if (reply.kind !== "encode") {
throw new Error("Unexpected image worker result");
}
const { data, ...value } = reply.value;
return { ...value, data: Buffer.from(data.buffer, data.byteOffset, data.byteLength) };
},
};
}
export async function convertBmpToPngWithWorker(input: Buffer): Promise<Buffer> {
const reply = await runImageTask(input, { kind: "bmpToPng" });
if (reply.kind === "failed") {
throw reply.error;
}
if (reply.kind !== "bmpToPng") {
throw new Error("Unexpected image worker result");
}
return Buffer.from(reply.value.buffer, reply.value.byteOffset, reply.value.byteLength);
}