Slack GIF 创建者
代理技能演练,用于生成主题 Slack GIF 包、管理提示、框架管道和审查清单。
来源:内容改编自人类/技能(麻省理工学院)。
一个工具包,提供用于创建针对 Slack 优化的动画 GIF 的实用程序和知识。
松弛要求
方面:
- 表情符号 GIF:128x128(推荐)
- 消息 GIF:480x480
参数:
- FPS:10-30(越低文件大小越小)
- 颜色:48-128(越少=文件大小越小)
- 持续时间:表情符号 GIF 的时长保持在 3 秒以内
核心工作流程
from core.gif_builder import GIFBuilder
from PIL import Image, ImageDraw
# 1. Create builder
builder = GIFBuilder(width=128, height=128, fps=10)
# 2. Generate frames
for i in range(12):
frame = Image.new('RGB', (128, 128), (240, 248, 255))
draw = ImageDraw.Draw(frame)
# Draw your animation using PIL primitives
# (circles, polygons, lines, etc.)
builder.add_frame(frame)
# 3. Save with optimization
builder.save('output.gif', num_colors=48, optimize_for_emoji=True)绘制图形
使用用户上传的图像
如果用户上传图像,请考虑他们是否想要:
- 直接使用它(例如,“为其设置动画”、“将其分割为帧”)
- 用它作为灵感(例如,“做这样的东西”)
使用 PIL 加载和处理图像:
from PIL import Image
uploaded = Image.open('file.png')
# Use directly, or just as reference for colors/style从头开始绘图
从头开始绘制图形时,请使用 PIL ImageDraw 原语:
from PIL import ImageDraw
draw = ImageDraw.Draw(frame)
# Circles/ovals
draw.ellipse([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)
# Stars, triangles, any polygon
points = [(x1, y1), (x2, y2), (x3, y3), ...]
draw.polygon(points, fill=(r, g, b), outline=(r, g, b), width=3)
# Lines
draw.line([(x1, y1), (x2, y2)], fill=(r, g, b), width=5)
# Rectangles
draw.rectangle([x1, y1, x2, y2], fill=(r, g, b), outline=(r, g, b), width=3)不要使用: 表情符号字体(跨平台不可靠)或假设此技能中存在预先打包的图形。
让图形看起来不错
图形应该看起来精致且富有创意,而不是简单的。方法如下:
使用较粗的线条 - 始终将轮廓和线条设置为width=2或更高。细线(宽度=1)看起来不稳定且业余。
添加视觉深度:
- 使用渐变背景 (
create_gradient_background) - 将多个形状分层以实现复杂性(例如,内部有较小星星的星星)
让形状更有趣:
- 不要只画一个简单的圆圈 - 添加高光、环或图案
- 星星可以发光(在后面画更大的半透明版本)
- 组合多种形状(星星+闪光、圆形+戒指)
注意颜色:
- 使用充满活力的互补色
- 添加对比度(浅色形状上的深色轮廓,深色形状上的浅色轮廓)
- 考虑整体构成
对于复杂形状(心形、雪花等):
- 使用多边形和椭圆的组合
- 仔细计算点的对称性
- 添加细节(心可以有高光曲线,雪花有复杂的树枝)
要有创意和细节!好的 Slack GIF 应该看起来很精美,而不是像占位符图形。
可用的实用程序
GIF生成器 (core.gif_builder)
组装框架并针对 Slack 进行优化:
builder = GIFBuilder(width=128, height=128, fps=10)
builder.add_frame(frame) # Add PIL Image
builder.add_frames(frames) # Add list of frames
builder.save('out.gif', num_colors=48, optimize_for_emoji=True, remove_duplicates=True)验证器 (core.validators)
检查 GIF 是否满足 Slack 要求:
from core.validators import validate_gif, is_slack_ready
# Detailed validation
passes, info = validate_gif('my.gif', is_emoji=True, verbose=True)
# Quick check
if is_slack_ready('my.gif'):
print("Ready!")缓动函数 (core.easing)
平滑运动而不是线性运动:
from core.easing import interpolate
# Progress from 0.0 to 1.0
t = i / (num_frames - 1)
# Apply easing
y = interpolate(start=0, end=400, t=t, easing='ease_out')
# Available: linear, ease_in, ease_out, ease_in_out,
# bounce_out, elastic_out, back_out框架助手 (core.frame_composer)
满足常见需求的便捷功能:
from core.frame_composer import (
create_blank_frame, # Solid color background
create_gradient_background, # Vertical gradient
draw_circle, # Helper for circles
draw_text, # Simple text rendering
draw_star # 5-pointed star
)动画概念
摇动/振动
通过振荡偏移物体位置:
- 使用带有框架索引的
math.sin()或math.cos() - 添加小的随机变化以获得自然的感觉
- 应用于 x 和/或 y 位置
脉搏/心跳
有节奏地缩放对象大小:
- 使用
math.sin(t * frequency * 2 * math.pi)平滑脉冲 - 对于心跳:两个快速脉冲然后暂停(调整正弦波)
- 基础尺寸的比例在 0.8 到 1.2 之间
弹跳
物体下落和弹起:
- 使用
interpolate()和easing='bounce_out'进行着陆 - 使用
easing='ease_in'进行下降(加速) - 通过增加每帧的 y 速度来应用重力
旋转/旋转
绕中心旋转对象:
- 船运:
image.rotate(angle, resample=Image.BICUBIC) - 对于摆动:使用正弦波作为角度而不是线性
淡入/淡出
逐渐出现或消失:
- 创建RGBA图像,调整alpha通道
- 或者使用
Image.blend(image1, image2, alpha) - 淡入:alpha 从 0 到 1
- 淡出:alpha 从 1 到 0
滑动
将对象从屏幕外移动到以下位置:
- 起始位置:框架边界外
- 结束位置:目标位置
- 将
interpolate()与easing='ease_out'配合使用可实现平滑停止 - 对于过冲:使用
easing='back_out'
飞涨
缩放效果的比例和位置:
- 放大:比例从 0.1 到 2.0,裁剪中心
- 缩小:从 2.0 缩放到 1.0
- 可以为戏剧添加运动模糊(PIL 滤镜)
爆炸/粒子爆发
创建向外辐射的粒子:
- 生成具有随机角度和速度的粒子
- 更新每个粒子:
x += vx、y += vy - 添加重力:
vy += gravity_constant - 随着时间的推移逐渐淡出粒子(减少 Alpha)
优化策略
仅当要求减小文件大小时,才实施以下一些方法:
- 更少的帧 - 较低的 FPS(10 而不是 20)或较短的持续时间
- 更少的颜色 -
num_colors=48而不是 128 - 尺寸较小 - 128x128 而不是 480x480
- 删除重复项 - save() 中的
remove_duplicates=True - 表情符号模式 -
optimize_for_emoji=True自动优化
# Maximum optimization for emoji
builder.save(
'emoji.gif',
num_colors=48,
optimize_for_emoji=True,
remove_duplicates=True
)哲学
该技能提供:
- 知识:Slack的要求和动画概念
- 实用程序:GIFBuilder、验证器、缓动函数
- 灵活性:使用 PIL 原语创建动画逻辑
它不提供:
- 刚性动画模板或预制函数
- 表情符号字体渲染(跨平台不可靠)
- 技能中内置的预打包图形库
关于用户上传的注意事项:此技能不包括预先构建的图形,但如果用户上传图像,请使用 PIL 来加载和使用它 - 根据他们的请求进行解释,无论他们希望直接使用它还是只是作为灵感。
有创意!结合概念(弹跳 + 旋转、脉冲 + 滑动等)并使用 PIL 的全部功能。
依赖关系
pip install pillow imageio numpy资源文件
许可证.txt
二进制资源
核心/缓动.py
二进制资源
核心/frame_composer.py
#!/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核心/gif_builder.py
二进制资源
核心/验证器.py
#!/usr/bin/env python3
"""
Validators - Check if GIFs meet Slack's requirements.
These validators help ensure your GIFs meet Slack's size and dimension constraints.
"""
from pathlib import Path
def validate_gif(
gif_path: str | Path, is_emoji: bool = True, verbose: bool = True
) -> tuple[bool, dict]:
"""
Validate GIF for Slack (dimensions, size, frame count).
Args:
gif_path: Path to GIF file
is_emoji: True for emoji (128x128 recommended), False for message GIF
verbose: Print validation details
Returns:
Tuple of (passes: bool, results: dict with all details)
"""
from PIL import Image
gif_path = Path(gif_path)
if not gif_path.exists():
return False, {"error": f"File not found: {gif_path}"}
# Get file size
size_bytes = gif_path.stat().st_size
size_kb = size_bytes / 1024
size_mb = size_kb / 1024
# Get dimensions and frame info
try:
with Image.open(gif_path) as img:
width, height = img.size
# Count frames
frame_count = 0
try:
while True:
img.seek(frame_count)
frame_count += 1
except EOFError:
pass
# Get duration
try:
duration_ms = img.info.get("duration", 100)
total_duration = (duration_ms * frame_count) / 1000
fps = frame_count / total_duration if total_duration > 0 else 0
except:
total_duration = None
fps = None
except Exception as e:
return False, {"error": f"Failed to read GIF: {e}"}
# Validate dimensions
if is_emoji:
optimal = width == height == 128
acceptable = width == height and 64 <= width <= 128
dim_pass = acceptable
else:
aspect_ratio = (
max(width, height) / min(width, height)
if min(width, height) > 0
else float("inf")
)
dim_pass = aspect_ratio <= 2.0 and 320 <= min(width, height) <= 640
results = {
"file": str(gif_path),
"passes": dim_pass,
"width": width,
"height": height,
"size_kb": size_kb,
"size_mb": size_mb,
"frame_count": frame_count,
"duration_seconds": total_duration,
"fps": fps,
"is_emoji": is_emoji,
"optimal": optimal if is_emoji else None,
}
# Print if verbose
if verbose:
print(f"\nValidating {gif_path.name}:")
print(
f" Dimensions: {width}x{height}"
+ (
f" ({'optimal' if optimal else 'acceptable'})"
if is_emoji and acceptable
else ""
)
)
print(
f" Size: {size_kb:.1f} KB"
+ (f" ({size_mb:.2f} MB)" if size_mb >= 1.0 else "")
)
print(
f" Frames: {frame_count}"
+ (f" @ {fps:.1f} fps ({total_duration:.1f}s)" if fps else "")
)
if not dim_pass:
print(
f" Note: {'Emoji should be 128x128' if is_emoji else 'Unusual dimensions for Slack'}"
)
if size_mb > 5.0:
print(f" Note: Large file size - consider fewer frames/colors")
return dim_pass, results
def is_slack_ready(
gif_path: str | Path, is_emoji: bool = True, verbose: bool = True
) -> bool:
"""
Quick check if GIF is ready for Slack.
Args:
gif_path: Path to GIF file
is_emoji: True for emoji GIF, False for message GIF
verbose: Print feedback
Returns:
True if dimensions are acceptable
"""
passes, _ = validate_gif(gif_path, is_emoji, verbose)
return passes要求.txt
pillow>=10.0.0
imageio>=2.31.0
imageio-ffmpeg>=0.4.9
numpy>=1.24.0
claudeskills文档