@thi.ng/imago
Version:
JSON & API-based declarative and extensible image processing trees/pipelines
144 lines (143 loc) • 4.14 kB
JavaScript
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
};