@thi.ng/imago
Version:
JSON & API-based declarative and extensible image processing trees/pipelines
107 lines (106 loc) • 3.21 kB
JavaScript
import { isArrayBufferView, isString } from "@thi.ng/checks";
import { defmulti } from "@thi.ng/defmulti";
import { createTempFile, deleteFile } from "@thi.ng/file-io";
import { ROOT } from "@thi.ng/logger";
import sharp, {} from "sharp";
import { blurProc } from "./ops/blur.js";
import { compositeProc } from "./ops/composite.js";
import { cropProc } from "./ops/crop.js";
import { ditherProc } from "./ops/dither.js";
import { exifProc } from "./ops/exif.js";
import { extendProc } from "./ops/extend.js";
import { gammaProc } from "./ops/gamma.js";
import { grayscaleProc } from "./ops/grayscale.js";
import { hsblProc } from "./ops/hsbl.js";
import { iccProc } from "./ops/icc.js";
import { nestProc } from "./ops/nest.js";
import { outputProc } from "./ops/output.js";
import { resizeProc } from "./ops/resize.js";
import { rotateProc } from "./ops/rotate.js";
import { ensureSize, isIntBufferLike } from "./utils.js";
const LOGGER = ROOT.childLogger("imgproc");
const processImage = async (src, specs, opts = {}, parentCtx) => {
let img = isString(src) || isArrayBufferView(src) ? sharp(src) : isIntBufferLike(src) ? sharp(new Uint8Array(src.data.buffer), {
raw: {
width: src.width,
height: src.height,
channels: src.format.channels.length
}
}) : src;
const meta = await img.metadata();
ensureSize(meta);
const ctx = {
path: isString(src) ? src : parentCtx?.path,
outputs: parentCtx ? parentCtx.outputs : {},
outputMeta: parentCtx ? parentCtx.outputMeta : {},
env: parentCtx ? parentCtx.env : opts.env || {},
logger: opts.logger || LOGGER,
size: [meta.width, meta.height],
exif: parentCtx ? structuredClone(parentCtx.exif) : {},
opts: { ...opts },
meta
};
if (!parentCtx) {
if (meta.exif && opts.keepEXIF) {
ctx.logger.warn("TODO keeping input EXIF still unsupported");
}
if (meta.icc && opts.keepICC) {
ctx.logger.debug("storing input ICC profile...");
ctx.iccFile = createTempFile(meta.icc, ctx.logger);
}
}
try {
let bake;
for (let spec of specs) {
ctx.logger.debug("processing spec:", spec);
[img, bake] = await processor(spec, img, ctx);
if (!bake) {
ctx.logger.debug("skip baking processor's results...");
continue;
}
const { data, info } = await img.raw().toBuffer({ resolveWithObject: true });
ctx.size = [info.width, info.height];
img = sharp(data, {
raw: {
width: info.width,
height: info.height,
channels: info.channels
}
});
}
return {
img,
meta,
env: ctx.env,
outputs: ctx.outputs,
outputMeta: ctx.outputMeta
};
} finally {
if (ctx.iccFile) deleteFile(ctx.iccFile, ctx.logger);
}
};
const processor = defmulti(
(spec) => spec.op,
{},
{
blur: blurProc,
composite: compositeProc,
crop: cropProc,
dither: ditherProc,
exif: exifProc,
extend: extendProc,
gamma: gammaProc,
gray: grayscaleProc,
hsbl: hsblProc,
icc: iccProc,
nest: nestProc,
output: outputProc,
resize: resizeProc,
rotate: rotateProc
}
);
export {
LOGGER,
processImage,
processor
};