openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
166 lines (165 loc) • 6.48 kB
JavaScript
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DOKojISm.js";
import { t as resolveSystemBin } from "./resolve-system-bin-CJQrVEby.js";
import { RastermillError, RastermillUnavailableError, createRastermill, isRastermillUnavailableError, readImageMetadataFromHeader, 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;
/** 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);
}
/** Reads dimensions from image header bytes without invoking a full image decode. */
function readImageMetadataFromHeader$1(buffer) {
return readImageMetadataFromHeader(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 image dimensions through Rastermill when header-only metadata is insufficient. */
async function getImageMetadata(buffer) {
const info = await createImageProcessor().probe(buffer);
return info ? {
width: info.width,
height: info.height
} : null;
}
/** Normalizes EXIF orientation when possible while leaving bytes unchanged if the backend is unavailable. */
async function normalizeExifOrientation(buffer) {
try {
const rastermill = createImageProcessor();
const info = await rastermill.probe(buffer);
if (!info) return (await rastermill.encode(buffer, {
format: "jpeg",
autoOrient: true
})).data;
if (!info?.orientation || info.orientation === 1) return buffer;
return (await rastermill.encode(buffer, {
format: "jpeg",
autoOrient: true
})).data;
} catch (error) {
if (isImageProcessorUnavailableError(error)) return buffer;
throw error;
}
}
/** 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);
}
}
/** Converts HEIC/HEIF-like image bytes into JPEG through the shared image processor. */
async function convertHeicToJpeg(buffer) {
try {
return (await createImageProcessor().encode(buffer, { format: "jpeg" })).data;
} catch (error) {
return wrapRastermillUnavailable("convertHeicToJpeg", error);
}
}
/** Detects alpha support using a full transparency probe, falling back to trusted header metadata. */
async function hasAlphaChannel(buffer) {
try {
return (await createImageProcessor().transparency(buffer)).hasAlphaChannel;
} catch (error) {
const headerHasAlpha = readImageProbeFromHeader(buffer)?.hasAlpha === true;
if (isRastermillUnavailableError(error)) return headerHasAlpha;
if (error instanceof RastermillError && error.code === "RASTERMILL_UNDECODABLE" && readImageProbeFromHeader(buffer)) return headerHasAlpha;
throw error;
}
}
/** Resizes or encodes image bytes as PNG through the shared image processor. */
async function resizeToPng(params) {
try {
return (await createImageProcessor().encode(params.buffer, {
format: "png",
resize: {
maxSide: params.maxSide,
enlarge: params.withoutEnlargement === false
},
...params.compressionLevel === void 0 ? {} : { compressionLevel: params.compressionLevel }
})).data;
} catch (error) {
return wrapRastermillUnavailable("resizeToPng", 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 { convertHeicToJpeg as a, hasAlphaChannel as c, optimizeImageToPng as d, readImageMetadataFromHeader$1 as f, resizeToPng as h, buildImageResizeSideGrid as i, isImageProcessorUnavailableError as l, resizeToJpeg as m, ImageProcessorUnavailableError as n, createImageProcessor as o, readImageProbeFromHeader$1 as p, MAX_IMAGE_INPUT_PIXELS as r, getImageMetadata as s, IMAGE_REDUCE_QUALITY_STEPS as t, normalizeExifOrientation as u };