UNPKG

@thi.ng/imago

Version:

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

144 lines (143 loc) 4.14 kB
import { isArray, isArrayLike, isNumber, isPlainObject, isString, isTypedArray } from "@thi.ng/checks"; import { illegalArgs } from "@thi.ng/errors"; import { GRAVITY_MAP } from "./api.js"; const round = Math.round; const isIntBufferLike = (x) => isNumber(x.width) && isNumber(x.height) && isTypedArray(x.data) && isPlainObject(x.format); const ensureSize = (meta) => !(isNumber(meta.width) && isNumber(meta.height)) && illegalArgs("can't determine image size"); const coerceColor = (col) => isString(col) ? col : isArrayLike(col) ? { r: col[0], g: col[1], b: col[2], alpha: col[3] ?? 1 } : col; const positionOrGravity = ([w, h], parentSize, { pos, gravity, origin, ref, unit = "px" }) => { if (!pos) return gravity ? { gravity: GRAVITY_MAP[gravity] } : void 0; const [parentW, parentH] = parentSize; let { l, r, t, b } = pos; [l, r, t, b] = computeMargins( [l || 0, r || 0, t || 0, b || 0], parentSize, ref, unit ); let left, top; const [isE, isW, isN, isS] = origin ? gravityFlags(origin) : []; const w2 = w >> 1; const h2 = h >> 1; if (pos.l != null) left = round(l) + (origin ? isW ? 0 : isE ? -w : -w2 : 0); if (pos.r != null) left = round(parentW - r) + (origin ? isW ? 0 : isE ? -w : -w2 : -w); if (pos.t != null) top = round(t) + (origin ? isN ? 0 : isS ? -h : -h2 : 0); if (pos.b != null) top = round(parentH - b) + (origin ? isN ? 0 : isS ? -h : -h2 : -h); return { left, top }; }; const gravityFlags = (gravity) => ["e", "w", "n", "s"].map((x) => gravity.includes(x)); const gravityPosition = (gravity, [w, h], [parentW, parentH]) => [ gravity.includes("w") ? 0 : gravity.includes("e") ? parentW - w : parentW - w >> 1, gravity.includes("n") ? 0 : gravity.includes("s") ? parentH - h : parentH - h >> 1 ]; const refSize = ([w, h], ref) => { let v; switch (ref) { case "w": return [w, w]; case "h": return [h, h]; case "max": v = Math.max(w, h); return [v, v]; case "min": v = Math.min(w, h); return [v, v]; case "both": default: return [w, h]; } }; const computeSize = (size, curr, ref, unit = "px") => { const aspect = curr[0] / curr[1]; let res; if (isNumber(size)) { if (unit === "%") { res = refSize(curr, ref); res = [res[0] * size / 100, res[1] * size / 100]; } else { res = [size, size]; } } else { let [w, h] = size; if (unit === "%") { const [rw, rh] = refSize(curr, ref); w *= rw / 100; h *= rh / 100; size = [w, h]; } res = w >= 0 ? h >= 0 ? size : [w, w / aspect] : h >= 0 ? [h * aspect, h] : illegalArgs( `require at least width or height, but got: ${JSON.stringify( size )}` ); } res[0] = round(res[0]); res[1] = round(res[1]); return res; }; const computeSizeWithAspect = (size, [w, h], aspect, unit = "px", clamp = true) => { const origAspect = w / h; const min = Math.min(w, h); const max = Math.max(w, h); let res; if (unit === "%") { size = size / 100 * max; } if (clamp) { size = Math.min(size, max); if (size / aspect > min) size = min * aspect; } res = origAspect > 1 ? [size, size / aspect] : [size / aspect, size]; res[0] = round(res[0]); res[1] = round(res[1]); return res; }; const computeMargins = (size, curr, ref, unit = "px") => { let res; const refSide = refSize(curr, ref); const isPC = unit === "%"; if (isArray(size) && size.length === 4) { res = isPC ? size.map((x, i) => round(x * refSide[i >> 1] / 100)) : size.map(round); } else if (isNumber(size)) { const w = round(isPC ? refSide[0] * size / 100 : size); const h = round(isPC ? refSide[1] * size / 100 : size); res = [w, w, h, h]; } else { const w = round(isPC ? refSide[0] * size[0] / 100 : size[0]); const h = round(isPC ? refSide[1] * size[1] / 100 : size[1]); res = [w, w, h, h]; } return res; }; export { coerceColor, computeMargins, computeSize, computeSizeWithAspect, ensureSize, gravityFlags, gravityPosition, isIntBufferLike, positionOrGravity, refSize };