clipspace feature added

maskeditor feature added
This commit is contained in:
Dr.Lt.Data 2023-04-22 12:31:48 +09:00
parent f5af731ffd
commit 588456583d
3 changed files with 437 additions and 271 deletions

View File

@ -8,6 +8,7 @@ import uuid
import json import json
import glob import glob
from PIL import Image from PIL import Image
import numpy as np
try: try:
import aiohttp import aiohttp
@ -112,19 +113,24 @@ class PromptServer():
files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True) files = glob.glob(os.path.join(self.web_root, 'extensions/**/*.js'), recursive=True)
return web.json_response(list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files))) return web.json_response(list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files)))
def get_dir_by_type(dir_type):
if dir_type is None:
type_dir = folder_paths.get_input_directory()
elif dir_type == "input":
type_dir = folder_paths.get_input_directory()
elif dir_type == "temp":
type_dir = folder_paths.get_temp_directory()
elif dir_type == "output":
type_dir = folder_paths.get_output_directory()
return type_dir
@routes.post("/upload/image") @routes.post("/upload/image")
async def upload_image(request): async def upload_image(request):
post = await request.post() post = await request.post()
image = post.get("image") image = post.get("image")
if post.get("type") is None: upload_dir = get_dir_by_type(post.get("type"))
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "input":
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "temp":
upload_dir = folder_paths.get_temp_directory()
elif post.get("type") == "output":
upload_dir = folder_paths.get_output_directory()
if not os.path.exists(upload_dir): if not os.path.exists(upload_dir):
os.makedirs(upload_dir) os.makedirs(upload_dir)
@ -155,14 +161,7 @@ class PromptServer():
image = post.get("image") image = post.get("image")
original_image = post.get("original_image") original_image = post.get("original_image")
if post.get("type") is None: upload_dir = get_dir_by_type(post.get("type"))
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "input":
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "temp":
upload_dir = folder_paths.get_temp_directory()
elif post.get("type") == "output":
upload_dir = folder_paths.get_output_directory()
if not os.path.exists(upload_dir): if not os.path.exists(upload_dir):
os.makedirs(upload_dir) os.makedirs(upload_dir)
@ -180,10 +179,13 @@ class PromptServer():
filepath = os.path.join(upload_dir, filename) filepath = os.path.join(upload_dir, filename)
original_pil = Image.open(original_image) original_pil = Image.open(original_image.file).convert('RGBA')
mask_pil = Image.open(image.file) mask_pil = Image.open(image.file).convert('RGBA')
alpha_channel = mask_pil.getchannel('A')
original_pil.putalpha(alpha_channel, (0, 0)) # alpha copy
new_alpha = mask_pil.getchannel('A')
original_pil.putalpha(new_alpha)
original_pil.save(filepath) original_pil.save(filepath)
return web.json_response({"name": filename}) return web.json_response({"name": filename})

View File

@ -2,268 +2,54 @@ import { app } from "/scripts/app.js";
import { ComfyDialog, $el } from "/scripts/ui.js"; import { ComfyDialog, $el } from "/scripts/ui.js";
import { ComfyApp } from "/scripts/app.js"; import { ComfyApp } from "/scripts/app.js";
// Helper function to convert a data URL to a Blob object export class ClipspaceDialog extends ComfyDialog {
function dataURLToBlob(dataURL) { static items = [];
const parts = dataURL.split(';base64,'); static is_opened = false; // prevent redundant popup
const contentType = parts[0].split(':')[1];
const byteString = atob(parts[1]); static registerButton(name, callback) {
const arrayBuffer = new ArrayBuffer(byteString.length); const item =
const uint8Array = new Uint8Array(arrayBuffer); $el("button", {
for (let i = 0; i < byteString.length; i++) { type: "button",
uint8Array[i] = byteString.charCodeAt(i); textContent: name,
onclick: callback
})
ClipspaceDialog.items.push(item);
} }
return new Blob([arrayBuffer], { type: contentType });
}
async function invalidateImage(filepath, formData) {
await fetch('/upload/image', {
method: 'POST',
body: formData
}).then(response => {}).catch(error => {
console.error('Error:', error);
});
ComfyApp.clipspace.imgs[0] = new Image();
ComfyApp.clipspace.imgs[0].src = `view?filename=${filepath.filename}&type=${filepath.type}`;
}
class ClipspaceDialog extends ComfyDialog {
constructor() { constructor() {
super(); super();
this.element = $el("div.comfy-modal", { parent: document.body }, this.element =
[ $el("div.comfy-modal", { parent: document.body },
$el("div.comfy-modal-content", [$el("div.comfy-modal-content",[...this.createButtons()]),]
[ );
...this.createButtons()]),
]);
} }
createButtons() { createButtons() {
return [ const buttons = [];
for(let idx in ClipspaceDialog.items) {
const item = ClipspaceDialog.items[idx];
buttons.push(ClipspaceDialog.items[idx]);
}
buttons.push(
$el("button", { $el("button", {
type: "button", type: "button",
textContent: "Save", textContent: "Close",
onclick: () => { onclick: () => {
const backupCtx = this.backupCanvas.getContext('2d', {transparent: true}); ClipspaceDialog.is_opened = false;
backupCtx.clearRect(0,0,this.backupCanvas.width,this.backupCanvas.height);
backupCtx.drawImage(this.maskCanvas,
0, 0, this.maskCanvas.width, this.maskCanvas.height,
0, 0, this.backupCanvas.width, this.backupCanvas.height);
// paste mask data into alpha channel
const backupData = backupCtx.getImageData(0, 0, this.backupCanvas.width, this.backupCanvas.height);
for (let i = 0; i < backupData.data.length; i += 4) {
if(backupData.data[i+3] == 255)
backupData.data[i+3] = 0;
else
backupData.data[i+3] = 255;
backupData.data[i] = 0;
backupData.data[i+1] = 0;
backupData.data[i+2] = 0;
}
backupCtx.globalCompositeOperation = 'source-over';
backupCtx.putImageData(backupData, 0, 0);
const dataURL = this.backupCanvas.toDataURL();
const blob = dataURLToBlob(dataURL);
/*
// copy image data
backupCtx.globalCompositeOperation = 'copy';
backupCtx.globalAlpha = 1.0;
backupCtx.drawImage(this.image, 0, 0);
backupCtx.globalCompositeOperation = 'source-over';
const backupData2 = backupCtx.getImageData(0, 0, this.backupCanvas.width, this.backupCanvas.height);
// restore alpha channel
var cnt_r = 0;
for (let i = 0; i < backupData2.data.length; i += 4) {
if(backupData2.data[i] == 0) {
cnt_r++;
}
backupData2.data[i + 3] = backupData.data[i + 3];
}
// I don't know why RGB channel is effected by this code....
backupCtx.putImageData(backupData2, 0, 0);
const dataURL2 = this.backupCanvas.toDataURL();
const blob2 = dataURLToBlob(dataURL2);
*/
const formData = new FormData();
const filename = "clipspace-mask-" + performance.now() + ".png";
const item =
{
"filename": filename,
"subfolder": "",
"type": "temp",
};
console.log(ComfyApp.clipspace);
if(ComfyApp.clipspace.images)
ComfyApp.clipspace.images[0] = item;
if(ComfyApp.clipspace.widgets) {
const index = ComfyApp.clipspace.widgets.findIndex(obj => obj.name === 'image');
console.log(index);
ComfyApp.clipspace.widgets[index].value = item;
}
formData.append('image', blob, filename);
formData.append('original-imagepath', ComfyApp.clipspace.);
formData.append('type', "temp");
invalidateImage(item, formData);
this.close(); this.close();
} }
}), })
$el("button", { );
type: "button",
textContent: "Cancel", return buttons;
onclick: () => this.close(),
}),
$el("button", {
type: "button",
textContent: "Clear",
onclick: () => {
this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
},
}),
];
} }
show() { show() {
const imgCanvas = document.createElement('canvas'); ClipspaceDialog.is_opened = true;
const maskCanvas = document.createElement('canvas');
const backupCanvas = document.createElement('canvas');
imgCanvas.id = "imageCanvas";
maskCanvas.id = "maskCanvas";
backupCanvas.id = "backupCanvas";
this.element.appendChild(imgCanvas);
this.element.appendChild(maskCanvas);
this.element.style.display = "block"; this.element.style.display = "block";
imgCanvas.style.position = "relative";
imgCanvas.style.top = "200";
imgCanvas.style.left = "0";
maskCanvas.style.position = "absolute";
const imgCtx = imgCanvas.getContext('2d');
const maskCtx = maskCanvas.getContext('2d');
const backupCtx = backupCanvas.getContext('2d');
this.maskCanvas = maskCanvas;
this.maskCtx = maskCtx;
this.backupCanvas = backupCanvas;
window.addEventListener("resize", () => {
// repositioning
imgCanvas.width = window.innerWidth - 250;
imgCanvas.height = window.innerHeight - 300;
// redraw image
let drawWidth = image.width;
let drawHeight = image.height;
if (image.width > imgCanvas.width) {
drawWidth = imgCanvas.width;
drawHeight = (drawWidth / image.width) * image.height;
}
if (drawHeight > imgCanvas.height) {
drawHeight = imgCanvas.height;
drawWidth = (drawHeight / image.height) * image.width;
}
imgCtx.drawImage(image, 0, 0, drawWidth, drawHeight);
// update mask
backupCtx.drawImage(maskCanvas, 0, 0, maskCanvas.width, maskCanvas.height, 0, 0, backupCanvas.width, backupCanvas.height);
maskCanvas.width = drawWidth;
maskCanvas.height = drawHeight;
maskCanvas.style.top = imgCanvas.offsetTop + "px";
maskCanvas.style.left = imgCanvas.offsetLeft + "px";
maskCtx.drawImage(backupCanvas, 0, 0, backupCanvas.width, backupCanvas.height, 0, 0, maskCanvas.width, maskCanvas.height);
});
// image load
const image = new Image();
image.onload = function() {
backupCanvas.width = image.width;
backupCanvas.height = image.height;
window.dispatchEvent(new Event('resize'));
};
const filepath = ComfyApp.clipspace.images;
console.log(ComfyApp.clipspace);
console.log(ComfyApp.clipspace.imgs[0]);
image.src = ComfyApp.clipspace.imgs[0].src;
this.image = image;
// event handler for user drawing ------
let brush_size = 10;
function draw_point(event) {
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.fillStyle = "rgb(0,0,0)";
maskCtx.globalCompositeOperation = "source-over";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
function draw_move(event) {
if (event.buttons === 1) {
event.preventDefault();
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.fillStyle = "rgb(0,0,0)";
maskCtx.globalCompositeOperation = "source-over";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
else if(event.buttons === 2) {
event.preventDefault();
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.globalCompositeOperation = "destination-out";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
}
function handleWheelEvent(event) {
if(event.deltaY < 0)
brush_size = Math.min(brush_size+2, 100);
else
brush_size = Math.max(brush_size-2, 1);
}
maskCanvas.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
maskCanvas.addEventListener('wheel', handleWheelEvent);
maskCanvas.addEventListener('mousedown', draw_point);
maskCanvas.addEventListener('mousemove', draw_move);
maskCanvas.addEventListener('touchmove', draw_move);
} }
} }
@ -272,11 +58,13 @@ app.registerExtension({
init(app) { init(app) {
app.openClipspace = app.openClipspace =
function () { function () {
let dlg = new ClipspaceDialog(app); if(!ClipspaceDialog.is_opened) {
if(ComfyApp.clipspace) let dlg = new ClipspaceDialog(app);
dlg.show(); if(ComfyApp.clipspace)
else dlg.show();
app.ui.dialog.show("Clipspace is Empty!"); else
app.ui.dialog.show("Clipspace is Empty!");
}
}; };
} }
}); });

View File

@ -0,0 +1,376 @@
import { app } from "/scripts/app.js";
import { ComfyDialog, $el } from "/scripts/ui.js";
import { ComfyApp } from "/scripts/app.js";
import { ClipspaceDialog } from "/extensions/core/clipspace.js";
// Helper function to convert a data URL to a Blob object
function dataURLToBlob(dataURL) {
const parts = dataURL.split(';base64,');
const contentType = parts[0].split(':')[1];
const byteString = atob(parts[1]);
const arrayBuffer = new ArrayBuffer(byteString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uint8Array[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], { type: contentType });
}
function loadedImageToBlob(image) {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const dataURL = canvas.toDataURL('image/png', 1);
const blob = dataURLToBlob(dataURL);
return blob;
}
async function uploadMask(filepath, formData) {
await fetch('/upload/mask', {
method: 'POST',
body: formData
}).then(response => {}).catch(error => {
console.error('Error:', error);
});
ComfyApp.clipspace.imgs[0] = new Image();
ComfyApp.clipspace.imgs[0].src = `view?filename=${filepath.filename}&type=${filepath.type}`;
ComfyApp.clipspace.images = [filepath];
}
function removeRGB(image, backupCanvas, backupCtx, maskCtx) {
// paste mask data into alpha channel
backupCtx.drawImage(image, 0, 0, backupCanvas.width, backupCanvas.height);
const backupData = backupCtx.getImageData(0, 0, backupCanvas.width, backupCanvas.height);
// refine mask image
for (let i = 0; i < backupData.data.length; i += 4) {
if(backupData.data[i+3] == 255)
backupData.data[i+3] = 0;
else
backupData.data[i+3] = 255;
backupData.data[i] = 0;
backupData.data[i+1] = 0;
backupData.data[i+2] = 0;
}
backupCtx.globalCompositeOperation = 'source-over';
backupCtx.putImageData(backupData, 0, 0);
}
class MaskEditorDialog extends ComfyDialog {
constructor() {
super();
this.element = $el("div.comfy-modal", { parent: document.body },
[
$el("div.comfy-modal-content",
[
...this.createButtons()]),
]);
}
createButtons() {
return [];
// $el("button", {
// type: "button",
// textContent: "Save",
// onclick: () => this.save(),
// }),
// $el("button", {
// type: "button",
// textContent: "Cancel",
// onclick: () => this.close(),
// }),
// $el("button", {
// type: "button",
// textContent: "Clear",
// onclick: () => {
// this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
// this.backupCtx.clearRect(0, 0, this.backupCanvas.width, this.backupCanvas.height);
// },
// }),
// ];
}
clearMask(self) {
}
setlayout(imgCanvas, maskCanvas) {
const self = this;
var bottom_panel = document.createElement("div");
bottom_panel.style.position = "fixed";
bottom_panel.style.bottom = "0";
bottom_panel.style.left = "0";
bottom_panel.style.right = "0";
bottom_panel.style.height = "50px";
var clearButton = document.createElement("button");
clearButton.innerText = "Clear";
clearButton.style.position = "absolute";
clearButton.style.left = "20px";
clearButton.addEventListener("click", function() {
self.maskCtx.clearRect(0, 0, self.maskCanvas.width, self.maskCanvas.height);
self.backupCtx.clearRect(0, 0, self.backupCanvas.width, self.backupCanvas.height);
});
var saveButton = document.createElement("button");
saveButton.innerText = "Save";
saveButton.style.position = "absolute";
saveButton.style.right = "110px";
saveButton.addEventListener("click", function() { self.save(); });
var cancelButton = document.createElement("button");
cancelButton.innerText = "Cancel";
cancelButton.style.position = "absolute";
cancelButton.style.right = "20px";
cancelButton.addEventListener("click", function() { self.close(); });
this.element.appendChild(imgCanvas);
this.element.appendChild(maskCanvas);
this.element.appendChild(bottom_panel);
bottom_panel.appendChild(clearButton);
bottom_panel.appendChild(saveButton);
bottom_panel.appendChild(cancelButton);
this.element.style.display = "block";
imgCanvas.style.position = "relative";
imgCanvas.style.top = "200";
imgCanvas.style.left = "0";
maskCanvas.style.position = "absolute";
}
show() {
// layout
const imgCanvas = document.createElement('canvas');
const maskCanvas = document.createElement('canvas');
const backupCanvas = document.createElement('canvas');
imgCanvas.id = "imageCanvas";
maskCanvas.id = "maskCanvas";
backupCanvas.id = "backupCanvas";
this.setlayout(imgCanvas, maskCanvas);
// prepare content
this.maskCanvas = maskCanvas;
this.backupCanvas = backupCanvas;
this.maskCtx = maskCanvas.getContext('2d');
this.backupCtx = backupCanvas.getContext('2d');
// separate original_imgs and imgs
if(ComfyApp.clipspace.imgs[0] === ComfyApp.clipspace.original_imgs[0]) {
console.log(ComfyApp.clipspace.imgs[0]);
var copiedImage = new Image();
copiedImage.src = ComfyApp.clipspace.original_imgs[0].src;
ComfyApp.clipspace.imgs = [copiedImage];
}
this.setImages(imgCanvas, backupCanvas);
this.setEventHandler(maskCanvas);
}
setImages(imgCanvas, backupCanvas) {
const imgCtx = imgCanvas.getContext('2d');
const backupCtx = backupCanvas.getContext('2d');
const maskCtx = this.maskCtx;
const maskCanvas = this.maskCanvas;
// image load
const orig_image = new Image();
window.addEventListener("resize", () => {
// repositioning
imgCanvas.width = window.innerWidth - 250;
imgCanvas.height = window.innerHeight - 300;
// redraw image
let drawWidth = orig_image.width;
let drawHeight = orig_image.height;
if (orig_image.width > imgCanvas.width) {
drawWidth = imgCanvas.width;
drawHeight = (drawWidth / orig_image.width) * orig_image.height;
}
if (drawHeight > imgCanvas.height) {
drawHeight = imgCanvas.height;
drawWidth = (drawHeight / orig_image.height) * orig_image.width;
}
imgCtx.drawImage(orig_image, 0, 0, drawWidth, drawHeight);
// update mask
backupCtx.drawImage(maskCanvas, 0, 0, maskCanvas.width, maskCanvas.height, 0, 0, backupCanvas.width, backupCanvas.height);
maskCanvas.width = drawWidth;
maskCanvas.height = drawHeight;
maskCanvas.style.top = imgCanvas.offsetTop + "px";
maskCanvas.style.left = imgCanvas.offsetLeft + "px";
maskCtx.drawImage(backupCanvas, 0, 0, backupCanvas.width, backupCanvas.height, 0, 0, maskCanvas.width, maskCanvas.height);
});
const filepath = ComfyApp.clipspace.images;
const touched_image = new Image();
touched_image.onload = function() {
backupCanvas.width = touched_image.width;
backupCanvas.height = touched_image.height;
removeRGB(touched_image, backupCanvas, backupCtx, maskCtx);
};
touched_image.src = ComfyApp.clipspace.imgs[0].src;
// original image load
orig_image.onload = function() {
window.dispatchEvent(new Event('resize'));
};
orig_image.src = ComfyApp.clipspace.original_imgs[0].src;
this.image = orig_image;
}
setEventHandler(maskCanvas) {
let brush_size = 10;
const maskCtx = maskCanvas.getContext('2d');
function draw_point(event) {
console.log(event.button);
if (event.button == 0) {
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.fillStyle = "rgb(0,0,0)";
maskCtx.globalCompositeOperation = "source-over";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
}
function draw_move(event) {
if (event.buttons === 1) {
event.preventDefault();
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.fillStyle = "rgb(0,0,0)";
maskCtx.globalCompositeOperation = "source-over";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
else if(event.buttons === 2) {
event.preventDefault();
const maskRect = maskCanvas.getBoundingClientRect();
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
maskCtx.beginPath();
maskCtx.globalCompositeOperation = "destination-out";
maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
maskCtx.fill();
}
}
function handleWheelEvent(event) {
if(event.deltaY < 0)
brush_size = Math.min(brush_size+2, 100);
else
brush_size = Math.max(brush_size-2, 1);
}
maskCanvas.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
maskCanvas.addEventListener('wheel', handleWheelEvent);
maskCanvas.addEventListener('mousedown', draw_point);
maskCanvas.addEventListener('mousemove', draw_move);
maskCanvas.addEventListener('touchmove', draw_move);
}
save() {
const backupCtx = this.backupCanvas.getContext('2d', {willReadFrequently:true});
backupCtx.clearRect(0,0,this.backupCanvas.width,this.backupCanvas.height);
backupCtx.drawImage(this.maskCanvas,
0, 0, this.maskCanvas.width, this.maskCanvas.height,
0, 0, this.backupCanvas.width, this.backupCanvas.height);
// paste mask data into alpha channel
const backupData = backupCtx.getImageData(0, 0, this.backupCanvas.width, this.backupCanvas.height);
// refine mask image
for (let i = 0; i < backupData.data.length; i += 4) {
if(backupData.data[i+3] == 255)
backupData.data[i+3] = 0;
else
backupData.data[i+3] = 255;
backupData.data[i] = 0;
backupData.data[i+1] = 0;
backupData.data[i+2] = 0;
}
backupCtx.globalCompositeOperation = 'source-over';
backupCtx.putImageData(backupData, 0, 0);
const formData = new FormData();
const filename = "clipspace-mask-" + performance.now() + ".png";
const item =
{
"filename": filename,
"subfolder": "",
"type": "temp",
};
if(ComfyApp.clipspace.images)
ComfyApp.clipspace.images[0] = item;
if(ComfyApp.clipspace.widgets) {
const index = ComfyApp.clipspace.widgets.findIndex(obj => obj.name === 'image');
if(index >= 0)
ComfyApp.clipspace.widgets[index].value = item;
}
const dataURL = this.backupCanvas.toDataURL();
const blob = dataURLToBlob(dataURL);
const original_blob = loadedImageToBlob(ComfyApp.clipspace.original_imgs[0]);
formData.append('image', blob, filename);
formData.append('original_image', original_blob);
formData.append('type', "temp");
uploadMask(item, formData);
this.close();
}
}
app.registerExtension({
name: "Comfy.MaskEditor",
init(app) {
const callback =
function () {
let dlg = new MaskEditorDialog(app);
dlg.show();
};
ClipspaceDialog.registerButton("MaskEditor", callback);
}
});