From 89aca4ba2fcfa020bd758b7e58be84d6dd818f4b Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 25 Nov 2025 00:58:58 +0000 Subject: [PATCH] color match --- examples/ttm/ttm_example_wf.json | 705 ++++++++++++++++--------------- nodes/image_nodes.py | 96 ++++- web/create_image_list.js | 29 +- 3 files changed, 481 insertions(+), 349 deletions(-) diff --git a/examples/ttm/ttm_example_wf.json b/examples/ttm/ttm_example_wf.json index f9590b5..0448349 100644 --- a/examples/ttm/ttm_example_wf.json +++ b/examples/ttm/ttm_example_wf.json @@ -1,8 +1,8 @@ { "id": "c6e410bc-5e2c-460b-ae81-c91b6094fbb1", "revision": 0, - "last_node_id": 167, - "last_link_id": 363, + "last_node_id": 168, + "last_link_id": 372, "nodes": [ { "id": 92, @@ -890,7 +890,8 @@ 2, "linear", 0, - 0.01 + 0.01, + "[2.0, 1.0, 1.0, 1.0, 1.0, 1.0]" ] }, { @@ -1140,12 +1141,12 @@ "hidden": false, "paused": false, "params": { - "filename": "WanVideo2_2_I2V_00005.mp4", + "filename": "WanVideo2_2_I2V_00002.mp4", "subfolder": "", "type": "temp", "format": "video/h264-mp4", "frame_rate": 16, - "workflow": "WanVideo2_2_I2V_00005.png", + "workflow": "WanVideo2_2_I2V_00002.png", "fullpath": "" } } @@ -1278,12 +1279,12 @@ "hidden": false, "paused": false, "params": { - "filename": "WanVideo2_2_I2V_00003.mp4", + "filename": "WanVideo2_2_I2V_00001.mp4", "subfolder": "", "type": "temp", "format": "video/h264-mp4", "frame_rate": 16, - "workflow": "WanVideo2_2_I2V_00003.png", + "workflow": "WanVideo2_2_I2V_00001.png", "fullpath": "" } } @@ -1312,7 +1313,7 @@ 263, 278, 301, - 346 + 365 ] }, { @@ -1496,7 +1497,8 @@ "name": "IMAGE", "type": "IMAGE", "links": [ - 352 + 352, + 366 ] }, { @@ -1540,7 +1542,7 @@ "name": "IMAGE", "type": "IMAGE", "links": [ - 345 + 364 ] }, { @@ -1564,192 +1566,6 @@ "image" ] }, - { - "id": 155, - "type": "PreviewImage", - "pos": [ - -499.9862067775319, - 491.79242257161894 - ], - "size": [ - 210, - 667.7696706327888 - ], - "flags": {}, - "order": 26, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 347 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.71", - "Node name for S&R": "PreviewImage", - "ue_properties": { - "widget_ue_connectable": {}, - "input_ue_unconnectable": {}, - "version": "7.2.2" - } - }, - "widgets_values": [] - }, - { - "id": 158, - "type": "MaskPreview+", - "pos": [ - -268.09960736634764, - 490.45363849199936 - ], - "size": [ - 210, - 668.6430337189952 - ], - "flags": {}, - "order": 27, - "mode": 0, - "inputs": [ - { - "name": "mask", - "type": "MASK", - "link": 350 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfyui_essentials", - "ver": "9d9f4bedfc9f0321c19faf71855e228c93bd0dc9", - "Node name for S&R": "MaskPreview+", - "ue_properties": { - "widget_ue_connectable": {}, - "input_ue_unconnectable": {}, - "version": "7.2.2" - } - }, - "widgets_values": [] - }, - { - "id": 161, - "type": "CreateImageList", - "pos": [ - -492.1722697061646, - 281.26596728639873 - ], - "size": [ - 270, - 118 - ], - "flags": {}, - "order": 22, - "mode": 0, - "inputs": [ - { - "name": "image1", - "shape": 7, - "type": "IMAGE", - "link": 345 - }, - { - "name": "image2", - "type": "IMAGE", - "link": 346 - }, - { - "name": "image3", - "type": "IMAGE", - "link": 352 - }, - { - "name": "image4", - "type": "IMAGE", - "link": null - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "links": [ - 347, - 348, - 349 - ] - }, - { - "name": "masks", - "type": "MASK", - "links": [ - 350, - 351 - ] - } - ], - "properties": { - "cnr_id": "wanvideowrapper_qq", - "ver": "91d07cb0be299bb8c2c83b6d77faa422959a9b1f", - "Node name for S&R": "CreateImageList", - "ue_properties": { - "widget_ue_connectable": {}, - "input_ue_unconnectable": {}, - "version": "7.2.2" - } - }, - "widgets_values": [ - true - ], - "shape": 1 - }, - { - "id": 124, - "type": "LoadImage", - "pos": [ - -484.35588731056015, - -95.80907240982795 - ], - "size": [ - 274.080078125, - 314.0000000000001 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 218 - ] - }, - { - "name": "MASK", - "type": "MASK", - "links": null - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.71", - "Node name for S&R": "LoadImage", - "ue_properties": { - "widget_ue_connectable": {}, - "input_ue_unconnectable": {}, - "version": "7.2.2" - } - }, - "widgets_values": [ - "bg.png", - "image" - ], - "color": "#432", - "bgcolor": "#653", - "shape": 1 - }, { "id": 126, "type": "ImageResizeKJv2", @@ -1764,7 +1580,7 @@ "flags": { "collapsed": true }, - "order": 23, + "order": 22, "mode": 0, "inputs": [ { @@ -1842,7 +1658,8 @@ "0, 0, 0", "center", 16, - "cpu" + "cpu", + "Output: 1 x 800 x 656 | 6.01MB" ] }, { @@ -1995,12 +1812,12 @@ "hidden": false, "paused": false, "params": { - "filename": "AnimateDiff_00007.mp4", + "filename": "AnimateDiff_00003.mp4", "subfolder": "", "type": "temp", "format": "video/h264-mp4", "frame_rate": 16, - "workflow": "AnimateDiff_00007.png", + "workflow": "AnimateDiff_00003.png", "fullpath": "" } } @@ -2077,80 +1894,17 @@ "hidden": false, "paused": false, "params": { - "filename": "AnimateDiff_00008.mp4", + "filename": "AnimateDiff_00004.mp4", "subfolder": "", "type": "temp", "format": "video/h264-mp4", "frame_rate": 16, - "workflow": "AnimateDiff_00008.png", + "workflow": "AnimateDiff_00004.png", "fullpath": "" } } } }, - { - "id": 121, - "type": "GetImageRangeFromBatch", - "pos": [ - 723.1343342581331, - 178.8695429625597 - ], - "size": [ - 340.3267578125, - 102 - ], - "flags": { - "collapsed": true - }, - "order": 34, - "mode": 0, - "inputs": [ - { - "name": "images", - "shape": 7, - "type": "IMAGE", - "link": 211 - }, - { - "name": "masks", - "shape": 7, - "type": "MASK", - "link": null - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 212, - 362 - ] - }, - { - "name": "MASK", - "type": "MASK", - "links": null - } - ], - "properties": { - "cnr_id": "comfyui-kjnodes", - "ver": "6ee278aa7d802c03ad713a7c5fbc02861b6773a8", - "Node name for S&R": "GetImageRangeFromBatch", - "ue_properties": { - "widget_ue_connectable": { - "num_frames": true - }, - "version": "7.3", - "input_ue_unconnectable": {} - }, - "aux_id": "kijai/ComfyUI-KJNodes" - }, - "widgets_values": [ - 0, - 1 - ] - }, { "id": 89, "type": "WanVideoImageToVideoEncode", @@ -2288,7 +2042,7 @@ 139.1564075673432 ], "flags": {}, - "order": 14, + "order": 13, "mode": 0, "inputs": [], "outputs": [ @@ -2334,7 +2088,7 @@ 258 ], "flags": {}, - "order": 28, + "order": 26, "mode": 0, "inputs": [ { @@ -2433,7 +2187,7 @@ "flags": { "collapsed": true }, - "order": 15, + "order": 14, "mode": 0, "inputs": [], "outputs": [ @@ -2487,13 +2241,13 @@ { "name": "ref_images", "type": "IMAGE", - "link": 349 + "link": 369 }, { "name": "ref_masks", "shape": 7, "type": "MASK", - "link": 351 + "link": 371 }, { "name": "frames", @@ -2556,7 +2310,7 @@ 738 ], "flags": {}, - "order": 29, + "order": 28, "mode": 0, "inputs": [ { @@ -2569,7 +2323,7 @@ "name": "ref_images", "shape": 7, "type": "IMAGE", - "link": 348 + "link": 368 }, { "name": "frames", @@ -2620,7 +2374,7 @@ } }, "widgets_values": [ - "[{\"on\":true,\"name\":\"Box 1\",\"type\":\"box_layer\",\"interpolation\":\"box\",\"repeat\":1,\"offset\":0,\"a_pause\":0,\"z_pause\":0,\"easing\":\"in_out\",\"easingConfig\":{\"path\":\"full\",\"strength\":1,\"acceleration\":0},\"driven\":false,\"_drivenConfig\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"scale\":1,\"points_store\":\"[{\\\"x\\\":0.3474681352975962,\\\"y\\\":0.8035198065615633,\\\"highlighted\\\":false,\\\"boxScale\\\":0.49486250274459376,\\\"pointScale\\\":0.49486250274459376,\\\"scale\\\":0.49486250274459376,\\\"rotation\\\":0,\\\"boxRotation\\\":0}]\",\"coordinates\":\"[]\",\"box_keys\":[{\"frame\":1,\"x\":0.17509084505505768,\"y\":0.8303768607157191,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":3,\"x\":0.16230817242646217,\"y\":0.8434348208345157,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":8,\"x\":0.16230817242646217,\"y\":0.8434348208345157,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":17,\"x\":0.37061313065648793,\"y\":0.7985304297774442,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":22,\"x\":0.3101511961248443,\"y\":0.7530653147096042,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":25,\"x\":0.30787570430834565,\"y\":0.8420188007118998,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":30,\"x\":0.25489021568489956,\"y\":0.8325304530018323,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":35,\"x\":0.363786853610673,\"y\":0.8368793142254553,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":40,\"x\":0.2805702985195647,\"y\":0.8436002925393168,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":44,\"x\":0.3407072478218575,\"y\":0.8281817124290963,\"scale\":0.45690062408212573,\"rotation\":0}],\"box_timeline_point\":16,\"box_interpolation\":\"linear\",\"ref_attachment\":{\"entries\":[{\"path\":\"bg/ref_image_0.png\",\"type\":\"image/png\",\"width\":300,\"height\":546,\"name\":\"ref_image_0.png\"},{\"path\":\"bg/ref_image_1.png\",\"type\":\"image/png\",\"width\":457,\"height\":586,\"name\":\"ref_image_1.png\"},{\"path\":\"bg/ref_image_2.png\",\"type\":\"image/png\",\"width\":512,\"height\":768,\"name\":\"ref_image_2.png\"}]},\"ref_selection\":\"ref_2\"},{\"on\":true,\"name\":\"Box 1_1\",\"type\":\"box_layer\",\"interpolation\":\"box\",\"repeat\":1,\"offset\":0,\"a_pause\":0,\"z_pause\":0,\"easing\":\"in_out\",\"easingConfig\":{\"path\":\"full\",\"strength\":1,\"acceleration\":0},\"driven\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"_drivenConfig\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"scale\":1,\"points_store\":\"[{\\\"x\\\":0.6806348749052255,\\\"y\\\":0.7474313451231609,\\\"highlighted\\\":false,\\\"boxScale\\\":0.6940336804011772,\\\"pointScale\\\":0.6940336804011772,\\\"scale\\\":0.6940336804011772,\\\"rotation\\\":0,\\\"boxRotation\\\":0,\\\"uid\\\":16981}]\",\"coordinates\":\"[]\",\"box_keys\":[{\"frame\":1,\"x\":0.6302263722980433,\"y\":0.7737771727381784,\"scale\":0.312739506683858,\"rotation\":0},{\"frame\":9,\"x\":0.7801197829741051,\"y\":0.7550457649727679,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":16,\"x\":0.6806348749052255,\"y\":0.7474313451231609,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":21,\"x\":0.7156002654484349,\"y\":0.6992066860756498,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":24,\"x\":0.7384942877962534,\"y\":0.7692594416414953,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":36,\"x\":0.688959973940796,\"y\":0.7697671006146228,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":46,\"x\":0.8155014538752791,\"y\":0.7885493982099611,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":52,\"x\":0.7476519475477528,\"y\":0.7860112582600921,\"scale\":1.0468844700694617,\"rotation\":0}],\"box_timeline_point\":16,\"box_interpolation\":\"linear\",\"ref_attachment\":{\"entries\":[{\"path\":\"bg/ref_image_0.png\",\"type\":\"image/png\",\"width\":400,\"height\":471,\"name\":\"ref_image_0.png\"},{\"path\":\"bg/ref_image_1.png\",\"type\":\"image/png\",\"width\":457,\"height\":586,\"name\":\"ref_image_1.png\"},{\"path\":\"bg/ref_image_2.png\",\"type\":\"image/png\",\"width\":300,\"height\":546,\"name\":\"ref_image_2.png\"}]},\"ref_selection\":\"ref_1\"}]", + "[{\"on\":true,\"name\":\"Box 1\",\"type\":\"box_layer\",\"interpolation\":\"box\",\"repeat\":1,\"offset\":0,\"a_pause\":0,\"z_pause\":0,\"easing\":\"in_out\",\"easingConfig\":{\"path\":\"full\",\"strength\":1,\"acceleration\":0},\"driven\":false,\"_drivenConfig\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"scale\":1,\"points_store\":\"[{\\\"x\\\":0.3474681352975962,\\\"y\\\":0.8035198065615633,\\\"highlighted\\\":false,\\\"boxScale\\\":0.49486250274459376,\\\"pointScale\\\":0.49486250274459376,\\\"scale\\\":0.49486250274459376,\\\"rotation\\\":0,\\\"boxRotation\\\":0}]\",\"coordinates\":\"[]\",\"box_keys\":[{\"frame\":1,\"x\":0.17509084505505768,\"y\":0.8303768607157191,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":3,\"x\":0.16230817242646217,\"y\":0.8434348208345157,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":8,\"x\":0.16230817242646217,\"y\":0.8434348208345157,\"scale\":0.7985575320443379,\"rotation\":0},{\"frame\":17,\"x\":0.37061313065648793,\"y\":0.7985304297774442,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":22,\"x\":0.3101511961248443,\"y\":0.7530653147096042,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":25,\"x\":0.30787570430834565,\"y\":0.8420188007118998,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":30,\"x\":0.25489021568489956,\"y\":0.8325304530018323,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":35,\"x\":0.363786853610673,\"y\":0.8368793142254553,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":40,\"x\":0.2805702985195647,\"y\":0.8436002925393168,\"scale\":0.45690062408212573,\"rotation\":0},{\"frame\":44,\"x\":0.3407072478218575,\"y\":0.8281817124290963,\"scale\":0.45690062408212573,\"rotation\":0}],\"box_timeline_point\":16,\"box_interpolation\":\"linear\",\"ref_attachment\":{\"entries\":[{\"path\":\"bg/ref_image_0.png\",\"type\":\"image/png\",\"width\":400,\"height\":471,\"name\":\"ref_image_0.png\"},{\"path\":\"bg/ref_image_1.png\",\"type\":\"image/png\",\"width\":457,\"height\":586,\"name\":\"ref_image_1.png\"},{\"path\":\"bg/ref_image_2.png\",\"type\":\"image/png\",\"width\":300,\"height\":546,\"name\":\"ref_image_2.png\"}]},\"ref_selection\":\"ref_2\"},{\"on\":true,\"name\":\"Box 1_1\",\"type\":\"box_layer\",\"interpolation\":\"box\",\"repeat\":1,\"offset\":0,\"a_pause\":0,\"z_pause\":0,\"easing\":\"in_out\",\"easingConfig\":{\"path\":\"full\",\"strength\":1,\"acceleration\":0},\"driven\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"_drivenConfig\":{\"driver\":\"\",\"rotate\":0,\"d_scale\":1},\"scale\":1,\"points_store\":\"[{\\\"x\\\":0.6806348749052255,\\\"y\\\":0.7474313451231609,\\\"highlighted\\\":false,\\\"boxScale\\\":0.6940336804011772,\\\"pointScale\\\":0.6940336804011772,\\\"scale\\\":0.6940336804011772,\\\"rotation\\\":0,\\\"boxRotation\\\":0,\\\"uid\\\":16981}]\",\"coordinates\":\"[]\",\"box_keys\":[{\"frame\":1,\"x\":0.6302263722980433,\"y\":0.7737771727381784,\"scale\":0.312739506683858,\"rotation\":0},{\"frame\":9,\"x\":0.7801197829741051,\"y\":0.7550457649727679,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":16,\"x\":0.6806348749052255,\"y\":0.7474313451231609,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":21,\"x\":0.7156002654484349,\"y\":0.6992066860756498,\"scale\":0.6940336804011772,\"rotation\":0},{\"frame\":24,\"x\":0.7384942877962534,\"y\":0.7692594416414953,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":36,\"x\":0.688959973940796,\"y\":0.7697671006146228,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":46,\"x\":0.8155014538752791,\"y\":0.7885493982099611,\"scale\":0.5705899451391455,\"rotation\":0},{\"frame\":52,\"x\":0.7476519475477528,\"y\":0.7860112582600921,\"scale\":1.0468844700694617,\"rotation\":0}],\"box_timeline_point\":16,\"box_interpolation\":\"linear\",\"ref_attachment\":{\"entries\":[{\"path\":\"bg/ref_image_0.png\",\"type\":\"image/png\",\"width\":400,\"height\":471,\"name\":\"ref_image_0.png\"},{\"path\":\"bg/ref_image_1.png\",\"type\":\"image/png\",\"width\":457,\"height\":586,\"name\":\"ref_image_1.png\"},{\"path\":\"bg/ref_image_2.png\",\"type\":\"image/png\",\"width\":300,\"height\":546,\"name\":\"ref_image_2.png\"}]},\"ref_selection\":\"ref_1\"}]", null, "[{\"x\":200,\"y\":240},{\"x\":304,\"y\":240}]", 640, @@ -2733,8 +2487,8 @@ { "path": "bg/ref_image_0.png", "type": "image/png", - "width": 300, - "height": 546, + "width": 400, + "height": 471, "name": "ref_image_0.png" }, { @@ -2747,8 +2501,8 @@ { "path": "bg/ref_image_2.png", "type": "image/png", - "width": 512, - "height": 768, + "width": 300, + "height": 546, "name": "ref_image_2.png" } ] @@ -3267,7 +3021,7 @@ 472.44472370929736 ], "flags": {}, - "order": 16, + "order": 15, "mode": 0, "inputs": [ { @@ -3331,7 +3085,7 @@ 139.1564075673432 ], "flags": {}, - "order": 17, + "order": 16, "mode": 0, "inputs": [], "outputs": [ @@ -3364,6 +3118,263 @@ ], "color": "#323", "bgcolor": "#535" + }, + { + "id": 155, + "type": "PreviewImage", + "pos": [ + -502.6022947118135, + 646.6730215383718 + ], + "size": [ + 210, + 667.7696706327888 + ], + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 367 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.71", + "Node name for S&R": "PreviewImage", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.2.2" + } + }, + "widgets_values": [] + }, + { + "id": 158, + "type": "MaskPreview+", + "pos": [ + -261.5592877332859, + 646.6730215383718 + ], + "size": [ + 210, + 668.6430337189952 + ], + "flags": {}, + "order": 29, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 370 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfyui_essentials", + "ver": "9d9f4bedfc9f0321c19faf71855e228c93bd0dc9", + "Node name for S&R": "MaskPreview+", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.2.2" + } + }, + "widgets_values": [] + }, + { + "id": 124, + "type": "LoadImage", + "pos": [ + -484.35588731056015, + -95.80907240982795 + ], + "size": [ + 274.080078125, + 314.0000000000001 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 218, + 372 + ] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.71", + "Node name for S&R": "LoadImage", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.2.2" + } + }, + "widgets_values": [ + "bg.png", + "image" + ], + "color": "#432", + "bgcolor": "#653", + "shape": 1 + }, + { + "id": 121, + "type": "GetImageRangeFromBatch", + "pos": [ + 723.1343342581331, + 178.8695429625597 + ], + "size": [ + 340.3267578125, + 102 + ], + "flags": { + "collapsed": true + }, + "order": 34, + "mode": 0, + "inputs": [ + { + "name": "images", + "shape": 7, + "type": "IMAGE", + "link": 211 + }, + { + "name": "masks", + "shape": 7, + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 212, + 362 + ] + }, + { + "name": "MASK", + "type": "MASK", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "6ee278aa7d802c03ad713a7c5fbc02861b6773a8", + "Node name for S&R": "GetImageRangeFromBatch", + "ue_properties": { + "widget_ue_connectable": { + "num_frames": true + }, + "version": "7.3", + "input_ue_unconnectable": {} + }, + "aux_id": "kijai/ComfyUI-KJNodes" + }, + "widgets_values": [ + 0, + 1 + ] + }, + { + "id": 168, + "type": "CreateImageList", + "pos": [ + -504.06366922322417, + 335.96684961673895 + ], + "size": [ + 270, + 210 + ], + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "image1", + "shape": 7, + "type": "IMAGE", + "link": 364 + }, + { + "name": "image2", + "type": "IMAGE", + "link": 365 + }, + { + "name": "image3", + "type": "IMAGE", + "link": 366 + }, + { + "name": "image4", + "type": "IMAGE", + "link": null + }, + { + "name": "image_ref", + "shape": 7, + "type": "IMAGE", + "link": 372 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 367, + 368, + 369 + ] + }, + { + "name": "masks", + "type": "MASK", + "links": [ + 370, + 371 + ] + } + ], + "properties": { + "cnr_id": "wanvideowrapper_qq", + "ver": "b5fc414118021a88c7e70f178794fe8e2ac00225", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "Node name for S&R": "CreateImageList" + }, + "widgets_values": [ + true, + "mkl", + 0.4, + true + ] } ], "links": [ @@ -3823,68 +3834,12 @@ 0, "IMAGE" ], - [ - 345, - 118, - 0, - 161, - 0, - "IMAGE" - ], - [ - 346, - 117, - 0, - 161, - 1, - "IMAGE" - ], - [ - 347, - 161, - 0, - 155, - 0, - "IMAGE" - ], - [ - 348, - 161, - 0, - 137, - 1, - "IMAGE" - ], - [ - 349, - 161, - 0, - 123, - 2, - "IMAGE" - ], - [ - 350, - 161, - 1, - 158, - 0, - "MASK" - ], - [ - 351, - 161, - 1, - 123, - 3, - "MASK" - ], [ 352, 146, 0, 161, - 2, + 5, "IMAGE" ], [ @@ -3926,6 +3881,78 @@ 104, 2, "MASK" + ], + [ + 364, + 118, + 0, + 168, + 0, + "IMAGE" + ], + [ + 365, + 117, + 0, + 168, + 1, + "IMAGE" + ], + [ + 366, + 146, + 0, + 168, + 2, + "IMAGE" + ], + [ + 367, + 168, + 0, + 155, + 0, + "IMAGE" + ], + [ + 368, + 168, + 0, + 137, + 1, + "IMAGE" + ], + [ + 369, + 168, + 0, + 123, + 2, + "IMAGE" + ], + [ + 370, + 168, + 1, + 158, + 0, + "MASK" + ], + [ + 371, + 168, + 1, + 123, + 3, + "MASK" + ], + [ + 372, + 124, + 0, + 168, + 4, + "IMAGE" ] ], "groups": [ @@ -3946,10 +3973,10 @@ "config": {}, "extra": { "ds": { - "scale": 0.5559917313492839, + "scale": 0.6303940863128492, "offset": [ - 895.7180320253976, - 656.250667639637 + 1548.6123884557073, + 209.80359321404143 ] }, "frontendVersion": "1.33.5", diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 073c1b9..8464384 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -4,6 +4,7 @@ import folder_paths from PIL import Image import numpy as np import os +from concurrent.futures import ThreadPoolExecutor class ImageBlend_GPU: # Moved blend modes inside the class @@ -225,7 +226,21 @@ class CreateImageList: "export_alpha": ("BOOLEAN", {"default": True}) }, "optional": { - "image1": ("IMAGE",) + "image1": ("IMAGE",), + "image_ref": ("IMAGE",), + "method": ( + [ + 'mkl', + 'hm', + 'reinhard', + 'mvgd', + 'hm-mvgd-hm', + 'hm-mkl-hm', + ], { + "default": 'mkl' + }), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "multithread": ("BOOLEAN", {"default": True}), }, "hidden": { "unique_id": "UNIQUE_ID", @@ -237,6 +252,16 @@ class CreateImageList: RETURN_NAMES = ("images", "masks") FUNCTION = "batch" CATEGORY = "WanVideoWrapper_QQ/image" + DESCRIPTION = """ +Creates a batch of images from multiple dynamic image inputs. +Optionally applies color matching to all images if image_ref is provided. + +Color matching uses the color-matcher library to transfer color characteristics +from a reference image to all batch images. This is useful for maintaining +consistent color grading across multiple images. + +Supports alpha channel preservation with export_alpha option. +""" def load_image_with_alpha(self, image_path): """Load image from path with alpha channel preserved""" @@ -317,7 +342,66 @@ class CreateImageList: print(f"Error in trace: {e}") return None - def batch(self, export_alpha, unique_id=None, prompt=None, **kwargs): + def apply_color_match(self, images, image_ref, method, strength, multithread): + """Apply color matching to a batch of images using a reference image""" + try: + from color_matcher import ColorMatcher + except: + raise Exception("Can't import color-matcher, did you install requirements.txt? Manual install: pip install color-matcher") + + images = images.cpu() + image_ref = image_ref.cpu() + batch_size = images.size(0) + + # Handle alpha channel: extract RGB for color matching + has_alpha = images.shape[-1] == 4 + if has_alpha: + rgb_images = images[:, :, :, :3] + alpha_channel = images[:, :, :, 3] + else: + rgb_images = images + + # Extract RGB from reference if it has alpha + if image_ref.shape[-1] == 4: + image_ref_rgb = image_ref[:, :, :, :3] + else: + image_ref_rgb = image_ref + + images_rgb = rgb_images.squeeze() + images_ref = image_ref_rgb.squeeze() + + image_ref_np = images_ref.numpy() + images_rgb_np = images_rgb.numpy() + + def process(i): + cm = ColorMatcher() + image_rgb_np_i = images_rgb_np if batch_size == 1 else images_rgb[i].numpy() + image_ref_np_i = image_ref_np if image_ref_rgb.size(0) == 1 else images_ref[i].numpy() + try: + image_result = cm.transfer(src=image_rgb_np_i, ref=image_ref_np_i, method=method) + image_result = image_rgb_np_i + strength * (image_result - image_rgb_np_i) + return torch.from_numpy(image_result) + except Exception as e: + print(f"Color matching thread {i} error: {e}") + return torch.from_numpy(image_rgb_np_i) # fallback + + if multithread and batch_size > 1: + max_threads = min(os.cpu_count() or 1, batch_size) + with ThreadPoolExecutor(max_workers=max_threads) as executor: + out = list(executor.map(process, range(batch_size))) + else: + out = [process(i) for i in range(batch_size)] + + out = torch.stack(out, dim=0).to(torch.float32) + out.clamp_(0, 1) + + # Reattach alpha channel if it was present + if has_alpha: + out = torch.cat([out, alpha_channel.unsqueeze(-1)], dim=-1) + + return out + + def batch(self, export_alpha, unique_id=None, prompt=None, image_ref=None, method='mkl', strength=1.0, multithread=True, **kwargs): # Collect all image inputs from kwargs (dynamic inputs) images = [] @@ -350,6 +434,10 @@ class CreateImageList: if img.shape[-1] == 3: img = torch.nn.functional.pad(img, (0, 1), mode='constant', value=1.0) + # Apply color matching if image_ref is provided + if image_ref is not None: + img = self.apply_color_match(img, image_ref, method, strength, multithread) + # Extract masks masks = img[:, :, :, 3] @@ -406,6 +494,10 @@ class CreateImageList: # Concatenate all images s = torch.cat(fitted_images, dim=0) + # Apply color matching if image_ref is provided + if image_ref is not None: + s = self.apply_color_match(s, image_ref, method, strength, multithread) + # Extract alpha channel as masks (MASK format is BHW, not BHWC) masks = s[:, :, :, 3] # Shape: [B, H, W] diff --git a/web/create_image_list.js b/web/create_image_list.js index 6c6ef1c..552ef28 100644 --- a/web/create_image_list.js +++ b/web/create_image_list.js @@ -4,6 +4,16 @@ app.registerExtension({ name: "WanVideoWrapper.CreateImageList", async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeData.name === 'CreateImageList') { + // Helper function to ensure image_ref is at the bottom + const ensureImageRefAtBottom = function() { + const imageRefIndex = this.inputs.findIndex(inp => inp.name === 'image_ref'); + if (imageRefIndex !== -1 && imageRefIndex < this.inputs.length - 1) { + // Move image_ref to the end + const imageRefInput = this.inputs.splice(imageRefIndex, 1)[0]; + this.inputs.push(imageRefInput); + } + }; + nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) { const stackTrace = new Error().stack; @@ -29,14 +39,14 @@ app.registerExtension({ return; } - // Don't process the export_alpha input (it's a widget converted to input) + // Don't process specific inputs (widgets converted to inputs or color matching params) const input = this.inputs[index]; - if (input && input.name === 'export_alpha') { + if (input && (input.name === 'export_alpha' || input.name === 'image_ref' || input.name === 'method' || input.name === 'strength' || input.name === 'multithread')) { return; } - // Count how many IMAGE type inputs we have (excluding export_alpha) - const imageInputCount = this.inputs.filter(inp => inp.type === 'IMAGE').length; + // Count how many IMAGE type inputs we have (excluding image_ref, which is for color matching) + const imageInputCount = this.inputs.filter(inp => inp.type === 'IMAGE' && inp.name !== 'image_ref').length; // When disconnecting, remove the input if there's more than one IMAGE input if (!connected && imageInputCount > 1) { @@ -49,17 +59,17 @@ app.registerExtension({ } } - // Ensure at least one IMAGE input exists - const remainingImageInputs = this.inputs.filter(inp => inp.type === 'IMAGE').length; + // Ensure at least one IMAGE input exists (excluding image_ref) + const remainingImageInputs = this.inputs.filter(inp => inp.type === 'IMAGE' && inp.name !== 'image_ref').length; if (remainingImageInputs === 0) { this.addInput("image1", "IMAGE"); } - // Renumber all IMAGE inputs sequentially + // Renumber all IMAGE inputs sequentially (excluding image_ref) let slot_i = 1; for (let i = 0; i < this.inputs.length; i++) { const inp = this.inputs[i]; - if (inp.type === 'IMAGE') { + if (inp.type === 'IMAGE' && inp.name !== 'image_ref') { inp.name = `image${slot_i}`; slot_i++; } @@ -69,6 +79,9 @@ app.registerExtension({ if (connected) { this.addInput(`image${slot_i}`, "IMAGE"); } + + // Ensure image_ref stays at the bottom + ensureImageRefAtBottom.call(this); } } }