changes to checkpointloader. added hash + more

This commit is contained in:
bash-j
2024-03-06 12:59:22 +10:30
parent 88e644dcda
commit f74c0a5d22
2 changed files with 239 additions and 69 deletions
+22 -10
View File
@@ -3,6 +3,7 @@
This repository contains custom nodes for ComfyUI.
## Prompt With Style
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/0048faa8-5f46-4d92-8e38-e7ff603007da)
Example workflow in the prompt_with_styles.json file and mikey_node_workflow.json
@@ -19,7 +20,9 @@ There is a V1 and V2 Prompt with Style node with different functionality, style
### Wildcards
Wildcards are supported using the `__word__` syntax. Folder location is comfyui/wildcards You can select more than 1 line from the wildcard by using the syntax `[2$$__wildcard__]` for 2 lines in this example. You can also add a word to search for in the wildcard file e.g. `__wildcard|word__`
Wildcards are supported using the `__word__` syntax. Folder location is comfyui/wildcards You can select more than 1 line from the wildcard by using the syntax `2$$__wildcard__` for 2 lines in this example. You can also add a word to search for in the wildcard file e.g. `__wildcard|word__`
You can also make sure it selects the same line from the wildcard by using the `__!wildcard__` syntax, not the ! before the filename. You can also use `__+wildcard__` `__-wildcard__` and `__*wildcard__` to use the offset + 1 or - 1, or randomly select a different line.
### Styles
@@ -53,6 +56,7 @@ The format needs to look like the following example:
```
## Mikey Sampler and Mikey Sampler Base Only
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/beb24edb-4655-4d00-933a-e3fa2304aef2)
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/d7e1e34a-c84b-47c7-8c6c-cf2278cd1570)
@@ -60,7 +64,7 @@ Example workflow: mikey_node_workflow.json
This is a sampler node as a companion to the Prompt with Style node, to allow for very straightforward image generation with SDXL.
The node uses a base -> refiner -> upscale with model -> base to produce the final image. It has an inbuilt image complexity detection function to make sure there aren't too many steps run on the final base sampler which can scramble faces and other simple areas on a large image.
The node uses a base -> refiner -> upscale with model -> base to produce the final image. It has an inbuilt image complexity detection function to make sure there aren't too many steps run on the final base sampler which can scramble faces and other simple areas on a large image.
The Base Only version of the sampler uses a slightly different approach. It uses a base -> base -> upscale with model -> base to produce the final image.
The other key difference with the Base Only version is it skips a step in the middle and adds it to the end. This is a trick which adds extra detail to the image. The smooth step number will add even more extra steps to the end. This might be required if you have an image with a plain background and are seeing some spotty effects created by the skip step method. Usually only 1 extra step is required to clean up the spots. You can also go -1 for very busy images for even more detail.
@@ -71,7 +75,7 @@ The seed is used by the samplers.
The upscale_by setting is how large you want the final image to be before it runs the last base sampler over the image. A value of 1 will output the original image size. 2 will be 2x the size of the original.
The highres_strength setting allows you to control this last stage and how much detail it adds. Setting of 1 is the sweet spot I found didn't scramble faces in a few test images. Dial it down if you are still getting scrambled images, or turn it up if you want to add more detail.
The highres_strength setting allows you to control this last stage and how much detail it adds. Setting of 1 is the sweet spot I found didn't scramble faces in a few test images. Dial it down if you are still getting scrambled images, or turn it up if you want to add more detail.
## Mikey Sampler Tiled and Mikey Sampler Tiled Base Only
@@ -80,6 +84,7 @@ The highres_strength setting allows you to control this last stage and how much
These samplers use a tiled approach to resample the image after the upscale model has upscaled the image, to help enhance the image with more details.
## Style Conditioner
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/b742b8a4-6ab3-4311-b278-db8bda66b5ce)
This chonky node is for injecting the style prompt into your conditioner by averaging the conditioner passed through the node.
@@ -87,11 +92,13 @@ This chonky node is for injecting the style prompt into your conditioner by aver
A strength value of 0.5 would be even split between the conditioner passed in and the style selected.
## Empty Latent Ratio Select SDXL
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/610dae5e-7c86-45fb-893d-06f7ccfe242e)
This node allows you to select from a range of different aspect ratios.
## Empty Latent Ratio Custom SDXL
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/784d7b92-dfc7-4f79-9c81-272605ab7934)
This node allows you to enter your own aspect ratio or image size from wherever, and it will make it fit under 1024x1024 resolution.
@@ -124,7 +131,7 @@ It is a really powerful and convenient ratio selector, because you can create al
The swap axis option will rotate the dimensions by 90 degrees if set to true.
If use preset seed is set to true, it will use the seed number to select one of the available presets. This can be used to cycle or randomly select presets, which is super convenient.
If use preset seed is set to true, it will use the seed number to select one of the available presets. This can be used to cycle or randomly select presets, which is super convenient.
## Ratio Advanced
@@ -136,18 +143,20 @@ When creating a latent image you need to set the width and height. But the Clip
This node has options to setup the dimensions for each of these using different methods:
* Select a ratio from the select widget, or choose custom to enter your own dimensions.
* If you select custom, you need to fill in the width and height options.
* If you enter a number into the mult input e.g. cte_mult (clip text encoder) it will multiply the latent width and height to create the width and height for that dimension.
* If you enter a number into the res input, it will create a width and height that fits into the res^2 image size while maintining the same ratio as the latent width and height.
* If you enter a number into the fit_size input, it will create a width and height that has the largest dimension equal to fit_size while maintaining the same ratio as the latent width and height.
* Select a ratio from the select widget, or choose custom to enter your own dimensions.
* If you select custom, you need to fill in the width and height options.
* If you enter a number into the mult input e.g. cte_mult (clip text encoder) it will multiply the latent width and height to create the width and height for that dimension.
* If you enter a number into the res input, it will create a width and height that fits into the res^2 image size while maintining the same ratio as the latent width and height.
* If you enter a number into the fit_size input, it will create a width and height that has the largest dimension equal to fit_size while maintaining the same ratio as the latent width and height.
## Resize Image for SDXL
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/85cd45ef-933b-4d95-9c73-717b944df7e2)
This node allows you to resize an image to fit into 1024x1024 resolution. Good for Img2Img workflows.
## Batch Resize Image for SDXL
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/fb5ee831-9895-4901-91fa-fd0771f91a1d)
Given a path to a folder containing images, it will resize all images in the folder to fit the 1024^2 resolution and feed into the workflow. Careful of folders with lots of images!
@@ -161,6 +170,7 @@ The filename will start with a datestamp, then part of the positive prompt.
It will also save the positive prompt and negative prompt to the png data.
## Upscale Tile Calculator
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/6f550f38-3e6a-4659-87fb-92deb5d98460)
Made for the [Ultimate SD Upscaler](https://github.com/ssitu/ComfyUI_UltimateSDUpscale) to help calcuate tile size to evenly fit inside the larger image and not get weird edges.
@@ -168,6 +178,7 @@ Made for the [Ultimate SD Upscaler](https://github.com/ssitu/ComfyUI_UltimateSDU
The resolution is what is suitable for the model you are using, 1.5: 512, 2.1: 768, SDXL Base: 1024, SDXL Refiner: 768
## Wildcard Processor
![image](https://github.com/bash-j/mikey_nodes/assets/3195567/ce62e6d7-1982-4c53-bde1-7569c94322e3)
This node is for building a string using your wildcard files.
@@ -213,7 +224,8 @@ Converts an integer to a string.
Converts a float to a string.
## Removed Nodes
- VAE Decode 6GB
- VAE Decode 6GB
## Installation
+217 -59
View File
@@ -218,7 +218,8 @@ def read_styles():
def find_and_replace_wildcards(prompt, offset_seed, debug=False):
# wildcards use the __file_name__ syntax with optional |word_to_find
wildcard_path = os.path.join(folder_paths.base_path, 'wildcards')
wildcard_regex = r'(\[(\d+)\$\$)?__((?:[^|_]+_)*[^|_]+)((?:\|[^|]+)*)__\]?'
wildcard_regex = r'((\d+)\$\$)?__(!|\+|-|\*)?((?:[^|_]+_)*[^|_]+)((?:\|[^|]+)*)__'
# r'(\[(\d+)\$\$)?__((?:[^|_]+_)*[^|_]+)((?:\|[^|]+)*)__\]?'
match_strings = []
random.seed(offset_seed)
offset = offset_seed
@@ -227,10 +228,19 @@ def find_and_replace_wildcards(prompt, offset_seed, debug=False):
last_end = 0
for m in re.finditer(wildcard_regex, prompt):
full_match, lines_count_str, actual_match, words_to_find_str = m.groups()
full_match, lines_count_str, offset_type, actual_match, words_to_find_str = m.groups()
# Append everything up to this match
new_prompt += prompt[last_end:m.start()]
# lock indicator
lock_indicator = offset_type == '!'
# increment indicator
increment_indicator = offset_type == '+'
# decrement indicator
decrement_indicator = offset_type == '-'
# random indicator
random_indicator = offset_type == '*'
#for full_match, lines_count_str, actual_match, words_to_find_str in re.findall(wildcard_regex, prompt):
words_to_find = words_to_find_str.split('|')[1:] if words_to_find_str else None
if debug:
@@ -254,7 +264,16 @@ def find_and_replace_wildcards(prompt, offset_seed, debug=False):
store_offset = None
if actual_match in match_strings:
store_offset = offset
offset = random.randint(0, 1000000)
if lock_indicator:
offset = offset_seed
elif random_indicator:
offset = random.randint(0, 1000000)
elif increment_indicator:
offset = offset_seed + 1
elif decrement_indicator:
offset = offset_seed - 1
else:
offset = random.randint(0, 1000000)
selected_lines = []
with open(file_path, 'r', encoding='utf-8') as file:
file_lines = file.readlines()
@@ -781,14 +800,14 @@ class RatioAdvanced:
# check if target_fit_size not 0
if target_fit_size != 0:
target_w, target_h = self.fit(target_w, target_h, target_fit_size)
prompt.get(str(unique_id))['inputs']['output_latent_w'] = str(latent_width)
prompt.get(str(unique_id))['inputs']['output_latent_h'] = str(latent_height)
prompt.get(str(unique_id))['inputs']['output_cte_w'] = str(cte_w)
prompt.get(str(unique_id))['inputs']['output_cte_h'] = str(cte_h)
prompt.get(str(unique_id))['inputs']['output_target_w'] = str(target_w)
prompt.get(str(unique_id))['inputs']['output_target_h'] = str(target_h)
prompt.get(str(unique_id))['inputs']['output_crop_w'] = str(crop_w)
prompt.get(str(unique_id))['inputs']['output_crop_h'] = str(crop_h)
#prompt.get(str(unique_id))['inputs']['output_latent_w'] = str(latent_width)
#prompt.get(str(unique_id))['inputs']['output_latent_h'] = str(latent_height)
#prompt.get(str(unique_id))['inputs']['output_cte_w'] = str(cte_w)
#prompt.get(str(unique_id))['inputs']['output_cte_h'] = str(cte_h)
#prompt.get(str(unique_id))['inputs']['output_target_w'] = str(target_w)
#prompt.get(str(unique_id))['inputs']['output_target_h'] = str(target_h)
#prompt.get(str(unique_id))['inputs']['output_crop_w'] = str(crop_w)
#prompt.get(str(unique_id))['inputs']['output_crop_h'] = str(crop_h)
return (latent_width, latent_height,
cte_w, cte_h,
target_w, target_h,
@@ -837,14 +856,14 @@ class PresetRatioSelector:
cte_w, cte_h = cte_h, cte_w
target_w, target_h = target_h, target_w
crop_w, crop_h = crop_h, crop_w
prompt.get(str(unique_id))['inputs']['output_latent_w'] = str(latent_width)
prompt.get(str(unique_id))['inputs']['output_latent_h'] = str(latent_height)
prompt.get(str(unique_id))['inputs']['output_cte_w'] = str(cte_w)
prompt.get(str(unique_id))['inputs']['output_cte_h'] = str(cte_h)
prompt.get(str(unique_id))['inputs']['output_target_w'] = str(target_w)
prompt.get(str(unique_id))['inputs']['output_target_h'] = str(target_h)
prompt.get(str(unique_id))['inputs']['output_crop_w'] = str(crop_w)
prompt.get(str(unique_id))['inputs']['output_crop_h'] = str(crop_h)
#prompt.get(str(unique_id))['inputs']['output_latent_w'] = str(latent_width)
#prompt.get(str(unique_id))['inputs']['output_latent_h'] = str(latent_height)
#prompt.get(str(unique_id))['inputs']['output_cte_w'] = str(cte_w)
#prompt.get(str(unique_id))['inputs']['output_cte_h'] = str(cte_h)
#prompt.get(str(unique_id))['inputs']['output_target_w'] = str(target_w)
#prompt.get(str(unique_id))['inputs']['output_target_h'] = str(target_h)
#prompt.get(str(unique_id))['inputs']['output_crop_w'] = str(crop_w)
#prompt.get(str(unique_id))['inputs']['output_crop_h'] = str(crop_h)
return (latent_width, latent_height,
cte_w, cte_h,
target_w, target_h,
@@ -2064,16 +2083,16 @@ class PromptWithStyleV3:
sdxl_neg_cond = CLIPTextEncodeSDXL.encode(self, clip_base_neg, width, height, 0, 0, target_width, target_height, neg_prompt_, neg_style_)[0]
refiner_pos_cond = CLIPTextEncodeSDXLRefiner.encode(self, clip_refiner, 6, refiner_width, refiner_height, pos_prompt_)[0]
refiner_neg_cond = CLIPTextEncodeSDXLRefiner.encode(self, clip_refiner, 2.5, refiner_width, refiner_height, neg_prompt_)[0]
prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
#prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
#prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
#prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
#prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
#prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
#prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
#prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
#prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
#prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
#prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
return (base_model, {"samples":latent},
sdxl_pos_cond, sdxl_neg_cond,
refiner_pos_cond, refiner_neg_cond,
@@ -2127,16 +2146,16 @@ class PromptWithStyleV3:
sdxl_neg_cond = CLIPTextEncodeSDXL.encode(self, clip_base_neg, width, height, 0, 0, target_width, target_height, neg_prompt_, neg_style_)[0]
refiner_pos_cond = CLIPTextEncodeSDXLRefiner.encode(self, clip_refiner, 6, refiner_width, refiner_height, pos_prompt_)[0]
refiner_neg_cond = CLIPTextEncodeSDXLRefiner.encode(self, clip_refiner, 2.5, refiner_width, refiner_height, neg_prompt_)[0]
prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
#prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
#prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
#prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
#prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
#prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
#prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
#prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
#prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
#prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
#prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
return (base_model, {"samples":latent},
sdxl_pos_cond, sdxl_neg_cond,
refiner_pos_cond, refiner_neg_cond,
@@ -2168,16 +2187,16 @@ class PromptWithStyleV3:
refiner_neg_cond = ConditioningAverage.addWeighted(self, refiner_neg_conds[i], refiner_neg_cond, 1 / weight)[0]
# return
extra_pnginfo['PromptWithStyle'] = prompt_with_style
prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
#prompt.get(str(unique_id))['inputs']['output_positive_prompt'] = pos_prompt_
#prompt.get(str(unique_id))['inputs']['output_negative_prompt'] = neg_prompt_
#prompt.get(str(unique_id))['inputs']['output_latent_width'] = width
#prompt.get(str(unique_id))['inputs']['output_latent_height'] = height
#prompt.get(str(unique_id))['inputs']['output_target_width'] = target_width
#prompt.get(str(unique_id))['inputs']['output_target_height'] = target_height
#prompt.get(str(unique_id))['inputs']['output_refiner_width'] = refiner_width
#prompt.get(str(unique_id))['inputs']['output_refiner_height'] = refiner_height
#prompt.get(str(unique_id))['inputs']['output_crop_w'] = 0
#prompt.get(str(unique_id))['inputs']['output_crop_h'] = 0
return (base_model, {"samples":latent},
sdxl_pos_cond, sdxl_neg_cond,
refiner_pos_cond, refiner_neg_cond,
@@ -2293,7 +2312,7 @@ class WildcardAndLoraSyntaxProcessor:
if len(text_) != len(text):
seed = random.randint(0, 1000000)
else:
seed = 0
seed = 1
# extract and load loras
model, clip, stripped_text = self.extract_and_load_loras(text_, model, clip)
# process wildcards again
@@ -2347,7 +2366,7 @@ class StyleConditionerBaseOnly:
@classmethod
def INPUT_TYPES(s):
s.styles, s.pos_style, s.neg_style = read_styles()
return {"required": {"style": (s.styles,),"strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1}),
return {"required": {"style": (s.styles,),"strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"positive_cond_base": ("CONDITIONING",), "negative_cond_base": ("CONDITIONING",),
"base_clip": ("CLIP",),
"use_seed": (['true','false'], {'default': 'false'}),
@@ -3818,8 +3837,7 @@ class Text2InputOr3rdOption:
class CheckpointLoaderSimpleMikey:
@classmethod
def INPUT_TYPES(s):
return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
},
return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),},
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT"}}
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING", "STRING")
@@ -3831,13 +3849,43 @@ class CheckpointLoaderSimpleMikey:
def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True, unique_id=None, extra_pnginfo=None, prompt=None):
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
# get hash using python, there is no function in the comfy library
hash = get_file_hash(ckpt_path)[:10]
# just return the filename, not path
hash = get_file_hash(ckpt_path)
ckpt_name = os.path.basename(ckpt_name)
#prompt.get(str(unique_id))['inputs']['output_ckpt_hash'] = hash
#prompt.get(str(unique_id))['inputs']['output_ckpt_name'] = ckpt_name
return out[:3] + (ckpt_name, hash)
class CheckpointHash:
@classmethod
def INPUT_TYPES(s):
return {"required": { "ckpt_name": ("STRING", {"forceInput": True}),},
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT"}}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("ckpt_hash",)
FUNCTION = "get_hash"
CATEGORY = "Mikey/Loaders"
def get_hash(self, ckpt_name, extra_pnginfo, prompt, unique_id):
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
hash = get_file_hash(ckpt_path)
prompt.get(str(unique_id))['inputs']['output_ckpt_hash'] = hash
prompt.get(str(unique_id))['inputs']['output_ckpt_name'] = ckpt_name
return out[:3] + (ckpt_name, hash)
return (get_file_hash(ckpt_path),)
class SRPromptInputAdd:
@classmethod
def INPUT_TYPES(s):
return {'required': {'input_str': ('STRING', {'forceInput': True}),},
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT"}}
RETURN_TYPES = ("STRING",)
FUNCTION = "add"
CATEGORY = "Mikey/Meta"
def add(self, input_str, unique_id=None, extra_pnginfo=None, prompt=None):
prompt.get(str(unique_id))['inputs']['input_str'] = input_str
return (input_str,)
class TextPreserve:
@classmethod
@@ -3854,6 +3902,8 @@ class TextPreserve:
CATEGORY = 'Mikey/Text'
def process(self, text, result_text, unique_id=None, extra_pnginfo=None, prompt=None):
# reset random seed
random.seed()
preserve_text = text
# search and replace
text = search_and_replace(text, extra_pnginfo, prompt)
@@ -4673,6 +4723,110 @@ class CinematicLook:
# batch_tensor = torch.cat(images, dim=0)
# return (batch_tensor, )
class MosaicExpandImage:
@classmethod
def INPUT_TYPES(cls):
return {'required': {'image': ('IMAGE', {'default': None}),
'left': ('INT', {'default': 0, 'min': 0, 'max': 5}),
'top': ('INT', {'default': 0, 'min': 0, 'max': 5}),
'right': ('INT', {'default': 0, 'min': 0, 'max': 5}),
'bottom': ('INT', {'default': 0, 'min': 0, 'max': 5})}}
RETURN_TYPES = ('IMAGE','MASK',)
RETURN_NAMES = ('result_img',)
FUNCTION = 'mosaic_expand'
CATEGORY = 'Mikey/Image'
def mosaic_expand(self, image, left, top, right, bottom):
img = tensor2pil(image)
width, height = img.size
width_5th, height_5th = width // 5, height // 5
def create_mosaic(side_img, num_fifths, vertical=False):
block_size = 50
num_blocks_wide = side_img.width // block_size
num_blocks_high = (num_fifths * height_5th) // block_size if vertical else side_img.height // block_size
mosaic_width = block_size * num_blocks_wide
mosaic_height = block_size * num_blocks_high
mosaic_img = Image.new('RGB', (mosaic_width, mosaic_height))
for i in range(num_blocks_wide):
for j in range(num_blocks_high):
# Calculate the average color of each block
section = side_img.crop((i * block_size, j * block_size, (i + 1) * block_size, (j + 1) * block_size))
avg_color = np.array(section).mean(axis=(0, 1)).astype(np.uint8)
# Fill the corresponding block in the mosaic
for x in range(block_size):
for y in range(block_size):
mosaic_x = i * block_size + x
mosaic_y = j * block_size + y
mosaic_img.putpixel((mosaic_x, mosaic_y), tuple(avg_color))
return mosaic_img
self.new_width = width + width_5th * (left + right)
self.new_height = height + height_5th * (top + bottom)
new_img = Image.new('RGB', (self.new_width, self.new_height))
# Create and paste mosaic strips
if right > 0:
right_side = img.crop((width - width_5th * right, 0, width, height))
right_mosaic = create_mosaic(right_side, right)
right_mosaic = right_mosaic.transpose(Image.FLIP_LEFT_RIGHT)
# resize mosaic to match new height
right_mosaic = right_mosaic.resize((width_5th * right + 8, self.new_height))
new_img.paste(right_mosaic, (width + width_5th * left, 0))
if left > 0:
left_side = img.crop((0, 0, width_5th * left, height))
left_mosaic = create_mosaic(left_side, left)
left_mosaic = left_mosaic.transpose(Image.FLIP_LEFT_RIGHT)
# resize mosaic to match new height
left_mosaic = left_mosaic.resize((width_5th * left + 8, self.new_height))
new_img.paste(left_mosaic, (0, 0))
if top > 0:
top_side = img.crop((0, 0, width, height_5th * top))
top_mosaic = create_mosaic(top_side, top, vertical=True)
top_mosaic = top_mosaic.transpose(Image.FLIP_TOP_BOTTOM)
top_mosaic = top_mosaic.resize((width + 32, height_5th * top + 8))
new_img.paste(top_mosaic, (width_5th * left, 0))
if bottom > 0:
bottom_side = img.crop((0, height - height_5th * bottom, width, height))
bottom_mosaic = create_mosaic(bottom_side, bottom, vertical=True)
bottom_mosaic = bottom_mosaic.transpose(Image.FLIP_TOP_BOTTOM)
bottom_mosaic = bottom_mosaic.resize((width + 32, height_5th * bottom + 8))
new_img.paste(bottom_mosaic, (width_5th * left, height + height_5th * top))
# Paste original image
new_img.paste(img, (width_5th * left, height_5th * top))
new_img = pil2tensor(new_img)
# create black and white mask image where white is the original image
mask = Image.new('RGB', (self.new_width, self.new_height), (0, 0, 0))
white = Image.new('RGB', (width, height), (255, 255, 255))
# for each side that has been expanded, shrink that side of the white box by 8 pixels
if right > 0:
white = white.crop((0, 0, width - 64, height))
if left > 0:
white = white.crop((64, 0, width, height))
if top > 0:
white = white.crop((0, 64, width, height))
if bottom > 0:
white = white.crop((0, 0, width, height - 64))
paste_x = width_5th * left + 64
if left > 0:
paste_x += 64
paste_y = height_5th * top
if top > 0:
paste_y += 64
mask.paste(white, (paste_x, paste_y))
mask = np.array(mask.getchannel('R')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
mask = mask.unsqueeze(0)
return (new_img, mask)
NODE_CLASS_MAPPINGS = {
'Wildcard Processor': WildcardProcessor,
'Empty Latent Ratio Select SDXL': EmptyLatentRatioSelector,
@@ -4724,6 +4878,7 @@ NODE_CLASS_MAPPINGS = {
'TextCombinations3': TextCombinations3,
'Text2InputOr3rdOption': Text2InputOr3rdOption,
'Checkpoint Loader Simple Mikey': CheckpointLoaderSimpleMikey,
'CheckpointHash': CheckpointHash,
'TextPreserve': TextPreserve,
'TextConcat': TextConcat,
'OobaPrompt': OobaPrompt,
@@ -4731,7 +4886,8 @@ NODE_CLASS_MAPPINGS = {
'LMStudioPrompt': LMStudioPrompt,
'EvalFloats': EvalFloats,
'ImageOverlay': ImageOverlay,
'CinematicLook': CinematicLook
'CinematicLook': CinematicLook,
'MosaicExpandImage': MosaicExpandImage
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -4785,6 +4941,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
'TextCombinations3': 'Text Combinations 3 (Mikey)',
'Text2InputOr3rdOption': 'Text 2 Inputs Or 3rd Option Instead (Mikey)',
'Checkpoint Loader Simple Mikey': 'Checkpoint Loader Simple (Mikey)',
'CheckpointHash': 'Checkpoint Hash (Mikey)',
'TextPreserve': 'Text Preserve (Mikey)',
'TextConcat': 'Text Concat (Mikey)',
'OobaPrompt': 'OobaPrompt (Mikey)',
@@ -4792,5 +4949,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
'LMStudioPrompt': 'LM Studio Prompt (Mikey)',
'EvalFloats': 'Eval Floats (Mikey)',
'ImageOverlay': 'Image Overlay (Mikey)',
'CinematicLook': 'Cinematic Look (Mikey)'
'CinematicLook': 'Cinematic Look (Mikey)',
'MosaicExpandImage': 'Mosaic Expand Image (Mikey)'
}