@caddis/cli 0.4.10 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/bundles/antigravity-plugin/plugin.json +1 -1
  2. package/bundles/antigravity-plugin/scripts/caddis_spawn.py +60 -6
  3. package/bundles/antigravity-plugin/scripts/setup_project_ai.py +10 -0
  4. package/bundles/antigravity-plugin/skills/_registry.md +0 -1
  5. package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +2 -2
  6. package/bundles/antigravity-plugin/skills/explainer-doc/SKILL.md +1 -1
  7. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +1 -1
  8. package/bundles/antigravity-plugin/skills/spawn-session/SKILL.md +15 -0
  9. package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +6 -1
  10. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  11. package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -19
  12. package/bundles/codex/.codex/skills/docs/explainer-doc/SKILL.md +1 -1
  13. package/bundles/codex/.codex/skills/workflow/brainstorming/SKILL.md +2 -2
  14. package/bundles/manifest.json +3 -3
  15. package/package.json +1 -1
  16. package/bundles/antigravity-plugin-extras/skills/competitive-ads-extractor/SKILL.md +0 -294
  17. package/bundles/antigravity-plugin-extras/skills/content-research-writer/SKILL.md +0 -539
  18. package/bundles/antigravity-plugin-extras/skills/domain-name-brainstormer/SKILL.md +0 -213
  19. package/bundles/antigravity-plugin-extras/skills/humanizer/SKILL.md +0 -194
  20. package/bundles/antigravity-plugin-extras/skills/image-enhancer/SKILL.md +0 -100
  21. package/bundles/antigravity-plugin-extras/skills/invoice-organizer/SKILL.md +0 -447
  22. package/bundles/antigravity-plugin-extras/skills/job-application/SKILL.md +0 -188
  23. package/bundles/antigravity-plugin-extras/skills/lead-research-assistant/SKILL.md +0 -200
  24. package/bundles/antigravity-plugin-extras/skills/meeting-insights-analyzer/SKILL.md +0 -328
  25. package/bundles/antigravity-plugin-extras/skills/notebooklm/AUTHENTICATION.md +0 -154
  26. package/bundles/antigravity-plugin-extras/skills/notebooklm/SKILL.md +0 -270
  27. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/api_reference.md +0 -309
  28. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/troubleshooting.md +0 -376
  29. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/usage_patterns.md +0 -338
  30. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/__init__.py +0 -81
  31. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/ask_question.py +0 -256
  32. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/auth_manager.py +0 -358
  33. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_session.py +0 -255
  34. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_utils.py +0 -107
  35. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/cleanup_manager.py +0 -302
  36. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/config.py +0 -44
  37. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/notebook_manager.py +0 -410
  38. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/run.py +0 -102
  39. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/setup_environment.py +0 -204
  40. package/bundles/antigravity-plugin-extras/skills/onboard-project/SKILL.md +0 -192
  41. package/bundles/antigravity-plugin-extras/skills/pipeline-state/SKILL.md +0 -208
  42. package/bundles/antigravity-plugin-extras/skills/raffle-winner-picker/SKILL.md +0 -159
  43. package/bundles/antigravity-plugin-extras/skills/relay/SKILL.md +0 -246
  44. package/bundles/antigravity-plugin-extras/skills/requesting-code-review/SKILL.md +0 -105
  45. package/bundles/antigravity-plugin-extras/skills/requesting-code-review/code-reviewer.md +0 -146
  46. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/LICENSE.txt +0 -202
  47. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/SKILL.md +0 -646
  48. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/color_palettes.py +0 -302
  49. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/easing.py +0 -230
  50. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/frame_composer.py +0 -469
  51. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/gif_builder.py +0 -246
  52. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/typography.py +0 -357
  53. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/validators.py +0 -264
  54. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/visual_effects.py +0 -494
  55. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/requirements.txt +0 -4
  56. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/bounce.py +0 -106
  57. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/explode.py +0 -331
  58. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/fade.py +0 -329
  59. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/flip.py +0 -291
  60. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/kaleidoscope.py +0 -211
  61. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/morph.py +0 -329
  62. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/move.py +0 -293
  63. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/pulse.py +0 -268
  64. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/shake.py +0 -127
  65. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/slide.py +0 -291
  66. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/spin.py +0 -269
  67. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/wiggle.py +0 -300
  68. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/zoom.py +0 -312
  69. package/bundles/antigravity-plugin-extras/skills/state-tracking/SKILL.md +0 -131
  70. package/bundles/antigravity-plugin-extras/skills/video-downloader/SKILL.md +0 -107
  71. package/bundles/antigravity-plugin-extras/skills/youtube-transcript/SKILL.md +0 -416
  72. package/bundles/codex/.codex/skills/workflow/golden-plan/SKILL.md +0 -586
  73. /package/bundles/{antigravity-plugin → antigravity-plugin-extras}/skills/golden-plan/SKILL.md +0 -0
@@ -1,331 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Explode Animation - Break objects into pieces that fly outward.
4
-
5
- Creates explosion, shatter, and particle burst effects.
6
- """
7
-
8
- import sys
9
- from pathlib import Path
10
- import math
11
- import random
12
-
13
- sys.path.append(str(Path(__file__).parent.parent))
14
-
15
- from PIL import Image, ImageDraw
16
- import numpy as np
17
- from core.gif_builder import GIFBuilder
18
- from core.frame_composer import create_blank_frame, draw_emoji_enhanced
19
- from core.visual_effects import ParticleSystem
20
- from core.easing import interpolate
21
-
22
-
23
- def create_explode_animation(
24
- object_type: str = 'emoji',
25
- object_data: dict | None = None,
26
- num_frames: int = 30,
27
- explode_type: str = 'burst', # 'burst', 'shatter', 'dissolve', 'implode'
28
- num_pieces: int = 20,
29
- explosion_speed: float = 5.0,
30
- center_pos: tuple[int, int] = (240, 240),
31
- frame_width: int = 480,
32
- frame_height: int = 480,
33
- bg_color: tuple[int, int, int] = (255, 255, 255)
34
- ) -> list[Image.Image]:
35
- """
36
- Create explosion animation.
37
-
38
- Args:
39
- object_type: 'emoji', 'circle', 'text'
40
- object_data: Object configuration
41
- num_frames: Number of frames
42
- explode_type: Type of explosion
43
- num_pieces: Number of pieces/particles
44
- explosion_speed: Speed of explosion
45
- center_pos: Center position
46
- frame_width: Frame width
47
- frame_height: Frame height
48
- bg_color: Background color
49
-
50
- Returns:
51
- List of frames
52
- """
53
- frames = []
54
-
55
- # Default object data
56
- if object_data is None:
57
- if object_type == 'emoji':
58
- object_data = {'emoji': '💣', 'size': 100}
59
-
60
- # Generate pieces/particles
61
- pieces = []
62
- for _ in range(num_pieces):
63
- angle = random.uniform(0, 2 * math.pi)
64
- speed = random.uniform(explosion_speed * 0.5, explosion_speed * 1.5)
65
- vx = math.cos(angle) * speed
66
- vy = math.sin(angle) * speed
67
- size = random.randint(3, 12)
68
- color = (
69
- random.randint(100, 255),
70
- random.randint(100, 255),
71
- random.randint(100, 255)
72
- )
73
- rotation_speed = random.uniform(-20, 20)
74
-
75
- pieces.append({
76
- 'vx': vx,
77
- 'vy': vy,
78
- 'size': size,
79
- 'color': color,
80
- 'rotation': 0,
81
- 'rotation_speed': rotation_speed
82
- })
83
-
84
- for i in range(num_frames):
85
- t = i / (num_frames - 1) if num_frames > 1 else 0
86
- frame = create_blank_frame(frame_width, frame_height, bg_color)
87
- draw = ImageDraw.Draw(frame)
88
-
89
- if explode_type == 'burst':
90
- # Show object at start, then explode
91
- if t < 0.2:
92
- # Object still intact
93
- scale = interpolate(1.0, 1.2, t / 0.2, 'ease_out')
94
- if object_type == 'emoji':
95
- size = int(object_data['size'] * scale)
96
- draw_emoji_enhanced(
97
- frame,
98
- emoji=object_data['emoji'],
99
- position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
100
- size=size,
101
- shadow=False
102
- )
103
- else:
104
- # Exploded - draw pieces
105
- explosion_t = (t - 0.2) / 0.8
106
- for piece in pieces:
107
- # Update position
108
- x = center_pos[0] + piece['vx'] * explosion_t * 50
109
- y = center_pos[1] + piece['vy'] * explosion_t * 50 + 0.5 * 300 * explosion_t ** 2 # Gravity
110
-
111
- # Fade out
112
- alpha = 1.0 - explosion_t
113
- if alpha > 0:
114
- color = tuple(int(c * alpha) for c in piece['color'])
115
- size = int(piece['size'] * (1 - explosion_t * 0.5))
116
-
117
- draw.ellipse(
118
- [x - size, y - size, x + size, y + size],
119
- fill=color
120
- )
121
-
122
- elif explode_type == 'shatter':
123
- # Break into geometric pieces
124
- if t < 0.15:
125
- # Object intact
126
- if object_type == 'emoji':
127
- draw_emoji_enhanced(
128
- frame,
129
- emoji=object_data['emoji'],
130
- position=(center_pos[0] - object_data['size'] // 2,
131
- center_pos[1] - object_data['size'] // 2),
132
- size=object_data['size'],
133
- shadow=False
134
- )
135
- else:
136
- # Shattered
137
- shatter_t = (t - 0.15) / 0.85
138
-
139
- # Draw triangular shards
140
- for piece in pieces[:min(10, len(pieces))]:
141
- x = center_pos[0] + piece['vx'] * shatter_t * 30
142
- y = center_pos[1] + piece['vy'] * shatter_t * 30 + 0.5 * 200 * shatter_t ** 2
143
-
144
- # Update rotation
145
- rotation = piece['rotation_speed'] * shatter_t * 100
146
-
147
- # Draw triangle shard
148
- shard_size = piece['size'] * 2
149
- points = []
150
- for j in range(3):
151
- angle = (rotation + j * 120) * math.pi / 180
152
- px = x + shard_size * math.cos(angle)
153
- py = y + shard_size * math.sin(angle)
154
- points.append((px, py))
155
-
156
- alpha = 1.0 - shatter_t
157
- if alpha > 0:
158
- color = tuple(int(c * alpha) for c in piece['color'])
159
- draw.polygon(points, fill=color)
160
-
161
- elif explode_type == 'dissolve':
162
- # Dissolve into particles
163
- dissolve_scale = interpolate(1.0, 0.0, t, 'ease_in')
164
-
165
- if dissolve_scale > 0.1:
166
- # Draw fading object
167
- if object_type == 'emoji':
168
- size = int(object_data['size'] * dissolve_scale)
169
- size = max(12, size)
170
-
171
- emoji_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
172
- draw_emoji_enhanced(
173
- emoji_canvas,
174
- emoji=object_data['emoji'],
175
- position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
176
- size=size,
177
- shadow=False
178
- )
179
-
180
- # Apply opacity
181
- from templates.fade import apply_opacity
182
- emoji_canvas = apply_opacity(emoji_canvas, dissolve_scale)
183
-
184
- frame_rgba = frame.convert('RGBA')
185
- frame = Image.alpha_composite(frame_rgba, emoji_canvas)
186
- frame = frame.convert('RGB')
187
- draw = ImageDraw.Draw(frame)
188
-
189
- # Draw outward-moving particles
190
- for piece in pieces:
191
- x = center_pos[0] + piece['vx'] * t * 40
192
- y = center_pos[1] + piece['vy'] * t * 40
193
-
194
- alpha = 1.0 - t
195
- if alpha > 0:
196
- color = tuple(int(c * alpha) for c in piece['color'])
197
- size = int(piece['size'] * (1 - t * 0.5))
198
- draw.ellipse(
199
- [x - size, y - size, x + size, y + size],
200
- fill=color
201
- )
202
-
203
- elif explode_type == 'implode':
204
- # Reverse explosion - pieces fly inward
205
- if t < 0.7:
206
- # Pieces converging
207
- implode_t = 1.0 - (t / 0.7)
208
- for piece in pieces:
209
- x = center_pos[0] + piece['vx'] * implode_t * 50
210
- y = center_pos[1] + piece['vy'] * implode_t * 50
211
-
212
- alpha = 1.0 - (1.0 - implode_t) * 0.5
213
- color = tuple(int(c * alpha) for c in piece['color'])
214
- size = int(piece['size'] * alpha)
215
-
216
- draw.ellipse(
217
- [x - size, y - size, x + size, y + size],
218
- fill=color
219
- )
220
- else:
221
- # Object reforms
222
- reform_t = (t - 0.7) / 0.3
223
- scale = interpolate(0.5, 1.0, reform_t, 'elastic_out')
224
-
225
- if object_type == 'emoji':
226
- size = int(object_data['size'] * scale)
227
- draw_emoji_enhanced(
228
- frame,
229
- emoji=object_data['emoji'],
230
- position=(center_pos[0] - size // 2, center_pos[1] - size // 2),
231
- size=size,
232
- shadow=False
233
- )
234
-
235
- frames.append(frame)
236
-
237
- return frames
238
-
239
-
240
- def create_particle_burst(
241
- num_frames: int = 25,
242
- particle_count: int = 30,
243
- center_pos: tuple[int, int] = (240, 240),
244
- colors: list[tuple[int, int, int]] | None = None,
245
- frame_width: int = 480,
246
- frame_height: int = 480,
247
- bg_color: tuple[int, int, int] = (255, 255, 255)
248
- ) -> list[Image.Image]:
249
- """
250
- Create simple particle burst effect.
251
-
252
- Args:
253
- num_frames: Number of frames
254
- particle_count: Number of particles
255
- center_pos: Burst center
256
- colors: Particle colors (None for random)
257
- frame_width: Frame width
258
- frame_height: Frame height
259
- bg_color: Background color
260
-
261
- Returns:
262
- List of frames
263
- """
264
- particles = ParticleSystem()
265
-
266
- # Emit particles
267
- if colors is None:
268
- from core.color_palettes import get_palette
269
- palette = get_palette('vibrant')
270
- colors = [palette['primary'], palette['secondary'], palette['accent']]
271
-
272
- for _ in range(particle_count):
273
- color = random.choice(colors)
274
- particles.emit(
275
- center_pos[0], center_pos[1],
276
- count=1,
277
- speed=random.uniform(3, 8),
278
- color=color,
279
- lifetime=random.uniform(20, 30),
280
- size=random.randint(3, 8),
281
- shape='star'
282
- )
283
-
284
- frames = []
285
- for _ in range(num_frames):
286
- frame = create_blank_frame(frame_width, frame_height, bg_color)
287
-
288
- particles.update()
289
- particles.render(frame)
290
-
291
- frames.append(frame)
292
-
293
- return frames
294
-
295
-
296
- # Example usage
297
- if __name__ == '__main__':
298
- print("Creating explode animations...")
299
-
300
- builder = GIFBuilder(width=480, height=480, fps=20)
301
-
302
- # Example 1: Burst
303
- frames = create_explode_animation(
304
- object_type='emoji',
305
- object_data={'emoji': '💣', 'size': 100},
306
- num_frames=30,
307
- explode_type='burst',
308
- num_pieces=25
309
- )
310
- builder.add_frames(frames)
311
- builder.save('explode_burst.gif', num_colors=128)
312
-
313
- # Example 2: Shatter
314
- builder.clear()
315
- frames = create_explode_animation(
316
- object_type='emoji',
317
- object_data={'emoji': '🪟', 'size': 100},
318
- num_frames=30,
319
- explode_type='shatter',
320
- num_pieces=12
321
- )
322
- builder.add_frames(frames)
323
- builder.save('explode_shatter.gif', num_colors=128)
324
-
325
- # Example 3: Particle burst
326
- builder.clear()
327
- frames = create_particle_burst(num_frames=25, particle_count=40)
328
- builder.add_frames(frames)
329
- builder.save('explode_particles.gif', num_colors=128)
330
-
331
- print("Created explode animations!")
@@ -1,329 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Fade Animation - Fade in, fade out, and crossfade effects.
4
-
5
- Creates smooth opacity transitions for appearing, disappearing, and transitioning.
6
- """
7
-
8
- import sys
9
- from pathlib import Path
10
-
11
- sys.path.append(str(Path(__file__).parent.parent))
12
-
13
- from PIL import Image, ImageDraw
14
- import numpy as np
15
- from core.gif_builder import GIFBuilder
16
- from core.frame_composer import create_blank_frame, draw_emoji_enhanced
17
- from core.easing import interpolate
18
-
19
-
20
- def create_fade_animation(
21
- object_type: str = 'emoji',
22
- object_data: dict | None = None,
23
- num_frames: int = 30,
24
- fade_type: str = 'in', # 'in', 'out', 'in_out', 'blink'
25
- easing: str = 'ease_in_out',
26
- center_pos: tuple[int, int] = (240, 240),
27
- frame_width: int = 480,
28
- frame_height: int = 480,
29
- bg_color: tuple[int, int, int] = (255, 255, 255)
30
- ) -> list[Image.Image]:
31
- """
32
- Create fade animation.
33
-
34
- Args:
35
- object_type: 'emoji', 'text', 'image'
36
- object_data: Object configuration
37
- num_frames: Number of frames
38
- fade_type: Type of fade effect
39
- easing: Easing function
40
- center_pos: Center position
41
- frame_width: Frame width
42
- frame_height: Frame height
43
- bg_color: Background color
44
-
45
- Returns:
46
- List of frames
47
- """
48
- frames = []
49
-
50
- # Default object data
51
- if object_data is None:
52
- if object_type == 'emoji':
53
- object_data = {'emoji': '✨', 'size': 100}
54
-
55
- for i in range(num_frames):
56
- t = i / (num_frames - 1) if num_frames > 1 else 0
57
-
58
- # Calculate opacity based on fade type
59
- if fade_type == 'in':
60
- opacity = interpolate(0, 1, t, easing)
61
- elif fade_type == 'out':
62
- opacity = interpolate(1, 0, t, easing)
63
- elif fade_type == 'in_out':
64
- if t < 0.5:
65
- opacity = interpolate(0, 1, t * 2, easing)
66
- else:
67
- opacity = interpolate(1, 0, (t - 0.5) * 2, easing)
68
- elif fade_type == 'blink':
69
- # Quick fade out and back in
70
- if t < 0.2:
71
- opacity = interpolate(1, 0, t / 0.2, 'ease_in')
72
- elif t < 0.4:
73
- opacity = interpolate(0, 1, (t - 0.2) / 0.2, 'ease_out')
74
- else:
75
- opacity = 1.0
76
- else:
77
- opacity = interpolate(0, 1, t, easing)
78
-
79
- # Create background
80
- frame_bg = create_blank_frame(frame_width, frame_height, bg_color)
81
-
82
- # Create object layer with transparency
83
- if object_type == 'emoji':
84
- # Create RGBA canvas for emoji
85
- emoji_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
86
- emoji_size = object_data['size']
87
- draw_emoji_enhanced(
88
- emoji_canvas,
89
- emoji=object_data['emoji'],
90
- position=(center_pos[0] - emoji_size // 2, center_pos[1] - emoji_size // 2),
91
- size=emoji_size,
92
- shadow=object_data.get('shadow', False)
93
- )
94
-
95
- # Apply opacity
96
- emoji_canvas = apply_opacity(emoji_canvas, opacity)
97
-
98
- # Composite onto background
99
- frame_bg_rgba = frame_bg.convert('RGBA')
100
- frame = Image.alpha_composite(frame_bg_rgba, emoji_canvas)
101
- frame = frame.convert('RGB')
102
-
103
- elif object_type == 'text':
104
- from core.typography import draw_text_with_outline
105
-
106
- # Create text on separate layer
107
- text_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
108
- text_canvas_rgb = text_canvas.convert('RGB')
109
- text_canvas_rgb.paste(bg_color, (0, 0, frame_width, frame_height))
110
-
111
- draw_text_with_outline(
112
- text_canvas_rgb,
113
- text=object_data.get('text', 'FADE'),
114
- position=center_pos,
115
- font_size=object_data.get('font_size', 60),
116
- text_color=object_data.get('text_color', (0, 0, 0)),
117
- outline_color=object_data.get('outline_color', (255, 255, 255)),
118
- outline_width=3,
119
- centered=True
120
- )
121
-
122
- # Convert to RGBA and make background transparent
123
- text_canvas = text_canvas_rgb.convert('RGBA')
124
- data = text_canvas.getdata()
125
- new_data = []
126
- for item in data:
127
- if item[:3] == bg_color:
128
- new_data.append((255, 255, 255, 0))
129
- else:
130
- new_data.append(item)
131
- text_canvas.putdata(new_data)
132
-
133
- # Apply opacity
134
- text_canvas = apply_opacity(text_canvas, opacity)
135
-
136
- # Composite
137
- frame_bg_rgba = frame_bg.convert('RGBA')
138
- frame = Image.alpha_composite(frame_bg_rgba, text_canvas)
139
- frame = frame.convert('RGB')
140
-
141
- else:
142
- frame = frame_bg
143
-
144
- frames.append(frame)
145
-
146
- return frames
147
-
148
-
149
- def apply_opacity(image: Image.Image, opacity: float) -> Image.Image:
150
- """
151
- Apply opacity to an RGBA image.
152
-
153
- Args:
154
- image: RGBA image
155
- opacity: Opacity value (0.0 to 1.0)
156
-
157
- Returns:
158
- Image with adjusted opacity
159
- """
160
- if image.mode != 'RGBA':
161
- image = image.convert('RGBA')
162
-
163
- # Get alpha channel
164
- r, g, b, a = image.split()
165
-
166
- # Multiply alpha by opacity
167
- a_array = np.array(a, dtype=np.float32)
168
- a_array = a_array * opacity
169
- a = Image.fromarray(a_array.astype(np.uint8))
170
-
171
- # Merge back
172
- return Image.merge('RGBA', (r, g, b, a))
173
-
174
-
175
- def create_crossfade(
176
- object1_data: dict,
177
- object2_data: dict,
178
- num_frames: int = 30,
179
- easing: str = 'ease_in_out',
180
- object_type: str = 'emoji',
181
- center_pos: tuple[int, int] = (240, 240),
182
- frame_width: int = 480,
183
- frame_height: int = 480,
184
- bg_color: tuple[int, int, int] = (255, 255, 255)
185
- ) -> list[Image.Image]:
186
- """
187
- Crossfade between two objects.
188
-
189
- Args:
190
- object1_data: First object configuration
191
- object2_data: Second object configuration
192
- num_frames: Number of frames
193
- easing: Easing function
194
- object_type: Type of objects
195
- center_pos: Center position
196
- frame_width: Frame width
197
- frame_height: Frame height
198
- bg_color: Background color
199
-
200
- Returns:
201
- List of frames
202
- """
203
- frames = []
204
-
205
- for i in range(num_frames):
206
- t = i / (num_frames - 1) if num_frames > 1 else 0
207
-
208
- # Calculate opacities
209
- opacity1 = interpolate(1, 0, t, easing)
210
- opacity2 = interpolate(0, 1, t, easing)
211
-
212
- # Create background
213
- frame = create_blank_frame(frame_width, frame_height, bg_color)
214
-
215
- if object_type == 'emoji':
216
- # Create first emoji
217
- emoji1_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
218
- size1 = object1_data['size']
219
- draw_emoji_enhanced(
220
- emoji1_canvas,
221
- emoji=object1_data['emoji'],
222
- position=(center_pos[0] - size1 // 2, center_pos[1] - size1 // 2),
223
- size=size1,
224
- shadow=False
225
- )
226
- emoji1_canvas = apply_opacity(emoji1_canvas, opacity1)
227
-
228
- # Create second emoji
229
- emoji2_canvas = Image.new('RGBA', (frame_width, frame_height), (0, 0, 0, 0))
230
- size2 = object2_data['size']
231
- draw_emoji_enhanced(
232
- emoji2_canvas,
233
- emoji=object2_data['emoji'],
234
- position=(center_pos[0] - size2 // 2, center_pos[1] - size2 // 2),
235
- size=size2,
236
- shadow=False
237
- )
238
- emoji2_canvas = apply_opacity(emoji2_canvas, opacity2)
239
-
240
- # Composite both
241
- frame_rgba = frame.convert('RGBA')
242
- frame_rgba = Image.alpha_composite(frame_rgba, emoji1_canvas)
243
- frame_rgba = Image.alpha_composite(frame_rgba, emoji2_canvas)
244
- frame = frame_rgba.convert('RGB')
245
-
246
- frames.append(frame)
247
-
248
- return frames
249
-
250
-
251
- def create_fade_to_color(
252
- start_color: tuple[int, int, int],
253
- end_color: tuple[int, int, int],
254
- num_frames: int = 20,
255
- easing: str = 'linear',
256
- frame_width: int = 480,
257
- frame_height: int = 480
258
- ) -> list[Image.Image]:
259
- """
260
- Fade from one solid color to another.
261
-
262
- Args:
263
- start_color: Starting RGB color
264
- end_color: Ending RGB color
265
- num_frames: Number of frames
266
- easing: Easing function
267
- frame_width: Frame width
268
- frame_height: Frame height
269
-
270
- Returns:
271
- List of frames
272
- """
273
- frames = []
274
-
275
- for i in range(num_frames):
276
- t = i / (num_frames - 1) if num_frames > 1 else 0
277
-
278
- # Interpolate each color channel
279
- r = int(interpolate(start_color[0], end_color[0], t, easing))
280
- g = int(interpolate(start_color[1], end_color[1], t, easing))
281
- b = int(interpolate(start_color[2], end_color[2], t, easing))
282
-
283
- color = (r, g, b)
284
- frame = create_blank_frame(frame_width, frame_height, color)
285
- frames.append(frame)
286
-
287
- return frames
288
-
289
-
290
- # Example usage
291
- if __name__ == '__main__':
292
- print("Creating fade animations...")
293
-
294
- builder = GIFBuilder(width=480, height=480, fps=20)
295
-
296
- # Example 1: Fade in
297
- frames = create_fade_animation(
298
- object_type='emoji',
299
- object_data={'emoji': '✨', 'size': 120},
300
- num_frames=30,
301
- fade_type='in',
302
- easing='ease_out'
303
- )
304
- builder.add_frames(frames)
305
- builder.save('fade_in.gif', num_colors=128)
306
-
307
- # Example 2: Crossfade
308
- builder.clear()
309
- frames = create_crossfade(
310
- object1_data={'emoji': '😊', 'size': 100},
311
- object2_data={'emoji': '😂', 'size': 100},
312
- num_frames=30,
313
- object_type='emoji'
314
- )
315
- builder.add_frames(frames)
316
- builder.save('fade_crossfade.gif', num_colors=128)
317
-
318
- # Example 3: Blink
319
- builder.clear()
320
- frames = create_fade_animation(
321
- object_type='emoji',
322
- object_data={'emoji': '👀', 'size': 100},
323
- num_frames=20,
324
- fade_type='blink'
325
- )
326
- builder.add_frames(frames)
327
- builder.save('fade_blink.gif', num_colors=128)
328
-
329
- print("Created fade animations!")