2025-08-18 19:38:34 -07:00
import node_helpers
import comfy . utils
2025-08-19 13:49:01 -07:00
import math
2025-09-27 22:25:35 +03:00
from typing_extensions import override
from comfy_api . latest import ComfyExtension , io
2025-12-21 16:59:40 -08:00
import comfy . model_management
import torch
import nodes
2025-08-18 19:38:34 -07:00
2025-09-27 22:25:35 +03:00
class TextEncodeQwenImageEdit ( io . ComfyNode ) :
2025-08-18 19:38:34 -07:00
@classmethod
2025-09-27 22:25:35 +03:00
def define_schema ( cls ) :
return io . Schema (
node_id = " TextEncodeQwenImageEdit " ,
2026-06-17 08:33:09 +08:00
category = " model/conditioning/qwen image " ,
2025-09-27 22:25:35 +03:00
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 ( ) ,
] ,
)
2025-08-18 19:38:34 -07:00
2025-09-27 22:25:35 +03:00
@classmethod
def execute ( cls , clip , prompt , vae = None , image = None ) - > io . NodeOutput :
2025-08-18 19:38:34 -07:00
ref_latent = None
if image is None :
images = [ ]
else :
2025-08-19 13:49:01 -07:00
samples = image . movedim ( - 1 , 1 )
total = int ( 1024 * 1024 )
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 " )
image = s . movedim ( 1 , - 1 )
images = [ image [ : , : , : , : 3 ] ]
2025-08-18 19:38:34 -07:00
if vae is not None :
ref_latent = vae . encode ( image [ : , : , : , : 3 ] )
tokens = clip . tokenize ( prompt , images = images )
conditioning = clip . encode_from_tokens_scheduled ( tokens )
if ref_latent is not None :
conditioning = node_helpers . conditioning_set_values ( conditioning , { " reference_latents " : [ ref_latent ] } , append = True )
2025-09-27 22:25:35 +03:00
return io . NodeOutput ( conditioning )
2025-08-18 19:38:34 -07:00
2025-09-27 22:25:35 +03:00
class TextEncodeQwenImageEditPlus ( io . ComfyNode ) :
2025-09-22 13:49:48 -07:00
@classmethod
2025-09-27 22:25:35 +03:00
def define_schema ( cls ) :
return io . Schema (
node_id = " TextEncodeQwenImageEditPlus " ,
2026-06-17 08:33:09 +08:00
category = " model/conditioning/qwen image " ,
2025-09-27 22:25:35 +03:00
inputs = [
io . Clip . Input ( " clip " ) ,
io . String . Input ( " prompt " , multiline = True , dynamic_prompts = True ) ,
io . Vae . Input ( " vae " , optional = True ) ,
io . Image . Input ( " image1 " , optional = True ) ,
io . Image . Input ( " image2 " , optional = True ) ,
io . Image . Input ( " image3 " , optional = True ) ,
] ,
outputs = [
io . Conditioning . Output ( ) ,
] ,
)
@classmethod
def execute ( cls , clip , prompt , vae = None , image1 = None , image2 = None , image3 = None ) - > io . NodeOutput :
2025-09-22 13:49:48 -07:00
ref_latents = [ ]
images = [ image1 , image2 , image3 ]
images_vl = [ ]
llama_template = " <|im_start|>system \n Describe 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 = " "
for i , image in enumerate ( images ) :
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 {} : <|vision_start|><|image_pad|><|vision_end|> " . format ( i + 1 )
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 )
2025-09-27 22:25:35 +03:00
return io . NodeOutput ( conditioning )
2025-12-21 16:59:40 -08:00
class EmptyQwenImageLayeredLatentImage ( io . ComfyNode ) :
@classmethod
def define_schema ( cls ) :
return io . Schema (
node_id = " EmptyQwenImageLayeredLatentImage " ,
display_name = " Empty Qwen Image Layered Latent " ,
2026-05-27 17:43:33 -07:00
category = " model/latent/qwen " ,
2025-12-21 16:59:40 -08:00
inputs = [
io . Int . Input ( " width " , default = 640 , min = 16 , max = nodes . MAX_RESOLUTION , step = 16 ) ,
io . Int . Input ( " height " , default = 640 , min = 16 , max = nodes . MAX_RESOLUTION , step = 16 ) ,
2026-05-11 01:23:04 +08:00
io . Int . Input ( " layers " , default = 3 , min = 0 , max = nodes . MAX_RESOLUTION , step = 1 ) ,
2025-12-21 16:59:40 -08:00
io . Int . Input ( " batch_size " , default = 1 , min = 1 , max = 4096 ) ,
] ,
outputs = [
io . Latent . Output ( ) ,
] ,
)
@classmethod
def execute ( cls , width , height , layers , batch_size = 1 ) - > io . NodeOutput :
latent = torch . zeros ( [ batch_size , 16 , layers + 1 , height / / 8 , width / / 8 ] , device = comfy . model_management . intermediate_device ( ) )
return io . NodeOutput ( { " samples " : latent } )
2025-09-27 22:25:35 +03:00
class QwenExtension ( ComfyExtension ) :
@override
async def get_node_list ( self ) - > list [ type [ io . ComfyNode ] ] :
return [
TextEncodeQwenImageEdit ,
TextEncodeQwenImageEditPlus ,
2025-12-21 16:59:40 -08:00
EmptyQwenImageLayeredLatentImage ,
2025-09-27 22:25:35 +03:00
]
2025-09-22 13:49:48 -07:00
2025-09-27 22:25:35 +03:00
async def comfy_entrypoint ( ) - > QwenExtension :
return QwenExtension ( )