Sliders and region breakout node

This commit is contained in:
space-nuko
2023-05-23 13:34:56 -05:00
parent 9a40f84e79
commit 631029edf7
7 changed files with 717 additions and 591 deletions

View File

@@ -592,6 +592,7 @@ export default class ComfyApp {
setColor("COMFYBOX_IMAGES", "rebeccapurple") setColor("COMFYBOX_IMAGES", "rebeccapurple")
setColor("COMFYBOX_IMAGE", "fuchsia") setColor("COMFYBOX_IMAGE", "fuchsia")
setColor("COMFYBOX_REGION", "salmon")
setColor(BuiltInSlotType.EVENT, "lightseagreen") setColor(BuiltInSlotType.EVENT, "lightseagreen")
setColor(BuiltInSlotType.ACTION, "lightseagreen") setColor(BuiltInSlotType.ACTION, "lightseagreen")
} }

View File

@@ -151,5 +151,7 @@
.edit { .edit {
border: 2px dashed var(--color-blue-400); border: 2px dashed var(--color-blue-400);
pointer-events: none;
user-select: none;
} }
</style> </style>

View File

@@ -0,0 +1,34 @@
import { LiteGraph, type SlotLayout } from "@litegraph-ts/core";
import ComfyGraphNode from "./ComfyGraphNode";
export default class ComfyRegionToCoordsNode extends ComfyGraphNode {
static slotLayout: SlotLayout = {
inputs: [
{ name: "in", type: "COMFYBOX_REGION" },
],
outputs: [
{ name: "x", type: "number" },
{ name: "y", type: "number" },
{ name: "width", type: "number" },
{ name: "height", type: "number" },
],
}
override onExecute() {
const value = this.getInputData(0);
if (!Array.isArray(value))
return;
this.setOutputData(0, value[0])
this.setOutputData(1, value[1])
this.setOutputData(2, value[2])
this.setOutputData(3, value[3])
}
}
LiteGraph.registerNodeType({
class: ComfyRegionToCoordsNode,
title: "Comfy.RegionToCoords",
desc: "Converts a COMFYBOX_REGION to four outputs of [x, y, width, height]",
type: "utils/region_to_coords"
})

View File

@@ -1,5 +1,3 @@
import "$lib/nodes/ComfyGraphNode";
export { default as ComfyReroute } from "./ComfyReroute" export { default as ComfyReroute } from "./ComfyReroute"
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode" export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
export { default as ComfyValueControl } from "./ComfyValueControl" export { default as ComfyValueControl } from "./ComfyValueControl"
@@ -8,3 +6,4 @@ export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton" export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"
export { default as ComfyPickImageNode } from "./ComfyPickImageNode" export { default as ComfyPickImageNode } from "./ComfyPickImageNode"
export { default as ComfyNoChangeEvent } from "./ComfyNoChangeEvent" export { default as ComfyNoChangeEvent } from "./ComfyNoChangeEvent"
export { default as ComfyRegionToCoordsNode } from "./ComfyRegionToCoordsNode"

View File

@@ -43,10 +43,7 @@ export default class ComfyMultiRegionNode extends ComfyWidgetNode<BoundingBox[]>
{ name: "changed", type: BuiltInSlotType.EVENT }, { name: "changed", type: BuiltInSlotType.EVENT },
// dynamic outputs, may be removed later // dynamic outputs, may be removed later
{ name: "x1", type: "number" }, { name: "region1", type: "COMFYBOX_REGION" },
{ name: "y1", type: "number" },
{ name: "w1", type: "number" },
{ name: "h1", type: "number" },
] ]
} }
@@ -77,24 +74,24 @@ export default class ComfyMultiRegionNode extends ComfyWidgetNode<BoundingBox[]>
let height = this.getInputData(2) || 0 let height = this.getInputData(2) || 0
if (width != this.properties.canvasWidth || height != this.properties.canvasHeight) { if (width != this.properties.canvasWidth || height != this.properties.canvasHeight) {
console.warn("SIZCHANGE", width, height, this.properties.canvasWidth, this.properties.canvasHeight)
this.properties.canvasWidth = width; this.properties.canvasWidth = width;
this.properties.canvasHeight = height; this.properties.canvasHeight = height;
this.sizeChanged.set(true);
this.updateSize(); this.updateSize();
} }
const value = this.getValue(); const value = this.getValue();
for (let index = 0; index < this.properties.regionCount * 2; index += 2) { for (let index = 0; index < this.properties.regionCount; index++) {
const bbox = value[index] const bbox = value[index]
if (bbox != null) { if (bbox != null) {
const xOutput = this.outputs[index + 1] const output = this.outputs[index + 1] // + changed slot
if (xOutput != null) { if (output != null) {
this.setOutputData(index + 1, bbox[0] * this.properties.canvasWidth) let data = this.getOutputData(index) || [0, 0, 0, 0]
this.setOutputData(index + 2, bbox[1] * this.properties.canvasHeight) data[0] = bbox[0] * this.properties.canvasWidth
this.setOutputData(index + 3, bbox[2] * this.properties.canvasWidth) data[1] = bbox[1] * this.properties.canvasHeight
this.setOutputData(index + 4, bbox[3] * this.properties.canvasHeight) data[2] = bbox[2] * this.properties.canvasWidth
data[3] = bbox[3] * this.properties.canvasHeight
this.setOutputData(index, data)
} }
} }
} }
@@ -110,10 +107,7 @@ export default class ComfyMultiRegionNode extends ComfyWidgetNode<BoundingBox[]>
} }
for (let index = 0; index < this.properties.regionCount; index++) { for (let index = 0; index < this.properties.regionCount; index++) {
this.addOutput(`x${index + 1}`, "number") this.addOutput(`region${index + 1}`, "COMFYBOX_REGION")
this.addOutput(`y${index + 1}`, "number")
this.addOutput(`w${index + 1}`, "number")
this.addOutput(`h${index + 1}`, "number")
} }
this.regionsChanged.set(true); this.regionsChanged.set(true);

View File

@@ -9,10 +9,13 @@
import type { BoundingBox } from "$lib/nodes/widgets/ComfyMultiRegionNode"; import type { BoundingBox } from "$lib/nodes/widgets/ComfyMultiRegionNode";
import type { WidgetLayout } from "$lib/stores/layoutStates"; import type { WidgetLayout } from "$lib/stores/layoutStates";
import { Block, BlockLabel } from "@gradio/atoms"; import { Block, BlockLabel } from "@gradio/atoms";
import { Chart as SquareIcon } from "@gradio/icons"; import { Chart as ChartIcon } from "@gradio/icons";
import { Range } from "@gradio/form";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { generateBlankCanvas, loadImage } from "./utils"; import { generateBlankCanvas, loadImage } from "./utils";
import { clamp } from "$lib/utils"; import { clamp } from "$lib/utils";
import Row from "$lib/components/gradio/app/Row.svelte";
import Column from "$lib/components/gradio/app/Column.svelte";
// ref: https://html-color.codes/ // ref: https://html-color.codes/
const COLOR_MAP: [string, string][] = [ const COLOR_MAP: [string, string][] = [
@@ -50,7 +53,6 @@ const COLOR_MAP: [string, string][] = [
$: widget && setNodeValue(widget); $: widget && setNodeValue(widget);
function setNodeValue(widget: WidgetLayout) { function setNodeValue(widget: WidgetLayout) {
console.error("SETNODEVALUE")
if (widget) { if (widget) {
node = widget.node as ComfyMultiRegionNode node = widget.node as ComfyMultiRegionNode
nodeValue = node.value; nodeValue = node.value;
@@ -95,7 +97,6 @@ const COLOR_MAP: [string, string][] = [
} }
$: if (node != null && $sizeChanged) { $: if (node != null && $sizeChanged) {
console.warn("SIZCHANGEd")
updateImage(node.properties.canvasWidth, node.properties.canvasHeight) updateImage(node.properties.canvasWidth, node.properties.canvasHeight)
.then(() => { .then(() => {
return recreateDisplayBoxes() return recreateDisplayBoxes()
@@ -106,7 +107,15 @@ const COLOR_MAP: [string, string][] = [
} }
onMount(async () => { onMount(async () => {
displayBoxes = await recreateDisplayBoxes(node, $nodeValue); if (node) {
updateImage(node.properties.canvasWidth, node.properties.canvasHeight)
.then(() => {
return recreateDisplayBoxes()
})
.then(dbs => {
displayBoxes = dbs;
})
}
}) })
$: if ($regionsChanged) { $: if ($regionsChanged) {
@@ -116,7 +125,6 @@ const COLOR_MAP: [string, string][] = [
async function updateImage(width: number, height: number) { async function updateImage(width: number, height: number) {
showWidget = width > 0 && height > 0; showWidget = width > 0 && height > 0;
console.error("SHOW", showWidget, width, height)
const blank = generateBlankCanvas(width, height, "transparent"); const blank = generateBlankCanvas(width, height, "transparent");
const url = blank.toDataURL(); const url = blank.toDataURL();
const newImg = await loadImage(url); const newImg = await loadImage(url);
@@ -246,7 +254,6 @@ const COLOR_MAP: [string, string][] = [
// Calculate viewport scale based on the current canvas size and the natural image size // Calculate viewport scale based on the current canvas size and the natural image size
let vpScale = Math.min(imageElem.clientWidth / imageElem.naturalWidth, imageElem.clientHeight / imageElem.naturalHeight); let vpScale = Math.min(imageElem.clientWidth / imageElem.naturalWidth, imageElem.clientHeight / imageElem.naturalHeight);
let vpOffset = imageElem.getBoundingClientRect(); let vpOffset = imageElem.getBoundingClientRect();
console.warn(vpScale, vpOffset)
// Calculate scaled dimensions of the canvas // Calculate scaled dimensions of the canvas
let scaledX = imageElem.naturalWidth * vpScale; let scaledX = imageElem.naturalWidth * vpScale;
@@ -382,6 +389,62 @@ const COLOR_MAP: [string, string][] = [
async function onResize() { async function onResize() {
displayBoxes = await recreateDisplayBoxes(); displayBoxes = await recreateDisplayBoxes();
} }
function updateX(newX: number) {
const bbox = $nodeValue[selectedIndex]
const dbox = displayBoxes[selectedIndex]
if (!bbox || !dbox)
return
bbox[0] = newX
displayBoxes[selectedIndex] = displayBoundingBox(bbox, selectedIndex, imageElem, dbox);
}
function updateY(newY: number) {
const bbox = $nodeValue[selectedIndex]
const dbox = displayBoxes[selectedIndex]
if (!bbox || !dbox)
return
bbox[1] = newY
displayBoxes[selectedIndex] = displayBoundingBox(bbox, selectedIndex, imageElem, dbox);
}
function updateWidth(newWidth: number) {
const bbox = $nodeValue[selectedIndex]
const dbox = displayBoxes[selectedIndex]
if (!bbox || !dbox)
return
bbox[2] = newWidth
displayBoxes[selectedIndex] = displayBoundingBox(bbox, selectedIndex, imageElem, dbox);
}
function updateHeight(newHeight: number) {
const bbox = $nodeValue[selectedIndex]
const dbox = displayBoxes[selectedIndex]
if (!bbox || !dbox)
return
bbox[3] = newHeight
displayBoxes[selectedIndex] = displayBoundingBox(bbox, selectedIndex, imageElem, dbox);
}
function updateValue() {
// Clamp regions
const bbox = $nodeValue[selectedIndex]
const dbox = displayBoxes[selectedIndex]
if (bbox && dbox) {
bbox[2] = clamp(bbox[2], 0, 1)
bbox[3] = clamp(bbox[3], 0, 1)
bbox[0] = clamp(bbox[0], 0, 1 - bbox[2])
bbox[1] = clamp(bbox[1], 0, 1 - bbox[3])
displayBoxes[selectedIndex] = displayBoundingBox(bbox, selectedIndex, imageElem, dbox);
}
// Force reactivity after changing a bbox's internal values
$nodeValue = $nodeValue
}
</script> </script>
<svelte:window on:resize={onResize}/> <svelte:window on:resize={onResize}/>
@@ -393,11 +456,13 @@ const COLOR_MAP: [string, string][] = [
<BlockLabel <BlockLabel
label={label} label={label}
show_label={label != ""} show_label={label != ""}
Icon={SquareIcon} Icon={ChartIcon}
float={label != ""} float={label != ""}
/> />
{/if} {/if}
{#if showWidget} {#if showWidget}
{@const selectedBBox = $nodeValue[selectedIndex]}
{@const selectedDBox = displayBoxes[selectedIndex]}
<div class="regions-container"> <div class="regions-container">
<div bind:this={imageContainer} class="regions-image-container"> <div bind:this={imageContainer} class="regions-image-container">
<img bind:this={imageElem} class="regions-image"/> <img bind:this={imageElem} class="regions-image"/>
@@ -427,9 +492,37 @@ const COLOR_MAP: [string, string][] = [
{/each} {/each}
</div> </div>
</div> </div>
{:else} {#if selectedBBox}
<Block>
<Row>
<Range label="X" value={selectedBBox[0]}
show_label={true} minimum={0.0} maximum={1.0} step={0.01}
on:change={(e) => updateX(e.detail)}
on:release={updateValue}
/>
<Range label="Width" value={selectedBBox[2]}
show_label={true} minimum={0.0} maximum={1.0} step={0.01}
on:change={(e) => updateWidth(e.detail)}
on:release={updateValue}
/>
</Row>
<Row>
<Range label="Y" value={selectedBBox[1]}
show_label={true} minimum={0.0} maximum={1.0} step={0.01}
on:change={(e) => updateY(e.detail)}
on:release={updateValue}
/>
<Range label="Height" value={selectedBBox[3]}
show_label={true} minimum={0.0} maximum={1.0} step={0.01}
on:change={(e) => updateHeight(e.detail)}
on:release={updateValue}
/>
</Row>
</Block>
{/if}
{:else}
<div class="regions-empty"> <div class="regions-empty">
<span>(Empty canvas)</span> <span>(No regions)</span>
</div> </div>
{/if} {/if}
</Block> </Block>
@@ -441,7 +534,10 @@ const COLOR_MAP: [string, string][] = [
padding: 0; padding: 0;
.regions-image-container { .regions-image-container {
display: flex;
img { img {
height: 100%;
border: 3px solid var(--input-border-color); border: 3px solid var(--input-border-color);
} }
} }