add:easy imageSplitGrid

This commit is contained in:
yolain
2024-05-09 16:08:03 +08:00
parent 403562575c
commit 8d352b85bc
10 changed files with 268 additions and 49 deletions
+1
View File
@@ -35,6 +35,7 @@
**v1.1.7**
- Added `easy imageSplitGrid`
- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget
- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader
+1
View File
@@ -40,6 +40,7 @@
**v1.1.7**
- 增加 `easy imageSplitGrid` - 图像网格拆分
- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项
- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet)
- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader
+14
View File
@@ -153,6 +153,20 @@ LAYER_DIFFUSION = {
},
}
# IC Light
IC_LIGHT_DIR = os.path.join(folder_paths.models_dir, "ic_light")
IC_LIGHT_MODELS = {
"iclight_fc": {
"sd1": {
"model_url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fc.safetensors"
},
"sdxl": {
"model_url": None
}
}
}
# REMBG
REMBG_DIR = os.path.join(folder_paths.models_dir, "rembg")
REMBG_MODELS = {
+35 -32
View File
@@ -1,4 +1,4 @@
import sys, os, re, json, time, math, copy
import sys, os, re, json, time
import torch
import folder_paths
import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
@@ -16,7 +16,7 @@ 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, BRUSHNET_MODELS, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS
from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, BRUSHNET_MODELS, IPADAPTER_DIR, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS, IC_LIGHT_DIR, IC_LIGHT_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
@@ -52,6 +52,7 @@ add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter
add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("mediapipe", [os.path.join(model_path, "mediapipe")], set(['.tflite','.pth']))
add_folder_path_and_extensions("t5", [os.path.join(model_path, "t5")], set(['.safetensors','.bin','.json']))
add_folder_path_and_extensions("ic_light", [os.path.join(model_path, "ic_light")], set(['.safetensors','.bin','.json']))
add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs)
add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs)
@@ -2087,34 +2088,6 @@ class LLLiteLoader:
# FooocusInpaint
from .fooocus import InpaintHead, InpaintWorker
inpaint_head_model = None
# class fooocusInpaintLoader:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "head": (list(FOOOCUS_INPAINT_HEAD.keys()),),
# "patch": (list(FOOOCUS_INPAINT_PATCH.keys()),),
# }
# }
#
# RETURN_TYPES = ("INPAINT_PATCH",)
# RETURN_NAMES = ("patch",)
# CATEGORY = "EasyUse/Inpaint"
# FUNCTION = "apply"
#
# def apply(self, head, patch):
# global inpaint_head_model
#
# head_file = get_local_filepath(FOOOCUS_INPAINT_HEAD[head]["model_url"], INPAINT_DIR)
# if inpaint_head_model is None:
# inpaint_head_model = InpaintHead()
# sd = torch.load(head_file, map_location='cpu')
# inpaint_head_model.load_state_dict(sd)
#
# patch_file = get_local_filepath(FOOOCUS_INPAINT_PATCH[patch]["model_url"], INPAINT_DIR)
# inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True)
#
# return ((inpaint_head_model, inpaint_lora),)
class applyFooocusInpaint:
@classmethod
@@ -2155,6 +2128,8 @@ class applyFooocusInpaint:
#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------#
# 风格对齐
from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS
class styleAlignedBatchAlign:
@@ -2176,6 +2151,33 @@ class styleAlignedBatchAlign:
def align(self, model, share_norm, share_attn, scale):
return (styleAlignBatch(model, share_norm, share_attn, scale),)
# 光照对齐
from .ic_light.func import ICLight
class icLightApply:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": (list(IC_LIGHT_MODELS.keys()),),
"model": ("MODEL",),
"latent": ("LATENT",),
},
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "apply"
CATEGORY = "EasyUse/Adapter"
def apply(self, mode, model, latent):
model_type = get_sd_version(model)
if model_type == 'sdxl':
raise Exception("IC Light model is not supported for SDXL")
model_path = get_local_filepath(IC_LIGHT_MODELS[mode]['sd1']["model_url"], IC_LIGHT_DIR)
m = ICLight().apply(model_path, model, latent)
return (m,)
def insightface_loader(provider):
try:
from insightface.app import FaceAnalysis
@@ -5687,8 +5689,7 @@ class detailerFix:
del pipe
if image_output in ("Hide", "Hide&Save"):
return {"ui": {},
"result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )}
return (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images)
if image_output in ("Sender", "Sender&Save"):
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
@@ -7154,6 +7155,7 @@ NODE_CLASS_MAPPINGS = {
"easy instantIDApply": instantIDApply,
"easy instantIDApplyADV": instantIDApplyAdvanced,
"easy styleAlignedBatchAlign": styleAlignedBatchAlign,
"easy icLightApply": icLightApply,
# Inpaint 内补
# "easy fooocusInpaintLoader": fooocusInpaintLoader,
"easy applyFooocusInpaint": applyFooocusInpaint,
@@ -7261,6 +7263,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy instantIDApply": "Easy Apply InstantID",
"easy instantIDApplyADV": "Easy Apply InstantID (Advanced)",
"easy styleAlignedBatchAlign": "Easy Apply StyleAlign",
"easy icLightApply": "Easy Apply ICLight",
# Inpaint 内补
# "easy fooocusInpaintLoader": "Load Fooocus Inpaint(Removed)",
"easy applyFooocusInpaint": "Apply Fooocus Inpaint",
+96
View File
@@ -0,0 +1,96 @@
import os
import torch
import safetensors.torch
from typing import Tuple, TypedDict, Callable, NamedTuple
import folder_paths
import comfy.model_management
from comfy.diffusers_convert import convert_unet_state_dict
from comfy.model_patcher import ModelPatcher
from comfy.ldm.modules.diffusionmodules.openaimodel import UNetModel
from comfy.model_base import BaseModel
from comfy.conds import CONDRegular
class UnetParams(TypedDict):
input: torch.Tensor
timestep: torch.Tensor
c: dict
cond_or_uncond: torch.Tensor
class ICLight:
@staticmethod
def apply_c_concat(cond, uncond, c_concat: torch.Tensor):
def write_c_concat(cond):
new_cond = []
for t in cond:
n = [t[0], t[1].copy()]
if "model_conds" not in n[1]:
n[1]["model_conds"] = {}
n[1]["model_conds"]["c_concat"] = CONDRegular(c_concat)
new_cond.append(n)
return new_cond
return (write_c_concat(cond), write_c_concat(uncond))
@staticmethod
def create_custom_conv(
original_conv: torch.nn.Module,
dtype: torch.dtype,
device=torch.device,
) -> torch.nn.Module:
with torch.no_grad():
new_conv_in = torch.nn.Conv2d(
8,
original_conv.out_channels,
original_conv.kernel_size,
original_conv.stride,
original_conv.padding,
)
new_conv_in.weight.zero_()
new_conv_in.weight[:, :4, :, :].copy_(original_conv.weight)
new_conv_in.bias = original_conv.bias
return new_conv_in.to(dtype=dtype, device=device)
def apply(self, model_path, model: ModelPatcher, c_concat: dict,) -> Tuple[ModelPatcher]:
device = comfy.model_management.get_torch_device()
dtype = comfy.model_management.unet_dtype()
work_model = model.clone()
c_concat_samples: torch.Tensor = c_concat["samples"]
def wrapped_unet(unet_apply: Callable, params: UnetParams):
# Apply concat.
sample = params["input"]
params["c"]["c_concat"] = torch.cat(
(
[c_concat_samples.to(sample.device)]
* (sample.shape[0] // c_concat_samples.shape[0])
)
+ params["c"].get("c_concat", []),
dim=0,
)
return unet_apply(x=sample, t=params["timestep"], **params["c"])
work_model.add_object_patch(
"diffusion_model.input_blocks.0.0",
self.create_custom_conv(
original_conv=work_model.get_model_object("diffusion_model.input_blocks.0.0"),
dtype=dtype,
device=device,
),
)
work_model.set_model_unet_function_wrapper(wrapped_unet)
sd_offset = convert_unet_state_dict(safetensors.torch.load_file(model_path))
work_model.add_patches(
patches={
("diffusion_model." + key): (
sd_offset[key].to(dtype=dtype, device=device),
)
for key in sd_offset.keys()
}
)
return work_model
+81 -5
View File
@@ -540,6 +540,41 @@ class imageSplitList:
new_images[1].append(img)
return new_images
class imageSplitGrid:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE",),
"row": ("INT", {"default": 1,"min": 1,"max": 10,"step": 1,}),
"column": ("INT", {"default": 1,"min": 1,"max": 10,"step": 1,}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "doit"
CATEGORY = "EasyUse/Image"
def crop(self, image, width, height, x, y):
x = min(x, image.shape[2] - 1)
y = min(y, image.shape[1] - 1)
to_x = width + x
to_y = height + y
img = image[:, y:to_y, x:to_x, :]
return img
def doit(self, images, row, column):
_, height, width, _ = images.shape
sub_width = width // column
sub_height = height // row
new_images = []
for i in range(row):
for j in range(column):
new_images.append(self.crop(images, sub_width, sub_height, j * sub_width, i * sub_height))
return (torch.cat(new_images, dim=0),)
class imagesSplitImage:
@classmethod
def INPUT_TYPES(s):
@@ -558,6 +593,34 @@ class imagesSplitImage:
new_images = torch.chunk(images, len(images), dim=0)
return new_images
class imageConcat:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"direction": (['right','down','left','up',],{"default": 'right'}),
"match_image_size": ("BOOLEAN", {"default": False}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "concat"
CATEGORY = "EasyUse/image"
def concat(self, image1, image2, direction, match_image_size):
if match_image_size:
image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear")
if direction == 'right':
row = torch.cat((image1, image2), dim=2)
elif direction == 'down':
row = torch.cat((image1, image2), dim=1)
elif direction == 'left':
row = torch.cat((image2, image1), dim=2)
elif direction == 'up':
row = torch.cat((image2, image1), dim=1)
return (row,)
# 图片背景移除
from .briaai.rembg import BriaRMBG, preprocess_image, postprocess_image
from .libs.utils import get_local_filepath, easySave, install_package
@@ -633,12 +696,12 @@ class imageChooser(PreviewImage):
def INPUT_TYPES(self):
return {
"required":{
"mode": (['Always Pause', 'Keep Last Selection'], {"default": "Always Pause"}),
},
"optional": {
"images": ("IMAGE",),
},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"},
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ("IMAGE",)
@@ -661,8 +724,7 @@ class imageChooser(PreviewImage):
else:
return None
def chooser(self, prompt=None, my_unique_id=None, **kwargs):
def chooser(self, prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs):
id = my_unique_id[0]
if id not in ChooserMessage.stash:
ChooserMessage.stash[id] = {}
@@ -685,9 +747,19 @@ class imageChooser(PreviewImage):
images = result['ui']['images']
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
# 获取上次选择
mode = kwargs.pop('mode', 'Always Pause')
last_choosen = None
if mode == 'Keep Last Selection':
if id and extra_pnginfo[0] and "workflow" in extra_pnginfo[0]:
workflow = extra_pnginfo[0]["workflow"]
node = next((x for x in workflow["nodes"] if str(x["id"]) == id), None)
if node:
last_choosen = node['properties']['values']
# wait for selection
try:
selections = ChooserMessage.waitForMessage(id, asList=True)
selections = ChooserMessage.waitForMessage(id, asList=True) if last_choosen is None or len(last_choosen)<1 else last_choosen
choosen = [x for x in selections if x >= 0] if len(selections)>1 else [0]
except ChooserCancelled:
raise comfy.model_management.InterruptProcessingException()
@@ -1332,7 +1404,9 @@ NODE_CLASS_MAPPINGS = {
"easy imageScaleDownToSize": imageScaleDownToSize,
"easy imageRatio": imageRatio,
"easy imageToMask": imageToMask,
"easy imageConcat": imageConcat,
"easy imageSplitList": imageSplitList,
"easy imageSplitGrid": imageSplitGrid,
"easy imagesSplitImage": imagesSplitImage,
"easy imageCropFromMask": imageCropFromMask,
"easy imageUncropFromBBOX": imageUncropFromBBOX,
@@ -1362,7 +1436,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy imageRatio": "ImageRatio",
"easy imageToMask": "ImageToMask",
"easy imageHSVMask": "ImageHSVMask",
"easy imageConcat": "imageConcat",
"easy imageSplitList": "imageSplitList",
"easy imageSplitGrid": "imageSplitGrid",
"easy imagesSplitImage": "imagesSplitImage",
"easy imageCropFromMask": "imageCropFromMask",
"easy imageUncropFromBBOX": "imageUncropFromBBOX",
+5
View File
@@ -27,4 +27,9 @@
}
.easyuse-chooser-dialog-images img.selected{
border: 4px solid var(--success-color);
}
.easyuse-chooser-hidden{
display: none;
height:0;
}
+3 -6
View File
@@ -139,7 +139,7 @@
}
.easyuse-guide-dialog p{
margin:4px 0;
font-size: 10px;
font-size: 12px;
font-weight: 300;
}
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
@@ -161,7 +161,7 @@
font-size: 14px;
}
.markdown-body h4{
font-size: 12px;
font-size: 13px;
}
.markdown-body table {
display: block;
@@ -183,7 +183,7 @@
border-bottom: 1px solid var(--border-color);
}
.markdown-body table td {
font-size: 10px;
font-size: 12px;
}
.markdown-body table th:last-child, .markdown-body table td:last-child{
border-right: none;
@@ -199,9 +199,6 @@
.markdown-body table th:last-child{
width:100%;
}
.markdown-body a{
margin-right: 10px;
}
.markdown-body .warning{
color:var(--warning-color)
}
+8 -5
View File
@@ -29,12 +29,15 @@ api.addEventListener("easyuse-toast",event=>{
let draggerEl = null
let isGroupMapcanMove = true
function createGroupMap(){
let div = document.querySelector('#easyuse_groups_map')
if(div){
div.style.display = div.style.display == 'none' ? 'flex' : 'none'
return
}
let groups = app.canvas.graph._groups
let nodes = app.canvas.graph._nodes
let old_nodes = groups.length
let div =
document.querySelector('#easyuse_groups_map') ||
document.createElement('div')
div = document.createElement('div')
div.id = 'easyuse_groups_map'
div.innerHTML = ''
let btn = document.createElement('div')
@@ -381,10 +384,11 @@ class GuideDialog {
}
}
const getEnableToolBar = _ => app.ui.settings.getSettingValue(toolBarId, true)
const toolBarId = "Comfy.EasyUse.toolBar"
let enableToolBar = true
let enableToolBar = getEnableToolBar()
let disableRenderInfo = localStorage['Comfy.Settings.Comfy.EasyUse.disableRenderInfo'] ? true : false
export function addToolBar(app) {
app.ui.settings.addSetting({
@@ -400,7 +404,6 @@ export function addToolBar(app) {
},
});
}
const getEnableToolBar = _ => app.ui.settings.getSettingValue(toolBarId, true)
let note = null
let toolbar = null
+24 -1
View File
@@ -72,6 +72,10 @@ class chooserImageDialog extends ComfyDialog {
function progressButtonPressed() {
const node = app.graph._nodes_by_id[this.node_id];
if (node) {
const selected = [...node.selected]
if(selected?.length>0){
node.setProperty('values',selected)
}
if (FlowState.paused()) {
send_message(node.id, [...node.selected, -1, ...node.anti_selected]);
}
@@ -79,10 +83,26 @@ function progressButtonPressed() {
skip_next_restart_message();
restart_from_here(node.id).then(() => { send_message(node.id, [...node.selected, -1, ...node.anti_selected]); });
}
const maxlength = node.imgs.length;
if (FlowState.paused_here(node.id) && selected>0) {
node.send_button_widget.name = (selected>1) ? "Progress selected (" + selected + '/' + maxlength +")" : "Progress selected image";
} else if (FlowState.idle() && selected>0) {
node.send_button_widget.name = (selected>1) ? "Progress selected (" + selected + '/' + maxlength +")" : "Progress selected image as restart";
}
else {
node.send_button_widget.name = "";
}
}
}
function cancelButtonPressed() { if (FlowState.running()) { send_cancel(); } }
function cancelButtonPressed() {
if (FlowState.running()) { send_cancel();}
const node = app.graph._nodes_by_id[this.node_id];
if (node) {
node.send_button_widget.name = "";
node.cancel_button_widget.name = "";
}
}
app.registerExtension({
name:'comfy.easyuse.imageChooser',
@@ -126,9 +146,12 @@ app.registerExtension({
},
async nodeCreated(node, app) {
if(node.comfyClass == 'easy imageChooser'){
node.send_button_widget = node.addWidget("button", "", "", progressButtonPressed, {serialize: false});
node.cancel_button_widget = node.addWidget("button", "", "", cancelButtonPressed, {serialize: false});
node.setProperty('values',[])
/* Capture clicks */
const org_onMouseDown = node.onMouseDown;