Files
by-ae-ComfyUI-ByAE-Ideogram…/web/js/caption_layout.js
f87d64c3b1 fix: keep saved scene layout when adding it to the canvas
Additive scene load nudged each region further than the one before it, so
a saved composition was sheared. Every region now moves by the same offset
and is clamped back inside the canvas.

Co-authored-by: AE <by-ae@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-26 21:10:03 +00:00

122 lines
3.5 KiB
JavaScript

/** Pure region layout helpers for Ideogram 4 captions. No DOM. */
export function finiteCoord(value) {
if (typeof value === "boolean" || value == null) return null;
if (typeof value === "string") {
if (value.trim() === "") return null;
} else if (typeof value !== "number") {
return null;
}
const n = Number(value);
return Number.isFinite(n) ? n : null;
}
function clampBboxAxis(value) {
return Math.max(0, Math.min(1000, value));
}
/** Ideogram bbox is [ymin, xmin, ymax, xmax] in 0-1000, origin top-left. */
export function ideogramBboxToRect(bbox) {
if (!Array.isArray(bbox) || bbox.length !== 4) return null;
const coords = bbox.map(finiteCoord);
if (coords.some((v) => v == null)) return null;
let [ymin, xmin, ymax, xmax] = coords;
if (ymin > ymax) [ymin, ymax] = [ymax, ymin];
if (xmin > xmax) [xmin, xmax] = [xmax, xmin];
ymin = clampBboxAxis(ymin);
xmin = clampBboxAxis(xmin);
ymax = clampBboxAxis(ymax);
xmax = clampBboxAxis(xmax);
return {
x: xmin / 1000,
y: ymin / 1000,
w: (xmax - xmin) / 1000,
h: (ymax - ymin) / 1000,
};
}
export function fallbackRegion(index) {
const i = Number.isInteger(index) && index >= 0 ? index : 0;
const row = Math.floor(i / 4);
const col = i % 4;
return {
x: 0.02 + col * 0.25,
y: 0.02 + row * 0.17,
w: 0.22,
h: 0.14,
nobbox: true,
};
}
export function elementToRegion(el, idx) {
if (!el || typeof el !== "object") return null;
const box = {
type: el.type === "text" ? "text" : "obj",
text: el.text || "",
desc: el.desc || "",
palette: Array.isArray(el.color_palette) ? el.color_palette.slice() : [],
};
const rect = ideogramBboxToRect(el.bbox);
if (rect) Object.assign(box, rect);
else Object.assign(box, fallbackRegion(idx));
return box;
}
export function regionCoordsFinite(b) {
return !!b && [b.x, b.y, b.w, b.h].every((n) => typeof n === "number" && Number.isFinite(n));
}
const HEX6 = /^#[0-9A-F]{6}$/;
const HEX3 = /^#[0-9A-F]{3}$/;
/** Ideogram 4 accepts uppercase #RRGGBB. #RGB is expanded; anything else is dropped. */
export function normalizeHex(color) {
if (typeof color !== "string") return null;
const text = color.trim().toUpperCase();
if (HEX6.test(text)) return text;
if (HEX3.test(text)) return "#" + text.slice(1).split("").map((ch) => ch + ch).join("");
return null;
}
export function cleanPalette(colors, limit) {
const out = [];
for (const color of colors || []) {
const hex = normalizeHex(color);
if (!hex) continue;
out.push(hex);
if (limit != null && out.length >= limit) break;
}
return out;
}
function clamp01(v) {
return Math.max(0, Math.min(1, v));
}
function normalizeBoxCoords(b) {
let x = b.x;
let y = b.y;
let w = b.w;
let h = b.h;
if (typeof x !== "number" || !Number.isFinite(x)) x = 0;
if (typeof y !== "number" || !Number.isFinite(y)) y = 0;
if (typeof w !== "number" || !Number.isFinite(w)) w = 0;
if (typeof h !== "number" || !Number.isFinite(h)) h = 0;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
x = clamp01(x);
y = clamp01(y);
w = Math.min(w, 1 - x);
h = Math.min(h, 1 - y);
return { ...b, x, y, w: Math.max(0, w), h: Math.max(0, h) };
}
/** Move every region by the same nudge, then keep it inside the canvas. */
export function offsetAddedRegions(boxes, nudge = 0.05) {
return (boxes || []).map((b) => normalizeBoxCoords({
...b,
x: (typeof b.x === "number" && Number.isFinite(b.x) ? b.x : 0) + nudge,
y: (typeof b.y === "number" && Number.isFinite(b.y) ? b.y : 0) + nudge,
}));
}