commit 0a08f55f60cb4dbe2daf36ee005e9bf67d381cfc Author: Davemane42 Date: Sat Mar 25 06:18:34 2023 -0400 MultiAreaConditioning Initial diff --git a/MultiAreaConditioning.js b/MultiAreaConditioning.js new file mode 100644 index 0000000..b25860e --- /dev/null +++ b/MultiAreaConditioning.js @@ -0,0 +1,217 @@ +import { app } from "../scripts/app.js"; + +function CUSTOM_INT(node, inputName, val, func, config = {}) { + return { + widget: node.addWidget( + "number", + inputName, + val, + func, + Object.assign({}, { min: 0, max: 2048, step: 10, precision: 0 }, config) + ), + }; +} + +function addCanvas(node, app) { + const MIN_SIZE = 100; + + function computeSize(size) { + if (node.widgets[0].last_y == null) return; + + let y = node.widgets[0].last_y; + let freeSpace = size[1] - y; + + // Compute the height of all non customtext widgets + let widgetHeight = 0; + for (let i = 0; i < node.widgets.length; i++) { + const w = node.widgets[i]; + if (w.type === "customCanvas") { + + } else { + if (w.computeSize) { + widgetHeight += w.computeSize()[1] + 4; + } else { + widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4; + } + } + } + + // See how large each text input can be + freeSpace -= widgetHeight; + + if (freeSpace < MIN_SIZE) { + // There isnt enough space for all the widgets, increase the size of the node + freeSpace = MIN_SIZE; + node.size[1] = y + widgetHeight + freeSpace; + 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.inputHeight = freeSpace; + } + + const widget = { + type: "customCanvas", + name: "name", + get value() { + //return this.inputEl.value; + }, + set value(x) { + //this.inputEl.value = x; + }, + draw: function (ctx, node, widgetWidth, widgetY) { + if (!this.parent.inputHeight) { + // If we are initially offscreen when created we wont have received a resize event + // Calculate it here instead + computeSize(node.size); + } + + const visible = true //app.canvasblank.ds.scale > 0.5 && this.type === "customCanvas"; + this.canvas.hidden = !visible; + + + const colors = ["rgba(255,0,0,0.5)", "rgba(0,255,0,0.5)", "rgba(0,0,255,0.5)", "rgba(255,255,0,0.5)", "rgba(255,0,255,0.5)", "rgba(0,255,255,0.5)", "rgba(255,0,0,0.5)", "rgba(0,255,0,0.5)"] + const margin = 10 + + const widgetHeight = this.parent.inputHeight + const values = this.parent.properties["values"] + const width = this.parent.properties["width"] + const height = this.parent.properties["height"] + + const scale = Math.min((widgetWidth-margin*2)/width, (widgetHeight-margin*2)/height) + + let backgroudWidth = width * scale + let backgroundHeight = height * scale + + let xOffset = margin + if (backgroudWidth < widgetWidth) { + xOffset += (widgetWidth-backgroudWidth)/2 - margin + } + let yOffset = margin + if (backgroundHeight < widgetHeight) { + yOffset += (widgetHeight-backgroundHeight)/2 - margin + } + + let widgetX = xOffset + widgetY = widgetY + yOffset + + ctx.fillStyle = "#FFFFFF"; + //ctx.fillRect(xOffset, yOffset, backgroudSize, backgroudSize); + ctx.fillRect(widgetX, widgetY, backgroudWidth, backgroundHeight); + + for (const [k, v] of values.entries()) { + + if (node.inputs[k].link === null) {continue;} + + let x = widgetX + v[0]*backgroudWidth/width + let y = widgetY + v[1]*backgroudWidth/width + let w = v[2]*backgroundHeight/height + let h = v[3]*backgroundHeight/height + + ctx.fillStyle = colors[k]; + ctx.fillRect(x, y, w, h); + } + }, + }; + + widget.canvas = document.createElement("canvas"); + widget.canvas.className = "dave-custom-canvas"; + + widget.parent = node; + document.body.appendChild(widget.canvas); + + node.addCustomWidget(widget); + + app.canvas.onDrawBackground = function () { + // Draw node isnt fired once the node is off the screen + // if it goes off screen quickly, the input may not be removed + // this shifts it off screen so it can be moved back if the node is visible. + for (let n in app.graph._nodes) { + n = graph._nodes[n]; + for (let w in n.widgets) { + let wid = n.widgets[w]; + if (Object.hasOwn(wid, "canvas")) { + wid.canvas.style.left = -8000 + "px"; + wid.canvas.style.position = "absolute"; + } + } + } + }; + + node.onRemoved = function () { + // When removing this node we need to remove the input from the DOM + for (let y in this.widgets) { + if (this.widgets[y].canvas) { + this.widgets[y].canvas.remove(); + } + } + }; + + node.onResize = function (size) { + computeSize(size); + } + + return { minWidth: 200, minHeight: 200, widget } +} + +app.registerExtension({ + name: "Comfy.MultiAreaConditioning", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "MultiAreaConditioning") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + this.setProperty("width", 512) + this.setProperty("height", 512) + + this.setProperty("values", []) + for (let i = 0; i < 8; i++) { + this.properties["values"].push([0, 0, 256, 256]) + } + + this.serialize_widgets = true; + + CUSTOM_INT(this, "width", 512, function (v, _, node) {node.properties["width"] = v}) + CUSTOM_INT(this, "height", 512, function (v, _, node) {node.properties["height"] = v}) + + addCanvas(this, app) + + CUSTOM_INT( + this, + "index", + 0, + function (v, _, node) { + + let values = node.properties["values"] + + node.widgets[4].value = values[v][0] + node.widgets[5].value = values[v][1] + node.widgets[6].value = values[v][2] + node.widgets[7].value = values[v][3] + }, + { max: 7 } + + ) + + CUSTOM_INT(this, "x", 0, function (v, _, node) {node.properties["values"][node.widgets[3].value][0] = v}) + CUSTOM_INT(this, "y", 0, function (v, _, node) {node.properties["values"][node.widgets[3].value][1] = v}) + CUSTOM_INT(this, "width", 256, function (v, _, node) {node.properties["values"][node.widgets[3].value][2] = v}) + CUSTOM_INT(this, "height", 256, function (v, _, node) {node.properties["values"][node.widgets[3].value][3] = v}) + + return r; + }; + } + }, +}); diff --git a/MultiAreaConditioning.py b/MultiAreaConditioning.py new file mode 100644 index 0000000..351fdbd --- /dev/null +++ b/MultiAreaConditioning.py @@ -0,0 +1,119 @@ +# Made by Davemane42 + +class MultiAreaConditioning: + def __init__(self) -> None: + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "conditioning1": ("CONDITIONING", ), + "conditioning2": ("CONDITIONING", ), + "conditioning3": ("CONDITIONING", ), + "conditioning4": ("CONDITIONING", ), + "conditioning5": ("CONDITIONING", ), + "conditioning6": ("CONDITIONING", ), + "conditioning7": ("CONDITIONING", ), + "conditioning8": ("CONDITIONING", ), + }, + "hidden": {"extra_pnginfo": "EXTRA_PNGINFO"}, + } + + RETURN_TYPES = ("CONDITIONING", ) + FUNCTION = "doStuff" + CATEGORY = "Dave" + + def doStuff(self, conditioning1, conditioning2, conditioning3, conditioning4, conditioning5, conditioning6, conditioning7, conditioning8, extra_pnginfo=None): + + c = [] + clist = [conditioning1, conditioning2, conditioning3, conditioning4, conditioning5, conditioning6, conditioning7, conditioning8] + values = [] + + # Really jank, only work if 1 node exist + # cant find the node unique_id in python + if extra_pnginfo: + for node in extra_pnginfo["workflow"]["nodes"]: + if node["type"] == "MultiAreaConditioning": + values = node["properties"]["values"] + + for k, conditioning in enumerate(clist): + if values[k][2] == 0 or values[k][3] == 0: continue; + for t in conditioning: + n = [t[0], t[1].copy()] + n[1]['area'] = (values[k][3] // 8, values[k][2] // 8, values[k][1] // 8, values[k][0] // 8) + n[1]['strength'] = 1.0 + n[1]['min_sigma'] = 0.0 + n[1]['max_sigma'] = 99.0 + + c.append(n) + + return (c, ) + +class ConditioningUpscale(): + def __init__(self) -> None: + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "conditioning": ("CONDITIONING", ), + "scalar": ("INT", {"default": 2, "min": 1, "max": 100}), + } + } + + RETURN_TYPES = ("CONDITIONING",) + CATEGORY = "Dave" + + FUNCTION = 'upscale' + + def upscale(self, conditioning, scalar): + + c = [] + for t in conditioning: + + n = [t[0], t[1].copy()] + if 'area' in n[1]: + + n[1]['area'] = tuple(map(lambda x: x*scalar, n[1]['area'])) + + c.append(n) + + return (c, ) + +class ConditioningDebug(): + def __init__(self) -> None: + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "conditioning": ("CONDITIONING", ), + } + } + + RETURN_TYPES = () + FUNCTION = "debug" + + OUTPUT_NODE = True + + CATEGORY = "Dave" + + def debug(self, conditioning): + print("\nDebug") + for i, t in enumerate(conditioning): + print(f"{i}:") + if "area" in t[1]: + print(f"\tx{t[1]['area'][3]*8} y{t[1]['area'][2]*8} \n\tw{t[1]['area'][1]*8} h{t[1]['area'][0]*8} \n\tstrength: {t[1]['strength']}") + else: + print(f"\tFullscreen") + + return (None, ) + +NODE_CLASS_MAPPINGS = { + "MultiAreaConditioning": MultiAreaConditioning, + "ConditioningUpscale": ConditioningUpscale, + #"ConditioningDebug": ConditioningDebug, +} \ No newline at end of file