diff --git a/README.en.md b/README.en.md
index db69a41..ee0cbae 100644
--- a/README.en.md
+++ b/README.en.md
@@ -33,6 +33,13 @@
**v1.1.2 (2024/3/25)**
+PS: Please update [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) to v2, and moved v1 models to **ComfyUI\models\ipadapter** (Otherwise, the latest model is automatically downloaded from Huggingface)
+
+- Added `easy ipadapterApply`
+- Added `easy ipadapterApplyADV`
+
+(4c25580)
+
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
- Fixed `easy pipeEdit` error when add lora to prompt
- Fixed layerDiffuse xyplot bug
diff --git a/README.md b/README.md
index 62fde05..ab191e1 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@
- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade)
- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion), 首次使用您可能需要运行 `pip install -r requirements.txt` 安装所需依赖
- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID)
+- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus)
- 扩展 XYplot 的可用性
- 整合了Fooocus Inpaint功能
- 整合了常用的逻辑计算、转换类型、展示所有类型等
@@ -36,11 +37,18 @@
**v1.1.2 (2024/3/25)**
+PS: 请更新至最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus), 并移动v1版本模型文件至 ComfyUI\models\ipadapter (否则会自动从huggingface下载最新模型)
+
+- 增加 `easy ipadapterApply`
+- 增加 `easy ipadapterApplyADV`
+
+(4c25580)
+
- `easy kSamplerInpainting` 增加 *additional* 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning
- 修复 `easy pipeEdit` 提示词输入lora时报错
- 修复 layerDiffuse xyplot相关bug
-**v1.1.1 (2024/3/21)**
+**v1.1.1 (5c8af8f)**
- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题
- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise**
diff --git a/py/config.py b/py/config.py
index 1733335..cbe0958 100644
--- a/py/config.py
+++ b/py/config.py
@@ -141,4 +141,95 @@ REMBG_MODELS = {
"RMBG-1.4": {
"model_url": "https://huggingface.co/briaai/RMBG-1.4/resolve/main/model.pth"
}
+}
+
+#ipadapter
+IPADAPTER_DIR = os.path.join(folder_paths.models_dir, "ipadapter")
+IPADAPTER_MODELS = {
+ "LIGHT - SD1.5 only (low strength)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_light_v11.bin"
+ },
+ "sdxl": {
+ "model_url": ""
+ }
+ },
+ "STANDARD (medium strength)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl.safetensors"
+ }
+ },
+ "VIT-G (medium strength)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_vit-G.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter_sdxl_vit-h.safetensors"
+ }
+ },
+ "PLUS (high strength)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.safetensors"
+ }
+ },
+ "PLUS FACE (portraits)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus-face_sdxl_vit-h.safetensors"
+ }
+ },
+ "FULL FACE - SD1.5 only (portraits stronger)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-full-face_sd15.safetensors"
+ },
+ "sdxl": {
+ "model_url": ""
+ }
+ },
+ "FACEID": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15.bin",
+ "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15_lora.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sdxl.bin",
+ "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sdxl_lora.safetensors"
+ }
+ },
+ "FACEID PLUS - SD1.5 only": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15.bin",
+ "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15_lora.safetensors"
+ },
+ "sdxl": {
+ "model_url": "",
+ "lora_url": ""
+ }
+ },
+ "FACEID PLUS V2": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15.bin",
+ "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15_lora.safetensors"
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sdxl.bin",
+ "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sdxl_lora.safetensors"
+ }
+ },
+ "FACEID PORTRAIT (style transfer)": {
+ "sd15": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait-v11_sd15.bin",
+ },
+ "sdxl": {
+ "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sdxl.bin",
+ }
+ }
}
\ No newline at end of file
diff --git a/py/easyNodes.py b/py/easyNodes.py
index b938db8..9ae62f3 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -6,18 +6,19 @@ from comfy.sd import CLIP, VAE
from comfy.model_patcher import ModelPatcher
from comfy_extras.chainner_models import model_loading
from comfy_extras.nodes_mask import LatentCompositeMasked
+from comfy.clip_vision import load as load_clip_vision
from urllib.request import urlopen
from PIL import Image
from server import PromptServer
from nodes import MAX_RESOLUTION, LatentFromBatch, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode, VAEEncodeForInpaint, InpaintModelConditioning
-from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH
+from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, IPADAPTER_DIR, IPADAPTER_MODELS
from .log import log_node_info, log_node_error, log_node_warn
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, get_sd_version
+from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions
from .libs.loader import easyLoader
from .libs.sampler import easySampler
from .libs.xyplot import easyXYPlot
@@ -40,6 +41,7 @@ add_folder_path_and_extensions("onnx", [os.path.join(model_path, "onnx")], {'.on
add_folder_path_and_extensions("instantid", [os.path.join(model_path, "instantid")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("layer_model", [os.path.join(model_path, "layer_model")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], folder_paths.supported_pt_extensions)
+add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions)
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
@@ -1543,9 +1545,9 @@ class LLLiteLoader:
return (model_lllite,)
-#---------------------------------------------------------------测试 开始----------------------------------------------------------------------#
+#---------------------------------------------------------------Inpaint 开始----------------------------------------------------------------------#
-# FooocusInpaint (Testing)
+# FooocusInpaint
from .fooocus import InpaintHead, InpaintWorker
inpaint_head_model = None
class fooocusInpaintLoader:
@@ -1560,7 +1562,7 @@ class fooocusInpaintLoader:
RETURN_TYPES = ("INPAINT_PATCH",)
RETURN_NAMES = ("patch",)
- CATEGORY = "EasyUse/__for_testing"
+ CATEGORY = "EasyUse/Inpaint"
FUNCTION = "apply"
def apply(self, head, patch):
@@ -1577,6 +1579,345 @@ class fooocusInpaintLoader:
return ((inpaint_head_model, inpaint_lora),)
+#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
+def insightface_loader(provider):
+ try:
+ from insightface.app import FaceAnalysis
+ except ImportError as e:
+ raise Exception(e)
+
+ path = os.path.join(folder_paths.models_dir, "insightface")
+ model = FaceAnalysis(name="buffalo_l", root=path, providers=[provider + 'ExecutionProvider', ])
+ model.prepare(ctx_id=0, det_size=(640, 640))
+ return model
+
+# Apply Ipadapter
+class ipadapter:
+
+ def __init__(self):
+ self.normol_presets = [
+ 'LIGHT - SD1.5 only (low strength)',
+ 'STANDARD (medium strength)',
+ 'VIT-G (medium strength)',
+ 'PLUS (high strength)',
+ 'PLUS FACE (portraits)',
+ 'FULL FACE - SD1.5 only (portraits stronger)'
+ ]
+ self.faceid_presets = [
+ 'FACEID',
+ 'FACEID PLUS - SD1.5 only',
+ 'FACEID PLUS V2',
+ 'FACEID PORTRAIT (style transfer)'
+ ]
+ self.presets = self.normol_presets + self.faceid_presets
+
+
+ def error(self):
+ raise Exception(f"[ERROR] To use ipadapterApply, you need to install 'ComfyUI_IPAdapter_plus'")
+
+ def get_clipvision_file(self, preset, node_name):
+ preset = preset.lower()
+ clipvision_list = folder_paths.get_filename_list("clip_vision")
+
+ if preset.startswith("vit-g"):
+ pattern = '(ViT.bigG.14.*39B.b160k|ipadapter.*sdxl|sdxl.*model\.(bin|safetensors))'
+ else:
+ pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model\.(bin|safetensors))'
+ clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)]
+
+ clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None
+ clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None
+ if clipvision_name is not None:
+ log_node_info(node_name, f"Using {clipvision_name}")
+
+ return clipvision_file, clipvision_name
+
+ def get_ipadapter_file(self, preset, is_sdxl, node_name):
+ preset = preset.lower()
+ ipadapter_list = folder_paths.get_filename_list("ipadapter")
+ is_insightface = False
+ lora_pattern = None
+
+ if preset.startswith("light"):
+ if is_sdxl:
+ raise Exception("light model is not supported for SDXL")
+ pattern = 'sd15.light.v11\.(safetensors|bin)$'
+ # if light model v11 is not found, try with the old version
+ if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]:
+ pattern = 'sd15.light\.(safetensors|bin)$'
+ elif preset.startswith("standard"):
+ if is_sdxl:
+ pattern = 'ip.adapter.sdxl.vit.h\.(safetensors|bin)$'
+ else:
+ pattern = 'ip.adapter.sd15\.(safetensors|bin)$'
+ elif preset.startswith("vit-g"):
+ if is_sdxl:
+ pattern = 'ip.adapter.sdxl\.(safetensors|bin)$'
+ else:
+ pattern = 'sd15.vit.g\.(safetensors|bin)$'
+ elif preset.startswith("plus ("):
+ if is_sdxl:
+ pattern = 'plus.sdxl.vit.h\.(safetensors|bin)$'
+ else:
+ pattern = 'ip.adapter.plus.sd15\.(safetensors|bin)$'
+ elif preset.startswith("plus face"):
+ if is_sdxl:
+ pattern = 'plus.face.sdxl.vit.h\.(safetensors|bin)$'
+ else:
+ pattern = 'plus.face.sd15\.(safetensors|bin)$'
+ elif preset.startswith("full"):
+ if is_sdxl:
+ raise Exception("full face model is not supported for SDXL")
+ pattern = 'full.face.sd15\.(safetensors|bin)$'
+ elif preset.startswith("faceid portrait"):
+ if is_sdxl:
+ raise Exception("portrait model is not supported for SDXL")
+ pattern = 'portrait.sd15\.(safetensors|bin)$'
+ is_insightface = True
+ elif preset == "faceid":
+ if is_sdxl:
+ pattern = 'faceid.sdxl\.(safetensors|bin)$'
+ lora_pattern = 'faceid.sdxl.lora\.safetensors$'
+ else:
+ pattern = 'faceid.sd15\.(safetensors|bin)$'
+ lora_pattern = 'faceid.sd15.lora\.safetensors$'
+ is_insightface = True
+ elif preset.startswith("faceid plus -"):
+ if is_sdxl:
+ raise Exception("faceid plus model is not supported for SDXL")
+ pattern = 'faceid.plus.sd15\.(safetensors|bin)$'
+ lora_pattern = 'faceid.plus.sd15.lora\.safetensors$'
+ is_insightface = True
+ elif preset.startswith("faceid plus v2"):
+ if is_sdxl:
+ pattern = 'faceid.plusv2.sdxl\.(safetensors|bin)$'
+ lora_pattern = 'faceid.plusv2.sdxl.lora\.safetensors$'
+ else:
+ pattern = 'faceid.plusv2.sd15\.(safetensors|bin)$'
+ lora_pattern = 'faceid.plusv2.sd15.lora\.safetensors$'
+ is_insightface = True
+ else:
+ raise Exception(f"invalid type '{preset}'")
+
+ ipadapter_files = [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]
+ ipadapter_name = ipadapter_files[0] if len(ipadapter_files)>0 else None
+ ipadapter_file = folder_paths.get_full_path("ipadapter", ipadapter_name) if ipadapter_name else None
+ if ipadapter_name is not None:
+ log_node_info(node_name, f"Using {ipadapter_name}")
+
+ return ipadapter_file, ipadapter_name, is_insightface, lora_pattern
+
+ def get_lora_file(self, preset, pattern, model_type, model, model_strength, clip_strength, clip=None):
+ lora_list = folder_paths.get_filename_list("loras")
+ lora_files = [e for e in lora_list if re.search(pattern, e, re.IGNORECASE)]
+ lora_name = lora_files[0] if lora_files else None
+ if lora_name:
+ return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},)
+ else:
+ if "lora_url" in IPADAPTER_MODELS[preset][model_type]:
+ lora_name = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["lora_url"], os.path.join(folder_paths.models_dir, "loras"))
+ return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},)
+ return (model, clip)
+
+ def ipadapter_model_loader(self, file):
+ model = comfy.utils.load_torch_file(file, safe_load=True)
+
+ if file.lower().endswith(".safetensors"):
+ st_model = {"image_proj": {}, "ip_adapter": {}}
+ for key in model.keys():
+ if key.startswith("image_proj."):
+ st_model["image_proj"][key.replace("image_proj.", "")] = model[key]
+ elif key.startswith("ip_adapter."):
+ st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key]
+ model = st_model
+ del st_model
+
+ if not "ip_adapter" in model.keys() or not model["ip_adapter"]:
+ raise Exception("invalid IPAdapter model {}".format(file))
+
+ if 'plusv2' in file.lower():
+ model["faceidplusv2"] = True
+
+ return model
+
+ def load_model(self, model, preset, lora_model_strength, provider="CPU", clip_vision=None, optional_ipadapter=None, cache_mode='none', node_name='easy ipadapterApply'):
+ pipeline = {"clipvision": {'file': None, 'model': None}, "ipadapter": {'file': None, 'model': None},
+ "insightface": {'provider': None, 'model': None}}
+ if optional_ipadapter is not None:
+ pipeline = optional_ipadapter
+
+ # 1. Load the clipvision model
+ if not clip_vision:
+ clipvision_file, clipvision_name = self.get_clipvision_file(preset, node_name)
+ if clipvision_file is None:
+ raise Exception("ClipVision model not found.")
+ if clipvision_file == pipeline['clipvision']['file']:
+ clip_vision = pipeline['clipvision']['model']
+ elif cache_mode in ["all", "clip_vision only"] and clipvision_name in cache:
+ log_node_info("easy ipadapterApply", f"Using ClipModel {clipvision_name} Cached")
+ clip_vision = cache[clipvision_name][1]
+ else:
+ clip_vision = load_clip_vision(clipvision_file)
+ update_cache(clipvision_name, (False, clip_vision))
+ pipeline['clipvision']['file'] = clipvision_file
+ pipeline['clipvision']['model'] = clip_vision
+
+ # 2. Load the ipadapter model
+ is_sdxl = isinstance(model.model, comfy.model_base.SDXL)
+ ipadapter_file, ipadapter_name, is_insightface, lora_pattern = self.get_ipadapter_file(preset, is_sdxl, node_name)
+ model_type = 'sdxl' if is_sdxl else 'sd15'
+ if ipadapter_file is None:
+ ipadapter_file = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["model_url"], IPADAPTER_DIR)
+ ipadapter = self.ipadapter_model_loader(ipadapter_file)
+ pipeline['ipadapter']['file'] = ipadapter_file
+ pipeline['ipadapter']['model'] = ipadapter
+
+ # 3. Load the lora model if needed
+ if lora_pattern is not None:
+ if lora_model_strength > 0:
+ model, _ = self.get_lora_file(preset, lora_pattern, model_type, model, lora_model_strength, 1)
+
+ # 4. Load the insightface model if needed
+ if is_insightface:
+ icache_key = 'insightface-' + provider
+ if provider == pipeline['insightface']['provider']:
+ insightface = pipeline['insightface']['model']
+ elif icache_key in cache:
+ log_node_info("easy ipadapterApply", f"Using InsightFaceModel {icache_key} Cached")
+ insightface = cache[icache_key][1]
+ else:
+ insightface = insightface_loader(provider)
+ update_cache(icache_key, (False, insightface))
+ pipeline['insightface']['provider'] = provider
+ pipeline['insightface']['model'] = insightface
+
+ return (model, pipeline,)
+
+class ipadapterApply(ipadapter):
+ def __init__(self):
+ super().__init__()
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ presets = cls().presets
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "image": ("IMAGE",),
+ "preset": (presets,),
+ "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}),
+ "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],),
+ "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
+ "weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }),
+ "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"},),
+ "use_tiled": ("BOOLEAN", {"default": False},),
+ },
+
+ "optional": {
+ "attn_mask": ("MASK",),
+ "optional_ipadapter": ("IPADAPTER",),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",)
+ RETURN_NAMES = ("model", "tiles", "masks", "ipadapter", )
+ CATEGORY = "EasyUse/Adapter"
+ FUNCTION = "apply"
+
+ def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, start_at, end_at, cache_mode, use_tiled, attn_mask=None, optional_ipadapter=None):
+ tiles, masks = [None], [None]
+ model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
+ if use_tiled:
+ if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"]
+ model, tiles, masks = cls().apply_tiled(model, ipadapter, image, weight, "linear", start_at, end_at, sharpening=0.0, combine_embeds="concat", image_negative=None, attn_mask=attn_mask, clip_vision=None, embeds_scaling='V only')
+ else:
+ if preset in ['FACEID PLUS V2', 'FACEID PORTRAIT (style transfer)']:
+ if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
+ model, = cls().apply_ipadapter(model, ipadapter, image, weight, "linear", start_at, end_at, combine_embeds="concat", weight_faceidv2=weight_faceidv2, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only')
+ else:
+ if "IPAdapter" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapter"]
+ model, = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, attn_mask)
+
+ return (model, tiles, masks, ipadapter)
+
+class ipadapterApplyAdvanced(ipadapter):
+ def __init__(self):
+ super().__init__()
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ presets = cls().presets
+ WEIGHT_TYPES = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output',
+ 'weak middle', 'strong middle', 'style transfer (SDXL)']
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "image": ("IMAGE",),
+ "preset": (presets,),
+ "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}),
+ "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],),
+ "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}),
+ "weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }),
+ "weight_type": (WEIGHT_TYPES,),
+ "combine_embeds": (["concat", "add", "subtract", "average", "norm average"],),
+ "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],),
+ "cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"},),
+ "use_tiled": ("BOOLEAN", {"default": False},),
+ "use_batch": ("BOOLEAN", {"default": False},),
+ "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}),
+ },
+
+ "optional": {
+ "image_negative": ("IMAGE",),
+ "attn_mask": ("MASK",),
+ "clip_vision": ("CLIP_VISION",),
+ "optional_ipadapter": ("IPADAPTER",),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",)
+ RETURN_NAMES = ("model", "tiles", "masks", "ipadapter", )
+ CATEGORY = "EasyUse/Adapter"
+ FUNCTION = "apply"
+
+ def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None):
+ tiles, masks = [None], [None]
+ model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=clip_vision, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode)
+ if use_tiled:
+ if use_batch:
+ if "IPAdapterTiledBatch" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiledBatch"]
+ else:
+ if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"]
+ model, tiles, masks = cls().apply_tiled(model, ipadapter, image, weight, weight_type, start_at, end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling)
+ else:
+ if use_batch:
+ if "IPAdapterBatch" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterBatch"]
+ else:
+ if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
+ self.error()
+ cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
+ model, = cls().apply_ipadapter(model, ipadapter, image, weight, weight_type, start_at, end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image_negative=image_negative, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling)
+
+ return (model, tiles, masks, ipadapter)
+
#Apply InstantID
class instantID:
@@ -1670,7 +2011,7 @@ class instantIDApply(instantID):
OUTPUT_NODE = True
FUNCTION = "apply"
- CATEGORY = "EasyUse/__for_testing"
+ CATEGORY = "EasyUse/Adapter"
def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
@@ -1718,7 +2059,7 @@ class instantIDApplyAdvanced(instantID):
OUTPUT_NODE = True
FUNCTION = "apply_advanced"
- CATEGORY = "EasyUse/__for_testing"
+ CATEGORY = "EasyUse/Adapter"
def apply_advanced(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
@@ -5326,6 +5667,9 @@ class showLoaderSettingsNames:
NODE_CLASS_MAPPINGS = {
+ # seed 随机种
+ "easy seed": easySeed,
+ "easy globalSeed": globalSeed,
# prompt 提示词
"easy positive": positivePrompt,
"easy negative": negativePrompt,
@@ -5344,12 +5688,16 @@ NODE_CLASS_MAPPINGS = {
"easy controlnetLoader": controlnetSimple,
"easy controlnetLoaderADV": controlnetAdvanced,
"easy LLLiteLoader": LLLiteLoader,
+ # Adapter 适配器
+ "easy ipadapterApply": ipadapterApply,
+ "easy ipadapterApplyADV": ipadapterApplyAdvanced,
+ "easy instantIDApply": instantIDApply,
+ "easy instantIDApplyADV": instantIDApplyAdvanced,
+ # Inpaint 内补
+ "easy fooocusInpaintLoader": fooocusInpaintLoader,
# latent 潜空间
"easy latentNoisy": latentNoisy,
"easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond,
- # seed 随机种
- "easy seed": easySeed,
- "easy globalSeed": globalSeed,
# preSampling 预采样处理
"easy preSampling": samplerSettings,
"easy preSamplingAdvanced": samplerSettingsAdvanced,
@@ -5404,13 +5752,12 @@ NODE_CLASS_MAPPINGS = {
"easy showLoaderSettingsNames": showLoaderSettingsNames,
# "easy imageRemoveBG": imageREMBG,
"dynamicThresholdingFull": dynamicThresholdingFull,
- # __for_testing 测试
- "easy fooocusInpaintLoader": fooocusInpaintLoader,
- "easy instantIDApply": instantIDApply,
- "easy instantIDApplyADV": instantIDApplyAdvanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
+ # seed 随机种
+ "easy seed": "EasySeed",
+ "easy globalSeed": "EasyGlobalSeed",
# prompt 提示词
"easy positive": "Positive",
"easy negative": "Negative",
@@ -5429,12 +5776,16 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy controlnetLoader": "EasyControlnet",
"easy controlnetLoaderADV": "EasyControlnet (Advanced)",
"easy LLLiteLoader": "EasyLLLite",
+ # Adapter 适配器
+ "easy ipadapterApply": "Easy Apply IPAdapter",
+ "easy ipadapterApplyADV": "Easy Apply IPAdapter (Advanced)",
+ "easy instantIDApply": "Easy Apply InstantID",
+ "easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
+ # Inpaint 内补
+ "easy fooocusInpaintLoader": "Load Fooocus Inpaint",
# latent 潜空间
"easy latentNoisy": "LatentNoisy",
"easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond",
- # seed 随机种
- "easy seed": "EasySeed",
- "easy globalSeed": "EasyGlobalSeed",
# preSampling 预采样处理
"easy preSampling": "PreSampling",
"easy preSamplingAdvanced": "PreSampling (Advanced)",
@@ -5489,8 +5840,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy showLoaderSettingsNames": "Show Loader Settings Names",
"easy imageRemoveBG": "ImageRemoveBG",
"dynamicThresholdingFull": "DynamicThresholdingFull",
- # __for_testing 测试
- "easy fooocusInpaintLoader": "Load Fooocus Inpaint",
- "easy instantIDApply": "Easy Apply InstantID",
- "easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
}
\ No newline at end of file
diff --git a/py/image.py b/py/image.py
index 845f34c..89a5366 100644
--- a/py/image.py
+++ b/py/image.py
@@ -1,5 +1,4 @@
from PIL import Image
-from enum import Enum
import os
import hashlib
import folder_paths
@@ -7,37 +6,7 @@ import torch
import numpy as np
from nodes import MAX_RESOLUTION
from .log import log_node_info
-
-
-def pil2tensor(image):
- return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
-
-class ResizeMode(Enum):
- RESIZE = "Just Resize"
- INNER_FIT = "Crop and Resize"
- OUTER_FIT = "Resize and Fill"
- def int_value(self):
- if self == ResizeMode.RESIZE:
- return 0
- elif self == ResizeMode.INNER_FIT:
- return 1
- elif self == ResizeMode.OUTER_FIT:
- return 2
- assert False, "NOTREACHED"
-
-RESIZE_MODES = [ResizeMode.RESIZE.value, ResizeMode.INNER_FIT.value, ResizeMode.OUTER_FIT.value]
-
-def get_new_bounds(width, height, left, right, top, bottom):
- """Returns the new bounds for an image with inset crop data."""
- left = 0 + left
- right = width - right
- top = 0 + top
- bottom = height - bottom
- return (left, right, top, bottom)
-
-# Tensor to PIL
-def tensor2pil(image):
- return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds
# 图像裁切
class imageInsetCrop:
@@ -324,6 +293,7 @@ class imageScaleDownToSize(imageScaleDownBy):
class imagePixelPerfect:
@classmethod
def INPUT_TYPES(s):
+ RESIZE_MODES = [ResizeMode.RESIZE.value, ResizeMode.INNER_FIT.value, ResizeMode.OUTER_FIT.value]
return {
"required": {
"image": ("IMAGE",),
diff --git a/py/libs/image.py b/py/libs/image.py
new file mode 100644
index 0000000..31ae691
--- /dev/null
+++ b/py/libs/image.py
@@ -0,0 +1,35 @@
+import torch
+import numpy as np
+from enum import Enum
+from PIL import Image
+
+# PIL to Tensor
+def pil2tensor(image):
+ return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
+# Tensor to PIL
+def tensor2pil(image):
+ return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
+
+# Get new bounds
+def get_new_bounds(width, height, left, right, top, bottom):
+ """Returns the new bounds for an image with inset crop data."""
+ left = 0 + left
+ right = width - right
+ top = 0 + top
+ bottom = height - bottom
+ return (left, right, top, bottom)
+
+
+class ResizeMode(Enum):
+ RESIZE = "Just Resize"
+ INNER_FIT = "Crop and Resize"
+ OUTER_FIT = "Resize and Fill"
+ def int_value(self):
+ if self == ResizeMode.RESIZE:
+ return 0
+ elif self == ResizeMode.INNER_FIT:
+ return 1
+ elif self == ResizeMode.OUTER_FIT:
+ return 2
+ assert False, "NOTREACHED"
+
diff --git a/py/libs/utils.py b/py/libs/utils.py
index 86bc141..28a759b 100644
--- a/py/libs/utils.py
+++ b/py/libs/utils.py
@@ -154,31 +154,4 @@ def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=No
return results['ui']['images']
else:
results = SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
- return results['ui']['images']
-
-# Image Utils
-# from PIL import Image, ImageDraw
-# import numpy as np
-# import torch
-# def is_image_transparent(img):
-# print(img.shape)
-# if len(img.shape) > 3 and img.shape[3] == 4:
-# return True
-# else:
-# m = tensor2pil(img)
-# if m.mode == "RGBA":
-# return True
-# else:
-# return False
-#
-# def create_grid(image_size, box_size):
-# img = Image.new('RGBA', image_size, (255, 255, 255, 255)) # 白色背景
-# draw = ImageDraw.Draw(img)
-#
-# for x in range(0, img.width, box_size):
-# for y in range(0, img.height, box_size):
-# if (x // box_size % 2 == 0 and y // box_size % 2 == 0) or (x // box_size % 2 == 1 and y // box_size % 2 == 1):
-# draw.rectangle([(x, y), (x+box_size, y+box_size)], fill=(204, 204, 204, 255)) # 不透明
-# else:
-# continue # 保持透明
-# return img
+ return results['ui']['images']
\ No newline at end of file
diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js
index c231866..af88754 100644
--- a/web/js/easy/easyDynamicWidgets.js
+++ b/web/js/easy/easyDynamicWidgets.js
@@ -4,13 +4,13 @@ import { ComfyWidgets } from "/scripts/widgets.js";
let origProps = {};
+const seedNodes = ["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy preSamplingLayerDiffusion", "easy fullkSampler", "easy fullCascadeKSampler"]
+const loaderNodes = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader"]
const findWidgetByName = (node, name) => node.widgets.find((w) => w.name === name);
const doesInputWithNameExist = (node, name) => node.inputs ? node.inputs.some((input) => input.name === name) : false;
-function updateNodeHeight(node) {
- node.setSize([node.size[0], node.computeSize()[1]]);
-}
+function updateNodeHeight(node) {node.setSize([node.size[0], node.computeSize()[1]]);}
function toggleWidget(node, widget, show = false, suffix = "") {
if (!widget || doesInputWithNameExist(node, widget.name)) return;
@@ -26,7 +26,6 @@ function toggleWidget(node, widget, show = false, suffix = "") {
const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
node.setSize([node.size[0], height]);
-
}
function widgetLogic(node, widget) {
@@ -208,6 +207,45 @@ function widgetLogic(node, widget) {
toggleWidget(node, findWidgetByName(node, 'new_cond_end'), true)
}
}
+
+ if (widget.name === 'preset') {
+ const normol_presets = [
+ 'LIGHT - SD1.5 only (low strength)',
+ 'STANDARD (medium strength)',
+ 'VIT-G (medium strength)',
+ 'PLUS (high strength)', 'PLUS FACE (portraits)',
+ 'FULL FACE - SD1.5 only (portraits stronger)',
+ 'FACEID PORTRAIT (style transfer)'
+ ]
+ const faceid_presets = [
+ 'FACEID',
+ 'FACEID PLUS - SD1.5 only',
+ 'FACEID PLUS V2',
+ ]
+ if(normol_presets.includes(widget.value)){
+ toggleWidget(node, findWidgetByName(node, 'lora_strength'))
+ toggleWidget(node, findWidgetByName(node, 'provider'))
+ toggleWidget(node, findWidgetByName(node, 'weight_faceidv2'))
+ }
+ else if(faceid_presets.includes(widget.value)){
+ if(widget.value == 'FACEID PLUS V2'){
+ toggleWidget(node, findWidgetByName(node, 'weight_faceidv2'), true)
+ }else{
+ toggleWidget(node, findWidgetByName(node, 'weight_faceidv2'))
+ }
+ toggleWidget(node, findWidgetByName(node, 'lora_strength'), true)
+ toggleWidget(node, findWidgetByName(node, 'provider'), true)
+ }
+ updateNodeHeight(node)
+ }
+
+ if (widget.name === 'use_tiled') {
+ if(widget.value)
+ toggleWidget(node, findWidgetByName(node, 'sharpening'), true)
+ else
+ toggleWidget(node, findWidgetByName(node, 'sharpening'))
+ updateNodeHeight(node)
+ }
}
function widgetLogic2(node, widget) {
@@ -486,6 +524,8 @@ app.registerExtension({
case "easy rangeFloat":
case 'easy latentCompositeMaskedWithCond':
case 'easy pipeEdit':
+ case 'easy ipadapterApply':
+ case 'easy ipadapterApplyADV':
getSetters(node)
break
case "easy wildcards":
@@ -734,7 +774,7 @@ app.registerExtension({
};
}
- if (["easy fullLoader", "easy a1111Loader", "easy comfyLoader"].includes(nodeData.name)) {
+ if (loaderNodes.includes(nodeData.name)) {
function populate(text, type = 'positive') {
if (this.widgets) {
const pos = this.widgets.findIndex((w) => w.name === type + "_prompt");
@@ -781,7 +821,7 @@ app.registerExtension({
};
}
- if (["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy preSamplingLayerDiffusion", "easy fullkSampler", "easy fullCascadeKSampler"].includes(nodeData.name)) {
+ if (seedNodes.includes(nodeData.name)) {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
@@ -922,7 +962,7 @@ const getSetWidgets = ['rescale_after_model', 'rescale',
'refiner_lora1_name', 'refiner_lora2_name', 'upscale_method',
'image_output', 'add_noise', 'info', 'sampler_name',
'ckpt_B_name', 'ckpt_C_name', 'save_model', 'refiner_ckpt_name',
- 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode', 'input_mode','lora_count','ckpt_count', 'conditioning_mode']
+ 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode', 'input_mode','lora_count','ckpt_count', 'conditioning_mode', 'preset', 'use_tiled', 'use_batch']
function getSetters(node) {
if (node.widgets)
diff --git a/web/js/easy/easyExtraMenu.js b/web/js/easy/easyExtraMenu.js
index 2ad5ac7..c1d4a33 100644
--- a/web/js/easy/easyExtraMenu.js
+++ b/web/js/easy/easyExtraMenu.js
@@ -4,6 +4,7 @@ const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader']
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingLayerDiffusion', 'easy fullkSampler']
const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion']
const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy instantIDApply', 'easy instantIDApplyADV']
+const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV']
const positive_prompt = ['easy positive', 'easy wildcards']
const widgetMapping = {
"positive_prompt":{
@@ -45,6 +46,17 @@ const widgetMapping = {
"cn_strength": ["strength", "cn_strength"],
"cn_soft_weights": ["scale_soft_weights","cn_soft_weights"],
},
+ "ipadapter":{
+ "preset":"preset",
+ "lora_strength": "lora_strength",
+ "provider": "provider",
+ "weight":"weight",
+ "weight_faceidv2": "weight_faceidv2",
+ "start_at": "start_at",
+ "end_at": "end_at",
+ "cache_mode": "cache_mode",
+ "use_tiled": "use_tiled",
+ }
}
const inputMapping = {
"loaders":{
@@ -73,6 +85,12 @@ const inputMapping = {
"positive_prompt":{
},
+ "ipadapter":{
+ "model":"model",
+ "image":"image",
+ "attn_mask":"attn_mask",
+ "optional_ipadapter":"optional_ipadapter"
+ }
};
const outputMapping = {
@@ -104,6 +122,12 @@ const outputMapping = {
"load_image":{
"IMAGE":"IMAGE",
"MASK": "MASK"
+ },
+ "ipadapter":{
+ "model":"model",
+ "tiles":"tiles",
+ "masks":"masks",
+ "ipadapter":"ipadapter"
}
};
@@ -491,6 +515,10 @@ app.registerExtension({
if (controlnet.includes(nodeData.name)) {
addMenu("↪️ Swap EasyControlnet", 'controlnet', controlnet, nodeType)
}
+ // Swap IPAdapater
+ if (ipadapter.includes(nodeData.name)) {
+ addMenu("↪️ Swap EasyIPAdapater", 'ipadapter', ipadapter, nodeType)
+ }
}
});