diff --git a/maxedoutnodes.py b/maxedoutnodes.py index 5d260a8..a5e7d93 100644 --- a/maxedoutnodes.py +++ b/maxedoutnodes.py @@ -3,6 +3,7 @@ import torch, math, comfy, os, folder_paths, node_helpers, comfy.model_managemen from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict import numpy as np from PIL import Image, ImageOps, ImageSequence +from comfy_api.latest import io ######################################################################################################################## # Flux Empty Latent Image (SD3-compatible) @@ -425,6 +426,65 @@ class PromptWithGuidance(ComfyNodeABC): conditioning = clip.encode_from_tokens_scheduled(tokens) conditioning = node_helpers.conditioning_set_values(conditioning, {"guidance": guidance}) return (conditioning,) + +######################################################################################################################## +# Qwen Image Edit Single MXD +class QwenImageEditSingleMXD(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="QwenImageEditSingleMXD", + display_name="Qwen Image Edit Prompt Single MXD", + category="MXD/conditioning", + description="Encode a prompt and optional image for Qwen single-image editing.", + inputs=[ + io.Clip.Input("clip"), + io.String.Input("prompt", multiline=True, dynamic_prompts=True), + io.Vae.Input("vae", optional=True), + io.Image.Input("image", optional=True), + ], + outputs=[ + io.Conditioning.Output(), + ], + ) + + @classmethod + def execute(cls, clip, prompt, vae=None, image=None) -> io.NodeOutput: + ref_latents = [] + images_vl = [] + llama_template = "<|im_start|>system\nDescribe the key features of the input image (color, shape, size, texture, objects, background), then explain how the user's text instruction should alter or modify the image. Generate a new image that meets the user's requirements while maintaining consistency with the original input where appropriate.<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n" + image_prompt = "" + + if image is not None: + samples = image.movedim(-1, 1) + total = int(384 * 384) + + scale_by = math.sqrt(total / (samples.shape[3] * samples.shape[2])) + width = round(samples.shape[3] * scale_by) + height = round(samples.shape[2] * scale_by) + + s = comfy.utils.common_upscale(samples, width, height, "area", "disabled") + images_vl.append(s.movedim(1, -1)) + if vae is not None: + total = int(1024 * 1024) + scale_by = math.sqrt(total / (samples.shape[3] * samples.shape[2])) + width = round(samples.shape[3] * scale_by / 8.0) * 8 + height = round(samples.shape[2] * scale_by / 8.0) * 8 + + s = comfy.utils.common_upscale(samples, width, height, "area", "disabled") + ref_latents.append(vae.encode(s.movedim(1, -1)[:, :, :, :3])) + + image_prompt += "Picture 1: <|vision_start|><|image_pad|><|vision_end|>" + + tokens = clip.tokenize(image_prompt + prompt, images=images_vl, llama_template=llama_template) + conditioning = clip.encode_from_tokens_scheduled(tokens) + if len(ref_latents) > 0: + conditioning = node_helpers.conditioning_set_values( + conditioning, + {"reference_latents": ref_latents}, + append=True, + ) + return io.NodeOutput(conditioning) ######################################################################################################################## class FluxResolutionMatcher: @@ -1080,6 +1140,7 @@ NODE_CLASS_MAPPINGS = { "Image Scale To Total Pixels (SDXL Safe)": SDXLImageScaleToTotalPixelsSafe, "Flux Image Scale To Total Pixels (Flux Safe)": FluxImageScaleToTotalPixelsSafe, "Prompt With Guidance (Flux)": PromptWithGuidance, + "QwenImageEditSingleMXD": QwenImageEditSingleMXD, "FluxResolutionMatcher": FluxResolutionMatcher, "SDXLResolutionMatcher": SDXLResolutionMatcher, "LatentHalfMasks": LatentHalfMasks, @@ -1099,6 +1160,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Image Scale To Total Pixels (SDXL Safe)": "Scale SDXL Image MXD", "Flux Image Scale To Total Pixels (Flux Safe)": "Scale Flux Image MXD", "Prompt With Guidance (Flux)": "Prompt with Flux Guidance MXD", + "QwenImageEditSingleMXD": "Qwen Image Edit Prompt Single MXD", "FluxResolutionMatcher": "Flux Resolution Matcher MXD", "SDXLResolutionMatcher": "SDXL Resolution Matcher MXD", "LatentHalfMasks": "Latent to L/R Masks MXD", diff --git a/pyproject.toml b/pyproject.toml index 1f2f966..de47cb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "maxedout" description = "Custom ComfyUI nodes used in Maxed Out workflows (SDXL, Flux, Wan 2.2, etc.)" -version = "1.5.0" +version = "1.5.1" license = {file = "LICENSE"} # classifiers = [ # # For OS-independent nodes (works on all operating systems)