""" @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" }