diff --git a/README.en.md b/README.en.md
index 43a9127..a816ee7 100644
--- a/README.en.md
+++ b/README.en.md
@@ -33,6 +33,17 @@
-
## Changelog
+**v1.1.6 (2024/4/26)**
+
+- Added `easy styleAlignedBatchAlign` - Credit of [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
+- Added `easy ckptNames`
+- Added `easy controlnetNames`
+- Added `easy imagesSplitimage` - Batch images split into single images
+- Added `easy imageCount` - Get Image Count
+- Added `easy textSwitch` - Text Switch
+- `easy kSampler` & `easy fullkSampler` added **Preview&Choose** to **image_output** widget
+
+
**v1.1.5 (2024/4/24)**
- Rewrite `easy cleanGPUUsed` - the memory usage of the comfyUI can to be cleared
@@ -60,7 +71,7 @@
- Added `easy promptConcat`
- `easy wildcards` Added **multiline_mode**
-**v1.1.2 (39c5ccf)**
+**v1.1.2**
- Optimized some of the recommended nodes for slots related to EasyUse
- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews
@@ -76,7 +87,7 @@
- Fixed layerDiffuse xyplot bug
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
-**v1.1.1 (2024/3/16)**
+**v1.1.1**
- The issue that the seed is 0 when a node with a seed control is added and **control before generate** is fixed for the first time run queue prompt.
- `easy preSamplingAdvanced` Added **return_with_leftover_noise**
@@ -86,7 +97,7 @@
- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings
- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster
-**v1.1.0 (d5ff84e)**
+**v1.1.0**
- Added `easy imageSplitList` - to split every N images
- Added `easy preSamplingDiffusionADDTL` - It can modify foreground、background or blended additional prompt
@@ -102,15 +113,18 @@
- Fixed `easy instantIDApply` mask not input right
-**v1.0.9 (ff1add1)**
+
+v1.0.9
- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed
- Fixed `easy pipeIn`
- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID)
- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions
- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts
+
-**v1.0.8 (f28cbf7)**
+
+v1.0.8
- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models)
- `easy styleSelector` The search box is modified to be case-insensitive
@@ -120,8 +134,10 @@
- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision)
- Fixed `easy detailerFix` generation error when batch size is greater than 1
- Optimize the code, reduce a lot of redundant code and improve the running speed
+
-**v1.0.7 (2024-02-19)**
+
+v1.0.7
- Added `easy cascadeLoader` - stable cascade Loader
- Added `easy preSamplingCascade` - stable cascade preSampling Settings
@@ -129,22 +145,28 @@
- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple
-
- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image)
+
-**v1.0.6**
+
+v1.0.6
- Added `easy XYInputs: Checkpoint`
- Added `easy XYInputs: Lora`
- `easy seed` can manually switch the random seed when increasing the fixed seed value
- Fixed `easy fullLoader` and all loaders to automatically adjust the node size when switching LoRa
- Removed the original ttn image saving logic and adapted to the default image saving format extension of ComfyUI
+
-- **v1.0.5**
+
+v1.0.5
- Added `easy isSDXL`
- Added prompt word control on `easy svdLoader`, which can be used with open_clip model
- Added **populated_text** on `easy wildcards`, wildcard populated text can be output
+
-**v1.0.4**
+
+v1.0.4
- `easy showAnything` added support for converting other types (e.g., tensor conditions, images, etc.)
- Added `easy showLoaderSettingsNames` can display the model and VAE name in the output loader assembly
@@ -164,9 +186,10 @@
- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page
- `easy imageSave` added **only_preivew**
- Adjust the `easy latentCompositeMaskedWithCond` node
+
-
-**v1.0.3**
+
+v1.0.3
- Added `easy stylesSelector`
- Added **scale_soft_weights** in `easy controlnetLoader` and `easy controlnetLoaderADV`
@@ -184,9 +207,10 @@
- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings
- Modify the styles path to be compatible with other environments
+
-
-**v1.0.2**
+
+v1.0.2
- Added `easy XYPlotAdvanced` and some nodes about `easy XYInputs`
- Added **Alt+1-Alt+9** Shortcut keys to quickly paste node presets for Node templates (corresponding to 1~9 sequences)
@@ -203,7 +227,7 @@
- Removed `easy imageRemBg`
- Remove the introductory diagram and workflow files from the package to reduce the package size
- Replaced the font file used in the generation of XY diagrams
-
+
v1.0.1
@@ -298,6 +322,7 @@ Disclaimer: Opened source was not easy. I have a lot of respect for the contribu
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... |
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
+| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
## Workflow Examples
diff --git a/README.md b/README.md
index 085184e..6087e5d 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,17 @@
## 更新日志
-**v1.1.5 (2024/4/24)**
+**v1.1.6 (2024/4/26)**
+
+- 增加 `easy styleAlignedBatchAlign` - 风格对齐 [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
+- 增加 `easy ckptNames`
+- 增加 `easy controlnetNames`
+- 增加 `easy imagesSplitimage` - 批次图像拆分单张
+- 增加 `easy imageCount` - 图像数量
+- 增加 `easy textSwitch` - 文字切换
+- `easy kSampler` 和 `easy fullkSampler` 的 **image_output** 增加 **Preview&Choose**选项
+
+**v1.1.5**
- 重写 `easy cleanGPUUsed` - 可强制清理comfyUI的模型显存占用
- 增加 `easy humanSegmentation` - 多类分割、人像分割
@@ -47,7 +57,7 @@
- 增加 `easy imageInterrogator` - 图像反推
- 增加 `easy stableDiffusion3API` - 简易的Stable Diffusion 3 多账号API节点
-**v1.1.4 (2024/4/13)**
+**v1.1.4**
- 增加 `easy imageChooser` - 从[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker)简化的图片选择器
- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit
@@ -56,7 +66,7 @@
- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本
- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值
-**v1.1.3 (2024/4/4)**
+**v1.1.3**
- `easy ipadapterApply` 增加 **COMPOSITION** 预置项
- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持
@@ -66,7 +76,7 @@
- `easy wildcards` 增加 **multiline_mode**属性
- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型
-**v1.1.2 (39c5ccf)**
+**v1.1.2**
- 改写 EasyUse 相关节点的部分插槽推荐节点
- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图
@@ -82,7 +92,7 @@
- 修复 `easy pipeEdit` 提示词输入lora时报错
- 修复 layerDiffuse xyplot相关bug
-**v1.1.1 (5c8af8f)**
+**v1.1.1**
- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题
- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise**
@@ -93,7 +103,7 @@
- 去除强制**control_before_generate**设定
- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快
-**v1.1.0 (d5ff84e)**
+**v1.1.0**
- 增加 `easy imageSplitList` - 拆分每 N 张图像
- 增加 `easy preSamplingDiffusionADDTL` - 可配置前景、背景、blended的additional_prompt等
@@ -107,15 +117,18 @@
- 修复 `easy instantIDApply` mask 未传入正确值
- 修复 在 非a1111提示词风格下 BREAK 不生效的问题
-**v1.0.9 (ff1add1)**
+
+v1.0.9
- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错
- 修复 `easy pipeIn` - pipe设为可不必选
- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID)
- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表
- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错
+
-**v1.0.8 (f28cbf7)**
+
+v1.0.8
- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints))
- `easy styleSelector` 搜索框修改为不区分大小写匹配
@@ -129,13 +142,16 @@
(翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦!
首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。)
+
-**v1.0.7**
+
+v1.0.7
- 增加 `easy cascadeLoader` - stable cascade 加载器
- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数
- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器
- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple
+
v1.0.6
@@ -310,6 +326,7 @@
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
+| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign |
## 示例
diff --git a/__init__.py b/__init__.py
index 75110db..d75c3ea 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,3 +1,5 @@
+__version__ = "1.1.6"
+
import os
import glob
import folder_paths
@@ -85,4 +87,4 @@ WEB_DIRECTORY = "./web"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
-print('\033[34mComfy-Easy-Use (v1.1.5): \033[92mLoaded\033[0m')
\ No newline at end of file
+print(f'\033[34mComfy-Easy-Use v{__version__}: \033[92mLoaded\033[0m')
\ No newline at end of file
diff --git a/py/easyNodes.py b/py/easyNodes.py
index ccf7f83..7669810 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -22,7 +22,7 @@ from .wildcards import process_with_loras, get_wildcard_list, process
from .adv_encode import advanced_encode
from .layer_diffuse.func import LayerDiffuse, LayerMethod
-from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions, AlwaysEqualProxy
+from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions, AlwaysEqualProxy, get_sd_version
from .libs.loader import easyLoader
from .libs.sampler import easySampler
from .libs.xyplot import easyXYPlot
@@ -819,6 +819,39 @@ class globalSeed:
#---------------------------------------------------------------加载器 开始----------------------------------------------------------------------#
+class setCkptName:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ "ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
+ }
+ }
+
+ RETURN_TYPES = (AlwaysEqualProxy('*'),)
+ RETURN_NAMES = ("ckpt_name",)
+ FUNCTION = "set_name"
+ CATEGORY = "EasyUse/Utils"
+
+ def set_name(self, ckpt_name):
+ return (ckpt_name,)
+
+class setControlName:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ "controlnet_name": (folder_paths.get_filename_list("controlnet"),),
+ }
+ }
+
+ RETURN_TYPES = (AlwaysEqualProxy('*'),)
+ RETURN_NAMES = ("controlnet_name",)
+ FUNCTION = "set_name"
+ CATEGORY = "EasyUse/Utils"
+
+ def set_name(self, controlnet_name):
+ return (controlnet_name,)
+
# 简易加载器完整
class fullLoader:
@@ -2084,6 +2117,27 @@ class fooocusInpaintLoader:
return ((inpaint_head_model, inpaint_lora),)
#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
+from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS
+class styleAlignedBatchAlign:
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "share_norm": (SHARE_NORM_OPTIONS,),
+ "share_attn": (SHARE_ATTN_OPTIONS,),
+ "scale": ("FLOAT", {"default": 1, "min": 0, "max": 1.0, "step": 0.1}),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "align"
+ CATEGORY = "EasyUse/Adapter"
+
+ def align(self, model, share_norm, share_attn, scale):
+ return (styleAlignBatch(model, share_norm, share_attn, scale),)
+
def insightface_loader(provider):
try:
from insightface.app import FaceAnalysis
@@ -3236,7 +3290,7 @@ class samplerCustomSettings:
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"cfg_negative": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS + ['inversed_euler'],),
- "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential','sdturbo','vp'],),
+ "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential', 'sdturbo', 'vp'],),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
@@ -3327,6 +3381,7 @@ class samplerCustomSettings:
return (ksampler,)
def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, flip_sigmas, denoise, add_noise, seed, image_to_latent=None, latent=None, optional_sampler=None, optional_sigmas=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
+
# 图生图转换
vae = pipe["vae"]
model = pipe["model"]
@@ -3334,6 +3389,7 @@ class samplerCustomSettings:
negative = pipe['negative']
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
_guider, sigmas = None, None
+ print(get_sd_version(model))
if image_to_latent is not None:
if guider == "IP2P+DualCFG":
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent)
@@ -3384,6 +3440,8 @@ class samplerCustomSettings:
sigmas, = PolyexponentialScheduler().get_sigmas(steps, sigma_max, sigma_min, rho)
elif scheduler == 'sdturbo':
sigmas, = SDTurboScheduler().get_sigmas(model, steps, denoise)
+ elif scheduler == 'alignYourSteps':
+ pass
else:
sigmas, = BasicScheduler().get_sigmas(model, scheduler, steps, denoise)
@@ -3937,6 +3995,7 @@ class dynamicThresholdingFull:
#---------------------------------------------------------------采样器 开始----------------------------------------------------------------------
# 完整采样器
+from .libs.chooser import ChooserMessage, ChooserCancelled
class samplerFull(LayerDiffuse):
def __init__(self):
@@ -3951,7 +4010,7 @@ class samplerFull(LayerDiffuse):
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],),
+ "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide/Save", "Sender", "Sender/Save"],),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
@@ -4121,6 +4180,26 @@ class samplerFull(LayerDiffuse):
del pipe
+ if image_output == 'Preview&Choose':
+ if my_unique_id not in ChooserMessage.stash:
+ ChooserMessage.stash[my_unique_id] = {}
+ my_stash = ChooserMessage.stash[my_unique_id]
+
+ PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results})
+ # wait for selection
+ try:
+ selections = ChooserMessage.waitForMessage(my_unique_id, asList=True)
+ new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [0]
+ samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [0]
+ except ChooserCancelled:
+ raise comfy.model_management.InterruptProcessingException()
+
+ new_pipe['images'] = new_images
+ new_pipe['samp_images'] = samp_images
+
+ return {"ui": {"images": results},
+ "result": sampler.get_output(new_pipe,)}
+
if image_output in ("Hide", "Hide/Save"):
return {"ui": {},
"result": sampler.get_output(new_pipe,)}
@@ -4272,7 +4351,7 @@ class samplerSimple:
def INPUT_TYPES(cls):
return {"required":
{"pipe": ("PIPE_LINE",),
- "image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
+ "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide/Save", "Sender", "Sender/Save"],{"default": "Preview"}),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
@@ -6946,6 +7025,7 @@ NODE_CLASS_MAPPINGS = {
"easy ipadapterStyleComposition": ipadapterStyleComposition,
"easy instantIDApply": instantIDApply,
"easy instantIDApplyADV": instantIDApplyAdvanced,
+ "easy styleAlignedBatchAlign": styleAlignedBatchAlign,
# Inpaint 内补
"easy fooocusInpaintLoader": fooocusInpaintLoader,
# latent 潜空间
@@ -7009,6 +7089,9 @@ NODE_CLASS_MAPPINGS = {
"dynamicThresholdingFull": dynamicThresholdingFull,
# api 相关
"easy stableDiffusion3API": stableDiffusion3API,
+ # utils
+ "easy ckptNames": setCkptName,
+ "easy controlnetNames": setControlName,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -7048,6 +7131,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy ipadapterApplyFromParams": "Easy Apply IPAdapter (From Params)",
"easy instantIDApply": "Easy Apply InstantID",
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
+ "easy styleAlignedBatchAlign": "Easy Apply StyleAlign",
# Inpaint 内补
"easy fooocusInpaintLoader": "Load Fooocus Inpaint",
# latent 潜空间
@@ -7111,4 +7195,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"dynamicThresholdingFull": "DynamicThresholdingFull",
# api 相关
"easy stableDiffusion3API": "Stable Diffusion 3 (API)",
-}
+ # utils
+ "easy ckptNames": "Ckpt Names",
+ "easy controlnetNames": "ControlNet Names",
+}
\ No newline at end of file
diff --git a/py/image.py b/py/image.py
index 1cc22c7..e5ad8e1 100644
--- a/py/image.py
+++ b/py/image.py
@@ -15,6 +15,25 @@ from .libs.chooser import ChooserMessage, ChooserCancelled
from .config import REMBG_DIR, REMBG_MODELS, HUMANPARSING_MODELS, MEDIAPIPE_MODELS, MEDIAPIPE_DIR
+# 图像数量
+class imageCount:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "images": ("IMAGE",),
+ }
+ }
+
+ CATEGORY = "EasyUse/Image"
+
+ RETURN_TYPES = ("INT",)
+ RETURN_NAMES = ("count",)
+ FUNCTION = "get_count"
+
+ def get_count(self, images):
+ return (images.size(0),)
+
# 图像裁切
class imageInsetCrop:
@@ -519,6 +538,24 @@ class imageSplitList:
new_images[1].append(img)
return new_images
+class imagesSplitImage:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "images": ("IMAGE",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
+ RETURN_NAMES = ("image1", "image2", "image3", "image4", "image5")
+ FUNCTION = "split"
+ CATEGORY = "EasyUse/Image"
+
+ def split(self, images,):
+ new_images = torch.chunk(images, len(images), dim=0)
+ return new_images
+
# 图片背景移除
from .briaai.rembg import BriaRMBG, preprocess_image, postprocess_image
from .libs.utils import get_local_filepath, easySave, install_package
@@ -901,7 +938,7 @@ class loadImageBase64:
RETURN_TYPES = ("IMAGE", "MASK")
OUTPUT_NODE = True
FUNCTION = "load_image"
- CATEGORY = "image"
+ CATEGORY = "EasyUse/Image/LoadImage"
def convert_color(self, image,):
if len(image.shape) > 2 and image.shape[2] >= 4:
@@ -1005,6 +1042,7 @@ class poseEditor:
NODE_CLASS_MAPPINGS = {
"easy imageInsetCrop": imageInsetCrop,
+ "easy imageCount": imageCount,
"easy imageSize": imageSize,
"easy imageSizeBySide": imageSizeBySide,
"easy imageSizeByLongerSide": imageSizeByLongerSide,
@@ -1015,6 +1053,7 @@ NODE_CLASS_MAPPINGS = {
"easy imageRatio": imageRatio,
"easy imageToMask": imageToMask,
"easy imageSplitList": imageSplitList,
+ "easy imagesSplitImage": imagesSplitImage,
"easy imageSave": imageSaveSimple,
"easy imageRemBg": imageRemBg,
"easy imageChooser": imageChooser,
@@ -1029,6 +1068,7 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
"easy imageInsetCrop": "ImageInsetCrop",
+ "easy imageCount": "ImageCount",
"easy imageSize": "ImageSize",
"easy imageSizeBySide": "ImageSize (Side)",
"easy imageSizeByLongerSide": "ImageSize (LongerSide)",
@@ -1040,13 +1080,14 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy imageToMask": "ImageToMask",
"easy imageHSVMask": "ImageHSVMask",
"easy imageSplitList": "imageSplitList",
+ "easy imagesSplitImage": "imagesSplitImage",
"easy imageSave": "SaveImage (Simple)",
"easy imageRemBg": "Image Remove Bg",
"easy imageChooser": "Image Chooser",
"easy imageColorMatch": "Image Color Match",
"easy imageInterrogator": "Image To Prompt",
"easy joinImageBatch": "JoinImageBatch",
- "easy loadImageBase64": "LoadImage (Base64)",
+ "easy loadImageBase64": "Load Image (Base64)",
"easy imageToBase64": "Image To Base64",
"easy humanSegmentation": "Human Segmentation",
"easy poseEditor": "PoseEditor",
diff --git a/py/libs/image.py b/py/libs/image.py
index b8bca2a..a08a6c8 100644
--- a/py/libs/image.py
+++ b/py/libs/image.py
@@ -111,27 +111,26 @@ class CI_Inference:
def image_to_prompt(self, image, mode, model_name='ViT-L-14/openai', low_vram=False):
try:
- install_package("clip_interrogator", "0.6.0")
from clip_interrogator import Config, Interrogator
global Config, Interrogator
+ except:
+ install_package("clip_interrogator", "0.6.0")
+ from clip_interrogator import Config, Interrogator
- pbar = comfy.utils.ProgressBar(len(image))
+ pbar = comfy.utils.ProgressBar(len(image))
- self._load_model(model_name, low_vram)
- prompt = []
- for i in range(len(image)):
- im = image[i]
+ self._load_model(model_name, low_vram)
+ prompt = []
+ for i in range(len(image)):
+ im = image[i]
- im = tensor2pil(im)
- im = im.convert('RGB')
+ im = tensor2pil(im)
+ im = im.convert('RGB')
- _prompt = self._interrogate(im, mode)
- pbar.update(1)
- prompt.append(_prompt)
+ _prompt = self._interrogate(im, mode)
+ pbar.update(1)
+ prompt.append(_prompt)
- return prompt
- except Exception as e:
- print(e)
- return [""]
+ return prompt
ci = CI_Inference()
diff --git a/py/libs/styleAlign.py b/py/libs/styleAlign.py
new file mode 100644
index 0000000..fefae60
--- /dev/null
+++ b/py/libs/styleAlign.py
@@ -0,0 +1,148 @@
+import torch
+import torch.nn as nn
+from comfy.model_patcher import ModelPatcher
+from typing import Union
+
+T = torch.Tensor
+
+
+def exists(val):
+ return val is not None
+
+
+def default(val, d):
+ if exists(val):
+ return val
+ return d
+
+
+class StyleAlignedArgs:
+ def __init__(self, share_attn: str) -> None:
+ self.adain_keys = "k" in share_attn
+ self.adain_values = "v" in share_attn
+ self.adain_queries = "q" in share_attn
+
+ share_attention: bool = True
+ adain_queries: bool = True
+ adain_keys: bool = True
+ adain_values: bool = True
+
+
+def expand_first(
+ feat: T,
+ scale=1.0,
+) -> T:
+ """
+ Expand the first element so it has the same shape as the rest of the batch.
+ """
+ b = feat.shape[0]
+ feat_style = torch.stack((feat[0], feat[b // 2])).unsqueeze(1)
+ if scale == 1:
+ feat_style = feat_style.expand(2, b // 2, *feat.shape[1:])
+ else:
+ feat_style = feat_style.repeat(1, b // 2, 1, 1, 1)
+ feat_style = torch.cat([feat_style[:, :1], scale * feat_style[:, 1:]], dim=1)
+ return feat_style.reshape(*feat.shape)
+
+
+def concat_first(feat: T, dim=2, scale=1.0) -> T:
+ """
+ concat the the feature and the style feature expanded above
+ """
+ feat_style = expand_first(feat, scale=scale)
+ return torch.cat((feat, feat_style), dim=dim)
+
+
+def calc_mean_std(feat, eps: float = 1e-5) -> "tuple[T, T]":
+ feat_std = (feat.var(dim=-2, keepdims=True) + eps).sqrt()
+ feat_mean = feat.mean(dim=-2, keepdims=True)
+ return feat_mean, feat_std
+
+def adain(feat: T) -> T:
+ feat_mean, feat_std = calc_mean_std(feat)
+ feat_style_mean = expand_first(feat_mean)
+ feat_style_std = expand_first(feat_std)
+ feat = (feat - feat_mean) / feat_std
+ feat = feat * feat_style_std + feat_style_mean
+ return feat
+
+class SharedAttentionProcessor:
+ def __init__(self, args: StyleAlignedArgs, scale: float):
+ self.args = args
+ self.scale = scale
+
+ def __call__(self, q, k, v, extra_options):
+ if self.args.adain_queries:
+ q = adain(q)
+ if self.args.adain_keys:
+ k = adain(k)
+ if self.args.adain_values:
+ v = adain(v)
+ if self.args.share_attention:
+ k = concat_first(k, -2, scale=self.scale)
+ v = concat_first(v, -2)
+
+ return q, k, v
+
+
+def get_norm_layers(
+ layer: nn.Module,
+ norm_layers_: "dict[str, list[Union[nn.GroupNorm, nn.LayerNorm]]]",
+ share_layer_norm: bool,
+ share_group_norm: bool,
+):
+ if isinstance(layer, nn.LayerNorm) and share_layer_norm:
+ norm_layers_["layer"].append(layer)
+ if isinstance(layer, nn.GroupNorm) and share_group_norm:
+ norm_layers_["group"].append(layer)
+ else:
+ for child_layer in layer.children():
+ get_norm_layers(
+ child_layer, norm_layers_, share_layer_norm, share_group_norm
+ )
+
+
+def register_norm_forward(
+ norm_layer: Union[nn.GroupNorm, nn.LayerNorm],
+) -> Union[nn.GroupNorm, nn.LayerNorm]:
+ if not hasattr(norm_layer, "orig_forward"):
+ setattr(norm_layer, "orig_forward", norm_layer.forward)
+ orig_forward = norm_layer.orig_forward
+
+ def forward_(hidden_states: T) -> T:
+ n = hidden_states.shape[-2]
+ hidden_states = concat_first(hidden_states, dim=-2)
+ hidden_states = orig_forward(hidden_states) # type: ignore
+ return hidden_states[..., :n, :]
+
+ norm_layer.forward = forward_ # type: ignore
+ return norm_layer
+
+
+def register_shared_norm(
+ model: ModelPatcher,
+ share_group_norm: bool = True,
+ share_layer_norm: bool = True,
+):
+ norm_layers = {"group": [], "layer": []}
+ get_norm_layers(model.model, norm_layers, share_layer_norm, share_group_norm)
+ print(
+ f"Patching {len(norm_layers['group'])} group norms, {len(norm_layers['layer'])} layer norms."
+ )
+ return [register_norm_forward(layer) for layer in norm_layers["group"]] + [
+ register_norm_forward(layer) for layer in norm_layers["layer"]
+ ]
+
+
+SHARE_NORM_OPTIONS = ["both", "group", "layer", "disabled"]
+SHARE_ATTN_OPTIONS = ["q+k", "q+k+v", "disabled"]
+
+
+def styleAlignBatch(model, share_norm, share_attn, scale=1.0):
+ m = model.clone()
+ share_group_norm = share_norm in ["group", "both"]
+ share_layer_norm = share_norm in ["layer", "both"]
+ register_shared_norm(model, share_group_norm, share_layer_norm)
+ args = StyleAlignedArgs(share_attn)
+ m.set_model_attn1_patch(SharedAttentionProcessor(args, scale))
+ return m
\ No newline at end of file
diff --git a/py/libs/utils.py b/py/libs/utils.py
index 235b6ce..11f4fa5 100644
--- a/py/libs/utils.py
+++ b/py/libs/utils.py
@@ -98,12 +98,17 @@ import comfy.supported_models_base
def get_sd_version(model):
base: BaseModel = model.model
model_config: comfy.supported_models.supported_models_base.BASE = base.model_config
+ print(model_config)
if isinstance(model_config, comfy.supported_models.SDXL):
return 'sdxl'
elif isinstance(
model_config, (comfy.supported_models.SD15, comfy.supported_models.SD20)
):
return 'sd15'
+ elif isinstance(
+ model_config, (comfy.supported_models.SVD_img2vid)
+ ):
+ return 'svd'
else:
return 'unknown'
@@ -231,7 +236,7 @@ def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=No
from nodes import PreviewImage, SaveImage
if output_type == "Hide":
return list()
- if output_type == "Preview":
+ if output_type in ["Preview", "Preview&Choose"]:
filename_prefix = 'easyPreview'
results = PreviewImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
return results['ui']['images']
diff --git a/py/logic.py b/py/logic.py
index 8e3663c..c674532 100644
--- a/py/logic.py
+++ b/py/logic.py
@@ -277,6 +277,30 @@ class imageSwitch:
else:
return (image_b, )
+class textSwitch:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "input": ("INT", {"default": 1, "min": 1, "max": 2}),
+ },
+ "optional": {
+ "text1": ("STRING", {"forceInput": True}),
+ "text2": ("STRING", {"forceInput": True}),
+ }
+ }
+
+ RETURN_TYPES = ("STRING",)
+ RETURN_NAMES = ("STRING",)
+ CATEGORY = "EasyUse/Logic/Switch"
+ FUNCTION = "switch"
+
+ def switch(self, input, text1=None, text2=None,):
+ if input == 1:
+ return (text1,)
+ else:
+ return (text2,)
+
# ---------------------------------------------------------------运算 开始----------------------------------------------------------------------#
COMPARE_FUNCTIONS = {
@@ -549,6 +573,9 @@ class clearCacheAll:
remove_cache('*')
return ()
+
+
+
NODE_CLASS_MAPPINGS = {
"easy string": String,
"easy int": Int,
@@ -558,6 +585,7 @@ NODE_CLASS_MAPPINGS = {
"easy boolean": Boolean,
"easy compare": Compare,
"easy imageSwitch": imageSwitch,
+ "easy textSwitch": textSwitch,
"easy if": If,
"easy isSDXL": isSDXL,
"easy xyAny": xyAny,
@@ -577,6 +605,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy boolean": "Boolean",
"easy compare": "Compare",
"easy imageSwitch": "Image Switch",
+ "easy textSwitch": "Text Switch",
"easy if": "If",
"easy isSDXL": "Is SDXL",
"easy xyAny": "XYAny",
diff --git a/web/css/chooser.css b/web/css/chooser.css
new file mode 100644
index 0000000..3841061
--- /dev/null
+++ b/web/css/chooser.css
@@ -0,0 +1,30 @@
+.easyuse-chooser-dialog{
+ max-width: 600px;
+}
+.easyuse-chooser-dialog-title{
+ font-size: 18px;
+ font-weight: 700;
+ text-align: center;
+ color:var(--input-text);
+ margin:0;
+}
+.easyuse-chooser-dialog-images{
+ margin-top:10px;
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ box-sizing: border-box;
+}
+.easyuse-chooser-dialog-images img{
+ width: 50%;
+ height: auto;
+ cursor: pointer;
+ box-sizing: border-box;
+ filter:brightness(80%);
+}
+.easyuse-chooser-dialog-images img:hover{
+ filter:brightness(100%);
+}
+.easyuse-chooser-dialog-images img.selected{
+ border: 4px solid var(--success-color);
+}
\ No newline at end of file
diff --git a/web/css/index.css b/web/css/index.css
index 8284a4f..21f103b 100644
--- a/web/css/index.css
+++ b/web/css/index.css
@@ -5,4 +5,5 @@
@import "contextmenu.css";
@import "modelinfo.css";
@import "toast.css";
-@import "account.css";
\ No newline at end of file
+@import "account.css";
+@import "chooser.css";
\ No newline at end of file
diff --git a/web/js/common/i18n.js b/web/js/common/i18n.js
index de1deda..53711f3 100644
--- a/web/js/common/i18n.js
+++ b/web/js/common/i18n.js
@@ -59,6 +59,9 @@ const zhCN = {
"APIKEY is not Empty": "APIKEY 不能为空",
"Add Account": "添加账号",
"Getting Your APIKEY": "获取您的APIKEY",
+ // choosers
+ "Choose Selected Images": "选择选中的图片",
+ "Choose images to continue": "选择图片以继续",
// seg
"Background": "背景",
"Hat": "帽子",
diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js
index 12a236f..a82628a 100644
--- a/web/js/easy/easyDynamicWidgets.js
+++ b/web/js/easy/easyDynamicWidgets.js
@@ -56,7 +56,7 @@ function widgetLogic(node, widget) {
}else {
toggleWidget(node, findWidgetByName(node, 'link_id'))
}
- if (widget.value === 'Hide' || widget.value === 'Preview' || widget.value == 'PreviewChooser' || widget.value === 'Sender') {
+ if (widget.value === 'Hide' || widget.value === 'Preview' || widget.value == 'Preview&Choose' || widget.value === 'Sender') {
toggleWidget(node, findWidgetByName(node, 'save_prefix'))
toggleWidget(node, findWidgetByName(node, 'output_path'))
toggleWidget(node, findWidgetByName(node, 'embed_workflow'))
diff --git a/web/js/easy/easyExtraMenu.js b/web/js/easy/easyExtraMenu.js
index a74611d..510b475 100644
--- a/web/js/easy/easyExtraMenu.js
+++ b/web/js/easy/easyExtraMenu.js
@@ -273,11 +273,11 @@ const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => {
has_submenu: has_submenu,
callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include)
})
- if(type == 'loaders'){
+ if(type == 'loaders') {
options.unshift({
content: $t("💎 View Lora Info..."),
callback: (value, options, e, menu, node) => {
- const widget = node.widgets.find(cate=> cate.name == 'lora_name')
+ const widget = node.widgets.find(cate => cate.name == 'lora_name')
let name = widget.value;
if (!name || name == 'None') return
new LoraInfoDialog(name).show('loras', name);
@@ -522,7 +522,19 @@ app.registerExtension({
}
}
})
+ // ckptNames
+ if(nodeData.name == 'easy ckptNames'){
+ options.unshift({
+ content: $t("💎 View Checkpoint Info..."),
+ callback: (value, options, e, menu, node) => {
+ let name = node.widgets[0].value;
+ if (!name || name == 'None') return
+ new CheckpointInfoDialog(name).show('checkpoints', name);
+ }
+ })
+ }
})
+
// Swap提示词
if (positive_prompt.includes(nodeData.name)) {
addMenu("↪️ Swap EasyPrompt", 'positive_prompt', positive_prompt, nodeType)
diff --git a/web/js/easy/easyInterface.js b/web/js/easy/easyInterface.js
index 591006a..6d96de6 100644
--- a/web/js/easy/easyInterface.js
+++ b/web/js/easy/easyInterface.js
@@ -747,6 +747,7 @@ const NODE_COLORS = {
"easy promptReplace":"cyan",
"easy XYInputs: Seeds++ Batch": customXYLink,
"easy XYInputs: ModelMergeBlocks": customXYLink,
+ 'easy textSwitch': "pale_blue"
}
function setNodeColors(node, theme) {
diff --git a/web/js/image_chooser/chooser.js b/web/js/image_chooser/chooser.js
index fa53e98..2954baf 100644
--- a/web/js/image_chooser/chooser.js
+++ b/web/js/image_chooser/chooser.js
@@ -1,14 +1,70 @@
import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
+import { ComfyDialog, $el } from "/scripts/ui.js";
import { restart_from_here } from "./prompt.js";
import { FlowState } from "./state.js";
import { send_cancel, send_message, send_onstart, skip_next_restart_message } from "./messaging.js";
import { display_preview_images, additionalDrawBackground, click_is_in_image } from "./preview.js";
-import {toggleWidget} from "../common/utils.js";
+import {$t} from "../common/i18n.js";
-function progressButtonPressed() {
- const node = app.graph._nodes_by_id[this.node_id];
+
+class chooserImageDialog extends ComfyDialog {
+
+ constructor() {
+ super();
+ this.node = null
+ this.select_index = []
+ this.dialog_div = null
+ }
+
+ show(image,node){
+ this.select_index = []
+ this.node = node
+
+ const images_div = image.map((img, index) => {
+ const imgEl = $el('img', {
+ src: img.src,
+ onclick: _ => {
+ if(this.select_index.includes(index)){
+ this.select_index = this.select_index.filter(i => i !== index)
+ imgEl.classList.remove('selected')
+ } else {
+ this.select_index.push(index)
+ imgEl.classList.add('selected')
+ }
+ if (node.selected.has(index)) node.selected.delete(index);
+ else node.selected.add(index);
+ }
+ })
+ return imgEl
+ })
+ super.show($el('div.easyuse-chooser-dialog',[
+ $el('h5.easyuse-chooser-dialog-title', $t('Choose images to continue')),
+ $el('div.easyuse-chooser-dialog-images',images_div)
+ ]))
+ }
+ createButtons() {
+ const btns = super.createButtons();
+ btns[0].onclick = _ => {
+ cancelButtonPressed()
+ super.close()
+ }
+ btns.unshift($el('button', {
+ type: 'button',
+ textContent: $t('Choose Selected Images'),
+ onclick: _ => {
+ progressButtonPressed(this.node)
+ super.close()
+ }
+ }))
+ return btns
+ }
+
+}
+
+function progressButtonPressed(_node) {
+ const node = _node ? _node : app.graph._nodes_by_id[this.node_id];
if (node) {
if (FlowState.paused()) {
send_message(node.id, [...node.selected, -1, ...node.anti_selected]);
@@ -29,7 +85,11 @@ app.registerExtension({
},
setup(app) {
function easyuseImageChooser(event) {
- display_preview_images(event);
+ const {node,image,isKSampler} = display_preview_images(event);
+ if(isKSampler) {
+ const dialog = new chooserImageDialog();
+ dialog.show(image,node)
+ }
}
api.addEventListener("easyuse-image-choose", easyuseImageChooser);
diff --git a/web/js/image_chooser/preview.js b/web/js/image_chooser/preview.js
index 2bb6a89..588849f 100644
--- a/web/js/image_chooser/preview.js
+++ b/web/js/image_chooser/preview.js
@@ -1,11 +1,14 @@
import { app } from "/scripts/app.js";
+const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy fullkSampler']
+
function display_preview_images(event) {
const node = app.graph._nodes_by_id[event.detail.id];
if (node) {
node.selected = new Set();
node.anti_selected = new Set();
- showImages(node, event.detail.urls);
+ const image = showImages(node, event.detail.urls);
+ return {node,image,isKSampler:kSampler.includes(node.type)}
} else {
console.log(`Image Chooser Preview - failed to find ${event.detail.id}`)
}
@@ -20,6 +23,7 @@ function showImages(node, urls) {
img.src = `/view?filename=${encodeURIComponent(u.filename)}&type=temp&subfolder=${app.getPreviewFormatParam()}`
})
node.setSizeForImage?.();
+ return node.imgs
}
function drawRect(node, s, ctx) {