optimized + Scheduling + % complete in console

added the ability to schedule some values in the FX nodes, as well as optimized some code to make them a little faster. Iv also added a % complete print in the console so you can see how far along the processing is =)
This commit is contained in:
Fill
2024-05-11 08:54:22 +09:00
committed by GitHub
parent a5e677107a
commit 0bcd5c3c7c
9 changed files with 139 additions and 77 deletions
+3 -36
View File
@@ -45,7 +45,7 @@ current implementation requires you to break batches into a list and back into a
Audio Tools (WIP): - Load audio, scans for BPM, crops audio to desired bars and duration
-
other nodes that are a work in progress take the sliced audio/bpm/fps and hold an image for the duration.
other nodes that are a work in progress takes the sliced audio/bpm/fps and holds an image for the duration.
There is also a VHS converter node that allows you to load audio into the VHS video combine for audio insertion on the fly!
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/e1b642e2-29d7-442a-a657-a32ca0fac9c4)
@@ -56,44 +56,11 @@ Directory Crawler: - Simple node that loads all images in a directory and any su
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/7f6862c7-60dc-4561-8b58-72b489903107)
Raw Code Node: - Simple node that loads Python and allows you to dev inside comfy without having to reload the instance every time
Raw Code Node: - Simple node that loads python and allows you to dev inside comfy without having to reload the instance every time
-
Great for developing ideas and writing custom stuff quickly
Great for deving out ideas and write custom stuff quickly
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/db439865-e3c5-4e52-b37c-c3ba601c0840)
Glitch: Video and image effect
-
Slices up your image or video to make a glitching feel
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/b9bc2f82-19e3-4877-bb98-0801c4ceb96f)
Ripple: Video and image effect
-
Ripples your video or image
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/660983b1-1090-400b-9c92-4e5d3a1eb2b6)
Pixel Sort: Video and image
-
CAUTION: This node is a very heavy operation. It takes 5-10 seconds per frame. (WIP)
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/7ab1785a-fab7-4206-bf9b-fef48896b518)
Hexagon: Video an image
-
This one is really fun. It masks your image and video in slices, but thats not all! Each slice acts as its own video or image when you start rotating and messing with the parameters.
![image](https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/06ddaae1-2c1e-41c0-af7f-d713f1bc6d91)
Ascii: Video and image
-
This one allows for a TON of different styles. This node also works with Alt Codes like this: alt+3 = ♥ or alt+219 = █
If you play with the spacing of 219 you can actually get a pixel art effect. ALSO, the last character in the list will always be applied to the highest luminance areas of the image. This is useful because you can do silly things like leave the last character as a blank space, allowing for negative space to be applied to light areas.
<img width="1136" alt="Screenshot 2024-04-29 192646" src="https://github.com/filliptm/ComfyUI_Fill-Nodes/assets/55672949/926287e9-e22a-4e64-9e4f-7fd6e096b558">
+2
View File
@@ -14,6 +14,8 @@ from .fl_ripple import FL_Ripple
from .fl_pixelsort import FL_PixelSort
from .fl_hexagonalpattern import FL_HexagonalPattern
NODE_CLASS_MAPPINGS = {
"FL_ImageRandomizer": FL_ImageRandomizer,
"FL_ImageCaptionSaver": FL_ImageCaptionSaver,
+24 -2
View File
@@ -5,7 +5,8 @@ import sys
class FL_Ascii:
def __init__(self):
pass
self.spacing_index = 0
self.font_size_index = 0
@classmethod
def INPUT_TYPES(s):
@@ -42,7 +43,28 @@ class FL_Ascii:
for b in range(batch_size):
img_b = image[b] * 255.0
img_b = Image.fromarray(img_b.numpy().astype('uint8'), 'RGB')
result_b = ascii_art_effect(img_b, spacing, font_size, characters)
# Check if spacing is a list and get the current value
if isinstance(spacing, list):
if self.spacing_index >= len(spacing):
print("Warning: Spacing list index out of range. Using the last value.")
self.spacing_index = len(spacing) - 1
current_spacing = spacing[self.spacing_index]
self.spacing_index = (self.spacing_index + 1) % len(spacing)
else:
current_spacing = spacing
# Check if font_size is a list and get the current value
if isinstance(font_size, list):
if self.font_size_index >= len(font_size):
print("Warning: Font size list index out of range. Using the last value.")
self.font_size_index = len(font_size) - 1
current_font_size = font_size[self.font_size_index]
self.font_size_index = (self.font_size_index + 1) % len(font_size)
else:
current_font_size = font_size
result_b = ascii_art_effect(img_b, current_spacing, current_font_size, characters)
result_b = torch.tensor(np.array(result_b)) / 255.0
result[b] = result_b
+21 -2
View File
@@ -5,6 +5,9 @@ from glitch_this import ImageGlitcher
import sys
class FL_Glitch:
def __init__(self):
self.seed_index = 0
@classmethod
def INPUT_TYPES(cls):
return {
@@ -36,14 +39,30 @@ class FL_Glitch:
g = ImageGlitcher()
out = []
total_images = len(images)
# Convert seed to a list if it's a single value
if not isinstance(seed, list):
seed = [seed] * total_images
for i, image in enumerate(images, start=1):
p = self.t2p(image)
g1 = g.glitch_image(p, glitch_amount, color_offset=color_offset, seed=seed)
# Get the current seed value
current_seed = seed[i - 1]
# Ensure current_seed is a single integer value
if isinstance(current_seed, (int, float)):
current_seed = int(current_seed)
elif isinstance(current_seed, (list, tuple)):
current_seed = current_seed[0]
else:
current_seed = current_seed.iloc[0]
g1 = g.glitch_image(p, glitch_amount, color_offset=color_offset, seed=current_seed)
r1 = g1.rotate(90, expand=True)
g2 = g.glitch_image(r1, glitch_amount, color_offset=color_offset, seed=seed)
g2 = g.glitch_image(r1, glitch_amount, color_offset=color_offset, seed=current_seed)
f = g2.rotate(-90, expand=True)
+37 -17
View File
@@ -5,6 +5,14 @@ import math
import sys
class FL_HexagonalPattern:
def __init__(self):
self.hexagon_size_index = 0
self.shadow_offset_index = 0
self.shadow_color_index = 0
self.background_color_index = 0
self.rotation_index = 0
self.spacing_index = 0
@classmethod
def INPUT_TYPES(cls):
return {
@@ -37,14 +45,20 @@ class FL_HexagonalPattern:
draw.regular_polygon((size // 2, size // 2, size // 2), 6, fill=255)
return mask
def calculate_adjusted_hexagon_size(self, width, height, hexagon_size, spacing):
horizontal_count = math.ceil(width / (hexagon_size * spacing))
vertical_count = math.ceil(height / (hexagon_size * spacing * math.sqrt(3) / 2))
def process_input_value(self, value, index):
if isinstance(value, list):
if index >= len(value):
print(f"Warning: Value list index out of range. Using the last value.")
index = len(value) - 1
current_value = value[index]
index = (index + 1) % len(value)
else:
current_value = value
adjusted_width = width / horizontal_count
adjusted_height = height / (vertical_count * math.sqrt(3) / 2)
if hasattr(current_value, 'values'):
current_value = float(current_value.values[0])
return min(adjusted_width, adjusted_height) / spacing
return current_value, index
def hexagonal_pattern(self, images, hexagon_size=100, shadow_offset=5, shadow_color="black", shadow_opacity=0.5,
background_color="white", rotation=0.0, spacing=1.0):
@@ -54,24 +68,30 @@ class FL_HexagonalPattern:
p = self.t2p(img_tensor)
width, height = p.size
adjusted_hexagon_size = self.calculate_adjusted_hexagon_size(width, height, hexagon_size, spacing)
hexagon_mask = self.create_hexagon_mask(int(adjusted_hexagon_size))
current_hexagon_size, self.hexagon_size_index = self.process_input_value(hexagon_size, self.hexagon_size_index)
current_shadow_offset, self.shadow_offset_index = self.process_input_value(shadow_offset, self.shadow_offset_index)
current_shadow_color, self.shadow_color_index = self.process_input_value(shadow_color, self.shadow_color_index)
current_background_color, self.background_color_index = self.process_input_value(background_color, self.background_color_index)
current_rotation, self.rotation_index = self.process_input_value(rotation, self.rotation_index)
current_spacing, self.spacing_index = self.process_input_value(spacing, self.spacing_index)
output_image = Image.new("RGBA", (width, height), background_color)
hexagon_mask = self.create_hexagon_mask(current_hexagon_size)
for y in range(0, height, int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2)):
for x in range(0, width, int(adjusted_hexagon_size * spacing)):
if y % (2 * int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2)) == int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2):
x += int(adjusted_hexagon_size * spacing) // 2
output_image = Image.new("RGBA", (width, height), current_background_color)
cropped_hexagon = p.crop((x, y, x + int(adjusted_hexagon_size), y + int(adjusted_hexagon_size))).rotate(rotation, expand=True)
for y in range(0, height, int(current_hexagon_size * current_spacing * math.sqrt(3) / 2)):
for x in range(0, width, int(current_hexagon_size * current_spacing)):
if y % (2 * int(current_hexagon_size * current_spacing * math.sqrt(3) / 2)) == int(current_hexagon_size * current_spacing * math.sqrt(3) / 2):
x += int(current_hexagon_size * current_spacing) // 2
cropped_hexagon = p.crop((x, y, x + current_hexagon_size, y + current_hexagon_size)).rotate(current_rotation, expand=True)
shadow = Image.new("RGBA", cropped_hexagon.size, (0, 0, 0, 0))
shadow_mask = hexagon_mask.copy().resize(cropped_hexagon.size)
shadow.paste(shadow_color, (shadow_offset, shadow_offset), shadow_mask)
shadow.paste(current_shadow_color, (current_shadow_offset, current_shadow_offset), shadow_mask)
shadow.putalpha(int(255 * shadow_opacity))
output_image.paste(shadow, (x + shadow_offset, y + shadow_offset), shadow_mask)
output_image.paste(shadow, (x + current_shadow_offset, y + current_shadow_offset), shadow_mask)
output_image.paste(cropped_hexagon, (x, y), shadow_mask)
o = np.array(output_image.convert("RGB")).astype(np.float32) / 255.0
@@ -87,4 +107,4 @@ class FL_HexagonalPattern:
print()
out = torch.cat(out, 0)
return (out,)
return (out,)
+24 -5
View File
@@ -2,8 +2,12 @@ import torch
from PIL import Image
from kornia.morphology import gradient
import comfy.model_management
import sys
class FL_ImagePixelator:
def __init__(self):
self.modulation_index = 0
@classmethod
def INPUT_TYPES(cls):
return {
@@ -11,6 +15,7 @@ class FL_ImagePixelator:
"image": ("IMAGE", {}),
"scale_factor": ("FLOAT", {"default": 0.0500, "min": 0.0100, "max": 0.2000, "step": 0.0100}),
"kernel_size": ("INT", {"default": 3, "max": 10, "step": 1}),
"modulation": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
@@ -18,35 +23,49 @@ class FL_ImagePixelator:
FUNCTION = "pixelate_image"
CATEGORY = "🏵️Fill Nodes"
def pixelate_image(self, image, scale_factor, kernel_size):
def pixelate_image(self, image, scale_factor, kernel_size, modulation):
if isinstance(image, torch.Tensor):
if image.dim() == 4: # Batch dimension is present
output_images = []
total_frames = image.shape[0]
for i, single_image in enumerate(image, start=1):
single_image = single_image.unsqueeze(0) # Add batch dimension
single_image = self.apply_pixelation_tensor(single_image, scale_factor)
modulated_scale_factor = self.apply_modulation(scale_factor, modulation, total_frames)
single_image = self.apply_pixelation_tensor(single_image, modulated_scale_factor)
single_image = self.process(single_image, kernel_size)
output_images.append(single_image)
print(f"Processing frame {i}/{total_frames}")
progress = i / total_frames * 100
sys.stdout.write(f"\rProcessing frames: {progress:.2f}%")
sys.stdout.flush()
image = torch.cat(output_images, dim=0) # Concatenate processed images along batch dimension
elif image.dim() == 3: # No batch dimension, single image
image = image.unsqueeze(0) # Add batch dimension
image = self.apply_pixelation_tensor(image, scale_factor)
modulated_scale_factor = self.apply_modulation(scale_factor, modulation, 1)
image = self.apply_pixelation_tensor(image, modulated_scale_factor)
image = self.process(image, kernel_size)
image = image.squeeze(0) # Remove batch dimension
print("Processing single image")
else:
return (None,)
elif isinstance(image, Image.Image):
image = self.apply_pixelation_pil(image, scale_factor)
modulated_scale_factor = self.apply_modulation(scale_factor, modulation, 1)
image = self.apply_pixelation_pil(image, modulated_scale_factor)
image = self.process(image, kernel_size)
print("Processing single PIL image")
else:
return (None,)
# Print a new line after the progress update
print()
return (image,)
def apply_modulation(self, scale_factor, modulation, total_frames):
modulation_factor = 1 + modulation * torch.sin(2 * torch.pi * torch.tensor(self.modulation_index / total_frames))
modulated_scale_factor = scale_factor * modulation_factor.item()
self.modulation_index += 1
return modulated_scale_factor
def apply_pixelation_pil(self, input_image, scale_factor):
width, height = input_image.size
new_size = (int(width * scale_factor), int(height * scale_factor))
+7 -6
View File
@@ -21,7 +21,7 @@ class FL_PixelSort:
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "pixel_sort_hue"
FUNCTION = "pixel_sort_saturation"
CATEGORY = "🏵️Fill Nodes"
def t2p(self, t):
@@ -30,17 +30,17 @@ class FL_PixelSort:
p = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
return p
def hue(self, pixel):
def saturation(self, pixel):
r, g, b = pixel
h, _, _ = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
return h
_, s, _ = rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
return s
def pixel_sort_hue(self, images, direction="Horizontal", threshold=0.5, smoothing=0.1, rotation=0):
def pixel_sort_saturation(self, images, direction="Horizontal", threshold=0.5, smoothing=0.1, rotation=0):
out = []
total_images = len(images)
for i, img in enumerate(images, start=1):
p = self.t2p(img)
sorted_image = self.sort_pixels(p, self.hue, threshold, smoothing, rotation)
sorted_image = self.sort_pixels(p, self.saturation, threshold, smoothing, rotation)
o = np.array(sorted_image.convert("RGB")).astype(np.float32) / 255.0
o = torch.from_numpy(o).unsqueeze(0)
out.append(o)
@@ -63,6 +63,7 @@ class FL_PixelSort:
edges = np.maximum(edges, 0)
edges = np.minimum(edges, 1)
edges = np.convolve(edges.flatten(), np.ones(int(smoothing * pixels.shape[1])), 'same').reshape(edges.shape)
intervals = [np.flatnonzero(row) for row in edges]
for row, key in enumerate(values):
+21 -8
View File
@@ -5,6 +5,9 @@ import math
import sys
class FL_Ripple:
def __init__(self):
self.modulation_index = 0
@classmethod
def INPUT_TYPES(cls):
return {
@@ -15,6 +18,9 @@ class FL_Ripple:
"amplitude": ("FLOAT", {"default": 10.0, "min": 0.1, "max": 50.0, "step": 0.1}),
"frequency": ("FLOAT", {"default": 20.0, "min": 1.0, "max": 100.0, "step": 0.1}),
"phase": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 360.0, "step": 1.0}),
"center_x": ("FLOAT", {"default": 50.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"center_y": ("FLOAT", {"default": 50.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"modulation": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
}
@@ -28,23 +34,28 @@ class FL_Ripple:
p = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
return p
def ripple(self, images, amplitude=10.0, frequency=20.0, phase=0.0):
def ripple(self, images, amplitude=10.0, frequency=20.0, phase=0.0, center_x=50.0, center_y=50.0, modulation=0.0):
out = []
total_images = len(images)
for i, img in enumerate(images, start=1):
p = self.t2p(img)
width, height = p.size
center_x = width // 2
center_y = height // 2
center_x_pixel = int(center_x / 100 * width)
center_y_pixel = int(center_y / 100 * height)
x, y = np.meshgrid(np.arange(width), np.arange(height))
dx = x - center_x
dy = y - center_y
dx = x - center_x_pixel
dy = y - center_y_pixel
distance = np.sqrt(dx ** 2 + dy ** 2)
angle = distance / frequency * 2 * np.pi + np.radians(phase)
offset_x = (amplitude * np.sin(angle)).astype(int)
offset_y = (amplitude * np.cos(angle)).astype(int)
# Apply modulation to amplitude and frequency
modulation_factor = 1 + modulation * math.sin(2 * math.pi * self.modulation_index / total_images)
modulated_amplitude = amplitude * modulation_factor
modulated_frequency = frequency * modulation_factor
angle = distance / modulated_frequency * 2 * np.pi + np.radians(phase)
offset_x = (modulated_amplitude * np.sin(angle)).astype(int)
offset_y = (modulated_amplitude * np.cos(angle)).astype(int)
sample_x = np.clip(x + offset_x, 0, width - 1)
sample_y = np.clip(y + offset_y, 0, height - 1)
@@ -56,6 +67,8 @@ class FL_Ripple:
o = torch.from_numpy(o).unsqueeze(0)
out.append(o)
self.modulation_index += 1
# Print progress update
progress = i / total_images * 100
sys.stdout.write(f"\rProcessing images: {progress:.2f}%")
-1
View File
@@ -1,4 +1,3 @@
librosa
sounddevice
wave
glitch_this