add-effect
Add a new visual effect to ProAnimate. Covers effect function creation, dispatcher registration, and style filter integration.
SKILL.mdAutoAnimation/.claude/skills/add-effect/SKILL.md
GitHubAdd Visual Effect to ProAnimate
Create a new canvas-based visual effect that processes character sprites or images. Effects use an LRU cache system for sync lookups during RAF rendering.
Steps
Define effect settings in
src/types/styleEffects.tsAdd a new settings interface:
export interface MyEffectSettings { enabled: boolean // Effect-specific parameters with ranges in comments intensity: number // (0-1) someParam: number // (min-max) }Add the effect type to the
StyleEffectTypeunion:export type StyleEffectType = 'pixel-art' | 'woodcut' | ... | 'my-effect'Add to the
ActiveStyleEffectsettings union and theDEFAULT_SETTINGSmap.If the effect is animated (changes per frame), add it to the
ANIMATED_EFFECTSset.Create effect file in
src/services/effects/<myEffect>Effect.tsUse the
createEffectCachefactory:import type { MyEffectSettings } from '@/types/styleEffects' import { createEffectCache, loadImage } from '@/services/styleEffectCache' function processImage( img: HTMLImageElement, settings: MyEffectSettings, seed?: number, ): string { const canvas = document.createElement('canvas') canvas.width = img.naturalWidth canvas.height = img.naturalHeight const ctx = canvas.getContext('2d')! // Draw original image ctx.drawImage(img, 0, 0) // Get pixel data const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) const data = imageData.data // Apply effect to pixel data... // ... ctx.putImageData(imageData, 0, 0) return canvas.toDataURL('image/png') } export const myEffectCache = createEffectCache<MyEffectSettings>({ name: 'my-effect', maxEntries: 200, cacheKeyFn: (src, settings, seed) => `my-effect:${src}:${settings.intensity}:${settings.someParam}:${seed ?? 0}`, processFn: async (src, settings, seed) => { const img = await loadImage(src) return processImage(img, settings, seed) }, // Optional: canvas overlay for rigged character mode overlayFn: (source, output, settings, seed) => { // Process source canvas directly onto output canvas // Return true if applied, false if skipped return true }, })Register in the effect dispatcher at
src/services/effects/effectDispatcher.tsImport the cache:
import { myEffectCache } from './myEffectEffect'Add to the
getCache()switch:case 'my-effect': return myEffectCacheAdd to
clearEffectCache():myEffectCache.clear()Add SVG filter (optional) in
src/services/styleEffectFilters.tsIf the effect can also be expressed as an SVG filter for DOM-based rendering (Remotion export), add a filter definition and register it in
isSVGFilterEffect()andgetStyleEffectFilterStyle().Add UI controls in the Style & Effects panel
The effect settings panel at
src/components/panels/(mixed-media tab) should include controls for the new effect's settings. Add a section with sliders/toggles matching the settings interface.
Key Files
| Purpose | Path |
|---|---|
| Effect types & settings | src/types/styleEffects.ts |
| Cache factory | src/services/styleEffectCache.ts |
| Effect implementation | src/services/effects/<myEffect>Effect.ts |
| Effect dispatcher | src/services/effects/effectDispatcher.ts |
| SVG filter integration | src/services/styleEffectFilters.ts |
| SVG filter components | src/components/canvas/StyleEffectFilters.tsx |
| Remotion filter export | src/remotion/RemotionStyleFilter.tsx |
Cache System Architecture
The effect system uses a three-tier approach:
createEffectCache()-- LRU cache factory with inflight dedup and batch pre-cacheeffectDispatcher.ts-- Routes all effect calls to the correct cache by type string- Canvas components call
getEffectFromCache()synchronously during RAF for zero-flicker rendering
Key cache factory config (EffectCacheConfig):
name-- debug labelmaxEntries-- LRU limit (typically 200)cacheKeyFn-- builds unique key from src + settings + seedprocessFn-- async image processing functionoverlayFn-- optional sync canvas-to-canvas for rigged mode
Common Issues
- Effect not appearing: Verify the type string matches exactly in
StyleEffectType,getCache()switch, andclearEffectCache(). - Flickering: The effect must be pre-cached before rendering. Use
preCacheEffect()when the effect is enabled or settings change. - Animated effects: Must be in the
ANIMATED_EFFECTSset. The seed changes per frame based onspeedsetting viagetAnimatedSeed(). - Performance: Keep
processFnfast. Large images may need downscaling. The overlay path processes at source resolution.
Example
Reference: src/services/effects/woodcutEffect.ts
- Implements line pattern generation with configurable angle, weight, spacing
- Uses
createEffectCachewith customcacheKeyFnincorporating all settings - Provides both
processFn(async, returns data URL) andoverlayFn(sync, canvas-to-canvas) - Registered in
effectDispatcher.tsswitch as'woodcut': return woodcutCache