From c57d70c81085e3fe8c7bd0035251831447c1ba73 Mon Sep 17 00:00:00 2001 From: Juan Treminio Date: Thu, 24 Sep 2026 09:45:18 -0500 Subject: [PATCH 1/3] Adds Ming Image support --- .../ComfyUIBackend/WorkflowGenerator.cs | 30 ++++++++++++++++++- .../WorkflowGeneratorModelSupport.cs | 17 +++++++++-- src/Text2Image/CommonModels.cs | 1 + src/Text2Image/T2IModelClassSorter.cs | 17 +++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs index a40d5c097..448555c3a 100644 --- a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs +++ b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs @@ -1064,7 +1064,7 @@ public string CreateKSampler(JArray model, JArray pos, JArray neg, JArray latent defscheduler ??= "simple"; } // TODO: Registry of model default preferences instead of this - else if (IsFlux() || IsWanVideo() || IsWanVideo22() || IsOmniGen() || IsQwenImage() || IsQwenImage21() || IsZImage() || IsZetaChroma() || IsErnie() || IsHiDreamO1() || IsLens() || IsPixelDiT() || IsKrea2() || IsBoogu() || IsMageFlow() || IsMiniMaxMusic3() || IsSeedVR2()) + else if (IsFlux() || IsWanVideo() || IsWanVideo22() || IsOmniGen() || IsQwenImage() || IsQwenImage21() || IsZImage() || IsMingImage() || IsZetaChroma() || IsErnie() || IsHiDreamO1() || IsLens() || IsPixelDiT() || IsKrea2() || IsBoogu() || IsMageFlow() || IsMiniMaxMusic3() || IsSeedVR2()) { defscheduler ??= "simple"; } @@ -2758,6 +2758,34 @@ public JArray CreateConditioningDirect(string prompt, JArray clip, T2IModel mode ["images"] = imageNode }, id); } + else if (IsMingImage() && isPositive && GetPromptImage(false, true) is JArray mingImage) + { + string resized = CreateNode("ImageScale", new JObject() + { + ["image"] = mingImage, + ["width"] = width, + ["height"] = height, + ["crop"] = "center", + ["upscale_method"] = "lanczos" + }); + JObject inputs = new() + { + ["clip"] = clip, + ["vae"] = CurrentVae.Path, + ["prompt"] = prompt, + ["images.image_1"] = new JArray(resized, 0) + }; + for (int i = 1; i < 8; i++) + { + JArray image2 = GetPromptImage(false, true, i); + if (image2 is null) + { + break; + } + inputs[$"images.image_{i + 1}"] = image2; + } + node = CreateNode("TextEncodeMingImageEdit", inputs, id); + } else if (IsHunyuanVideoI2V() && prompt.StartsWith("'); diff --git a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGeneratorModelSupport.cs b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGeneratorModelSupport.cs index c527da94a..8e592cebe 100644 --- a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGeneratorModelSupport.cs +++ b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGeneratorModelSupport.cs @@ -130,6 +130,9 @@ public bool HasFluxGuidance() /// Returns true if the current model is a Z-Image model. public bool IsZImage() => IsModelCompatClass(T2IModelClassSorter.CompatZImage); + /// Returns true if the current model is a Ming Image model. + public bool IsMingImage() => IsModelCompatClass(T2IModelClassSorter.CompatMingImage); + /// Returns true if the current model is a Zeta Chroma model. public bool IsZetaChroma() => IsModelCompatClass(T2IModelClassSorter.CompatZetaChroma); @@ -349,7 +352,7 @@ public WGNodeData EmptyImage(int width, int height, int batchSize, string id = n ["width"] = width }, id)); } - else if (IsSD3() || IsFlux() || IsHiDream() || IsChroma() || IsOmniGen() || IsQwenImage() || IsZImage() || IsOvis() || IsKandinsky5ImgLite() || IsAnima() || IsLongcatImage() || IsKrea2()) + else if (IsSD3() || IsFlux() || IsHiDream() || IsChroma() || IsOmniGen() || IsQwenImage() || IsZImage() || IsOvis() || IsKandinsky5ImgLite() || IsAnima() || IsLongcatImage() || IsKrea2() || IsMingImage()) { return resultImage(CreateNode("EmptySD3LatentImage", new JObject() { @@ -708,6 +711,11 @@ public string GetQwenImage21TextEncoder() return RequireClipModel("qwen3vl_8b_int8_convrot.safetensors", "https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/text_encoders/qwen3vl_8b_int8_convrot.safetensors", "8bfd0f6e12abf2d2d697ecc888e5e90b0d6741d6708f05799f53afa560452e8f", T2IParamTypes.QwenModel); } + public string GetMingImageTextEncoder() + { + return RequireClipModel("ming_image_0.1_ling_mini_2.0_int8_convrot.safetensors", "https://huggingface.co/Comfy-Org/Ming-Image/resolve/main/text_encoders/ming_image_0.1_ling_mini_2.0_int8_convrot.safetensors", "9d9f31cfce37c24ae1589287f5c33bd9dda80e14ddf773b5e17a0bb69c8cc4b1", null); + } + public string GetQwenImage25_7b_tenc() { return RequireClipModel("qwen_2.5_vl_7b_fp8_scaled.safetensors", "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors", "cb5636d852a0ea6a9075ab1bef496c0db7aef13c02350571e388aea959c5c0b4", T2IParamTypes.QwenModel); @@ -1409,6 +1417,11 @@ public void LoadClip3(string type, string modelA, string modelB, string modelC) }); LoadingModel = [samplingNode, 0]; } + else if (IsMingImage()) + { + helpers.LoadClip("qwen_image", helpers.GetMingImageTextEncoder()); + helpers.DoVaeLoader(null, T2IModelClassSorter.CompatMingImage, "ming-image-vae"); + } else if (IsHunyuanImage()) { helpers.LoadClip2("hunyuan_image", helpers.GetQwenImage25_7b_tenc(), helpers.GetByT5SmallGlyphxl_tenc()); @@ -1616,7 +1629,7 @@ public void LoadClip3(string type, string modelA, string modelB, string modelC) }); LoadingModel = [samplingNode, 0]; } - else if (IsZImage() || IsAceStep15() || IsAnima() || IsBoogu()) + else if (IsZImage() || IsAceStep15() || IsAnima() || IsBoogu() || IsMingImage()) { string samplingNode = CreateNode("ModelSamplingAuraFlow", new JObject() { diff --git a/src/Text2Image/CommonModels.cs b/src/Text2Image/CommonModels.cs index 94c62ef2a..e022d8e14 100644 --- a/src/Text2Image/CommonModels.cs +++ b/src/Text2Image/CommonModels.cs @@ -94,6 +94,7 @@ public static void RegisterCoreSet() Register(new("ltx2-3-audio-vae", "LTX-2.3 Audio VAE", "The audio VAE for Lightricks LTX-2.3.", "https://huggingface.co/Kijai/LTX2.3_comfy/resolve/main/vae/LTX23_audio_vae_bf16.safetensors", "5bc10fa4adecf99dda132d916e23048cbd56797702c5fa50eb5d2079048a38c3", "VAE", "LTX-2/LTX23_audio_vae_bf16.safetensors")); Register(new("ltx2-5-video-vae", "LTX-2.5 Video VAE", "The video VAE for Lightricks LTX-2.5.", "https://huggingface.co/mcmonkey/swarm-vaes/resolve/main/ltx-2.5-video-vae-conv-bf16.safetensors", "685b06ee3d9b2039647698fc4ea33175112462fc374e2777312c907897dfce8d", "VAE", "LTX-2/ltx-2.5-video-vae-conv-bf16.safetensors")); Register(new("ltx2-5-audio-vae", "LTX-2.5 Audio VAE", "The audio VAE for Lightricks LTX-2.5.", "https://huggingface.co/mcmonkey/swarm-vaes/resolve/main/ltx-2.5-audio-vae-bf16.safetensors", "c52733d37f6a7fb7949c3dc0fb468c6cb2169e4d836983a73babb9f0d54837a5", "VAE", "LTX-2/ltx-2.5-audio-vae-bf16.safetensors")); + Register(new("ming-image-vae", "Ming Image VAE", "The VAE for Ming Image", "https://huggingface.co/Comfy-Org/Ming-Image/resolve/main/vae/ming_image_vae_bf16.safetensors", "7f5bed402dc8c77dc2e0ab1929a85d4df433b7cf7b599dfa8c353da98db0b90a", "VAE", "MingImage/ming_image_vae_bf16.safetensors")); Register(new("qwen-image-2.1-vae", "Qwen Image 2.1 VAE", "The RGBA VAE for Qwen Image 2.1", "https://huggingface.co/Comfy-Org/Qwen-Image-2.1/resolve/main/vae/qwen_image_2.1_vae_bf16.safetensors", "bb21f7473051e1ac368515dd3f2e15cd44d7a11748ee8823e1ddca3e4876b7c9", "VAE", "QwenImage/qwen_image_2.1_vae_bf16.safetensors")); Register(new("qwen-image-vae", "Qwen Image VAE", "The VAE for Qwen Image", "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/vae/qwen_image_vae.safetensors", "a70580f0213e67967ee9c95f05bb400e8fb08307e017a924bf3441223e023d1f", "VAE", "QwenImage/qwen_image_vae.safetensors")); Register(new("mage-flow-vae", "Mage-VAE", "The VAE for Mage-Flow", "https://huggingface.co/microsoft/Mage-Flow/resolve/main/vae/diffusion_pytorch_model.safetensors", "34e076dc1e8a15321e1e07be5111d59cf16dd10b804b7c7e20b4de29013427e0", "VAE", "MageFlow/mage_flow_vae.safetensors")); diff --git a/src/Text2Image/T2IModelClassSorter.cs b/src/Text2Image/T2IModelClassSorter.cs index f13d3e535..36ecc5fa0 100644 --- a/src/Text2Image/T2IModelClassSorter.cs +++ b/src/Text2Image/T2IModelClassSorter.cs @@ -49,7 +49,8 @@ public static T2IVAEFamily RegisterVaeFamily(string id, string knownVaeId, strin VaeSd3 = RegisterVaeFamily("sd3", "sd35-vae", "stable-diffusion-v3"), VaeSdxl = RegisterVaeFamily("sdxl", "sdxl-vae", "stable-diffusion-xl-v1"), VaeQwenImage21 = RegisterVaeFamily("qwenimage21", "qwen-image-2.1-vae", "qwen-image-2.1"), - VaeQwenImage = RegisterVaeFamily("qwenimage", "qwen-image-vae", "qwen-image"); + VaeQwenImage = RegisterVaeFamily("qwenimage", "qwen-image-vae", "qwen-image"), + VaeMingImage = RegisterVaeFamily("mingimage", "ming-image-vae", "ming-image"); /// Core Compatibility classes. public static T2IModelCompatClass @@ -78,6 +79,7 @@ public static T2IModelCompatClass CompatLumina2 = RegisterCompat(new() { ID = "lumina-2", ShortCode = "Lumi2", VaeFamily = VaeFlux1 }), CompatQwenImage21 = RegisterCompat(new() { ID = "qwen-image-2.1", ShortCode = "Qwen21", LorasTargetTextEnc = false, VaeFamily = VaeQwenImage21, ResolutionPrecision = 32, SupportsAlpha = true }), CompatQwenImage = RegisterCompat(new() { ID = "qwen-image", ShortCode = "Qwen", LorasTargetTextEnc = false, VaeFamily = VaeQwenImage }), + CompatMingImage = RegisterCompat(new() { ID = "ming-image", ShortCode = "Ming", LorasTargetTextEnc = false, VaeFamily = VaeMingImage, SupportsAlpha = true }), CompatHunyuanImage2_1 = RegisterCompat(new() { ID = "hunyuan-image-2_1", ShortCode = "HyImg", LorasTargetTextEnc = false }), CompatHunyuanImage2_1Refiner = RegisterCompat(new() { ID = "hunyuan-image-2_1-refiner", ShortCode = "HyImg", LorasTargetTextEnc = false }), CompatHunyuanVideo1_5 = RegisterCompat(new() { ID = "hunyuan-video-1_5", ShortCode = "HyVid", LorasTargetTextEnc = false, IsText2Video = true, IsImage2Video = true }), @@ -230,6 +232,8 @@ bool isLtxv2Lora(JObject h) => (hasLoraKey(h, "transformer_blocks.0.attn1.to_k") bool isCosmosPredict2_14B(JObject h) => h.ContainsKey("net.blocks.0.adaln_modulation_cross_attn.1.weight") && h.ContainsKey("net.pos_embedder.dim_temporal_range") && h.ContainsKey("net.x_embedder.proj.1.weight") && h.ContainsKey("net.blocks.35.adaln_modulation_mlp.2.weight"); bool isLumina2(JObject h) => hasKey(h, "cap_embedder.0.weight"); bool isZImage(JObject h) => (hasKey(h, "context_refiner.0.attention.k_norm.weight") || hasKey(h, "context_refiner.0.attention.norm_k.weight")) && hasKey(h, "layers.0.adaLN_modulation.0.bias"); + bool isMingImage(JObject h) => tryGetKey(h, "cap_embedder.1.weight", out JToken tok) && tok["shape"].ToArray()[0].Value() == 3840 && hasKey(h, "noise_refiner.0.attention.k_norm.weight") && !hasKey(h, "cap_pad_token") && !hasKey(h, "dec_net.cond_embed.weight"); + bool isMingImageVae(JObject h) => h.ContainsKey("decoder.upsamples.0.residual.2.weight") && h["decoder.head.2.weight"]?["shape"] is JArray shape && shape.Count == 5 && shape[0].Value() == 4; bool isZetaChroma(JObject h) => hasKey(h, "dec_net.input_embedder.embedder.0.bias") && hasKey(h, "__x0__"); bool isOvis(JObject h) => hasKey(h, "double_blocks.0.img_mlp.down_proj.weight"); bool isZImageLora(JObject h) => (hasLoraKey(h, "layers.0.adaLN_modulation.0") && hasLoraKey(h, "layers.9.feed_forward.w3")) @@ -675,7 +679,7 @@ JToken GetEmbeddingKey(JObject h) // ====================== Z-Image ====================== Register(new() { ID = "z-image", CompatClass = CompatZImage, Name = "Z-Image", StandardWidth = 1024, StandardHeight = 1024, IsThisModelOfClass = (m, h) => { - return isLumina2(h) && isZImage(h) && !isZetaChroma(h); + return isLumina2(h) && isZImage(h) && !isZetaChroma(h) && !isMingImage(h); }}); Register(new() { ID = "z-image/lora", CompatClass = CompatZImage, Name = "Z-Image LoRA", StandardWidth = 1024, StandardHeight = 1024, IsThisModelOfClass = (m, h) => { @@ -690,6 +694,15 @@ JToken GetEmbeddingKey(JObject h) { return isLumina2(h) && isZImage(h) && isZetaChroma(h); }}); + // ====================== Ming Image ====================== + Register(new() { ID = "ming-image", CompatClass = CompatMingImage, Name = "Ming Image", StandardWidth = 2048, StandardHeight = 2048, IsThisModelOfClass = (m, h) => + { + return isMingImage(h); + }}); + Register(new() { ID = "ming-image/vae", CompatClass = CompatMingImage, Name = "Ming Image VAE", StandardWidth = 1024, StandardHeight = 1024, IsThisModelOfClass = (m, h) => + { + return isMingImageVae(h); + }}); // ====================== Qwen Image 2.1 ====================== Register(new() { ID = "qwen-image-2.1", CompatClass = CompatQwenImage21, Name = "Qwen Image 2.1", StandardWidth = 1024, StandardHeight = 1024, IsThisModelOfClass = (m, h) => { From 89cf1e14224dd9908033280fcb0308082d6424e9 Mon Sep 17 00:00:00 2001 From: Juan Treminio Date: Thu, 24 Sep 2026 11:01:45 -0500 Subject: [PATCH 2/3] Use GetPromptImage for resizing --- .../ComfyUIBackend/WorkflowGenerator.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs index 448555c3a..6c199c197 100644 --- a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs +++ b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs @@ -2758,26 +2758,18 @@ public JArray CreateConditioningDirect(string prompt, JArray clip, T2IModel mode ["images"] = imageNode }, id); } - else if (IsMingImage() && isPositive && GetPromptImage(false, true) is JArray mingImage) + else if (IsMingImage() && isPositive && GetPromptImage(true, true) is JArray mingImage) { - string resized = CreateNode("ImageScale", new JObject() - { - ["image"] = mingImage, - ["width"] = width, - ["height"] = height, - ["crop"] = "center", - ["upscale_method"] = "lanczos" - }); JObject inputs = new() { ["clip"] = clip, ["vae"] = CurrentVae.Path, ["prompt"] = prompt, - ["images.image_1"] = new JArray(resized, 0) + ["images.image_1"] = mingImage }; for (int i = 1; i < 8; i++) { - JArray image2 = GetPromptImage(false, true, i); + JArray image2 = GetPromptImage(true, true, i); if (image2 is null) { break; From 0e139a21d784782b1e977edad99bda18a41b32c2 Mon Sep 17 00:00:00 2001 From: Juan Treminio Date: Thu, 24 Sep 2026 11:52:00 -0500 Subject: [PATCH 3/3] Use SwarmTextEncodeAdvanced --- .../ComfyUIBackend/WorkflowGenerator.cs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs index 6c199c197..938ae0df3 100644 --- a/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs +++ b/src/BuiltinExtensions/ComfyUIBackend/WorkflowGenerator.cs @@ -834,7 +834,7 @@ public List LoadPromptImagesForMainRef(List images) public (JArray, JArray, JArray, JArray) BuildInputImageHandling(List images, JArray pos, JArray neg, JArray latent) { JArray imgNeg = null; - if (IsKontext() || IsOmniGen() || IsQwenImage() || IsQwenImage21() || IsAnyFlux2() || IsBoogu() || IsMageFlow() || (IsKrea2() && UserInput.Get(ComfyUIBackendExtension.EnableReferenceLatents, "none") != "none")) + if (IsKontext() || IsOmniGen() || IsQwenImage() || IsQwenImage21() || IsAnyFlux2() || IsBoogu() || IsMageFlow() || IsMingImage() || (IsKrea2() && UserInput.Get(ComfyUIBackendExtension.EnableReferenceLatents, "none") != "none")) { if (IsOmniGen() || IsQwenImageEditPlus() || IsQwenImage21() || IsBoogu() || IsMageFlow()) { @@ -1427,6 +1427,10 @@ public JArray GetPromptImage(bool fixSize, bool promptSize = false, int index = target = 1024; // Qwen image targets 1328 for gen but wants 1024 inputs. doesFit = Math.Abs(actual - target) <= 64; } + else if (IsMingImage()) + { + doesFit = Math.Abs(actual - target) <= 64; + } if (fixSize && !doesFit) { (width, height) = Utilities.ResToModelFit(width, height, target * target, precision: promptSize ? 1 : 64); @@ -2758,15 +2762,9 @@ public JArray CreateConditioningDirect(string prompt, JArray clip, T2IModel mode ["images"] = imageNode }, id); } - else if (IsMingImage() && isPositive && GetPromptImage(true, true) is JArray mingImage) + else if (IsMingImage() && isPositive) { - JObject inputs = new() - { - ["clip"] = clip, - ["vae"] = CurrentVae.Path, - ["prompt"] = prompt, - ["images.image_1"] = mingImage - }; + JArray imageNode = GetPromptImage(true, true, 0); for (int i = 1; i < 8; i++) { JArray image2 = GetPromptImage(true, true, i); @@ -2774,9 +2772,25 @@ public JArray CreateConditioningDirect(string prompt, JArray clip, T2IModel mode { break; } - inputs[$"images.image_{i + 1}"] = image2; + string batched = CreateNode("ImageBatch", new JObject() + { + ["image1"] = imageNode, + ["image2"] = image2 + }); + imageNode = [batched, 0]; } - node = CreateNode("TextEncodeMingImageEdit", inputs, id); + node = CreateNode("SwarmTextEncodeAdvanced", new JObject() + { + ["clip"] = clip, + ["lora_hooks"] = CreateDynamicLoraHooks(), + ["steps"] = steps, + ["prompt"] = prompt, + ["width"] = width, + ["height"] = height, + ["target_width"] = width, + ["target_height"] = height, + ["images"] = imageNode + }, id); } else if (IsHunyuanVideoI2V() && prompt.StartsWith("