1#!/usr/bin/env python3
  2"""
  3Zoom Animation - Scale objects dramatically for emphasis.
  4
  5Creates zoom in, zoom out, and dramatic scaling effects.
  6"""
  7
  8import sys
  9from pathlib import Path
 10import math
 11
 12sys.path.append(str(Path(__file__).parent.parent))
 13
 14from PIL import Image, ImageFilter
 15from core.gif_builder import GIFBuilder
 16from core.frame_composer import create_blank_frame, draw_emoji_enhanced
 17from core.easing import interpolate
 18
 19
 20def create_zoom_animation(
 21    object_type: str = 'emoji',
 22    object_data: dict | None = None,
 23    num_frames: int = 30,
 24    zoom_type: str = 'in',  # 'in', 'out', 'in_out', 'punch'
 25    scale_range: tuple[float, float] = (0.1, 2.0),
 26    easing: str = 'ease_out',
 27    add_motion_blur: bool = False,
 28    center_pos: tuple[int, int] = (240, 240),
 29    frame_width: int = 480,
 30    frame_height: int = 480,
 31    bg_color: tuple[int, int, int] = (255, 255, 255)
 32) -> list[Image.Image]:
 33    """
 34    Create zoom animation.
 35
 36    Args:
 37        object_type: 'emoji', 'text', 'image'
 38        object_data: Object configuration
 39        num_frames: Number of frames
 40        zoom_type: Type of zoom effect
 41        scale_range: (start_scale, end_scale) tuple
 42        easing: Easing function
 43        add_motion_blur: Add blur for speed effect
 44        center_pos: Center position
 45        frame_width: Frame width
 46        frame_height: Frame height
 47        bg_color: Background color
 48
 49    Returns:
 50        List of frames
 51    """
 52    frames = []
 53
 54    # Default object data
 55    if object_data is None:
 56        if object_type == 'emoji':
 57            object_data = {'emoji': '🔍', 'size': 100}
 58
 59    base_size = object_data.get('size', 100) if object_type == 'emoji' else object_data.get('font_size', 60)
 60    start_scale, end_scale = scale_range
 61
 62    for i in range(num_frames):
 63        t = i / (num_frames - 1) if num_frames > 1 else 0
 64
 65        # Calculate scale based on zoom type
 66        if zoom_type == 'in':
 67            scale = interpolate(start_scale, end_scale, t, easing)
 68        elif zoom_type == 'out':
 69            scale = interpolate(end_scale, start_scale, t, easing)
 70        elif zoom_type == 'in_out':
 71            if t < 0.5:
 72                scale = interpolate(start_scale, end_scale, t * 2, easing)
 73            else:
 74                scale = interpolate(end_scale, start_scale, (t - 0.5) * 2, easing)
 75        elif zoom_type == 'punch':
 76            # Quick zoom in with overshoot then settle
 77            if t < 0.3:
 78                scale = interpolate(start_scale, end_scale * 1.2, t / 0.3, 'ease_out')
 79            else:
 80                scale = interpolate(end_scale * 1.2, end_scale, (t - 0.3) / 0.7, 'elastic_out')
 81        else:
 82            scale = interpolate(start_scale, end_scale, t, easing)
 83
 84        # Create frame
 85        frame = create_blank_frame(frame_width, frame_height, bg_color)
 86
 87        if object_type == 'emoji':
 88            current_size = int(base_size * scale)
 89
 90            # Clamp size to reasonable bounds
 91            current_size = max(12, min(current_size, frame_width * 2))
 92
 93            # Create emoji on transparent background
 94            canvas_size = max(frame_width, frame_height, current_size) * 2
 95            emoji_canvas = Image.new('RGBA', (canvas_size, canvas_size), (0, 0, 0, 0))
 96
 97            draw_emoji_enhanced(
 98                emoji_canvas,
 99                emoji=object_data['emoji'],
100                position=(canvas_size // 2 - current_size // 2, canvas_size // 2 - current_size // 2),
101                size=current_size,
102                shadow=False
103            )
104
105            # Optional motion blur for fast zooms
106            if add_motion_blur and abs(scale - 1.0) > 0.5:
107                blur_amount = min(5, int(abs(scale - 1.0) * 3))
108                emoji_canvas = emoji_canvas.filter(ImageFilter.GaussianBlur(blur_amount))
109
110            # Crop to frame size centered
111            left = (canvas_size - frame_width) // 2
112            top = (canvas_size - frame_height) // 2
113            emoji_cropped = emoji_canvas.crop((left, top, left + frame_width, top + frame_height))
114
115            # Composite
116            frame_rgba = frame.convert('RGBA')
117            frame = Image.alpha_composite(frame_rgba, emoji_cropped)
118            frame = frame.convert('RGB')
119
120        elif object_type == 'text':
121            from core.typography import draw_text_with_outline
122
123            current_size = int(base_size * scale)
124            current_size = max(10, min(current_size, 500))
125
126            # Create oversized canvas for large text
127            canvas_size = max(frame_width, frame_height, current_size * 10)
128            text_canvas = Image.new('RGB', (canvas_size, canvas_size), bg_color)
129
130            draw_text_with_outline(
131                text_canvas,
132                text=object_data.get('text', 'ZOOM'),
133                position=(canvas_size // 2, canvas_size // 2),
134                font_size=current_size,
135                text_color=object_data.get('text_color', (0, 0, 0)),
136                outline_color=object_data.get('outline_color', (255, 255, 255)),
137                outline_width=max(2, int(current_size * 0.05)),
138                centered=True
139            )
140
141            # Crop to frame
142            left = (canvas_size - frame_width) // 2
143            top = (canvas_size - frame_height) // 2
144            frame = text_canvas.crop((left, top, left + frame_width, top + frame_height))
145
146        frames.append(frame)
147
148    return frames
149
150
151def create_explosion_zoom(
152    emoji: str = '💥',
153    num_frames: int = 20,
154    frame_width: int = 480,
155    frame_height: int = 480,
156    bg_color: tuple[int, int, int] = (255, 255, 255)
157) -> list[Image.Image]:
158    """
159    Create dramatic explosion zoom effect.
160
161    Args:
162        emoji: Emoji to explode
163        num_frames: Number of frames
164        frame_width: Frame width
165        frame_height: Frame height
166        bg_color: Background color
167
168    Returns:
169        List of frames
170    """
171    frames = []
172
173    for i in range(num_frames):
174        t = i / (num_frames - 1) if num_frames > 1 else 0
175
176        # Exponential zoom
177        scale = 0.1 * math.exp(t * 5)
178
179        # Add rotation for drama
180        angle = t * 360 * 2
181
182        frame = create_blank_frame(frame_width, frame_height, bg_color)
183
184        current_size = int(100 * scale)
185        current_size = max(12, min(current_size, frame_width * 3))
186
187        # Create emoji
188        canvas_size = max(frame_width, frame_height, current_size) * 2
189        emoji_canvas = Image.new('RGBA', (canvas_size, canvas_size), (0, 0, 0, 0))
190
191        draw_emoji_enhanced(
192            emoji_canvas,
193            emoji=emoji,
194            position=(canvas_size // 2 - current_size // 2, canvas_size // 2 - current_size // 2),
195            size=current_size,
196            shadow=False
197        )
198
199        # Rotate
200        emoji_canvas = emoji_canvas.rotate(angle, center=(canvas_size // 2, canvas_size // 2), resample=Image.BICUBIC)
201
202        # Add motion blur for later frames
203        if t > 0.5:
204            blur_amount = int((t - 0.5) * 10)
205            emoji_canvas = emoji_canvas.filter(ImageFilter.GaussianBlur(blur_amount))
206
207        # Crop and composite
208        left = (canvas_size - frame_width) // 2
209        top = (canvas_size - frame_height) // 2
210        emoji_cropped = emoji_canvas.crop((left, top, left + frame_width, top + frame_height))
211
212        frame_rgba = frame.convert('RGBA')
213        frame = Image.alpha_composite(frame_rgba, emoji_cropped)
214        frame = frame.convert('RGB')
215
216        frames.append(frame)
217
218    return frames
219
220
221def create_mind_blown_zoom(
222    emoji: str = '🤯',
223    num_frames: int = 30,
224    frame_width: int = 480,
225    frame_height: int = 480,
226    bg_color: tuple[int, int, int] = (255, 255, 255)
227) -> list[Image.Image]:
228    """
229    Create "mind blown" dramatic zoom with shake.
230
231    Args:
232        emoji: Emoji to use
233        num_frames: Number of frames
234        frame_width: Frame width
235        frame_height: Frame height
236        bg_color: Background color
237
238    Returns:
239        List of frames
240    """
241    frames = []
242
243    for i in range(num_frames):
244        t = i / (num_frames - 1) if num_frames > 1 else 0
245
246        # Zoom in then shake
247        if t < 0.5:
248            scale = interpolate(0.3, 1.2, t * 2, 'ease_out')
249            shake_x = 0
250            shake_y = 0
251        else:
252            scale = 1.2
253            # Shake intensifies
254            shake_intensity = (t - 0.5) * 40
255            shake_x = int(math.sin(t * 50) * shake_intensity)
256            shake_y = int(math.cos(t * 45) * shake_intensity)
257
258        frame = create_blank_frame(frame_width, frame_height, bg_color)
259
260        current_size = int(100 * scale)
261        center_x = frame_width // 2 + shake_x
262        center_y = frame_height // 2 + shake_y
263
264        emoji_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
265        draw_emoji_enhanced(
266            emoji_canvas,
267            emoji=emoji,
268            position=(center_x - current_size // 2, center_y - current_size // 2),
269            size=current_size,
270            shadow=False
271        )
272
273        frame_rgba = frame.convert('RGBA')
274        frame = Image.alpha_composite(frame_rgba, emoji_canvas)
275        frame = frame.convert('RGB')
276
277        frames.append(frame)
278
279    return frames
280
281
282# Example usage
283if __name__ == '__main__':
284    print("Creating zoom animations...")
285
286    builder = GIFBuilder(width=480, height=480, fps=20)
287
288    # Example 1: Zoom in
289    frames = create_zoom_animation(
290        object_type='emoji',
291        object_data={'emoji': '🔍', 'size': 100},
292        num_frames=30,
293        zoom_type='in',
294        scale_range=(0.1, 1.5),
295        easing='ease_out'
296    )
297    builder.add_frames(frames)
298    builder.save('zoom_in.gif', num_colors=128)
299
300    # Example 2: Explosion zoom
301    builder.clear()
302    frames = create_explosion_zoom(emoji='💥', num_frames=20)
303    builder.add_frames(frames)
304    builder.save('zoom_explosion.gif', num_colors=128)
305
306    # Example 3: Mind blown
307    builder.clear()
308    frames = create_mind_blown_zoom(emoji='🤯', num_frames=30)
309    builder.add_frames(frames)
310    builder.save('zoom_mind_blown.gif', num_colors=128)
311
312    print("Created zoom animations!")