refactor(js): extract shared utilities from MaskRectArea modules
- Move readLinkedNumber, getDrawColor, computeCanvasSize to common.js - Import shared functions in mask-rect-area.js and mask-rect-area-advanced.js - Remove duplicate implementations from both MaskRectArea modules - Clean up unused debug parameter in syncLinkedInputsToProperties
This commit is contained in:
+144
@@ -135,3 +135,147 @@ function refreshPreview(event) {
|
||||
}
|
||||
|
||||
api.addEventListener("impact-preview", refreshPreview);
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// MaskRectArea Shared Utilities
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Reads a numeric value from a connected link by inspecting the origin node widget.
|
||||
* More reliable than getInputData() in ComfyUI's frontend execution model.
|
||||
*
|
||||
* @param {LGraphNode} node - LiteGraph node instance
|
||||
* @param {string} inputName - Name of the input to read
|
||||
* @returns {number|null} The numeric value or null if not available
|
||||
*/
|
||||
export function readLinkedNumber(node, inputName) {
|
||||
try {
|
||||
if (!node || !node.graph || !Array.isArray(node.inputs)) {
|
||||
return null;
|
||||
}
|
||||
const inp = node.inputs.find(i => i && i.name === inputName);
|
||||
if (!inp || inp.link == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const link = node.graph.links && node.graph.links[inp.link];
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originNode = node.graph.getNodeById
|
||||
? node.graph.getNodeById(link.origin_id)
|
||||
: null;
|
||||
if (!originNode || !Array.isArray(originNode.widgets) || originNode.widgets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const w = originNode.widgets.find(ww => ww && ww.name === "value")
|
||||
|| originNode.widgets[0];
|
||||
const v = w ? w.value : null;
|
||||
|
||||
return (typeof v === "number") ? v : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a color based on percentage using HSL color space.
|
||||
*
|
||||
* @param {number} percent - Value between 0 and 1
|
||||
* @param {string} alpha - Hex alpha value (e.g., "ff", "80")
|
||||
* @returns {string} Hex color string with alpha (e.g., "#ff8040ff")
|
||||
*/
|
||||
export function getDrawColor(percent, alpha) {
|
||||
let h = 360 * percent;
|
||||
let s = 50;
|
||||
let l = 50;
|
||||
l /= 100;
|
||||
const a = s * Math.min(l, 1 - l) / 100;
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and adjusts canvas size for preview widgets.
|
||||
*
|
||||
* @param {LGraphNode} node - LiteGraph node instance
|
||||
* @param {[number, number]} size - [width, height] array
|
||||
* @param {number} minHeight - Minimum canvas height (REQUIRED)
|
||||
* @param {number} minWidth - Minimum canvas width (REQUIRED)
|
||||
* @returns {void}
|
||||
*/
|
||||
export function computeCanvasSize(node, size, minHeight, minWidth) {
|
||||
// Validate required parameters
|
||||
if (typeof minHeight !== 'number' || typeof minWidth !== 'number') {
|
||||
console.warn('[computeCanvasSize] minHeight and minWidth are required parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
// Null safety check for widgets array
|
||||
if (!node.widgets?.length || node.widgets[0].last_y == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LiteGraph global availability check
|
||||
const NODE_WIDGET_HEIGHT = (typeof LiteGraph !== 'undefined' && LiteGraph.NODE_WIDGET_HEIGHT)
|
||||
? LiteGraph.NODE_WIDGET_HEIGHT
|
||||
: 20;
|
||||
|
||||
let y = node.widgets[0].last_y + 5;
|
||||
let freeSpace = size[1] - y;
|
||||
|
||||
// Compute the height of all non-customCanvas widgets
|
||||
let widgetHeight = 0;
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type !== "customCanvas") {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += NODE_WIDGET_HEIGHT + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there is enough vertical space
|
||||
freeSpace -= widgetHeight;
|
||||
|
||||
// Clamp minimum canvas height
|
||||
if (freeSpace < minHeight) {
|
||||
freeSpace = minHeight;
|
||||
}
|
||||
|
||||
// Allow both grow and shrink to fit content
|
||||
const targetHeight = y + widgetHeight + freeSpace;
|
||||
if (node.size[1] !== targetHeight) {
|
||||
node.size[1] = targetHeight;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Ensure the node width meets the minimum width requirement
|
||||
if (node.size[0] < minWidth) {
|
||||
node.size[0] = minWidth;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "customCanvas") {
|
||||
y += freeSpace;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
|
||||
node.canvasHeight = freeSpace;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { readLinkedNumber, getDrawColor, computeCanvasSize } from "./common.js";
|
||||
function showPreviewCanvas(node, app) {
|
||||
|
||||
const widget = {
|
||||
@@ -15,7 +16,7 @@ function showPreviewCanvas(node, app) {
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
if (!node.canvasHeight) {
|
||||
computeCanvasSize(node, node.size);
|
||||
computeCanvasSize(node, node.size, 220, 240);
|
||||
}
|
||||
|
||||
const visible = true;
|
||||
@@ -202,7 +203,7 @@ function showPreviewCanvas(node, app) {
|
||||
};
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeCanvasSize(node, size);
|
||||
computeCanvasSize(node, size, 220, 240);
|
||||
};
|
||||
|
||||
return {minWidth: 200, minHeight: 200, widget};
|
||||
@@ -377,112 +378,6 @@ function CUSTOM_INT(node, inputName, val, func, config = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function getDrawColor(percent, alpha) {
|
||||
let h = 360 * percent;
|
||||
let s = 50;
|
||||
let l = 50;
|
||||
l /= 100;
|
||||
const a = s * Math.min(l, 1 - l) / 100;
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
|
||||
}
|
||||
|
||||
function computeCanvasSize(node, size) {
|
||||
if (node.widgets[0].last_y == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 220;
|
||||
const MIN_WIDTH = 240;
|
||||
|
||||
// FIX: use real last_y from layout, not slot count
|
||||
let y = node.widgets[0].last_y + 5;
|
||||
let freeSpace = size[1] - y;
|
||||
|
||||
// Compute the height of all non-customCanvas widgets
|
||||
let widgetHeight = 0;
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type !== "customCanvas") {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there is enough vertical space
|
||||
freeSpace -= widgetHeight;
|
||||
|
||||
// Minimum canvas height clamp
|
||||
if (freeSpace < MIN_HEIGHT) {
|
||||
freeSpace = MIN_HEIGHT;
|
||||
}
|
||||
|
||||
// Allow the node height to grow and shrink
|
||||
const targetHeight = y + widgetHeight + freeSpace;
|
||||
if (node.size[1] !== targetHeight) {
|
||||
node.size[1] = targetHeight;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Ensure the node width meets the minimum width requirement
|
||||
if (node.size[0] < MIN_WIDTH) {
|
||||
node.size[0] = MIN_WIDTH;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "customCanvas") {
|
||||
y += freeSpace;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
|
||||
node.canvasHeight = freeSpace;
|
||||
}
|
||||
|
||||
// Reads a numeric value from a connected link by inspecting the origin node widget.
|
||||
// This is more reliable than getInputData() in ComfyUI's frontend execution model.
|
||||
function readLinkedNumber(node, inputName) {
|
||||
try {
|
||||
if (!node || !node.graph || !Array.isArray(node.inputs)) {
|
||||
return null;
|
||||
}
|
||||
const inp = node.inputs.find(i => i && i.name === inputName);
|
||||
if (!inp || inp.link == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const link = node.graph.links && node.graph.links[inp.link];
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originNode = node.graph.getNodeById ? node.graph.getNodeById(link.origin_id) : null;
|
||||
if (!originNode || !Array.isArray(originNode.widgets) || originNode.widgets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Most "Int" nodes expose the value in the first widget named "value".
|
||||
const w = originNode.widgets.find(ww => ww && ww.name === "value") || originNode.widgets[0];
|
||||
const v = w ? w.value : null;
|
||||
|
||||
return (typeof v === "number") ? v : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function syncLinkedInputsToPropertiesAdvanced(node) {
|
||||
let changed = false;
|
||||
|
||||
|
||||
+5
-122
@@ -1,4 +1,5 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { readLinkedNumber, getDrawColor, computeCanvasSize } from "./common.js";
|
||||
function showPreviewCanvas(node, app) {
|
||||
|
||||
const widget = {
|
||||
@@ -15,7 +16,7 @@ function showPreviewCanvas(node, app) {
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
if (!node.canvasHeight) {
|
||||
computeCanvasSize(node, node.size);
|
||||
computeCanvasSize(node, node.size, 200, 200);
|
||||
}
|
||||
|
||||
const visible = true;
|
||||
@@ -65,8 +66,7 @@ function showPreviewCanvas(node, app) {
|
||||
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
|
||||
|
||||
// Keep preview in sync when inputs are driven by links.
|
||||
const DEBUG_PREVIEW_SYNC = false;
|
||||
syncLinkedInputsToProperties(node, DEBUG_PREVIEW_SYNC);
|
||||
syncLinkedInputsToProperties(node);
|
||||
|
||||
// Draw the conditioning zone
|
||||
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
@@ -196,7 +196,7 @@ function showPreviewCanvas(node, app) {
|
||||
};
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeCanvasSize(node, size);
|
||||
computeCanvasSize(node, size, 200, 200);
|
||||
};
|
||||
|
||||
return {minWidth: 200, minHeight: 200, widget};
|
||||
@@ -435,114 +435,7 @@ function CUSTOM_INT(node, inputName, val, func, config = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function getDrawColor(percent, alpha) {
|
||||
let h = 360 * percent;
|
||||
let s = 50;
|
||||
let l = 50;
|
||||
l /= 100;
|
||||
const a = s * Math.min(l, 1 - l) / 100;
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
|
||||
}
|
||||
|
||||
function computeCanvasSize(node, size) {
|
||||
if (node.widgets[0].last_y == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 200;
|
||||
const MIN_WIDTH = 200;
|
||||
|
||||
// Use last_y from LiteGraph layout (fixes excessive node height)
|
||||
let y = node.widgets[0].last_y + 5;
|
||||
let freeSpace = size[1] - y;
|
||||
|
||||
// Compute the height of all non-customCanvas widgets
|
||||
let widgetHeight = 0;
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type !== "customCanvas") {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there is enough vertical space
|
||||
freeSpace -= widgetHeight;
|
||||
|
||||
// Clamp minimum canvas height
|
||||
if (freeSpace < MIN_HEIGHT) {
|
||||
freeSpace = MIN_HEIGHT;
|
||||
}
|
||||
|
||||
// Allow both grow and shrink to fit content
|
||||
const targetHeight = y + widgetHeight + freeSpace;
|
||||
if (node.size[1] !== targetHeight) {
|
||||
node.size[1] = targetHeight;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Ensure the node width meets the minimum width requirement
|
||||
if (node.size[0] < MIN_WIDTH) {
|
||||
node.size[0] = MIN_WIDTH;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "customCanvas") {
|
||||
y += freeSpace;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
|
||||
node.canvasHeight = freeSpace;
|
||||
}
|
||||
|
||||
// Reads a numeric value from a connected link by inspecting the origin node widget.
|
||||
// This is more reliable than getInputData() in ComfyUI's frontend execution model.
|
||||
function readLinkedNumber(node, inputName) {
|
||||
try {
|
||||
if (!node || !node.graph || !Array.isArray(node.inputs)) {
|
||||
return null;
|
||||
}
|
||||
const inp = node.inputs.find(i => i && i.name === inputName);
|
||||
if (!inp || inp.link == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const link = node.graph.links && node.graph.links[inp.link];
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originNode = node.graph.getNodeById ? node.graph.getNodeById(link.origin_id) : null;
|
||||
if (!originNode || !Array.isArray(originNode.widgets) || originNode.widgets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Most "Int" nodes expose the value in the first widget named "value".
|
||||
const w = originNode.widgets.find(ww => ww && ww.name === "value") || originNode.widgets[0];
|
||||
const v = w ? w.value : null;
|
||||
|
||||
return (typeof v === "number") ? v : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function syncLinkedInputsToProperties(node, debug) {
|
||||
function syncLinkedInputsToProperties(node) {
|
||||
let changed = false;
|
||||
|
||||
const vx = readLinkedNumber(node, "x");
|
||||
@@ -590,15 +483,5 @@ function syncLinkedInputsToProperties(node, debug) {
|
||||
}
|
||||
}
|
||||
|
||||
if (debug && changed) {
|
||||
console.log("[MaskRectArea] preview sync from links", {
|
||||
x: node.properties["x"],
|
||||
y: node.properties["y"],
|
||||
w: node.properties["w"],
|
||||
h: node.properties["h"],
|
||||
blur_radius: node.properties["blur_radius"]
|
||||
});
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import configparser
|
||||
import logging
|
||||
import os
|
||||
|
||||
version_code = [8, 28]
|
||||
version_code = [8, 28, 1]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
my_path = os.path.dirname(__file__)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-impact-pack"
|
||||
description = "This node pack offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
|
||||
version = "8.28"
|
||||
version = "8.28.1"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = [
|
||||
"segment-anything",
|
||||
|
||||
Reference in New Issue
Block a user