UNPKG

@thi.ng/imago

Version:

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

130 lines (129 loc) 3.85 kB
import { encode } from "@thi.ng/blurhash"; import { isNumber, isPlainObject } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; import { writeFile, writeJSON } from "@thi.ng/file-io"; import { firstNonNullKey } from "@thi.ng/object-utils"; import { formatPath } from "../path.js"; const outputProc = async (spec, input, ctx) => { const opts = spec; let output = input.clone(); if (opts.blurhash) { await __outputBlurHash(opts, output, ctx); return [input, false]; } if (opts.raw) { await __outputRaw(opts, output, ctx); return [input, false]; } if (ctx.meta.exif && ctx.opts.keepEXIF) { ctx.logger.warn( "TODO injecting & merging EXIF in output still not supported" ); } if (Object.keys(ctx.exif).length) { ctx.logger.debug("setting custom EXIF", ctx.exif); output = output.withExif(ctx.exif); } if (ctx.iccFile && ctx.opts.keepICC) { ctx.logger.debug("using stored ICC profile:", ctx.iccFile); output = output.withIccProfile(ctx.iccFile); } let format = opts.path ? /\.(\w+)$/.exec(opts.path)?.[1] : firstNonNullKey(opts, [ "avif", "gif", "jp2", "jpeg", "jxl", "png", "raw", "tile", "tiff", "webp" ]); switch (format) { case "avif": if (opts.avif) output = output.avif(opts.avif); break; case "gif": if (opts.gif) output = output.gif(opts.gif); break; case "jpg": case "jpeg": if (opts.jpeg) output = output.jpeg(opts.jpeg); break; case "jp2": if (opts.jp2) output = output.jp2(opts.jp2); break; case "jxl": if (opts.jxl) output = output.jxl(opts.jxl); break; case "png": if (opts.png) output = output.png(opts.png); break; case "tiff": if (opts.tiff) output = output.tiff(opts.tiff); break; case "webp": if (opts.webp) output = output.webp(opts.webp); break; } if (opts.tile) output = output.tile(opts.tile); if (format) output = output.toFormat(format); const result = await output.toBuffer(); if (opts.path !== void 0) { const path = formatPath(opts.path, ctx, spec, result); writeFile(path, result, null, ctx.logger); ctx.outputs[opts.id] = path; } else { ctx.outputs[opts.id] = format && opts.dataURL ? asDataURL(`image/${format}`, result) : result; } return [input, false]; }; const __outputRaw = async (opts, output, ctx) => { const { alpha = false, meta = false } = isPlainObject(opts.raw) ? opts.raw : {}; if (alpha) output = output.ensureAlpha(); else output = output.removeAlpha(); const { data, info } = await output.raw().toBuffer({ resolveWithObject: true }); if (meta) { ctx.outputMeta[opts.id] = { ...info, exif: ctx.exif }; } if (opts.path !== void 0) { const path = formatPath(opts.path, ctx, opts, data); writeFile(path, data, null, ctx.logger); ctx.outputs[opts.id] = path; if (meta) { writeJSON( path + ".meta.json", ctx.outputMeta[opts.id], void 0, void 0, ctx.logger ); } } else { ctx.outputs[opts.id] = data; } }; const __outputBlurHash = async (opts, output, ctx) => { const { data, info } = await output.ensureAlpha().raw().toBuffer({ resolveWithObject: true }); const detail = opts.blurhash === true ? 4 : opts.blurhash; const [dx, dy] = isNumber(detail) ? [detail, detail] : detail; const hash = encode( new Uint32Array(data.buffer), info.width, info.height, dx, dy ); ctx.logger.debug("computed blurhash:", hash); ctx.outputs[opts.id] = hash; }; const asDataURL = (mime, data) => { if (data.length > 32768) illegalArgs("encoded image too large for dataURL (max. 32KB allowed)"); return `data:${mime};base64,${data.toString("base64")}`; }; export { asDataURL, outputProc };