Initial commit
This commit is contained in:
331
templates/explode.py
Executable file
331
templates/explode.py
Executable file
@@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Explode Animation - Break objects into pieces that fly outward.
|
||||
|
||||
Creates explosion, shatter, and particle burst effects.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import math
|
||||
import random
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
import numpy as np
|
||||
from core.gif_builder import GIFBuilder
|
||||
from core.frame_composer import create_blank_frame, draw_emoji_enhanced
|
||||
from core.visual_effects import ParticleSystem
|
||||
from core.easing import interpolate
|
||||
|
||||
|
||||
def create_explode_animation(
|
||||
object_type: str = 'emoji',
|
||||
object_data: dict | None = None,
|
||||
num_frames: int = 30,
|
||||
explode_type: str = 'burst', # 'burst', 'shatter', 'dissolve', 'implode'
|
||||
num_pieces: int = 20,
|
||||
explosion_speed: float = 5.0,
|
||||
center_pos: tuple[int, int] = (240, 240),
|
||||
frame_width: int = 480,
|
||||
frame_height: int = 480,
|
||||
bg_color: tuple[int, int, int] = (255, 255, 255)
|
||||
) -> list[Image.Image]:
|
||||
"""
|
||||
Create explosion animation.
|
||||
|
||||
Args:
|
||||
object_type: 'emoji', 'circle', 'text'
|
||||
object_data: Object configuration
|
||||
num_frames: Number of frames
|
||||
explode_type: Type of explosion
|
||||
num_pieces: Number of pieces/particles
|
||||
explosion_speed: Speed of explosion
|
||||
center_pos: Center position
|
||||
frame_width: Frame width
|
||||
frame_height: Frame height
|
||||
bg_color: Background color
|
||||
|
||||
Returns:
|
||||
List of frames
|
||||
"""
|
||||
frames = []
|
||||
|
||||
# Default object data
|
||||
if object_data is None:
|
||||
if object_type == 'emoji':
|
||||
object_data = {'emoji': '💣', 'size': 100}
|
||||
|
||||
# Generate pieces/particles
|
||||
pieces = []
|
||||
for _ in range(num_pieces):
|
||||
angle = random.uniform(0, 2 * math.pi)
|
||||
speed = random.uniform(explosion_speed * 0.5, explosion_speed * 1.5)
|
||||
vx = math.cos(angle) * speed
|
||||
vy = math.sin(angle) * speed
|
||||
size = random.randint(3, 12)
|
||||
color = (
|
||||
random.randint(100, 255),
|
||||
random.randint(100, 255),
|
||||
random.randint(100, 255)
|
||||
)
|
||||
rotation_speed = random.uniform(-20, 20)
|
||||
|
||||
pieces.append({
|
||||
'vx': vx,
|
||||
'vy': vy,
|
||||
'size': size,
|
||||
'color': color,
|
||||
'rotation': 0,
|
||||
'rotation_speed': rotation_speed
|
||||
})
|
||||
|
||||
for i in range(num_frames):
|
||||
t = i / (num_frames - 1) if num_frames > 1 else 0
|
||||
frame = create_blank_frame(frame_width, frame_height, bg_color)
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
if explode_type == 'burst':
|
||||
# Show object at start, then explode
|
||||
if t < 0.2:
|
||||
# Object still intact
|
||||
scale = interpolate(1.0, 1.2, t / 0.2, 'ease_out')
|
||||
if object_type == 'emoji':
|
||||
size = int(object_data['size'] * scale)
|
||||
draw_emoji_enhanced(
|
||||
frame,
|
||||
emoji=object_data['emoji'],
|
||||
position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
|
||||
size=size,
|
||||
shadow=False
|
||||
)
|
||||
else:
|
||||
# Exploded - draw pieces
|
||||
explosion_t = (t - 0.2) / 0.8
|
||||
for piece in pieces:
|
||||
# Update position
|
||||
x = center_pos[0] + piece['vx'] * explosion_t * 50
|
||||
y = center_pos[1] + piece['vy'] * explosion_t * 50 + 0.5 * 300 * explosion_t ** 2 # Gravity
|
||||
|
||||
# Fade out
|
||||
alpha = 1.0 - explosion_t
|
||||
if alpha > 0:
|
||||
color = tuple(int(c * alpha) for c in piece['color'])
|
||||
size = int(piece['size'] * (1 - explosion_t * 0.5))
|
||||
|
||||
draw.ellipse(
|
||||
[x - size, y - size, x + size, y + size],
|
||||
fill=color
|
||||
)
|
||||
|
||||
elif explode_type == 'shatter':
|
||||
# Break into geometric pieces
|
||||
if t < 0.15:
|
||||
# Object intact
|
||||
if object_type == 'emoji':
|
||||
draw_emoji_enhanced(
|
||||
frame,
|
||||
emoji=object_data['emoji'],
|
||||
position=(center_pos[0] - object_data['size'] // 2,
|
||||
center_pos[1] - object_data['size'] // 2),
|
||||
size=object_data['size'],
|
||||
shadow=False
|
||||
)
|
||||
else:
|
||||
# Shattered
|
||||
shatter_t = (t - 0.15) / 0.85
|
||||
|
||||
# Draw triangular shards
|
||||
for piece in pieces[:min(10, len(pieces))]:
|
||||
x = center_pos[0] + piece['vx'] * shatter_t * 30
|
||||
y = center_pos[1] + piece['vy'] * shatter_t * 30 + 0.5 * 200 * shatter_t ** 2
|
||||
|
||||
# Update rotation
|
||||
rotation = piece['rotation_speed'] * shatter_t * 100
|
||||
|
||||
# Draw triangle shard
|
||||
shard_size = piece['size'] * 2
|
||||
points = []
|
||||
for j in range(3):
|
||||
angle = (rotation + j * 120) * math.pi / 180
|
||||
px = x + shard_size * math.cos(angle)
|
||||
py = y + shard_size * math.sin(angle)
|
||||
points.append((px, py))
|
||||
|
||||
alpha = 1.0 - shatter_t
|
||||
if alpha > 0:
|
||||
color = tuple(int(c * alpha) for c in piece['color'])
|
||||
draw.polygon(points, fill=color)
|
||||
|
||||
elif explode_type == 'dissolve':
|
||||
# Dissolve into particles
|
||||
dissolve_scale = interpolate(1.0, 0.0, t, 'ease_in')
|
||||
|
||||
if dissolve_scale > 0.1:
|
||||
# Draw fading object
|
||||
if object_type == 'emoji':
|
||||
size = int(object_data['size'] * dissolve_scale)
|
||||
size = max(12, size)
|
||||
|
||||
emoji_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
|
||||
draw_emoji_enhanced(
|
||||
emoji_canvas,
|
||||
emoji=object_data['emoji'],
|
||||
position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
|
||||
size=size,
|
||||
shadow=False
|
||||
)
|
||||
|
||||
# Apply opacity
|
||||
from templates.fade import apply_opacity
|
||||
emoji_canvas = apply_opacity(emoji_canvas, dissolve_scale)
|
||||
|
||||
frame_rgba = frame.convert('RGBA')
|
||||
frame = Image.alpha_composite(frame_rgba, emoji_canvas)
|
||||
frame = frame.convert('RGB')
|
||||
draw = ImageDraw.Draw(frame)
|
||||
|
||||
# Draw outward-moving particles
|
||||
for piece in pieces:
|
||||
x = center_pos[0] + piece['vx'] * t * 40
|
||||
y = center_pos[1] + piece['vy'] * t * 40
|
||||
|
||||
alpha = 1.0 - t
|
||||
if alpha > 0:
|
||||
color = tuple(int(c * alpha) for c in piece['color'])
|
||||
size = int(piece['size'] * (1 - t * 0.5))
|
||||
draw.ellipse(
|
||||
[x - size, y - size, x + size, y + size],
|
||||
fill=color
|
||||
)
|
||||
|
||||
elif explode_type == 'implode':
|
||||
# Reverse explosion - pieces fly inward
|
||||
if t < 0.7:
|
||||
# Pieces converging
|
||||
implode_t = 1.0 - (t / 0.7)
|
||||
for piece in pieces:
|
||||
x = center_pos[0] + piece['vx'] * implode_t * 50
|
||||
y = center_pos[1] + piece['vy'] * implode_t * 50
|
||||
|
||||
alpha = 1.0 - (1.0 - implode_t) * 0.5
|
||||
color = tuple(int(c * alpha) for c in piece['color'])
|
||||
size = int(piece['size'] * alpha)
|
||||
|
||||
draw.ellipse(
|
||||
[x - size, y - size, x + size, y + size],
|
||||
fill=color
|
||||
)
|
||||
else:
|
||||
# Object reforms
|
||||
reform_t = (t - 0.7) / 0.3
|
||||
scale = interpolate(0.5, 1.0, reform_t, 'elastic_out')
|
||||
|
||||
if object_type == 'emoji':
|
||||
size = int(object_data['size'] * scale)
|
||||
draw_emoji_enhanced(
|
||||
frame,
|
||||
emoji=object_data['emoji'],
|
||||
position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
|
||||
size=size,
|
||||
shadow=False
|
||||
)
|
||||
|
||||
frames.append(frame)
|
||||
|
||||
return frames
|
||||
|
||||
|
||||
def create_particle_burst(
|
||||
num_frames: int = 25,
|
||||
particle_count: int = 30,
|
||||
center_pos: tuple[int, int] = (240, 240),
|
||||
colors: list[tuple[int, int, int]] | None = None,
|
||||
frame_width: int = 480,
|
||||
frame_height: int = 480,
|
||||
bg_color: tuple[int, int, int] = (255, 255, 255)
|
||||
) -> list[Image.Image]:
|
||||
"""
|
||||
Create simple particle burst effect.
|
||||
|
||||
Args:
|
||||
num_frames: Number of frames
|
||||
particle_count: Number of particles
|
||||
center_pos: Burst center
|
||||
colors: Particle colors (None for random)
|
||||
frame_width: Frame width
|
||||
frame_height: Frame height
|
||||
bg_color: Background color
|
||||
|
||||
Returns:
|
||||
List of frames
|
||||
"""
|
||||
particles = ParticleSystem()
|
||||
|
||||
# Emit particles
|
||||
if colors is None:
|
||||
from core.color_palettes import get_palette
|
||||
palette = get_palette('vibrant')
|
||||
colors = [palette['primary'], palette['secondary'], palette['accent']]
|
||||
|
||||
for _ in range(particle_count):
|
||||
color = random.choice(colors)
|
||||
particles.emit(
|
||||
center_pos[0], center_pos[1],
|
||||
count=1,
|
||||
speed=random.uniform(3, 8),
|
||||
color=color,
|
||||
lifetime=random.uniform(20, 30),
|
||||
size=random.randint(3, 8),
|
||||
shape='star'
|
||||
)
|
||||
|
||||
frames = []
|
||||
for _ in range(num_frames):
|
||||
frame = create_blank_frame(frame_width, frame_height, bg_color)
|
||||
|
||||
particles.update()
|
||||
particles.render(frame)
|
||||
|
||||
frames.append(frame)
|
||||
|
||||
return frames
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == '__main__':
|
||||
print("Creating explode animations...")
|
||||
|
||||
builder = GIFBuilder(width=480, height=480, fps=20)
|
||||
|
||||
# Example 1: Burst
|
||||
frames = create_explode_animation(
|
||||
object_type='emoji',
|
||||
object_data={'emoji': '💣', 'size': 100},
|
||||
num_frames=30,
|
||||
explode_type='burst',
|
||||
num_pieces=25
|
||||
)
|
||||
builder.add_frames(frames)
|
||||
builder.save('explode_burst.gif', num_colors=128)
|
||||
|
||||
# Example 2: Shatter
|
||||
builder.clear()
|
||||
frames = create_explode_animation(
|
||||
object_type='emoji',
|
||||
object_data={'emoji': '🪟', 'size': 100},
|
||||
num_frames=30,
|
||||
explode_type='shatter',
|
||||
num_pieces=12
|
||||
)
|
||||
builder.add_frames(frames)
|
||||
builder.save('explode_shatter.gif', num_colors=128)
|
||||
|
||||
# Example 3: Particle burst
|
||||
builder.clear()
|
||||
frames = create_particle_burst(num_frames=25, particle_count=40)
|
||||
builder.add_frames(frames)
|
||||
builder.save('explode_particles.gif', num_colors=128)
|
||||
|
||||
print("Created explode animations!")
|
||||
Reference in New Issue
Block a user