609 lines
34 KiB
Python
609 lines
34 KiB
Python
"""
|
|
@author: mgfxer
|
|
@title: FrameFX
|
|
@nickname: FrameFX 💫
|
|
@description: This extension provides various frame and mask sequence manipulation tools for animation workflows.
|
|
"""
|
|
import os
|
|
from PIL import Image, ImageDraw, ImageFilter, ImageOps, ImageChops, ImageEnhance
|
|
import numpy as np
|
|
import torch
|
|
import random
|
|
|
|
class DynamicAnimatedWeightsHelper:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
base_animation_types = ["LeftToRight", "RightToLeft", "TopDown", "BottomToTop", "GrowingCircle", "ShrinkingCircle",
|
|
"DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft",
|
|
"DiagonalBottomLeft-TopRight", "Fade", "SqSpinCw", "SqSpinCCW", "VenetianBlindsHorizontal",
|
|
"VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
|
animation_types = base_animation_types + ["Random", "RandomNoVenetian"]
|
|
easing_options = ["ease_in", "ease_out", "ease_in_out", "false"]
|
|
generation_options = ["Only Transitions", "Generate QR", "Generate Edge-FX", "Generate All"]
|
|
return {
|
|
"required": {
|
|
"animation_type_1": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_2": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_3": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_4": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_5": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_6": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_7": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_8": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_9": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_10": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_11": (animation_types, {"default": cls.random_animation()}),
|
|
"animation_type_12": (animation_types, {"default": cls.random_animation()}),
|
|
"transition_easing": (easing_options, {"default": "false"}),
|
|
"blur_easing": (easing_options, {"default": "false"}),
|
|
"frame_width": ("INT", {"default": 512, "min": 1, "step": 1, "display": "number"}),
|
|
"frame_height": ("INT", {"default": 512, "min": 1, "step": 1, "display": "number"}),
|
|
"hold_frames": ("INT", {"default": 8, "min": 1, "step": 1, "display": "number"}),
|
|
"transition_frames": ("INT", {"default": 20, "min": 1, "step": 1, "display": "number"}),
|
|
"padding_frames": ("INT", {"default": 6, "min": 0, "step": 1, "display": "number"}),
|
|
"input_frames": ("INT", {"default": 5, "min": 1, "step": 1, "display": "number"}),
|
|
"gaussian_blur_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}),
|
|
"edge_fx_thickness": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1, "display": "number"}),
|
|
"push_fx": ("INT", {"default": 0, "min": 0, "max": 30, "step": 1, "display": "number"}),
|
|
"retract_fx": ("INT", {"default": 0, "min": 0, "max": 30, "step": 1, "display": "number"}),
|
|
"fx_cull_white_frames": ("FLOAT", {"default": 10.0, "min": 0.0, "max": 100.0, "step": 0.1, "display": "slider"}),
|
|
"qr_greyness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "display": "slider"}),
|
|
"random_seed": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}),
|
|
"edgeFade_contrast": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 6.0, "step": 0.1, "display": "slider"}), # Increased contrast effectiveness
|
|
"edgeFade_blur": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.1, "display": "slider"}), # Final blur adjustment for Edge FX Fade
|
|
"generation_mode": (generation_options, {"default": "Only Transitions"}),
|
|
"edge_fx_fade_balance": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 0.9, "step": 0.05, "display": "slider"}),
|
|
"venetian_bars": ("INT", {"default": 4, "min": 1, "step": 1, "display": "number"})
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def random_animation(cls, exclude_venetian=False):
|
|
venetian_types = ["VenetianBlindsHorizontal", "VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
|
base_animation_types = ["LeftToRight", "RightToLeft", "TopDown", "BottomToTop", "GrowingCircle", "ShrinkingCircle",
|
|
"DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft",
|
|
"DiagonalBottomLeft-TopRight", "Fade", "SqSpinCw", "SqSpinCCW", "VenetianBlindsHorizontal",
|
|
"VenetianBlindsVertical", "DiagonalVenetianBlinds1", "DiagonalVenetianBlinds2"]
|
|
available_types = base_animation_types
|
|
if exclude_venetian:
|
|
available_types = [t for t in available_types if t not in venetian_types]
|
|
return random.choice(available_types)
|
|
|
|
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "INT")
|
|
RETURN_NAMES = ("transitions", "qr_mode", "edge_fx", "edge_fx_fade", "total_frames")
|
|
FUNCTION = "run"
|
|
CATEGORY = "mgfxer"
|
|
|
|
def run(self, **kwargs):
|
|
animation_types = [kwargs[f'animation_type_{i}'] for i in range(1, 13)]
|
|
frame_width = kwargs['frame_width']
|
|
frame_height = kwargs['frame_height']
|
|
hold_frames = kwargs['hold_frames']
|
|
transition_frames = kwargs['transition_frames']
|
|
padding_frames = kwargs['padding_frames']
|
|
input_frames = kwargs['input_frames']
|
|
gaussian_blur_amount = kwargs['gaussian_blur_amount'] * 20 # Amplify the blur amount here
|
|
edgeFade_blur = kwargs['edgeFade_blur'] * 10 # Amplify the final blur amount by 10
|
|
edgeFade_contrast = kwargs['edgeFade_contrast']
|
|
transition_easing = kwargs['transition_easing']
|
|
blur_easing = kwargs['blur_easing']
|
|
qr_greyness = kwargs['qr_greyness']
|
|
random_seed = int(kwargs['random_seed'] * 10)
|
|
edge_fx_thickness = kwargs['edge_fx_thickness']
|
|
push_fx = kwargs['push_fx']
|
|
retract_fx = kwargs['retract_fx']
|
|
fx_cull_white_frames = kwargs['fx_cull_white_frames']
|
|
generation_mode = kwargs['generation_mode']
|
|
edge_fx_fade_balance = kwargs['edge_fx_fade_balance']
|
|
venetian_bars = kwargs['venetian_bars']
|
|
|
|
random.seed(random_seed)
|
|
|
|
images = []
|
|
qr_images = []
|
|
edge_fx_frames = []
|
|
edge_fx_fade_frames = []
|
|
total_frames = 0
|
|
for i in range(input_frames):
|
|
animation_type = animation_types[i % 12]
|
|
if animation_type == "Random":
|
|
animation_type = self.random_animation()
|
|
elif animation_type == "RandomNoVenetian":
|
|
animation_type = self.random_animation(exclude_venetian=True)
|
|
|
|
frames, frame_count = self.generate_animation(animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, venetian_bars)
|
|
|
|
if generation_mode in ["Generate QR", "Generate All"]:
|
|
qr_frames = self.apply_qr_mode(frames, i, input_frames, hold_frames + transition_frames)
|
|
qr_images.extend(qr_frames)
|
|
|
|
images.extend(frames)
|
|
total_frames += frame_count
|
|
|
|
final_padding_color_qr = 'black' if input_frames % 2 == 0 else 'white'
|
|
for _ in range(padding_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=False)
|
|
images.append(frame)
|
|
if generation_mode in ["Generate QR", "Generate All"]:
|
|
qr_frame = self.create_frame(frame_width, frame_height, is_black=(final_padding_color_qr == 'black'))
|
|
qr_images.append(qr_frame)
|
|
total_frames += 1
|
|
|
|
if generation_mode in ["Generate QR", "Generate All"]:
|
|
qr_images = self.apply_qr_greyness(qr_images, qr_greyness, frame_width, frame_height)
|
|
|
|
if generation_mode in ["Generate Edge-FX", "Generate All"]:
|
|
edge_fx_frames = self.generate_edge_fx(images, edge_fx_thickness, frame_width, frame_height, fx_cull_white_frames)
|
|
edge_fx_frames = self.apply_push_retract_fx(edge_fx_frames, push_fx, retract_fx, frame_width, frame_height)
|
|
edge_fx_frames = self.check_and_correct_white_frames(edge_fx_frames, frame_width, frame_height, fx_cull_white_frames)
|
|
|
|
if generation_mode == "Generate All":
|
|
unblurred_edge_fx = edge_fx_frames.copy()
|
|
final_edge_fx = self.composite_edge_fx(unblurred_edge_fx, edge_fx_frames)
|
|
edge_fx_fade_frames = self.apply_fade_to_edge_fx(final_edge_fx, transition_frames, hold_frames, edge_fx_fade_balance)
|
|
edge_fx_fade_frames = self.apply_contrast_to_frames(edge_fx_fade_frames, edgeFade_contrast)
|
|
edge_fx_fade_frames = self.apply_blur_to_frames(edge_fx_fade_frames, edgeFade_blur)
|
|
|
|
image_batch = torch.cat([self.process_image_for_output(frame) for frame in images], dim=0)
|
|
qr_image_batch = torch.cat([self.process_image_for_output(frame) for frame in (qr_images if qr_images else images)], dim=0)
|
|
edge_fx_batch = torch.cat([self.process_image_for_output(frame) for frame in (edge_fx_frames if edge_fx_frames else images)], dim=0)
|
|
edge_fx_fade_batch = torch.cat([self.process_image_for_output(frame) for frame in (edge_fx_fade_frames if edge_fx_fade_frames else images)], dim=0)
|
|
|
|
return (image_batch, qr_image_batch, edge_fx_batch, edge_fx_fade_batch, total_frames)
|
|
|
|
def generate_animation(self, animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, venetian_bars):
|
|
if animation_type == "LeftToRight":
|
|
return self.generate_left_to_right_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
elif animation_type == "RightToLeft":
|
|
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 180)
|
|
elif animation_type == "TopDown":
|
|
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 90, resize=True)
|
|
elif animation_type == "BottomToTop":
|
|
return self.generate_rotated_animation(self.generate_left_to_right_animation, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, 270, resize=True)
|
|
elif animation_type == "GrowingCircle":
|
|
return self.generate_growing_circle_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
elif animation_type == "ShrinkingCircle":
|
|
return self.generate_shrinking_circle_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
elif animation_type in ["DiagonalTopLeft-BottomRight", "DiagonalBottomRight-TopLeft", "DiagonalTopRight-BottomLeft", "DiagonalBottomLeft-TopRight"]:
|
|
return self.generate_diagonal_animation(animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
elif animation_type == "Fade":
|
|
return self.generate_fade_animation(frame_width, frame_height, hold_frames, transition_frames, transition_easing, blur_easing)
|
|
elif animation_type == "SqSpinCw":
|
|
return self.generate_sq_spin_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=True)
|
|
elif animation_type == "SqSpinCCW":
|
|
return self.generate_sq_spin_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=False)
|
|
elif animation_type == "VenetianBlindsHorizontal":
|
|
return self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=False, venetian_bars=venetian_bars)
|
|
elif animation_type == "VenetianBlindsVertical":
|
|
return self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=venetian_bars)
|
|
elif animation_type == "DiagonalVenetianBlinds1":
|
|
return self.generate_diagonal_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle=45, venetian_bars=venetian_bars)
|
|
elif animation_type == "DiagonalVenetianBlinds2":
|
|
return self.generate_diagonal_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle=135, venetian_bars=venetian_bars)
|
|
|
|
def generate_edge_fx(self, frames, offset, frame_width, frame_height, fx_cull_white_frames):
|
|
edge_fx_frames = []
|
|
for i in range(len(frames)):
|
|
current_frame = frames[i]
|
|
if i + offset < len(frames):
|
|
next_frame = frames[i + offset]
|
|
else:
|
|
next_frame = Image.new('RGB', (frame_width, frame_height), color='white')
|
|
|
|
mask = ImageChops.difference(current_frame, next_frame)
|
|
mask = mask.convert('L').point(lambda x: 255 if x > 0 else 0, mode='1')
|
|
|
|
edge_fx_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
edge_fx_frame.paste(Image.new('RGB', (frame_width, frame_height), color='white'), mask=mask)
|
|
|
|
if ImageChops.invert(edge_fx_frame).getbbox() is None:
|
|
edge_fx_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
|
|
edge_fx_frames.append(edge_fx_frame)
|
|
|
|
edge_fx_frames = self.check_and_correct_white_frames(edge_fx_frames, frame_width, frame_height, fx_cull_white_frames)
|
|
|
|
return edge_fx_frames
|
|
|
|
def check_and_correct_white_frames(self, frames, frame_width, frame_height, fx_cull_white_frames):
|
|
corrected_frames = []
|
|
for frame in frames:
|
|
white_pixels = np.sum(np.array(frame) == 255)
|
|
total_pixels = frame_width * frame_height * 3
|
|
white_percentage = (white_pixels / total_pixels) * 100
|
|
|
|
if white_percentage > fx_cull_white_frames:
|
|
corrected_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
else:
|
|
corrected_frame = frame
|
|
corrected_frames.append(corrected_frame)
|
|
return corrected_frames
|
|
|
|
def apply_push_retract_fx(self, frames, push_fx, retract_fx, frame_width, frame_height):
|
|
black_frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
|
|
if push_fx > 0:
|
|
frames = [black_frame] * push_fx + frames[:-push_fx]
|
|
elif retract_fx > 0:
|
|
frames = frames[retract_fx:] + [black_frame] * retract_fx
|
|
|
|
return frames
|
|
|
|
def apply_qr_greyness(self, frames, qr_greyness, frame_width, frame_height):
|
|
grey_image_white = Image.new('RGB', (frame_width, frame_height), color=(255, 255, 255))
|
|
grey_image_black = Image.new('RGB', (frame_width, frame_height), color=(235, 235, 235))
|
|
grey_blend_white = lambda img: Image.blend(img, grey_image_white, qr_greyness)
|
|
grey_blend_black = lambda img: Image.blend(img, grey_image_black, qr_greyness)
|
|
frames = [grey_blend_black(grey_blend_white(frame)) for frame in frames]
|
|
return frames
|
|
|
|
def generate_diagonal_venetian_blinds_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, angle, venetian_bars):
|
|
frames, _ = self.generate_venetian_blinds_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=venetian_bars)
|
|
|
|
enlarged_frames = []
|
|
for frame in frames:
|
|
large_canvas = Image.new('RGB', (frame_width * 2, frame_height * 2), 'black')
|
|
enlarged_frame = frame.resize((int(frame_width * 1.5), int(frame_height * 1.5)), Image.LANCZOS)
|
|
large_canvas.paste(enlarged_frame, ((large_canvas.width - enlarged_frame.width) // 2, (large_canvas.height - enlarged_frame.height) // 2))
|
|
|
|
rotated_frame = large_canvas.rotate(angle, expand=True)
|
|
|
|
cropped_frame = rotated_frame.crop(((rotated_frame.width - frame_width) // 2, (rotated_frame.height - frame_height) // 2,
|
|
(rotated_frame.width + frame_width) // 2, (rotated_frame.height + frame_height) // 2))
|
|
|
|
enlarged_frames.append(cropped_frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return enlarged_frames, total_frames
|
|
|
|
def generate_venetian_blinds_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, vertical=True, venetian_bars=4):
|
|
images = []
|
|
bar_size = frame_height // venetian_bars if vertical else frame_width // venetian_bars
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
|
|
|
frame = self.create_venetian_blinds_transition_frame(frame_width, frame_height, venetian_bars, bar_size, ease_factor, blur_factor['gaussian'], vertical)
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def create_venetian_blinds_transition_frame(self, frame_width, frame_height, num_bars, bar_size, ease_factor, gaussian_blur_amount, vertical=True):
|
|
frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
draw = ImageDraw.Draw(frame)
|
|
|
|
for j in range(num_bars):
|
|
if vertical:
|
|
left = j * bar_size
|
|
right = left + int(bar_size * ease_factor)
|
|
draw.rectangle([left, 0, right, frame_height], fill='white')
|
|
else:
|
|
top = j * bar_size
|
|
bottom = top + int(bar_size * ease_factor)
|
|
draw.rectangle([0, top, frame_width, bottom], fill='white')
|
|
|
|
if gaussian_blur_amount > 0:
|
|
frame = frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount))
|
|
|
|
return frame
|
|
|
|
def generate_sq_spin_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, clockwise=True):
|
|
images = []
|
|
initial_square_size = 2
|
|
max_square_size = int(frame_width * 1.65)
|
|
canvas_size = int(frame_width * 2.5)
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
|
|
|
square_size = initial_square_size + int((max_square_size - initial_square_size) * ease_factor)
|
|
rotation_angle = (235 * ease_factor) if clockwise else (-235 * ease_factor)
|
|
|
|
frame = self.create_sq_spin_transition_frame(canvas_size, frame_width, frame_height, square_size, rotation_angle, blur_factor['gaussian'])
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def create_sq_spin_transition_frame(self, canvas_size, frame_width, frame_height, square_size, rotation_angle, gaussian_blur_amount):
|
|
frame = Image.new('RGB', (canvas_size, canvas_size), color='black')
|
|
draw = ImageDraw.Draw(frame)
|
|
|
|
top_left = ((canvas_size - square_size) // 2, (canvas_size - square_size) // 2)
|
|
bottom_right = (top_left[0] + square_size, top_left[1] + square_size)
|
|
draw.rectangle([top_left, bottom_right], fill='white')
|
|
|
|
frame = frame.rotate(rotation_angle, expand=True)
|
|
|
|
left = (frame.width - frame_width) // 2
|
|
top = (frame.height - frame_height) // 2
|
|
frame = frame.crop((left, top, left + frame_width, top + frame_height))
|
|
|
|
if gaussian_blur_amount > 0:
|
|
frame = frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount))
|
|
|
|
return frame
|
|
|
|
def generate_rotated_animation(self, animation_func, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing, rotation_angle, resize=False):
|
|
images, total_frames = animation_func(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
rotated_images = [img.rotate(rotation_angle, expand=True) for img in images]
|
|
if resize:
|
|
rotated_images = [img.resize((frame_width, frame_height), Image.LANCZOS) for img in rotated_images]
|
|
return rotated_images, total_frames
|
|
|
|
def generate_left_to_right_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
|
images = []
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
|
frame = self.create_left_to_right_transition_frame(frame_width, frame_height, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def generate_growing_circle_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
|
images = []
|
|
circle_size = int(frame_width * 1.5)
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
|
frame = self.create_growing_circle_transition_frame(frame_width, frame_height, circle_size, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def generate_shrinking_circle_animation(self, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
|
images = []
|
|
circle_size = int(frame_width * 1.5)
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, gaussian_blur_amount)
|
|
frame = self.create_shrinking_circle_transition_frame(frame_width, frame_height, circle_size, blur_factor['gaussian'], transition_frames, frame_index=i, ease_factor=ease_factor)
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def generate_diagonal_animation(self, animation_type, frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing):
|
|
frames, _ = self.generate_left_to_right_animation(frame_width, frame_height, hold_frames, transition_frames, gaussian_blur_amount, transition_easing, blur_easing)
|
|
|
|
enlarged_frames = []
|
|
for frame in frames:
|
|
large_canvas = Image.new('RGB', (frame_width * 2, frame_height * 2), 'black')
|
|
enlarged_frame = frame.resize((int(frame_width * 1.5), int(frame_height * 1.5)), Image.LANCZOS)
|
|
large_canvas.paste(enlarged_frame, ((large_canvas.width - enlarged_frame.width) // 2, (large_canvas.height - enlarged_frame.height) // 2))
|
|
|
|
if animation_type == "DiagonalTopLeft-BottomRight":
|
|
rotated_frame = large_canvas.rotate(45, expand=True)
|
|
elif animation_type == "DiagonalBottomRight-TopLeft":
|
|
rotated_frame = large_canvas.rotate(225, expand=True)
|
|
elif animation_type == "DiagonalTopRight-BottomLeft":
|
|
rotated_frame = large_canvas.rotate(135, expand=True)
|
|
elif animation_type == "DiagonalBottomLeft-TopRight":
|
|
rotated_frame = large_canvas.rotate(315, expand=True)
|
|
|
|
cropped_frame = rotated_frame.crop(((rotated_frame.width - frame_width) // 2, (rotated_frame.height - frame_height) // 2,
|
|
(rotated_frame.width + frame_width) // 2, (rotated_frame.height + frame_height) // 2))
|
|
|
|
enlarged_frames.append(cropped_frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return enlarged_frames, total_frames
|
|
|
|
def generate_fade_animation(self, frame_width, frame_height, hold_frames, transition_frames, transition_easing, blur_easing):
|
|
images = []
|
|
|
|
for _ in range(hold_frames):
|
|
frame = self.create_frame(frame_width, frame_height, is_black=True)
|
|
images.append(frame)
|
|
|
|
for i in range(transition_frames):
|
|
ease_factor = self.calculate_ease_factor(i, transition_frames, transition_easing)
|
|
blur_factor = self.calculate_blur_factor(i, transition_frames, blur_easing, 0)
|
|
gray_value = int(255 * (i / transition_frames) * ease_factor)
|
|
frame = Image.new('RGB', (frame_width, frame_height), color=(gray_value, gray_value, gray_value))
|
|
images.append(frame)
|
|
|
|
total_frames = hold_frames + transition_frames
|
|
return images, total_frames
|
|
|
|
def create_frame(self, frame_width, frame_height, is_black=True):
|
|
color = 'black' if is_black else 'white'
|
|
frame = Image.new('RGB', (frame_width, frame_height), color=color)
|
|
return frame
|
|
|
|
def create_left_to_right_transition_frame(self, frame_width, frame_height, blur_factor, transition_frames, frame_index, ease_factor):
|
|
frame = Image.new('RGB', (frame_width, frame_height), color='black')
|
|
draw = ImageDraw.Draw(frame)
|
|
|
|
box_width = int(frame_width * ease_factor)
|
|
draw.rectangle([0, 0, box_width, frame_height], fill='white')
|
|
|
|
if blur_factor > 0:
|
|
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
|
|
|
return frame
|
|
|
|
def create_growing_circle_transition_frame(self, frame_width, frame_height, circle_size, blur_factor, transition_frames, frame_index, ease_factor):
|
|
frame = Image.new('RGB', (circle_size, circle_size), color='black')
|
|
draw = ImageDraw.Draw(frame)
|
|
|
|
max_radius = circle_size // 2
|
|
radius = int(max_radius * ease_factor)
|
|
|
|
draw.ellipse([(circle_size // 2 - radius, circle_size // 2 - radius),
|
|
(circle_size // 2 + radius, circle_size // 2 + radius)], fill='white')
|
|
|
|
frame = frame.crop((circle_size//2-frame_width//2, circle_size//2-frame_height//2, circle_size//2+frame_width//2, circle_size//2+frame_height//2))
|
|
|
|
if blur_factor > 0:
|
|
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
|
|
|
return frame
|
|
|
|
def create_shrinking_circle_transition_frame(self, frame_width, frame_height, circle_size, blur_factor, transition_frames, frame_index, ease_factor):
|
|
frame = Image.new('RGB', (circle_size, circle_size), color='white')
|
|
draw = ImageDraw.Draw(frame)
|
|
|
|
max_radius = circle_size // 2
|
|
radius = int(max_radius * (1 - ease_factor))
|
|
|
|
draw.ellipse([(circle_size // 2 - radius, circle_size // 2 - radius),
|
|
(circle_size // 2 + radius, circle_size // 2 + radius)], fill='black')
|
|
|
|
frame = frame.crop((circle_size//2-frame_width//2, circle_size//2-frame_height//2, circle_size//2+frame_width//2, circle_size//2+frame_height//2))
|
|
|
|
if blur_factor > 0:
|
|
frame = frame.filter(ImageFilter.GaussianBlur(blur_factor))
|
|
|
|
return frame
|
|
|
|
def calculate_ease_factor(self, frame_index, transition_frames, transition_easing):
|
|
t = frame_index / transition_frames
|
|
if transition_easing == "ease_in":
|
|
return self.ease_in_quad(t)
|
|
elif transition_easing == "ease_out":
|
|
return self.ease_out_quad(t)
|
|
elif transition_easing == "ease_in_out":
|
|
return self.ease_in_out_quad(t)
|
|
else: # "false" or any other value
|
|
return t # Linear easing
|
|
|
|
def calculate_blur_factor(self, frame_index, transition_frames, blur_easing, gaussian_blur_amount):
|
|
t = frame_index / transition_frames
|
|
if blur_easing == "ease_in":
|
|
blur_factor = self.ease_in_quad(t)
|
|
elif blur_easing == "ease_out":
|
|
blur_factor = 1 - self.ease_out_quad(1 - t)
|
|
elif blur_easing == "ease_in_out":
|
|
blur_factor = self.ease_in_out_quad_for_blur(t)
|
|
else: # "false" or any other value
|
|
blur_factor = 1 # Constant blur amount for linear
|
|
|
|
return {
|
|
'gaussian': gaussian_blur_amount * blur_factor
|
|
}
|
|
|
|
def ease_in_quad(self, t):
|
|
return t * t
|
|
|
|
def ease_out_quad(self, t):
|
|
return t * (2 - t)
|
|
|
|
def ease_in_out_quad(self, t):
|
|
return 2 * t * t if t < 0.5 else -1 + (4 - 2 * t) * t
|
|
|
|
def ease_in_out_quad_for_blur(self, t):
|
|
if t < 0.5:
|
|
return 2 * t * t
|
|
else:
|
|
return 1 - pow(-2 * t + 2, 2) / 2
|
|
|
|
def apply_qr_mode(self, frames, cycle_index, input_frames, cycle_length):
|
|
if cycle_index % 2 == 1: # Apply inversion for even cycles
|
|
frames = [ImageOps.invert(frame) for frame in frames]
|
|
return frames
|
|
|
|
def process_image_for_output(self, image) -> torch.Tensor:
|
|
i = ImageOps.exif_transpose(image)
|
|
if i.mode == 'I':
|
|
i = i.point(lambda i: i * (1 / 255))
|
|
image = i.convert("RGB")
|
|
image_np = np.array(image).astype(np.float32) / 255.0
|
|
return torch.from_numpy(image_np)[None,]
|
|
|
|
def apply_blur_to_frames(self, frames, gaussian_blur_amount):
|
|
return [frame.filter(ImageFilter.GaussianBlur(gaussian_blur_amount)) for frame in frames]
|
|
|
|
def apply_contrast_to_frames(self, frames, contrast_factor):
|
|
return [ImageEnhance.Contrast(frame).enhance(contrast_factor * 2) for frame in frames] # Apply 3x contrast factor
|
|
|
|
def composite_edge_fx(self, unblurred_frames, blurred_frames):
|
|
final_frames = []
|
|
for unblurred, blurred in zip(unblurred_frames, blurred_frames):
|
|
# Create a black background
|
|
final_frame = Image.new('RGB', unblurred.size, color='black')
|
|
|
|
# Check if the unblurred frame has an alpha channel
|
|
if unblurred.mode == 'RGBA':
|
|
mask = unblurred.split()[3]
|
|
else:
|
|
# If no alpha channel, use the image itself as a mask
|
|
mask = unblurred.convert('L')
|
|
|
|
# Use unblurred frame as mask to cut out blurred frame
|
|
final_frame.paste(blurred, mask=mask)
|
|
|
|
final_frames.append(final_frame)
|
|
|
|
return final_frames
|
|
|
|
def apply_fade_to_edge_fx(self, edge_fx_frames, transition_frames, hold_frames, fade_balance):
|
|
faded_frames = []
|
|
frames_per_cycle = transition_frames + hold_frames
|
|
|
|
# Calculate fade-up and fade-down frames, ensuring they sum to transition_frames
|
|
fade_up_frames = max(1, min(transition_frames - 1, round(transition_frames * fade_balance)))
|
|
fade_down_frames = transition_frames - fade_up_frames
|
|
|
|
for i, frame in enumerate(edge_fx_frames):
|
|
cycle_position = i % frames_per_cycle
|
|
|
|
if cycle_position < fade_up_frames:
|
|
# Fade in
|
|
fade_factor = cycle_position / fade_up_frames
|
|
elif cycle_position >= frames_per_cycle - fade_down_frames:
|
|
# Fade out
|
|
fade_factor = (frames_per_cycle - cycle_position) / fade_down_frames
|
|
else:
|
|
# Hold at full opacity
|
|
fade_factor = 1.0
|
|
|
|
# Apply fade effect
|
|
faded_frame = Image.new('RGB', frame.size, (0, 0, 0))
|
|
faded_frame = Image.blend(faded_frame, frame, fade_factor)
|
|
faded_frames.append(faded_frame)
|
|
|
|
return faded_frames
|
|
|
|
# Mapping the class to its name for the node system
|
|
NODE_CLASS_MAPPINGS = {
|
|
"DynamicAnimatedWeightsHelper": DynamicAnimatedWeightsHelper
|
|
}
|
|
|
|
# Display name mappings for the node system
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"DynamicAnimatedWeightsHelper": "Dynamic Animated Weights"
|
|
}
|