Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Creates optimized animated GIFs for Slack emoji (128x128) or messages (480x480) using Python PIL with polished drawing primitives.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/frame_composer.py
1#!/usr/bin/env python32"""3Frame Composer - Utilities for composing visual elements into frames.45Provides functions for drawing shapes, text, emojis, and compositing elements6together to create animation frames.7"""89from typing import Optional1011import numpy as np12from PIL import Image, ImageDraw, ImageFont131415def create_blank_frame(16width: int, height: int, color: tuple[int, int, int] = (255, 255, 255)17) -> Image.Image:18"""19Create a blank frame with solid color background.2021Args:22width: Frame width23height: Frame height24color: RGB color tuple (default: white)2526Returns:27PIL Image28"""29return Image.new("RGB", (width, height), color)303132def draw_circle(33frame: Image.Image,34center: tuple[int, int],35radius: int,36fill_color: Optional[tuple[int, int, int]] = None,37outline_color: Optional[tuple[int, int, int]] = None,38outline_width: int = 1,39) -> Image.Image:40"""41Draw a circle on a frame.4243Args:44frame: PIL Image to draw on45center: (x, y) center position46radius: Circle radius47fill_color: RGB fill color (None for no fill)48outline_color: RGB outline color (None for no outline)49outline_width: Outline width in pixels5051Returns:52Modified frame53"""54draw = ImageDraw.Draw(frame)55x, y = center56bbox = [x - radius, y - radius, x + radius, y + radius]57draw.ellipse(bbox, fill=fill_color, outline=outline_color, width=outline_width)58return frame596061def draw_text(62frame: Image.Image,63text: str,64position: tuple[int, int],65color: tuple[int, int, int] = (0, 0, 0),66centered: bool = False,67) -> Image.Image:68"""69Draw text on a frame.7071Args:72frame: PIL Image to draw on73text: Text to draw74position: (x, y) position (top-left unless centered=True)75color: RGB text color76centered: If True, center text at position7778Returns:79Modified frame80"""81draw = ImageDraw.Draw(frame)8283# Uses Pillow's default font.84# If the font should be changed for the emoji, add additional logic here.85font = ImageFont.load_default()8687if centered:88bbox = draw.textbbox((0, 0), text, font=font)89text_width = bbox[2] - bbox[0]90text_height = bbox[3] - bbox[1]91x = position[0] - text_width // 292y = position[1] - text_height // 293position = (x, y)9495draw.text(position, text, fill=color, font=font)96return frame979899def create_gradient_background(100width: int,101height: int,102top_color: tuple[int, int, int],103bottom_color: tuple[int, int, int],104) -> Image.Image:105"""106Create a vertical gradient background.107108Args:109width: Frame width110height: Frame height111top_color: RGB color at top112bottom_color: RGB color at bottom113114Returns:115PIL Image with gradient116"""117frame = Image.new("RGB", (width, height))118draw = ImageDraw.Draw(frame)119120# Calculate color step for each row121r1, g1, b1 = top_color122r2, g2, b2 = bottom_color123124for y in range(height):125# Interpolate color126ratio = y / height127r = int(r1 * (1 - ratio) + r2 * ratio)128g = int(g1 * (1 - ratio) + g2 * ratio)129b = int(b1 * (1 - ratio) + b2 * ratio)130131# Draw horizontal line132draw.line([(0, y), (width, y)], fill=(r, g, b))133134return frame135136137def draw_star(138frame: Image.Image,139center: tuple[int, int],140size: int,141fill_color: tuple[int, int, int],142outline_color: Optional[tuple[int, int, int]] = None,143outline_width: int = 1,144) -> Image.Image:145"""146Draw a 5-pointed star.147148Args:149frame: PIL Image to draw on150center: (x, y) center position151size: Star size (outer radius)152fill_color: RGB fill color153outline_color: RGB outline color (None for no outline)154outline_width: Outline width155156Returns:157Modified frame158"""159import math160161draw = ImageDraw.Draw(frame)162x, y = center163164# Calculate star points165points = []166for i in range(10):167angle = (i * 36 - 90) * math.pi / 180 # 36 degrees per point, start at top168radius = size if i % 2 == 0 else size * 0.4 # Alternate between outer and inner169px = x + radius * math.cos(angle)170py = y + radius * math.sin(angle)171points.append((px, py))172173# Draw star174draw.polygon(points, fill=fill_color, outline=outline_color, width=outline_width)175176return frame177