mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-13 09:02:54 +00:00
8 KiB
8 KiB
reanimate
Reanimate is a reactive framework for creating non-interactive animations from SVG images. This package consists of a set of arrow combinators, a renderer (using ffmpeg), and a web-based previewer. Inline latex code is supported when 'latex' and 'dvisvgm' are installed.
Nothing about the API is stable at this point.
Live coding playground: https://lemmih.github.io/reanimate/
TODO
website for live codingbounding boxes- Improve bounding box approximations
- Select desired FPS in web previewer
- Download rendered animations in web previewer
- alignment and positioning combinators
- Use svg-tree instead of lucid-svg
Examples
The example gifs are displayed at 25 fps.
Drawing latex equations
latex_draw :: Ani ()
latex_draw = pauseAtEnd 1 $ proc () -> do
emit -< toHtml $ mkBackground "black"
drawText msg `andThen` fillText msg -< ()
where
msg = "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}"
placement = translate (320/2) (180/2) . scale 5
fillText txt = proc () -> do
duration 1 -< ()
s <- signal 0 1 -< ()
emit -< toHtml $ placement $
withFillColor "white" $ withFillOpacity s $
center $ latexAlign txt
drawText txt = proc () -> do
duration 2 -< ()
s <- signal 0 1 -< ()
emit -< toHtml $ placement $
withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) $
partialSvg s $ center $ latexAlign txt
Bounding boxes
bbox :: Ani ()
bbox = proc () -> do
emit -< toHtml $ mkBackground "black"
duration 5 -< ()
annotate' bbox1 -< g_ [transform_ $ Lucid.translate (320/2-50) (180/2)]
annotate' bbox2 -< g_ [transform_ $ Lucid.translate (320/2+50) (180/2)]
bbox1 :: Ani ()
bbox1 = proc () -> do
s <- signal 0 1 -< ()
emit -< do
toHtml $ mkBoundingBox $ rotate (360*s) svg
toHtml $ withFillColor "white" $ rotate (360*s) svg
where
svg = scale 3 $ center $ latexAlign "\\sum_{k=1}^\\infty"
bbox2 :: Ani ()
bbox2 = proc () -> do
s <- signalOscillate 0 1 -< ()
emit -< do
toHtml $ mkBoundingBox $ partialSvg s heartShape
toHtml $ withStrokeColor "white" $ withFillOpacity 0 $ partialSvg s heartShape
Colorful LaTeX
latex_color :: Ani ()
latex_color = proc () -> do
duration 0.1 -< ()
emit -< toHtml $ mkBackground "black"
emit -< toHtml $ translate (320/2) (180/2) $ withStrokeWidth (Num 0.2) $
withStrokeColor "white" $
withSubglyphs [0] (withFillColor "blue") $
withSubglyphs [1] (withFillColor "yellow") $
withSubglyphs [2] (withFillColor "green") $
withSubglyphs [3] (withFillColor "red") $
withSubglyphs [4] (withFillColor "darkslategrey") $
svg
where
svg = scale 10 $ center $ latex "\\LaTeX"
Bezier curves
Sine wave
sinewave :: Ani ()
sinewave = proc () -> do
duration 10 -< ()
emit -< toHtml $ mkBackground "black"
idx <- signalOscillate 0 1 -< ()
emit -< do
defs_ $ clipPath_ [id_ "clip"] $ toHtml $
mkRect (Num 0, Num (-height)) (Num $ idx*width) (Percent 100)
toHtml $ translate margin height $ withStrokeColor "white" $
withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
toHtml $ withStrokeColor "white" $
mkLine (Num margin, Num 10) (Num margin, Num 170)
toHtml $ withStrokeColor "white" $
mkLine (Num margin, Num height) (Num (margin+width), Num height)
let (circX, circY) = wave idx
emit -< g_ [transform_ $ Lucid.translate margin height] $
circle_ [num_ cx_ circX, num_ cy_ circY, r_ "3", fill_ "red"]
where
freq = 3; margin = 30; width = 260; height = 90
wave idx = (idx*width, sin (idx*pi*2*freq) * 50)
Morphing wave
morph_wave_circle :: Ani ()
morph_wave_circle = proc t -> do
duration 5 -< ()
idx <- signalOscillate 0 1 -< ()
emit -< toHtml $ withStrokeColor "white" $ mkGroup
[ mkBackground "black"
, translate 30 90 $ mkPathText $ renderPathText $ morphPath circle wave1 idx
, mkLine (Num 30, Num 10) (Num 30, Num 170)
, mkLine (Num 30, Num 90) (Num 290, Num 90) ]
where
freq = 5; width = 260; radius = 50
wave1 = approxFnData 1000 $ \idx -> (idx*width, sin (idx*pi*2*freq) * 20)
circle = approxFnData 1000 $ \idx ->
(cos (idx*pi*2+pi/2)*radius + width/2, sin (idx*pi*2+pi/2)*radius)
Morph wave to circle
morph_wave_circle :: Ani ()
morph_wave_circle = proc t -> do
duration 5 -< ()
emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
idx <- signalOscillate 0 1 -< ()
emit -< do
g_ [transform_ $ translate 30 90] $
renderPath $ morphPath circle wave1 idx
line_ [x1_ "30", x2_ "30", y1_ "10", y2_ "170", stroke_ "white"]
line_ [x1_ "30", x2_ "290", y1_ "90", y2_ "90", stroke_ "white"]
where
freq = 5; width = 260; radius = 50
wave1 = approxFnData 1000 $ \idx -> (idx*width, sin (idx*pi*2*freq) * 20)
circle = approxFnData 1000 $ \idx ->
(cos (idx*pi*2+pi/2)*radius + width/2, sin (idx*pi*2+pi/2)*radius)
Speed modification
progressMeters :: Ani ()
progressMeters = proc () -> do
emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
annotate' (adjustSpeed 1.0 progressMeter) -< g_ [transform_ $ translate 40 20]
annotate' (adjustSpeed 2.0 progressMeter) -< g_ [transform_ $ translate 140 20]
annotate' (adjustSpeed 0.5 progressMeter) -< g_ [transform_ $ translate 240 20]
emit -< do
text_ [x_ "55", y_ "150", font_size_ "20"
, text_anchor_ "middle"
, fill_ "white"] "1x"
text_ [x_ "155", y_ "150", font_size_ "20"
, text_anchor_ "middle"
, fill_ "white"] "2x"
text_ [x_ "255", y_ "150", font_size_ "20"
, text_anchor_ "middle"
, fill_ "white"] "0.5x"
Highlights
Clipping
clip_rect :: Ani ()
clip_rect = proc () -> do
emit -< toHtml $ mkBackground "black"
annotate' $ follow
[ sim
[ sim [ paintStatic prev | prev <- [max 0 (n-4) .. n-1] ]
, sim [ runAni "black" i | i <- [n-4], i>=0 ]
, runAni "white" n ]
| n <- [0..15]
] -< g_ [transform_ $ Lucid.translate (320/2) (180/2)]
where
paintStatic nth = proc () ->
emit -< toHtml $ withStrokeColor "white" $
square (20+nth*10)
runAni color nth = circle_clip $ proc () -> do
duration 1 -< ()
emit -< toHtml $ withStrokeColor color $
square (20+nth*10)
square side = center $ withFillOpacity 0 $ withStrokeWidth (Num 2) $
mkRect (Num 0, Num 0) (Num side) (Num side)
scaling
Animations can be reused, scaled and stretched in time. This example reuse four different animations of different lengths, scales their runtime so they're all in sync, and increases the playback speed.
scaling :: Ani ()
scaling = adjustSpeed 2 $ syncAll
[ proc () ->
annotate' animation -< g_ [transform_ $ Lucid.translate x y <> " " <> Lucid.scale 0.5 0.5]
| x <- [0,160]
, y <- [0,90]
| animation <- [sinewave, morph_wave, highlight, progressMeters]]
Valentine's Day
Basic LaTeX
latex_basic :: Ani ()
latex_basic = proc () -> do
duration 2 -< ()
s <- signalOscillate 0 1 -< ()
emit -< toHtml $ mkGroup
[ mkBackground "black"
, translate (320/2) (180/2) $ mkGroup
[ withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) text
, withFillColor "white" $ withFillOpacity s text] ]
where
text = scale 4 $ center $ latexAlign
"\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}"












