Files
2026-07-18 13:16:11 -04:00

111 lines
3.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Live resolution preview for the Resolution Selector node.
// Mirrors compute_dimensions() in resolution_selector.py - keep in sync.
import { app } from "../../../scripts/app.js";
const NODE_NAME = "ResolutionSelectorMP";
console.log("[ResolutionSelector] preview extension loaded");
function computeDimensions(megapixels, ratio, multiple) {
multiple = Math.max(1, Math.floor(multiple || 1));
const target = Math.max(megapixels || 0.01, 0.01) * 1_000_000;
ratio = Math.max(ratio || 1, 1e-6);
const idealH = Math.sqrt(target / ratio);
const idealW = idealH * ratio;
let best = null;
for (const sw of [Math.floor(idealW / multiple), Math.ceil(idealW / multiple)]) {
for (const sh of [Math.floor(idealH / multiple), Math.ceil(idealH / multiple)]) {
const w = Math.max(multiple, sw * multiple);
const h = Math.max(multiple, sh * multiple);
const pxErr = Math.abs(w * h - target) / target;
const ratioErr = Math.abs(Math.log((w / h) / ratio));
const score = pxErr + ratioErr;
if (!best || score < best.score) best = { score, w, h };
}
}
return [best.w, best.h];
}
function updatePreview(node) {
const widgets = node.widgets;
if (!widgets) return;
const val = (n) => widgets.find((w) => w.name === n)?.value;
const preview = widgets.find((w) => w.name === "resolution");
if (!preview) return;
const imageLinked = node.inputs?.some(
(i) => i.name === "image" && i.link != null
);
let text;
if (val("use_image") && val("use_image_ratio") && imageLinked) {
text = "runtime (image ratio)";
} else {
const aw = Math.max(1, val("aspect_width") || 1);
const ah = Math.max(1, val("aspect_height") || 1);
const [w, h] = computeDimensions(
val("megapixels"), aw / ah, val("multiple")
);
text = `${w} × ${h} (${((w * h) / 1_000_000).toFixed(2)} MP)`;
}
if (preview.value !== text) {
preview.value = text;
node.setDirtyCanvas?.(true, false);
}
}
app.registerExtension({
name: "resolutionSelector.livePreview",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== NODE_NAME) return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated?.apply(this, arguments);
if (!this.widgets?.some((w) => w.name === "resolution")) {
const w = this.addWidget(
"text", "resolution", "", () => {}, { serialize: false }
);
w.disabled = true;
}
// Recompute whenever any input widget changes.
const self = this;
for (const widget of this.widgets) {
if (widget.name === "resolution") continue;
const cb = widget.callback;
widget.callback = function (...args) {
const res = cb?.apply(this, args);
updatePreview(self);
return res;
};
}
updatePreview(this);
return r;
};
for (const hook of ["onConfigure", "onConnectionsChange"]) {
const orig = nodeType.prototype[hook];
nodeType.prototype[hook] = function (...args) {
const r = orig?.apply(this, args);
updatePreview(this);
return r;
};
}
// Safety net: keeps the readout current even if a change slips
// past the callbacks (runs on every canvas redraw, cheap).
const onDrawForeground = nodeType.prototype.onDrawForeground;
nodeType.prototype.onDrawForeground = function (...args) {
const r = onDrawForeground?.apply(this, args);
updatePreview(this);
return r;
};
},
});