openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
151 lines (150 loc) • 5.96 kB
JavaScript
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DnyL0lW9.js";
import { t as resolveSystemBin } from "./resolve-system-bin-IIzLwiri.js";
import { RastermillUnavailableError, createRastermill, isRastermillUnavailableError, readImageProbeFromHeader } from "rastermill";
//#region src/media/image-ops.ts
/** OpenClaw-facing image backend availability error, preserving the failed operation and causes. */
var ImageProcessorUnavailableError = class extends Error {
constructor(operation, message, causes = []) {
super(message ?? `Image processor unavailable for ${operation}`, { cause: causes.find((cause) => cause instanceof Error) });
this.code = "IMAGE_PROCESSOR_UNAVAILABLE";
this.name = "ImageProcessorUnavailableError";
this.operation = operation;
this.causes = causes;
}
};
/** Ordered JPEG quality ladder used when shrinking generated or attached images. */
const IMAGE_REDUCE_QUALITY_STEPS = [
85,
75,
65,
55,
45,
35
];
/** Shared input/output pixel cap for Rastermill-backed image operations. */
const MAX_IMAGE_INPUT_PIXELS = 25e6;
const loadPhotonRuntime = createLazyRuntimeModule(() => import("./photon.runtime.js"));
/** Creates a Rastermill processor with OpenClaw temp-dir, pixel-limit, and command trust policy. */
function createImageProcessor() {
return createRastermill({
execution: "auto",
limits: {
inputPixels: MAX_IMAGE_INPUT_PIXELS,
outputPixels: MAX_IMAGE_INPUT_PIXELS
},
temp: {
rootDir: resolvePreferredOpenClawTmpDir(),
prefix: "openclaw-img-"
},
commandResolver: (command) => resolveSystemBin(command, { trust: command === "powershell" ? "strict" : "standard" })
});
}
/** Detects either OpenClaw's wrapper error or Rastermill's native unavailable error. */
function isImageProcessorUnavailableError(err) {
return err instanceof ImageProcessorUnavailableError || isRastermillUnavailableError(err);
}
/** Builds a descending, de-duplicated max-side search grid for iterative image resizing. */
function buildImageResizeSideGrid(maxSide, sideStart) {
return [
sideStart,
1800,
1600,
1400,
1200,
1e3,
800
].map((value) => Math.min(maxSide, value)).filter((value, idx, arr) => value > 0 && arr.indexOf(value) === idx).toSorted((a, b) => b - a);
}
function resolveDisplayImageMetadata(probe) {
if (!probe) return null;
if (probe.orientation && probe.orientation >= 5 && probe.orientation <= 8) return {
width: probe.height,
height: probe.width
};
return {
width: probe.width,
height: probe.height
};
}
/** Reads display dimensions from image header bytes without invoking a full image decode. */
function readImageMetadataFromHeader(buffer) {
return resolveDisplayImageMetadata(readImageProbeFromHeader(buffer));
}
/** Reads image probe data from header bytes without invoking a full image decode. */
function readImageProbeFromHeader$1(buffer) {
return readImageProbeFromHeader(buffer);
}
function wrapRastermillUnavailable(operation, error) {
if (error instanceof RastermillUnavailableError) throw new ImageProcessorUnavailableError(operation, error.message, error.causes);
throw error;
}
/** Fully probes display dimensions through Rastermill when header-only metadata is insufficient. */
async function getImageMetadata(buffer) {
return resolveDisplayImageMetadata(await createImageProcessor().probe(buffer));
}
/** Resizes or encodes image bytes as JPEG through the shared image processor. */
async function resizeToJpeg(params) {
try {
return (await createImageProcessor().encode(params.buffer, {
format: "jpeg",
resize: {
maxSide: params.maxSide,
enlarge: params.withoutEnlargement === false
},
quality: params.quality
})).data;
} catch (error) {
return wrapRastermillUnavailable("resizeToJpeg", error);
}
}
async function encodeImageToJpeg(buffer, operation) {
try {
return (await createImageProcessor().encode(buffer, { format: "jpeg" })).data;
} catch (error) {
return wrapRastermillUnavailable(operation, error);
}
}
/** Converts image bytes into JPEG through the shared image processor. */
async function convertImageToJpeg(buffer) {
return await encodeImageToJpeg(buffer, "convertImageToJpeg");
}
/** Converts HEIC/HEIF-like image bytes into JPEG through the shared image processor. */
async function convertHeicToJpeg(buffer) {
return await encodeImageToJpeg(buffer, "convertHeicToJpeg");
}
/** Converts image bytes to PNG, including BMP fallback unsupported by Rastermill's Photon gate. */
async function convertImageToPng(buffer) {
try {
return (await createImageProcessor().encode(buffer, { format: "png" })).data;
} catch (error) {
const probe = readImageProbeFromHeader(buffer);
if (!(probe && probe.format === "bmp" && probe.width > 0 && probe.height > 0 && probe.width <= 25e6 / probe.height)) throw error;
try {
return (await loadPhotonRuntime()).convertBmpToPngWithPhoton(buffer);
} catch {
throw error;
}
}
}
/** Optimizes PNG bytes under a target size and returns the chosen search parameters. */
async function optimizeImageToPng(buffer, maxBytes, options) {
let out;
try {
out = await createImageProcessor().encode(buffer, {
format: "png",
maxBytes,
search: options?.sides === void 0 ? {} : { maxSide: options.sides }
});
} catch (error) {
wrapRastermillUnavailable("optimizeImageToPng", error);
}
return {
buffer: out.data,
optimizedSize: out.bytes,
resizeSide: out.chosen.maxSide ?? out.width,
compressionLevel: out.chosen.compressionLevel ?? 6
};
}
//#endregion
export { convertImageToJpeg as a, getImageMetadata as c, readImageMetadataFromHeader as d, readImageProbeFromHeader$1 as f, convertHeicToJpeg as i, isImageProcessorUnavailableError as l, MAX_IMAGE_INPUT_PIXELS as n, convertImageToPng as o, resizeToJpeg as p, buildImageResizeSideGrid as r, createImageProcessor as s, IMAGE_REDUCE_QUALITY_STEPS as t, optimizeImageToPng as u };