diff --git a/nodes/image_processing.py b/nodes/image_processing.py index c3fe2c0..c9badc1 100644 --- a/nodes/image_processing.py +++ b/nodes/image_processing.py @@ -436,8 +436,11 @@ class MaskToImage: return (pil2tensor(images),) +from typing import Optional + + class ColoredImage: - """Constant color image of given size""" + """Constant color image of given size.""" def __init__(self) -> None: pass @@ -565,43 +568,52 @@ class ColoredImage: return img.crop((left, top, right, bottom)) def render_img( - self, color, width, height, foreground_image=None, foreground_mask=None + self, + color, + width, + height, + foreground_image: Optional[torch.Tensor] = None, + foreground_mask: Optional[torch.Tensor] = None, ): image = Image.new("RGBA", (width, height), color=color) output = [] if foreground_image is not None: - if foreground_mask is None: - fg_images = tensor2pil(foreground_image) - for img in fg_images: - if image.size != img.size: - raise ValueError( - f"Dimension mismatch: image {image.size}, img {img.size}" - ) + fg_images = tensor2pil(foreground_image) + fg_masks = [None] * len( + fg_images + ) # Default to None for each foreground image - if img.mode != "RGBA": - raise ValueError( - f"Foreground image must be in 'RGBA' mode when no mask is provided, got {img.mode}" - ) + if foreground_mask is not None: + if foreground_image.size()[0] != foreground_mask.size()[0]: + raise ValueError( + "Foreground image and mask must have same batch size" + ) + fg_masks = tensor2pil(foreground_mask) + for fg_image, fg_mask in zip(fg_images, fg_masks): + # Resize and crop if dimensions mismatch + if fg_image.size != image.size: + fg_image = self.resize_and_crop(fg_image, image.size) + if fg_mask: + fg_mask = self.resize_and_crop(fg_mask, image.size) + + if fg_mask: output.append( - Image.alpha_composite(image, img).convert("RGB") + Image.composite( + fg_image.convert("RGBA"), + image, + fg_mask, + ).convert("RGB") + ) + else: + if fg_image.mode != "RGBA": + raise ValueError( + "Foreground image must be in 'RGBA' mode when no mask is provided, got {fg_image.mode}" + ) + output.append( + Image.alpha_composite(image, fg_image).convert("RGB") ) - elif foreground_image.size[0] != foreground_mask.size[0]: - raise ValueError( - "Foreground image and mask must have same batch size" - ) - else: - fg_images = tensor2pil(foreground_image) - fg_masks = tensor2pil(foreground_mask) - output.extend( - Image.composite( - fg_image.convert("RGBA"), - image, - fg_mask, - ).convert("RGB") - for fg_image, fg_mask in zip(fg_images, fg_masks) - ) elif foreground_mask is not None: log.warn("Mask ignored because no foreground image is given") diff --git a/web/comfy_shared.js b/web/comfy_shared.js index f679353..a60602e 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -20,6 +20,38 @@ export function makeUUID() { return uuid } +//- local storage manager +export class LocalStorageManager { + constructor(namespace) { + this.namespace = namespace; + } + + _namespacedKey(key) { + return `${this.namespace}:${key}`; + } + + set(key, value) { + const serializedValue = JSON.stringify(value); + localStorage.setItem(this._namespacedKey(key), serializedValue); + } + + get(key, default_val = null) { + const value = localStorage.getItem(this._namespacedKey(key)); + return value ? JSON.parse(value) : default_val; + } + + remove(key) { + localStorage.removeItem(this._namespacedKey(key)); + } + + clear() { + Object.keys(localStorage) + .filter(k => k.startsWith(this.namespace + ':')) + .forEach(k => localStorage.removeItem(k)); + } +} + + // - log utilities function createLogger(emoji, color, consoleMethod = 'log') { @@ -378,7 +410,7 @@ function getBrightness(rgbObj) { (parseInt(rgbObj[0]) * 299 + parseInt(rgbObj[1]) * 587 + parseInt(rgbObj[2]) * 114) / - 1000 + 1000 ) } diff --git a/web/imageFeed.js b/web/imageFeed.js index 71d6a7f..c5894e4 100644 --- a/web/imageFeed.js +++ b/web/imageFeed.js @@ -11,7 +11,7 @@ import { api } from '../../scripts/api.js' import { app } from '../../scripts/app.js' - +import { LocalStorageManager } from "./comfy_shared.js" const styles = { lighbox: { position: 'fixed', @@ -53,11 +53,31 @@ let currentImageIndex = 0 const imageUrls = [] let image_menu = null -let activated = true +const storage = new LocalStorageManager('mtb'); + +let activated = storage.get("image_feed", true) app.registerExtension({ name: 'mtb.ImageFeed', + setup: () => { + app.ui.settings.addSetting({ + id: 'mtb.imageFeed.enabled', + name: '[⚡mtb] Enable image feed', + type: 'boolean', + defaultValue: true, + attrs: { + style: { + fontFamily: 'monospace', + }, + }, + async onChange(value) { + storage.set("image_feed", value) + activated = value + }, + }) + }, init: async () => { + if (!activated) { return } const pythongossFeed = app.extensions.find( (e) => e.name == 'pysssss.ImageFeed' ) @@ -230,9 +250,8 @@ app.registerExtension({ objectFit: 'cover', }) - img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${ - src.type - }&subfolder=${encodeURIComponent(src.subfolder)}` + img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${src.type + }&subfolder=${encodeURIComponent(src.subfolder)}` imageUrls.push(img.src) diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index c9b58a9..689659d 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -582,7 +582,7 @@ const mtb_widgets = { setup: () => { app.ui.settings.addSetting({ id: 'mtb.Debug.enabled', - name: '[mtb] Enable Debug (py and js)', + name: '[⚡mtb] Enable Debug (py and js)', type: 'boolean', defaultValue: false,