new mask editor

This commit is contained in:
Chris
2025-11-29 19:39:02 +11:00
parent dc60707eba
commit c49151495f
4 changed files with 73 additions and 12 deletions
+20
View File
@@ -3,6 +3,11 @@ from comfy.model_management import InterruptProcessingException
import os, random
import torch
import base64
import io
from PIL import Image
import numpy as np
from .image_filter_messaging import send_and_wait, Response, TimeoutResponse
HIDDEN = {
@@ -111,6 +116,7 @@ class TextImageFilterWithExtras(PreviewImage):
"extra2" : ("STRING", {"default":""}),
"extra3" : ("STRING", {"default":""}),
"textareaheight" : ("INT", {"default": 150, "min": 50, "max": 500, "tooltip": "Height of text area in pixels"}),
"graph_id": ("STRING", {"default":""}),
},
"hidden": HIDDEN,
}
@@ -155,6 +161,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
"extra1" : ("STRING", {"default":""}),
"extra2" : ("STRING", {"default":""}),
"extra3" : ("STRING", {"default":""}),
"graph_id": ("STRING", {"default":""}),
},
"hidden": HIDDEN,
}
@@ -181,6 +188,19 @@ class MaskImageFilter(PreviewImage, LoadImage):
return ( *(self.load_image(os.path.join('clipspace', response.masked_image)+" [input]")), *response.get_extras([extra1, extra2, extra3]) )
except FileNotFoundError:
pass
elif (response.masked_data):
data = response.masked_data.split(',',1)[-1]
bytes_data = data.encode('utf-8')
image_data = base64.decodebytes(bytes_data)
data_io = io.BytesIO(image_data)
img = Image.open(data_io)
mask = np.array(img.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
mask = mask.unsqueeze(0)
return ( image, mask, *response.get_extras([extra1, extra2, extra3]) )
if if_no_mask == 'cancel':
raise InterruptProcessingException()
+8 -2
View File
@@ -11,11 +11,17 @@ WAITING_FOR_RESPONSE = "-9"
SPECIALS = [REQUEST_RESHOW, CANCEL, WAITING_FOR_RESPONSE]
class Response:
def __init__(self, selection:Optional[list[str]] = None, text:Optional[str] = None,
masked_image:Optional[str] = None, extras:Optional[list[str]] = None):
def __init__(
self,
selection:Optional[list[str]] = None,
text:Optional[str] = None,
masked_image:Optional[str] = None,
masked_data:Optional[str] = None,
extras:Optional[list[str]] = None):
self.selection:list[int] = [int(x) for x in selection] if selection else []
self.text:Optional[str] = text
self.masked_image:Optional[str] = masked_image
self.masked_data:Optional[str] = masked_data
self.extras:Optional[list[str]] = extras
def get_extras(self,defaults:list[str]) -> list[str]:
+33 -5
View File
@@ -5,6 +5,8 @@ export function new_editor() {
}
function get_mask_editor_element() {
const newer = document.getElementsByClassName('p-dialog-mask')
if (newer.length==1) return newer[0]
return new_editor() ? document.getElementById('maskEditor') : document.getElementById('maskCanvas')?.parentElement
}
@@ -13,17 +15,30 @@ export function mask_editor_showing() {
}
export function hide_mask_editor() {
if (mask_editor_showing()) document.getElementById('maskEditor').style.display = 'none'
if (mask_editor_showing() && document.getElementById('maskEditor')) document.getElementById('maskEditor').style.display = 'none'
}
function get_mask_editor_cancel_button() {
if (document.getElementById("maskEditor_topBarCancelButton")) return document.getElementById("maskEditor_topBarCancelButton")
return get_mask_editor_element?.parentElement?.lastChild?.childNodes[2]
var button = document.getElementById("maskEditor_topBarCancelButton")
if (button) return button
try {
button = Array.from(get_mask_editor_element().getElementsByTagName('button')).find((b)=>(b.ariaLabel=='Cancel'))
if (button) return button
} catch {}
return get_mask_editor_element().parentElement.lastChild.childNodes[2]
}
function get_mask_editor_save_button() {
if (document.getElementById("maskEditor_topBarSaveButton")) return document.getElementById("maskEditor_topBarSaveButton")
return get_mask_editor_element?.parentElement?.lastChild?.childNodes[2]
var button = document.getElementById("maskEditor_topBarSaveButton")
if (button) return button
try {
button = Array.from(get_mask_editor_element().getElementsByTagName('button')).find((b)=>(b.ariaLabel=='Save'))
if (button) return button
} catch {}
return get_mask_editor_element?.parentElement?.lastChild?.childNodes[1]
}
export function mask_editor_listen_for_cancel(callback) {
@@ -40,4 +55,17 @@ export function press_maskeditor_save() {
export function press_maskeditor_cancel() {
get_mask_editor_cancel_button()?.click()
}
export function open_maskeditor(node) {
if (ComfyApp.open_maskeditor) {
ComfyApp.copyToClipspace(node)
ComfyApp.clipspace_return_node = node
ComfyApp.open_maskeditor()
} else {
const me_extension = app.extensions.find((e)=>(e.name=='Comfy.MaskEditor'))
const me_command = me_extension.commands.find((c)=>(c.id=='Comfy.MaskEditor.OpenMaskEditor'))
app.canvas.selected_nodes = [node,]
me_command.function()
}
}
+12 -5
View File
@@ -1,7 +1,7 @@
import { app, ComfyApp } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { mask_editor_listen_for_cancel, mask_editor_showing, hide_mask_editor, press_maskeditor_cancel, press_maskeditor_save, new_editor } from "./mask_utils.js";
import { mask_editor_listen_for_cancel, mask_editor_showing, hide_mask_editor, press_maskeditor_cancel, press_maskeditor_save, new_editor, open_maskeditor } from "./mask_utils.js";
import { Log } from "./log.js";
import { create } from "./utils.js";
import { FloatingWindow } from "./floating_window.js";
@@ -351,13 +351,14 @@ class Popup extends HTMLElement {
} else {
this.state = State.MASK
this.node.imgs = []
this.node.images = []
detail.urls.forEach((url, i)=>{
this.node.imgs.push( new Image() );
this.node.imgs[i].src = api.apiURL( `/view?filename=${encodeURIComponent(url.filename)}&type=${url.type}&subfolder=${url.subfolder}`)
this.node.images.push( url )
})
ComfyApp.copyToClipspace(this.node)
ComfyApp.clipspace_return_node = this.node
ComfyApp.open_maskeditor()
this.node.imageIndex = 0
open_maskeditor(this.node)
this.seen_editor = false
}
setTimeout(this.wait_while_mask_editing.bind(this), 200)
@@ -373,7 +374,13 @@ class Popup extends HTMLElement {
if (mask_editor_showing()) {
setTimeout(this.wait_while_mask_editing.bind(this), 100)
} else {
this._send_response({masked_image:this.extract_filename(this.node.imgs[0].src)})
const masked_image = this.extract_filename(this.node.imgs[0].src)
if (masked_image) {
this._send_response({masked_image:this.extract_filename(this.node.imgs[0].src)})
} else {
this._send_response({masked_data:this.node.imgs[0].src})
}
}
}