Update to 1.3

1.3
This commit is contained in:
chrisgoringe
2025-04-25 18:35:46 +10:00
committed by GitHub
4 changed files with 128 additions and 32 deletions
+26 -15
View File
@@ -2,7 +2,7 @@ from server import PromptServer
from aiohttp import web
from nodes import PreviewImage, LoadImage
from comfy.model_management import InterruptProcessingException, throw_exception_if_processing_interrupted
import time, os
import time, os, random
import torch
REQUEST_RESHOW = "-1"
@@ -32,27 +32,30 @@ async def cg_image_filter_message(request):
print(f"Ignoring response {response} as current response is {Message.data}")
return web.json_response({})
def wait(secs, uid):
def wait(secs, uid, unique):
Message.setdata(None, "start of wait")
end_time = time.monotonic() + secs
while(time.monotonic() < end_time and Message.data is None):
throw_exception_if_processing_interrupted()
PromptServer.instance.send_sync("cg-image-filter-images", {"tick": int(end_time - time.monotonic()), "uid": uid})
PromptServer.instance.send_sync("cg-image-filter-images", {"tick": int(end_time - time.monotonic()), "uid": uid, "unique":unique})
time.sleep(0.2)
response = Message.data
Message.setdata(None, "read response")
if response is None:
PromptServer.instance.send_sync("cg-image-filter-images", {"timeout": True, "uid": uid})
PromptServer.instance.send_sync("cg-image-filter-images", {"timeout": True, "uid": uid, "unique":unique})
return response
def send_with_resend(payload, timeout, uid):
def send_with_resend(payload, timeout, uid, unique):
response = WAITING_FOR_RESPONSE
payload['unique'] = unique
while response in SPECIALS:
PromptServer.instance.send_sync("cg-image-filter-images", payload)
response = wait(timeout, uid)
response = wait(timeout, uid, unique)
if response == CANCEL:
raise InterruptProcessingException()
if Message.unique != unique:
print("Mismatched uniques...")
return response
def mask_to_image(mask:torch.Tensor):
@@ -60,10 +63,17 @@ def mask_to_image(mask:torch.Tensor):
class Message:
data:str = None
unique:str = None
@classmethod
def setdata(cls, v, comment):
#print(f"Message.data set to {v} (${comment})")
cls.data = v
if v:
chop = v.split('!unique!')
cls.data = chop[0]
cls.unique = chop[1] if len(chop)>1 else None
else:
cls.data = None
cls.unique = None
HIDDEN = {
"prompt": "PROMPT",
@@ -87,6 +97,7 @@ class ImageFilter(PreviewImage):
"images" : ("IMAGE", ),
"timeout": ("INT", {"default": 600, "tooltip": "Timeout in seconds."}),
"ontimeout": (["send none", "send all", "send first", "send last"], {}),
"node_identifier": ("INT", {"default":0, "max":99999999}),
},
"optional": {
"latents" : ("LATENT", {"tooltip": "Optional - if provided, will be output"}),
@@ -104,7 +115,7 @@ class ImageFilter(PreviewImage):
def IS_CHANGED(cls, pick_list, **kwargs):
return pick_list or float("NaN")
def func(self, images, timeout, ontimeout, uid, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list:str="", **kwargs):
def func(self, images, timeout, ontimeout, uid, node_identifier, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list:str="", **kwargs):
e1, e2, e3 = extra1, extra2, extra3
B = images.shape[0]
@@ -118,7 +129,7 @@ class ImageFilter(PreviewImage):
urls:list[str] = self.save_images(images=images, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "allsame":all_the_same, "extras":[extra1, extra2, extra3], "tip":tip}
response = send_with_resend(payload, timeout, uid)
response = send_with_resend(payload, timeout, uid, node_identifier)
if response:
response, e1, e2, e3 = response.split("|||")
@@ -151,7 +162,7 @@ class TextImageFilterWithExtras(PreviewImage):
"image" : ("IMAGE", ),
"text" : ("STRING", {"default":""}),
"timeout": ("INT", {"default": 600, "tooltip": "Timeout in seconds."}),
},
"node_identifier": ("INT", {"default":0, "max":99999999}), },
"optional": {
"mask" : ("MASK", {"tooltip": "Optional - if provided, will be overlaid on image"}),
"tip" : ("STRING", {"default":"", "tooltip": "Optional - if provided, will be displayed in popup window"}),
@@ -167,13 +178,13 @@ class TextImageFilterWithExtras(PreviewImage):
def IS_CHANGED(cls, **kwargs):
return float("NaN")
def func(self, image, text, timeout, uid, extra1="", extra2="", extra3="", mask=None, tip="", textareaheight=None, **kwargs):
def func(self, image, text, timeout, uid, node_identifier, extra1="", extra2="", extra3="", mask=None, tip="", textareaheight=None, **kwargs):
urls:list[str] = self.save_images(images=image, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "text":text, "extras":[extra1, extra2, extra3], "tip":tip}
if textareaheight is not None: payload['textareaheight'] = textareaheight
if mask is not None: payload['mask_urls'] = self.save_images(images=mask_to_image(mask), **kwargs)['ui']['images']
response = send_with_resend(payload, timeout, uid)
response = send_with_resend(payload, timeout, uid, node_identifier)
response = response.split("|||") if response else [text, extra1, extra2, extra3]
return (image, *response)
@@ -193,7 +204,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
"image" : ("IMAGE", ),
"timeout": ("INT", {"default": 600, "tooltip": "Timeout in seconds."}),
"if_no_mask": (["cancel", "send blank"], {}),
},
"node_identifier": ("INT", {"default":0, "max":99999999}), },
"optional": {
"mask" : ("MASK", {"tooltip":"optional initial mask"})
},
@@ -207,7 +218,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
@classmethod
def VALIDATE_INPUTS(cls, **kwargs): return True
def func(self, image, timeout, uid, if_no_mask, mask=None, **kwargs):
def func(self, image, timeout, uid, if_no_mask, node_identifier, mask=None, **kwargs):
if mask is not None and mask.shape[:3] == image.shape[:3] and not torch.all(mask==0):
saveable = torch.cat((image, mask.unsqueeze(-1)), dim=-1)
else:
@@ -215,7 +226,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
urls:list[str] = self.save_images(images=saveable, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "maskedit":True}
response = send_with_resend(payload, timeout, uid)
response = send_with_resend(payload, timeout, uid, node_identifier)
if (response):
try:
+16
View File
@@ -126,6 +126,22 @@
}
.cg_popup .zoomed_number {
position: absolute;
top: 40%;
left: 30px;
color: white;
padding: 10px;
z-index: 110000;
background-color: rgba(128, 128, 128, 0.5);
/*text-align: center;*/
}
.cg_popup .zoomed.highlighted .zoomed_image {
border: 3px solid green;
padding: 2px;
}
.cg_popup .buttons button {
margin: 5px 0 5px 0;
padding: 4px;
+24 -1
View File
@@ -3,6 +3,9 @@ import { api } from "../../scripts/api.js";
import { create } from "./utils.js";
import { popup } from "./popup.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
const FILTER_TYPES = ["Image Filter","Text Image Filter","Text Image Filter with Extras","Mask Image Filter"]
app.registerExtension({
name: "cg.image_filter",
@@ -59,5 +62,25 @@ app.registerExtension({
}
}
}
}
if (FILTER_TYPES.includes(nodeType.comfyClass )) {
nodeType.prototype.choose_id = function() {
const v = Math.floor(Math.random() * 1000000)
this.widgets.find((n)=>n.name=='node_identifier').value = v
console.log(`Chose ${v}`)
}
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const idx = this.widgets.findIndex((n)=>n.name=='node_identifier')
const old_widget = (idx>=0) ? this.widgets.splice(idx,1)[0] : null
const new_widget = ComfyWidgets["INT"](this, "node_identifier", ["INT", { "default":0 }], app).widget
new_widget.label = new_widget.name
new_widget.type = 'hidden'
this.choose_id()
return onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
}
}
},
})
+62 -16
View File
@@ -43,6 +43,10 @@ class State {
if (value) item.classList.remove('hidden')
else item.classList.add('hidden')
}
static highlighted(item, value) {
if (value) item.classList.add('highlighted')
else item.classList.remove('highlighted')
}
static render(popup) {
const state = popup.state
@@ -53,6 +57,12 @@ class State {
State.visible(popup.text_edit, state==State.TEXT)
State.visible(popup.send_button, true /*!app.ui.settings.getSettingValue("ImageFilter.ClickSends")*/)
if (state==State.ZOOMED) {
const img_index = popup.zoomed_image_holder.image_index
State.highlighted(popup.zoomed, popup.picked.has(`${img_index}`))
popup.zoomed_number.innerHTML = `${img_index+1}/${popup.n_images}`
}
if (document.getElementById('maskEditor') && state!=State.MASK) {
document.getElementById('maskEditor').style.display = 'none'
}
@@ -70,6 +80,7 @@ class Popup extends HTMLSpanElement {
this.overlaygrid = create('span', 'grid overlaygrid', this)
this.zoomed = create('span', 'zoomed', this)
this.zoomed_image = create('img', 'zoomed_image', this.zoomed)
this.zoomed_number = create('span', 'zoomed_number', this.zoomed)
this.text_edit = create('textarea', 'text_edit', this)
this.title_bar = create('span', 'title', this)
this.buttons = create('span', 'buttons', this)
@@ -93,7 +104,7 @@ class Popup extends HTMLSpanElement {
this.cancel_button.addEventListener('click', this.send_cancel.bind(this) )
this.counter_reset_button.addEventListener('click', this.request_reset.bind(this) )
document.addEventListener("keypress", this.on_key_press.bind(this))
document.addEventListener("keydown", this.on_key_down.bind(this))
document.body.appendChild(this)
this.last_response_sent = 0
@@ -114,6 +125,7 @@ class Popup extends HTMLSpanElement {
try {
const body = new FormData();
msg = `${msg}!unique!${this.unique}`
body.append('response', msg);
api.fetchApi("/cg-image-filter-message", { method: "POST", body, });
Log.message_out(msg)
@@ -130,6 +142,7 @@ class Popup extends HTMLSpanElement {
close() {
this.state = State.INACTIVE
this.node.choose_id()
State.render(this)
}
@@ -153,6 +166,22 @@ class Popup extends HTMLSpanElement {
_handle_message(message, use_saved) {
const uid = message.detail.uid
const unique = message.detail.unique
const node = app.graph._nodes_by_id[uid]
if (!node) {
console.log(`Message was for ${uid} which doesn't exist`)
return
}
if (node.widgets?.find((n)=>n.label=='node_identifier')?.value != message.detail.unique) {
console.log(`Message unique id wasn't mine`)
return
}
this.node = node
this.allsame = message.detail.allsame || false
if (this.state==State.INACTIVE && message.detail.urls && app.ui.settings.getSettingValue("ImageFilter.SmallWindow") && !use_saved && !this.autosend()) {
@@ -170,16 +199,9 @@ class Popup extends HTMLSpanElement {
try {
const detail = message.detail
if (detail.uid) {
const node = app.graph._nodes_by_id[detail.uid]
if (!node) return `Node ${detail.uid} not found`
if (!EXTENSION_NODES.includes(node.type)) return `Node ${detail.uid} is not an image filter node`
} else {
return `No uid in message`
}
if (detail.tick) {
this.counter_text.innerText = `${detail.tick}s`
if (this.state==State.INACTIVE) this.request_reset()
return
}
@@ -300,16 +322,40 @@ class Popup extends HTMLSpanElement {
this.redraw()
}
on_key_press(e) {
if (e.key==' ') {
if (this.state==State.ZOOMED) {
this.state = State.FILTER
} else if (this.mouse_is_over) {
on_key_down(e) {
var used_keypress = false;
if (this.state==State.FILTER) {
if (e.key==' ' && this.mouse_is_over) {
this.state = State.ZOOMED
this.zoomed_image.src = this.mouse_is_over.src
this.zoomed_image_holder = this.mouse_is_over
this.on_mouse_out(this.mouse_is_over)
used_keypress = true
}
State.render(this)
} else if (this.state==State.ZOOMED) {
if (e.key==' ') {
this.state = State.FILTER
this.zoomed_image_holder = null
used_keypress = true
} else if (e.key=='ArrowUp') {
const fake_event = { target:this.zoomed_image_holder}
this.on_click(fake_event)
} else if (e.key=='ArrowDown') {
// select or unselect
} else if (e.key=='ArrowRight') {
this.zoomed_image_holder = this.zoomed_image_holder.nextSibling || this.zoomed_image_holder.parentNode.firstChild
used_keypress = true
} else if (e.key=='ArrowLeft') {
this.zoomed_image_holder = this.zoomed_image_holder.previousSibling || this.zoomed_image_holder.parentNode.lastChild
used_keypress = true
}
}
if (used_keypress) {
e.stopPropagation()
e.preventDefault()
if (this.zoomed_image_holder) this.zoomed_image.src = this.zoomed_image_holder.src
State.render(this)
}
}