Files
2026-11-10 03:00:46 +08:00

137 lines
4.7 KiB
JavaScript

/**
* XB 批量缩放图像 — 动态输入/输出槽位管理
* ===========================================
* N进N出,输入接入后自动新增(最多9个),输出槽位同步匹配。
*/
import { app } from "../../scripts/app.js";
const NODE_TYPES = new Set(["XB_ImageScale", "XB_ImageScalePixels"]);
const MAX_IMAGES = 9;
const FIXED_INPUT_NAMES = new Set([
"upscale_method", "width", "height", "crop",
"megapixels", "resolution_step", "image1",
]);
function isImageInput(inp) {
return !FIXED_INPUT_NAMES.has(inp.name) && (inp.type === "IMAGE" || inp.type === "*");
}
app.registerExtension({
name: "XB_ToolBox.ImageScale",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (!NODE_TYPES.has(nodeData?.name)) return;
const orig = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = orig?.apply(this, arguments);
const self = this;
// Ensure one dynamic image slot exists
const hasDyn = (self.inputs || []).some(inp => isImageInput(inp));
if (!hasDyn) {
self.addInput("image2", "IMAGE");
// Also ensure output slots: image1 is fixed, add image2
manageOutputs(self);
}
const stabilize = () => {
if (!self.graph || self.__xb_removed) return;
let dirty = false;
const inputs = self.inputs;
// ---- manage dynamic inputs ----
const dynIndices = [];
for (let i = 0; i < inputs.length; i++) {
if (isImageInput(inputs[i])) dynIndices.push(i);
}
const inCount = dynIndices.length;
if (inCount > 0 && inCount < MAX_IMAGES) {
const lastDyn = inputs[dynIndices[inCount - 1]];
if (lastDyn?.link != null) {
self.addInput(`image${inCount + 2}`, "IMAGE");
dirty = true;
}
}
for (let di = dynIndices.length - 2; di >= 0; di--) {
if (inputs[dynIndices[di]].link == null) {
self.removeInput(dynIndices[di]);
dirty = true;
}
}
// ---- manage dynamic outputs (match input count) ----
dirty = manageOutputs(self) || dirty;
if (dirty) self.graph.setDirtyCanvas(true, true);
if (!self.__xb_removed) {
self.__xb_timer = setTimeout(stabilize, 300);
}
};
const origConn = self.onConnectionsChange;
self.onConnectionsChange = function () {
if (origConn) origConn.apply(this, arguments);
if (self.__xb_timer) clearTimeout(self.__xb_timer);
self.__xb_timer = setTimeout(stabilize, 80);
};
const origRem = self.onRemoved;
self.onRemoved = function () {
self.__xb_removed = true;
if (self.__xb_timer) { clearTimeout(self.__xb_timer); self.__xb_timer = null; }
if (origRem) origRem.apply(this, arguments);
};
stabilize();
return r;
};
},
async loadedGraphNode(node) {
if (!NODE_TYPES.has(node.type)) return;
const hasDyn = (node.inputs || []).some(inp => isImageInput(inp));
if (!hasDyn) node.addInput("image2", "IMAGE");
manageOutputs(node);
},
});
/** Count connected image inputs (image1 + dynamic). */
function countImageInputs(node) {
let n = 0;
for (const inp of (node.inputs || [])) {
if (inp.type === "IMAGE" || inp.type === "*") n++;
}
return n;
}
/** Sync output slots to match connected input count (min 1, max 9). */
function manageOutputs(node) {
let changed = false;
const totalIns = countImageInputs(node);
const targetOuts = Math.min(totalIns, MAX_IMAGES);
const currentOuts = (node.outputs || []).length;
// Remove trailing unconnected outputs if too many
while ((node.outputs || []).length > targetOuts) {
const last = node.outputs[node.outputs.length - 1];
if (!last.links || last.links.length === 0) {
node.removeOutput(node.outputs.length - 1);
changed = true;
} else {
break; // don't remove connected outputs
}
}
// Add outputs if needed
while ((node.outputs || []).length < targetOuts && (node.outputs || []).length < MAX_IMAGES) {
const idx = node.outputs.length + 1;
node.addOutput(`图像${idx}`, "IMAGE");
changed = true;
}
return changed;
}