| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- #!/usr/bin/env python3
- """
- Frame Composer - Utilities for composing visual elements into frames.
- Provides functions for drawing shapes, text, emojis, and compositing elements
- together to create animation frames.
- """
- from typing import Optional
- import numpy as np
- from PIL import Image, ImageDraw, ImageFont
- def create_blank_frame(
- width: int, height: int, color: tuple[int, int, int] = (255, 255, 255)
- ) -> Image.Image:
- """
- Create a blank frame with solid color background.
- Args:
- width: Frame width
- height: Frame height
- color: RGB color tuple (default: white)
- Returns:
- PIL Image
- """
- return Image.new("RGB", (width, height), color)
- def draw_circle(
- frame: Image.Image,
- center: tuple[int, int],
- radius: int,
- fill_color: Optional[tuple[int, int, int]] = None,
- outline_color: Optional[tuple[int, int, int]] = None,
- outline_width: int = 1,
- ) -> Image.Image:
- """
- Draw a circle on a frame.
- Args:
- frame: PIL Image to draw on
- center: (x, y) center position
- radius: Circle radius
- fill_color: RGB fill color (None for no fill)
- outline_color: RGB outline color (None for no outline)
- outline_width: Outline width in pixels
- Returns:
- Modified frame
- """
- draw = ImageDraw.Draw(frame)
- x, y = center
- bbox = [x - radius, y - radius, x + radius, y + radius]
- draw.ellipse(bbox, fill=fill_color, outline=outline_color, width=outline_width)
- return frame
- def draw_text(
- frame: Image.Image,
- text: str,
- position: tuple[int, int],
- color: tuple[int, int, int] = (0, 0, 0),
- centered: bool = False,
- ) -> Image.Image:
- """
- Draw text on a frame.
- Args:
- frame: PIL Image to draw on
- text: Text to draw
- position: (x, y) position (top-left unless centered=True)
- color: RGB text color
- centered: If True, center text at position
- Returns:
- Modified frame
- """
- draw = ImageDraw.Draw(frame)
- # Uses Pillow's default font.
- # If the font should be changed for the emoji, add additional logic here.
- font = ImageFont.load_default()
- if centered:
- bbox = draw.textbbox((0, 0), text, font=font)
- text_width = bbox[2] - bbox[0]
- text_height = bbox[3] - bbox[1]
- x = position[0] - text_width // 2
- y = position[1] - text_height // 2
- position = (x, y)
- draw.text(position, text, fill=color, font=font)
- return frame
- def create_gradient_background(
- width: int,
- height: int,
- top_color: tuple[int, int, int],
- bottom_color: tuple[int, int, int],
- ) -> Image.Image:
- """
- Create a vertical gradient background.
- Args:
- width: Frame width
- height: Frame height
- top_color: RGB color at top
- bottom_color: RGB color at bottom
- Returns:
- PIL Image with gradient
- """
- frame = Image.new("RGB", (width, height))
- draw = ImageDraw.Draw(frame)
- # Calculate color step for each row
- r1, g1, b1 = top_color
- r2, g2, b2 = bottom_color
- for y in range(height):
- # Interpolate color
- ratio = y / height
- r = int(r1 * (1 - ratio) + r2 * ratio)
- g = int(g1 * (1 - ratio) + g2 * ratio)
- b = int(b1 * (1 - ratio) + b2 * ratio)
- # Draw horizontal line
- draw.line([(0, y), (width, y)], fill=(r, g, b))
- return frame
- def draw_star(
- frame: Image.Image,
- center: tuple[int, int],
- size: int,
- fill_color: tuple[int, int, int],
- outline_color: Optional[tuple[int, int, int]] = None,
- outline_width: int = 1,
- ) -> Image.Image:
- """
- Draw a 5-pointed star.
- Args:
- frame: PIL Image to draw on
- center: (x, y) center position
- size: Star size (outer radius)
- fill_color: RGB fill color
- outline_color: RGB outline color (None for no outline)
- outline_width: Outline width
- Returns:
- Modified frame
- """
- import math
- draw = ImageDraw.Draw(frame)
- x, y = center
- # Calculate star points
- points = []
- for i in range(10):
- angle = (i * 36 - 90) * math.pi / 180 # 36 degrees per point, start at top
- radius = size if i % 2 == 0 else size * 0.4 # Alternate between outer and inner
- px = x + radius * math.cos(angle)
- py = y + radius * math.sin(angle)
- points.append((px, py))
- # Draw star
- draw.polygon(points, fill=fill_color, outline=outline_color, width=outline_width)
- return frame
|