Sliders and region breakout node
This commit is contained in:
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
34
src/lib/nodes/ComfyRegionToCoordsNode.ts
Normal file
34
src/lib/nodes/ComfyRegionToCoordsNode.ts
Normal 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"
|
||||||
|
})
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
{#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}
|
{: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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user