UNPKG

@thi.ng/imago

Version:

JSON & API-based declarative and extensible image processing trees/pipelines

107 lines (106 loc) 3.21 kB
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 };