File size: 3,932 Bytes
441b0c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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)