@@ -28,6 +28,12 @@ class Text2ImageInputField(BaseModel):
28
28
negative_prompt : Optional [str ] = Field (None )
29
29
30
30
31
+ class Image2ImageInputField (BaseModel ):
32
+ prompt : str = Field (...)
33
+ negative_prompt : Optional [str ] = Field (None )
34
+ images : list [str ] = Field (..., min_length = 1 , max_length = 2 )
35
+
36
+
31
37
class Text2VideoInputField (BaseModel ):
32
38
prompt : str = Field (...)
33
39
negative_prompt : Optional [str ] = Field (None )
@@ -49,6 +55,13 @@ class Txt2ImageParametersField(BaseModel):
49
55
watermark : bool = Field (True )
50
56
51
57
58
+ class Image2ImageParametersField (BaseModel ):
59
+ size : Optional [str ] = Field (None )
60
+ n : int = Field (1 , description = "Number of images to generate." ) # we support only value=1
61
+ seed : int = Field (..., ge = 0 , le = 2147483647 )
62
+ watermark : bool = Field (True )
63
+
64
+
52
65
class Text2VideoParametersField (BaseModel ):
53
66
size : str = Field (...)
54
67
seed : int = Field (..., ge = 0 , le = 2147483647 )
@@ -73,6 +86,12 @@ class Text2ImageTaskCreationRequest(BaseModel):
73
86
parameters : Txt2ImageParametersField = Field (...)
74
87
75
88
89
+ class Image2ImageTaskCreationRequest (BaseModel ):
90
+ model : str = Field (...)
91
+ input : Image2ImageInputField = Field (...)
92
+ parameters : Image2ImageParametersField = Field (...)
93
+
94
+
76
95
class Text2VideoTaskCreationRequest (BaseModel ):
77
96
model : str = Field (...)
78
97
input : Text2VideoInputField = Field (...)
@@ -135,7 +154,12 @@ async def process_task(
135
154
url : str ,
136
155
request_model : Type [T ],
137
156
response_model : Type [R ],
138
- payload : Union [Text2ImageTaskCreationRequest , Text2VideoTaskCreationRequest , Image2VideoTaskCreationRequest ],
157
+ payload : Union [
158
+ Text2ImageTaskCreationRequest ,
159
+ Image2ImageTaskCreationRequest ,
160
+ Text2VideoTaskCreationRequest ,
161
+ Image2VideoTaskCreationRequest ,
162
+ ],
139
163
node_id : str ,
140
164
estimated_duration : int ,
141
165
poll_interval : int ,
@@ -288,6 +312,128 @@ async def execute(
288
312
return comfy_io .NodeOutput (await download_url_to_image_tensor (str (response .output .results [0 ].url )))
289
313
290
314
315
+ class WanImageToImageApi (comfy_io .ComfyNode ):
316
+ @classmethod
317
+ def define_schema (cls ):
318
+ return comfy_io .Schema (
319
+ node_id = "WanImageToImageApi" ,
320
+ display_name = "Wan Image to Image" ,
321
+ category = "api node/image/Wan" ,
322
+ description = "Generates an image from one or two input images and a text prompt. "
323
+ "The output image is currently fixed at 1.6 MP; its aspect ratio matches the input image(s)." ,
324
+ inputs = [
325
+ comfy_io .Combo .Input (
326
+ "model" ,
327
+ options = ["wan2.5-i2i-preview" ],
328
+ default = "wan2.5-i2i-preview" ,
329
+ tooltip = "Model to use." ,
330
+ ),
331
+ comfy_io .Image .Input (
332
+ "image" ,
333
+ tooltip = "Single-image editing or multi-image fusion, maximum 2 images." ,
334
+ ),
335
+ comfy_io .String .Input (
336
+ "prompt" ,
337
+ multiline = True ,
338
+ default = "" ,
339
+ tooltip = "Prompt used to describe the elements and visual features, supports English/Chinese." ,
340
+ ),
341
+ comfy_io .String .Input (
342
+ "negative_prompt" ,
343
+ multiline = True ,
344
+ default = "" ,
345
+ tooltip = "Negative text prompt to guide what to avoid." ,
346
+ optional = True ,
347
+ ),
348
+ # redo this later as an optional combo of recommended resolutions
349
+ # comfy_io.Int.Input(
350
+ # "width",
351
+ # default=1280,
352
+ # min=384,
353
+ # max=1440,
354
+ # step=16,
355
+ # optional=True,
356
+ # ),
357
+ # comfy_io.Int.Input(
358
+ # "height",
359
+ # default=1280,
360
+ # min=384,
361
+ # max=1440,
362
+ # step=16,
363
+ # optional=True,
364
+ # ),
365
+ comfy_io .Int .Input (
366
+ "seed" ,
367
+ default = 0 ,
368
+ min = 0 ,
369
+ max = 2147483647 ,
370
+ step = 1 ,
371
+ display_mode = comfy_io .NumberDisplay .number ,
372
+ control_after_generate = True ,
373
+ tooltip = "Seed to use for generation." ,
374
+ optional = True ,
375
+ ),
376
+ comfy_io .Boolean .Input (
377
+ "watermark" ,
378
+ default = True ,
379
+ tooltip = "Whether to add an \" AI generated\" watermark to the result." ,
380
+ optional = True ,
381
+ ),
382
+ ],
383
+ outputs = [
384
+ comfy_io .Image .Output (),
385
+ ],
386
+ hidden = [
387
+ comfy_io .Hidden .auth_token_comfy_org ,
388
+ comfy_io .Hidden .api_key_comfy_org ,
389
+ comfy_io .Hidden .unique_id ,
390
+ ],
391
+ is_api_node = True ,
392
+ )
393
+
394
+ @classmethod
395
+ async def execute (
396
+ cls ,
397
+ model : str ,
398
+ image : torch .Tensor ,
399
+ prompt : str ,
400
+ negative_prompt : str = "" ,
401
+ # width: int = 1024,
402
+ # height: int = 1024,
403
+ seed : int = 0 ,
404
+ watermark : bool = True ,
405
+ ):
406
+ n_images = get_number_of_images (image )
407
+ if n_images not in (1 , 2 ):
408
+ raise ValueError (f"Expected 1 or 2 input images, got { n_images } ." )
409
+ images = []
410
+ for i in image :
411
+ images .append ("data:image/png;base64," + tensor_to_base64_string (i , total_pixels = 4096 * 4096 ))
412
+ payload = Image2ImageTaskCreationRequest (
413
+ model = model ,
414
+ input = Image2ImageInputField (prompt = prompt , negative_prompt = negative_prompt , images = images ),
415
+ parameters = Image2ImageParametersField (
416
+ # size=f"{width}*{height}",
417
+ seed = seed ,
418
+ watermark = watermark ,
419
+ ),
420
+ )
421
+ response = await process_task (
422
+ {
423
+ "auth_token" : cls .hidden .auth_token_comfy_org ,
424
+ "comfy_api_key" : cls .hidden .api_key_comfy_org ,
425
+ },
426
+ "/proxy/wan/api/v1/services/aigc/image2image/image-synthesis" ,
427
+ request_model = Image2ImageTaskCreationRequest ,
428
+ response_model = ImageTaskStatusResponse ,
429
+ payload = payload ,
430
+ node_id = cls .hidden .unique_id ,
431
+ estimated_duration = 42 ,
432
+ poll_interval = 3 ,
433
+ )
434
+ return comfy_io .NodeOutput (await download_url_to_image_tensor (str (response .output .results [0 ].url )))
435
+
436
+
291
437
class WanTextToVideoApi (comfy_io .ComfyNode ):
292
438
@classmethod
293
439
def define_schema (cls ):
@@ -593,6 +739,7 @@ class WanApiExtension(ComfyExtension):
593
739
async def get_node_list (self ) -> list [type [comfy_io .ComfyNode ]]:
594
740
return [
595
741
WanTextToImageApi ,
742
+ WanImageToImageApi ,
596
743
WanTextToVideoApi ,
597
744
WanImageToVideoApi ,
598
745
]
0 commit comments