import numpy as np import cv2 import numexpr import re from PIL import Image # --- Math Parsing --- def parse_weight_string(string, max_frames): """Parses schedule strings with math support (e.g., '0:(0.5), 50:(sin(t/10))').""" string = re.sub(r'\s+', '', str(string)) keyframes = {} parts = string.split(',') for part in parts: try: if ':' not in part: continue frame_str, val_str = part.split(':', 1) keyframes[int(frame_str)] = val_str.strip('()') except: continue if 0 not in keyframes: keyframes[0] = "0" sorted_frames = sorted(keyframes.keys()) series = np.zeros(int(max_frames)) for i in range(len(sorted_frames)): f_start = sorted_frames[i] f_end = sorted_frames[i+1] if i < len(sorted_frames)-1 else int(max_frames) formula = keyframes[f_start] for f in range(f_start, f_end): t = f try: val = numexpr.evaluate(formula, local_dict={'t': t, 'pi': np.pi, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan}) series[f] = float(val) except: try: series[f] = float(formula) except: series[f] = series[f-1] if f > 0 else 0.0 return series # --- Image Processing --- def get_border_mode(mode_str): return { 'Reflect': cv2.BORDER_REFLECT_101, 'Replicate': cv2.BORDER_REPLICATE, 'Wrap': cv2.BORDER_WRAP, 'Black': cv2.BORDER_CONSTANT }.get(mode_str, cv2.BORDER_REFLECT_101) def maintain_colors(prev_img, color_match_sample, mode='LAB'): """Matches colors using LAB or HSV space to prevent drift.""" if mode == 'None' or prev_img is None or color_match_sample is None: return prev_img prev_np = np.array(prev_img).astype(np.uint8) sample_np = np.array(color_match_sample).astype(np.uint8) if mode == 'LAB': prev_lab = cv2.cvtColor(prev_np, cv2.COLOR_RGB2LAB) sample_lab = cv2.cvtColor(sample_np, cv2.COLOR_RGB2LAB) for i in range(3): # Match L, A, and B channels avg_p = np.mean(prev_lab[:,:,i]) avg_s = np.mean(sample_lab[:,:,i]) prev_lab[:,:,i] = np.clip(prev_lab[:,:,i] - avg_p + avg_s, 0, 255) return Image.fromarray(cv2.cvtColor(prev_lab, cv2.COLOR_LAB2RGB)) elif mode == 'HSV': prev_hsv = cv2.cvtColor(prev_np, cv2.COLOR_RGB2HSV) sample_hsv = cv2.cvtColor(sample_np, cv2.COLOR_RGB2HSV) # Match Saturation and Value only, keep Hue for i in [1, 2]: avg_p = np.mean(prev_hsv[:,:,i]) avg_s = np.mean(sample_hsv[:,:,i]) prev_hsv[:,:,i] = np.clip(prev_hsv[:,:,i] - avg_p + avg_s, 0, 255) return Image.fromarray(cv2.cvtColor(prev_hsv, cv2.COLOR_HSV2RGB)) return prev_img def add_noise(img, noise_amt): """Adds uniform noise for texture injection.""" if noise_amt <= 0 or img is None: return img img_np = np.array(img).astype(np.float32) noise = np.random.normal(0, noise_amt * 255, img_np.shape).astype(np.float32) noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8) return Image.fromarray(noisy_img) def anim_frame_warp_2d(prev_img_pil, args_dict, border_mode_str='Reflect'): """Performs 2D affine transformation.""" if prev_img_pil is None: return None cv2_img = np.array(prev_img_pil) height, width = cv2_img.shape[:2] center = (width // 2, height // 2) angle = args_dict.get('angle', 0) zoom = args_dict.get('zoom', 1.0) tx = args_dict.get('translation_x', 0) ty = args_dict.get('translation_y', 0) trans_mat = cv2.getRotationMatrix2D(center, angle, zoom) trans_mat[0, 2] += tx trans_mat[1, 2] += ty border_mode = get_border_mode(border_mode_str) warped = cv2.warpAffine(cv2_img, trans_mat, (width, height), borderMode=border_mode) return Image.fromarray(warped)