Feature showcase (#29)

* Add 'mkText'.

* Add builtin svg logo.

* Bump dependency on reanimate-svg.


Former-commit-id: 94cbf80b828ca3cb59e3bcf577299a6f84418fbf
This commit is contained in:
David Himmelstrup 2019-11-17 13:02:37 +08:00 committed by GitHub
commit 8694425284
21 changed files with 747 additions and 135 deletions

64
data/svg-logo.svg Normal file
View file

@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%"
height="100%"
viewBox="0 0 300 300">
<title>SVG Logo</title>
<desc>Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site.</desc>
<metadata id="license">
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/">
<cc:Work rdf:about="">
<dc:title>SVG Logo</dc:title>
<dc:date>14-08-2009</dc:date>
<dc:creator>
<cc:Agent><dc:title>W3C</dc:title></cc:Agent>
<cc:Agent><dc:title>Harvey Rayner, designer</dc:title></cc:Agent>
</dc:creator>
<dc:description>See document description</dc:description>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-nc-sa/2.5/" />
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-nc-sa/2.5/">
<cc:permits rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:prohibits rdf:resource="http://web.resource.org/cc/CommercialUse" />
<cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<defs>
<g id="SVG" fill="#ffffff" transform="scale(2) translate(20,79)">
<path id="S" d="M 5.482,31.319 C2.163,28.001 0.109,23.419 0.109,18.358 C0.109,8.232 8.322,0.024 18.443,0.024 C28.569,0.024 36.782,8.232 36.782,18.358 L26.042,18.358 C26.042,14.164 22.638,10.765 18.443,10.765 C14.249,10.765 10.850,14.164 10.850,18.358 C10.850,20.453 11.701,22.351 13.070,23.721 L13.075,23.721 C14.450,25.101 15.595,25.500 18.443,25.952 L18.443,25.952 C23.509,26.479 28.091,28.006 31.409,31.324 L31.409,31.324 C34.728,34.643 36.782,39.225 36.782,44.286 C36.782,54.412 28.569,62.625 18.443,62.625 C8.322,62.625 0.109,54.412 0.109,44.286 L10.850,44.286 C10.850,48.480 14.249,51.884 18.443,51.884 C22.638,51.884 26.042,48.480 26.042,44.286 C26.042,42.191 25.191,40.298 23.821,38.923 L23.816,38.923 C22.441,37.548 20.468,37.074 18.443,36.697 L18.443,36.692 C13.533,35.939 8.800,34.638 5.482,31.319 L5.482,31.319 L5.482,31.319 Z"/>
<path id="V" d="M 73.452,0.024 L60.482,62.625 L49.742,62.625 L36.782,0.024 L47.522,0.024 L55.122,36.687 L62.712,0.024 L73.452,0.024 Z"/>
<path id="G" d="M 91.792,25.952 L110.126,25.952 L110.126,44.286 L110.131,44.286 C110.131,54.413 101.918,62.626 91.792,62.626 C81.665,62.626 73.458,54.413 73.458,44.286 L73.458,44.286 L73.458,18.359 L73.453,18.359 C73.453,8.233 81.665,0.025 91.792,0.025 C101.913,0.025 110.126,8.233 110.126,18.359 L99.385,18.359 C99.385,14.169 95.981,10.765 91.792,10.765 C87.597,10.765 84.198,14.169 84.198,18.359 L84.198,44.286 L84.198,44.286 C84.198,48.481 87.597,51.880 91.792,51.880 C95.981,51.880 99.380,48.481 99.385,44.291 L99.385,44.286 L99.385,36.698 L91.792,36.698 L91.792,25.952 L91.792,25.952 Z"/>
</g>
</defs>
<path id="base" fill="#000" d="M8.5,150 H291.5 V250 C291.5,273.5 273.5,291.5 250,291.5 H50 C26.5,291.5 8.5,273.5 8.5,250 Z"/>
<g stroke-width="38.0086" stroke="#000">
<g id="svgstar" transform="translate(150, 150)">
<path id="svgbar" fill="#ffb13b"
d="M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z"/>
<use xlink:href="#svgbar" transform="rotate(45)"/>
<use xlink:href="#svgbar" transform="rotate(90)"/>
<use xlink:href="#svgbar" transform="rotate(135)"/>
</g>
</g>
<use xlink:href="#svgstar"/>
<use xlink:href="#base" opacity="0.85"/>
<use xlink:href="#SVG"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

11
examples/doc_githubIcon.hs Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Reanimate
import Reanimate.Builtin.Documentation
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ docEnv $ animate $ const githubIcon

11
examples/doc_haskellLogo.hs Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Reanimate
import Reanimate.Builtin.Documentation
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ docEnv $ animate $ const haskellLogo

11
examples/doc_svgLogo.hs Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Reanimate
import Reanimate.Builtin.Documentation
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ docEnv $ animate $ const svgLogo

View file

@ -32,6 +32,7 @@ data-files: viewer/build/*.js
data/CIExyz.csv
data/CIE_XYZ.csv
data/cone_sensitivity_lms.csv
data/*.svg
Source-Repository head
Type: git
@ -68,6 +69,7 @@ library
Reanimate.Chiphunk
Reanimate.PolyShape
Reanimate.Builtin.Documentation
Reanimate.Builtin.Images
other-modules: Reanimate.Cache
Reanimate.Driver.Check
Reanimate.Driver.CLI
@ -76,7 +78,7 @@ library
Paths_reanimate
build-depends: base >=4.10 && <5,
time, text, filepath, process, directory,
containers, reanimate-svg >= 0.9.3.1, xml, bytestring, lens, linear, mtl, matrix,
containers, reanimate-svg >= 0.9.4.0, xml, bytestring, lens, linear, mtl, matrix,
JuicyPixels, attoparsec, parallel,
cubicbezier, websockets,
hashable, fsnotify, open-browser, random-shuffle, base64-bytestring,

View file

@ -2,7 +2,6 @@ module Reanimate.Animation where
import Control.Arrow ()
import Data.Fixed (mod')
import qualified Data.Map as M
import Graphics.SvgTree (Alignment (..), Document (..),
Number (..),
PreserveAspectRatio (..),
@ -148,7 +147,6 @@ renderSvg w h t = ppDocument doc
, _width = w
, _height = h
, _elements = [withStrokeWidth defaultStrokeWidth $ scaleXY 1 (-1) t]
, _definitions = M.empty
, _description = ""
, _documentLocation = ""
, _documentAspectRatio = PreserveAspectRatio False AlignNone Nothing
@ -264,3 +262,15 @@ freezeAtPercentage frac (Animation d genFrame) =
-- <<docs/gifs/doc_signalA.gif>>
signalA :: Signal -> Animation -> Animation
signalA fn (Animation d gen) = Animation d $ gen . fn
takeA :: Double -> Animation -> Animation
takeA len (Animation d gen) = Animation len' $ \t ->
gen (t * len'/d)
where
len' = min len d
dropA :: Double -> Animation -> Animation
dropA len (Animation d gen) = Animation len' $ \t ->
gen (t * len'/d + len/d)
where
len' = max (d-len) 0

View file

@ -0,0 +1,32 @@
module Reanimate.Builtin.Images
( svgLogo
, haskellLogo
, githubIcon
) where
import qualified Data.ByteString as B
import Graphics.SvgTree (parseSvgFile)
import Paths_reanimate
import Reanimate.Animation
import Reanimate.Svg
import System.IO.Unsafe
embedImage :: FilePath -> IO SVG
embedImage key = do
svg_file <- getDataFileName key
svg_data <- B.readFile svg_file
case parseSvgFile svg_file svg_data of
Nothing -> error "Malformed svg"
Just svg -> return $ embedDocument svg
-- | <<docs/gifs/doc_svgLogo.gif>>
svgLogo :: SVG
svgLogo = unsafePerformIO $ embedImage "data/svg-logo.svg"
-- | <<docs/gifs/doc_haskellLogo.gif>>
haskellLogo :: SVG
haskellLogo = unsafePerformIO $ embedImage "data/haskell.svg"
-- | <<docs/gifs/doc_githubIcon.gif>>
githubIcon :: SVG
githubIcon = unsafePerformIO $ embedImage "data/github-icon.svg"

View file

@ -1,28 +1,44 @@
module Reanimate.Cache
( cacheMem
( cacheFile -- :: FilePath -> (FilePath -> IO ()) -> IO FilePath
, cacheMem
, cacheDisk
, cacheDiskSvg
, cacheDiskKey
, cacheDiskLines
) where
import Control.Monad (unless)
import Data.Hashable
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Tree (..), unparse)
import Reanimate.Animation (renderTree)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Tree (..), unparse)
import Reanimate.Animation (renderTree)
import System.Directory
import System.FilePath
import System.IO
import Control.Exception
import System.IO.Unsafe
import Text.XML.Light (Content (..), parseXML)
import Text.XML.Light (Content (..), parseXML)
import System.IO.Temp
-- Memory cache and disk cache
cacheFile :: FilePath -> (FilePath -> IO ()) -> IO FilePath
cacheFile template gen = do
root <- getXdgDirectory XdgCache "reanimate"
createDirectoryIfMissing True root
let path = root </> template
hit <- doesFileExist path
unless hit $ withSystemTempFile template $ \tmp h -> do
hClose h
gen tmp
renameFile tmp path
evaluate path
cacheDisk :: (T.Text -> Maybe a) -> (a -> T.Text) -> (Text -> IO a) -> (Text -> IO a)
cacheDisk parse render gen key = do
root <- getXdgDirectory XdgCache "reanimate"

View file

@ -38,6 +38,12 @@ fadeInE d t = withGroupOpacity (t/d)
fadeOutE :: Effect
fadeOutE = reverseE fadeInE
fadeLineInE :: Double -> Effect
fadeLineInE w d t = withStrokeWidth (w*(t/d))
fadeLineOutE :: Double -> Effect
fadeLineOutE = reverseE . fadeLineInE
drawInE :: Effect
drawInE d t = withFillOpacity 0 . partialSvg (t/d) . pathify
@ -48,3 +54,6 @@ fillInE :: Effect
fillInE d t = withFillOpacity f
where
f = t/d
scaleE :: Double -> Effect
scaleE target d t = scale (1 + (target-1) * t/d)

View file

@ -28,8 +28,10 @@ requireExecutable exec = do
runCmd :: FilePath -> [String] -> IO ()
runCmd exec args = do
_ <- runCmd_ exec args
return ()
ret <- runCmd_ exec args
case ret of
Left err -> error $ "exec " ++ exec ++ show args ++ " failed:\n" ++ err
Right{} -> return ()
runCmd_ :: FilePath -> [String] -> IO (Either String String)
runCmd_ exec args = do

View file

@ -1,50 +1,76 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedStrings #-}
module Reanimate.Povray
( povray
, povrayQuick
, povraySlow
, povrayExtreme
, povray'
, povrayQuick'
, povraySlow'
, povrayExtreme'
) where
import Codec.Picture.Png
import qualified Data.ByteString as B
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Tree (..))
import qualified Data.ByteString as B
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Tree (..))
import Reanimate.Cache
import Reanimate.Misc
import Reanimate.Raster
import Reanimate.Svg.Constructors
import System.FilePath (replaceExtension)
import System.IO.Unsafe (unsafePerformIO)
import System.FilePath (replaceExtension, (<.>))
import System.IO.Unsafe (unsafePerformIO)
import Data.Hashable
povrayRaw :: [String] -> Text -> Tree
povrayRaw args script =
-- memo [Key mkPovrayImage, KeyPrim args, KeyPrim script]
(unsafePerformIO $ mkPovrayImage args script)
povrayRaw' :: [String] -> Text -> FilePath
povrayRaw' args script =
(unsafePerformIO $ mkPovrayImage' args script)
povray :: [String] -> Text -> Tree
povray args = povrayRaw (["+H180","+W320", "+A"] ++ args)
povray' :: [String] -> Text -> FilePath
povray' args = povrayRaw' (["+H180","+W320", "+A"] ++ args)
povrayQuick :: [String] -> Text -> Tree
povrayQuick args = povrayRaw (["+H180","+W320"] ++ args)
povrayQuick' :: [String] -> Text -> FilePath
povrayQuick' args = povrayRaw' (["+H180","+W320"] ++ args)
povraySlow :: [String] -> Text -> Tree
povraySlow args = povrayRaw (["+H1440","+W2560", "+A"] ++ args)
povraySlow' :: [String] -> Text -> FilePath
povraySlow' args = povrayRaw' (["+H1440","+W2560", "+A"] ++ args)
povrayExtreme :: [String] -> Text -> Tree
povrayExtreme args = povrayRaw (["+H2160","+W3840", "+A"] ++ args)
povrayExtreme' :: [String] -> Text -> FilePath
povrayExtreme' args = povrayRaw' (["+H2160","+W3840", "+A"] ++ args)
mkPovrayImage :: [String] -> Text -> IO Tree
mkPovrayImage args script = cacheDiskKey key $ do
exec <- requireExecutable "povray"
withTempFile "pov" $ \pov_file -> do
let out = replaceExtension pov_file "png"
mkPovrayImage args script = do
out <- mkPovrayImage' args script
-- return $ center $ scaleToSize 16 9 $ embedImageFile out
png <- B.readFile out
case decodePng png of
Left{} -> error "bad image"
Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img
mkPovrayImage' :: [String] -> Text -> IO FilePath
mkPovrayImage' args script = cacheFile template $ \target -> do
exec <- requireExecutable "povray"
let pov_file = replaceExtension target "pov"
T.writeFile pov_file script
ret <- runCmd_ exec (args ++ ["-D","+UA", pov_file, "+o"++out])
case ret of
Left err -> error $ "povray went wrong:\n" ++ err
Right{} -> do
png <- B.readFile out
case decodePng png of
Left{} -> error "bad image"
Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img
runCmd exec (args ++ ["-D","+UA", pov_file, "+o"++target])
where
template = show (hash key) <.> "png"
key = T.concat (script:map T.pack args)

View file

@ -3,25 +3,33 @@ module Reanimate.Raster
, embedDynamicImage
, embedPng
, raster
, vectorize
, vectorize_
, svgAsPngFile
) where
import Codec.Picture.Types
import Codec.Picture
import Control.Lens ((.~),(&))
import Codec.Picture.Types (dynamicMap)
import Control.Lens ((&), (.~))
import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64.Lazy as Base64
import qualified Data.ByteString.Lazy.Char8 as LBS
import Graphics.SvgTree (Number (..), Tree (..),
defaultSvg)
import qualified Graphics.SvgTree as Svg
import Reanimate.Misc
import Reanimate.Animation
import Reanimate.Svg.Constructors
import System.FilePath
import System.Directory
import Data.Hashable
import Graphics.SvgTree (Number (..), Tree (..),
defaultSvg,parseSvgFile)
import qualified Graphics.SvgTree as Svg
import Reanimate.Animation
import Reanimate.Misc
import Reanimate.Cache
import Reanimate.Svg.Constructors
import Reanimate.Svg.Unuse
import System.Directory
import System.FilePath
import System.IO.Unsafe
import System.IO.Temp
import System.IO
{-# INLINE embedImage #-}
@ -53,6 +61,22 @@ embedDynamicImage img = embedPng width height imgData
Left err -> error err
Right dat -> dat
-- embedImageFile :: FilePath -> Tree
-- embedImageFile path = unsafePerformIO $ do
-- png <- B.readFile path
-- case decodePng png of
-- Left{} -> error "bad image"
-- Right img -> return $
-- let width = fromIntegral $ dynamicMap imageWidth img
-- height = fromIntegral $ dynamicMap imageHeight img in
-- ImageTree $ defaultSvg
-- & Svg.imageCornerUpperLeft .~ (Svg.Num (-width/2), Svg.Num (-height/2))
-- & Svg.imageWidth .~ Svg.Num width
-- & Svg.imageHeight .~ Svg.Num height
-- & Svg.imageHref .~ ("file://" ++ path)
raster :: Tree -> DynamicImage
raster svg = unsafePerformIO $ do
png <- B.readFile (svgAsPngFile svg)
@ -60,27 +84,48 @@ raster svg = unsafePerformIO $ do
Left{} -> error "bad image"
Right img -> return img
vectorize :: FilePath -> Tree
vectorize = vectorize_ []
vectorize_ :: [String] -> FilePath -> Tree
vectorize_ args path = unsafePerformIO $ do
root <- getXdgDirectory XdgCache "reanimate"
createDirectoryIfMissing True root
let svgPath = root </> show key <.> "svg"
hit <- doesFileExist svgPath
unless hit $
withSystemTempFile "file.svg" $ \tmpSvgPath svgH ->
withSystemTempFile "file.bmp" $ \tmpBmpPath bmpH -> do
hClose svgH
hClose bmpH
potrace <- requireExecutable "potrace"
convert <- requireExecutable "convert"
runCmd convert [ path, tmpBmpPath ]
runCmd potrace (args ++ ["--svg", "--output", tmpSvgPath, tmpBmpPath])
renameFile tmpSvgPath svgPath
svg_data <- B.readFile svgPath
case parseSvgFile svgPath svg_data of
Nothing -> do
removeFile svgPath
error "Malformed svg"
Just svg -> return $ unbox $ replaceUses svg
where
key = hash (path, args)
-- imageAsFile :: DynamicImage -> FilePath
-- imageAsFile img
svgAsPngFile :: Tree -> FilePath
svgAsPngFile svg = unsafePerformIO $ do
root <- getXdgDirectory XdgCache "reanimate"
createDirectoryIfMissing True root
let svgPath = root </> show (hash rendered) <.> "svg"
pngPath = replaceExtension svgPath "png"
hit <- doesFileExist pngPath
if hit
then return pngPath
else do
-- ffmpeg <- requireExecutable "ffmpeg"
-- convert <- requireExecutable "convert"
inkscape <- requireExecutable "inkscape"
writeFile svgPath rendered
-- runCmd convert [ "-background", "none", "-antialias", svgPath, pngPath ]
runCmd inkscape [ svgPath, "--export-png=" ++ pngPath, "--without-gui" ]
return pngPath
svgAsPngFile svg = unsafePerformIO $ cacheFile template $ \pngPath -> do
let svgPath = replaceExtension pngPath "svg"
-- ffmpeg <- requireExecutable "ffmpeg"
-- convert <- requireExecutable "convert"
inkscape <- requireExecutable "inkscape"
writeFile svgPath rendered
-- runCmd convert [ "-background", "none", "-antialias", svgPath, pngPath ]
runCmd inkscape [ svgPath, "--export-png=" ++ pngPath, "--without-gui" ]
where
template = show (hash rendered) <.> "png"
rendered = renderSvg (Just $ Num width) (Just $ Num height) svg
width = 2560
height = width * 9 / 16

View file

@ -290,6 +290,14 @@ spriteE (Sprite born ref) effect = do
let (svg', z) = render d t svg
in (delayE (now-born) effect d t svg', z))
spriteZ :: Sprite s -> ZIndex -> Scene s ()
spriteZ (Sprite born ref) zindex = do
now <- queryNow
liftST $ modifySTRef ref $ \(ttl, render) ->
(ttl, \d t svg ->
let (svg', z) = render d t svg
in (svg', if t < now-born then z else zindex))
{-
data Var s a = Var (STRef s (Time -> a))
data Sprite s = Sprite (STRef s (Duration, Duration -> Time -> (SVG, ZIndex)))

View file

@ -4,6 +4,7 @@ module Reanimate.Signal
, fromToS
, reverseS
, curveS
, powerS
, bellS
, oscillateS
, fromListS
@ -65,6 +66,9 @@ curveS steepness s =
then 0.5 * (2*s)**steepness
else 1-0.5 * (2 - 2*s)**steepness
powerS :: Double -> Signal
powerS steepness s = s**steepness
-- | Oscillate signal.
--
-- Example:

View file

@ -86,6 +86,9 @@ mkColor name =
withStrokeColor :: String -> Tree -> Tree
withStrokeColor color = strokeColor .~ pure (mkColor color)
withStrokeColorPixel :: PixelRGBA8 -> Tree -> Tree
withStrokeColorPixel color = strokeColor .~ pure (ColorRef color)
withStrokeLineJoin :: LineJoin -> Tree -> Tree
withStrokeLineJoin ljoin = strokeLineJoin .~ pure ljoin
@ -180,3 +183,10 @@ gridLayout rows = mkGroup
where
rowSep = screenHeight / fromIntegral (nRows+1)
nRows = length rows
mkText :: T.Text -> Tree
mkText str =
TextTree Nothing $ defaultSvg
& textRoot .~ span_
where
span_ = defaultSvg & spanContent .~ [SpanText str]

View file

@ -1,18 +1,17 @@
module Reanimate.Svg.Unuse
( replaceUses
, unbox
, embedDocument
) where
import Control.Lens ((%~), (&), (.~), (^.),(?~))
import qualified Data.Map as Map
import Graphics.SvgTree hiding (height, line, path, use,
width)
import Control.Lens ((%~), (&), (.~), (?~), (^.))
import qualified Data.Map as Map
import Graphics.SvgTree hiding (line, path, use)
import Reanimate.Constants
import Reanimate.Svg.Constructors
replaceUses :: Document -> Document
replaceUses doc = doc & elements %~ map (mapTree replace)
& definitions .~ Map.empty
where
replaceDefinition PathTree{} = None
replaceDefinition t = t
@ -32,8 +31,7 @@ replaceUses doc = doc & elements %~ map (mapTree replace)
(Num a, Num b) -> Translate a b
_ -> TransformUnknown
docTree = mkGroup (doc^.elements)
idMap = foldTree updMap Map.empty docTree `Map.union`
(doc^.definitions)
idMap = foldTree updMap Map.empty docTree
updMap m tree =
case tree^.attrId of
Nothing -> m
@ -48,3 +46,12 @@ unbox doc@Document{_viewBox = Just (minx, minw, _width, _height)} =
unbox doc =
GroupTree $ defaultSvg
& groupChildren .~ doc^.elements
embedDocument :: Document -> Tree
embedDocument doc =
translate (-screenWidth/2) (screenHeight/2) $
withFillOpacity 1 $
withStrokeWidth 0 $
flipYAxis $
SvgTree $ doc & width .~ Nothing
& height .~ Nothing

View file

@ -6,7 +6,7 @@ packages:
- .
extra-deps:
- reanimate-svg-0.9.3.1
- reanimate-svg-0.9.4.0
- chiphunk-0.1.2.1
- cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3
- fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7

View file

@ -6,7 +6,7 @@ packages:
- .
extra-deps:
- reanimate-svg-0.9.3.1
- reanimate-svg-0.9.4.0
- chiphunk-0.1.2.1
- cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3
- fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7

View file

@ -6,7 +6,7 @@ packages:
- .
extra-deps:
- reanimate-svg-0.9.3.1
- reanimate-svg-0.9.4.0
- chiphunk-0.1.2.1
- cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3
- fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7

View file

@ -5,61 +5,19 @@
packages:
- completed:
hackage: reanimate-svg-0.9.3.1@sha256:ce3783b5d8437ec39f60c4bc9199068dbfe5d074baecee085303c5c850f9428f,2448
hackage: reanimate-svg-0.9.4.0@sha256:6acada3f3fea83e8b5e8ae5c9960b2fafdde8e6a48c32f094e6aa346bc7cc226,2448
pantry-tree:
size: 1117
sha256: 470085c4740a4790e9d96c588c17ddea879f7b4a793041eebfa982625b69450f
sha256: 4cdd5f71443fc486f561b5a0d50b87d6890cd7fb5fbf1f8c0899b40a0e70d27e
original:
hackage: reanimate-svg-0.9.3.1
hackage: reanimate-svg-0.9.4.0
- completed:
hackage: palette-0.3.0.2@sha256:50e210b7d21a0c394a6d672cc3c6d3bffcfa1be07419d72b99e68cfb65d6ca3c,1485
pantry-tree:
size: 6342
sha256: 9285e95a5701636961e83ed2036cca52a6b66e5ea809500ed6b845bc920f2791
original:
hackage: palette-0.3.0.2
- completed:
hackage: chiphunk-0.1.2.0@sha256:bd129c91033140cb5ad9cc3c446baccdc377a17214026c79c9705c7d886e54cb,4619
hackage: chiphunk-0.1.2.1@sha256:1e17e0e3d2fc91317e2cfcb8fd9f7add4a5038d76a4ae6b444641c0a2adec881,4619
pantry-tree:
size: 6333
sha256: 155b48a7a4ecd4a36b853445b27acec55ebe1247f206cf719613fee43a1a4d7b
sha256: 505d40929bd792b8003c0d03ec93d99ecba5e7927fbc6193824c0dab92c54dec
original:
hackage: chiphunk-0.1.2.0
- completed:
hackage: diagrams-1.4@sha256:3e36369e84115b900fd9dcb570672a188339a470eb19ca62170775cd835cf8ca,5483
pantry-tree:
size: 208
sha256: 8f2ea432c67f4711446942e032be53ed7dc49c1b1dd7d26ff71fbd4d05a02c4d
original:
hackage: diagrams-1.4@sha256:3e36369e84115b900fd9dcb570672a188339a470eb19ca62170775cd835cf8ca
- completed:
hackage: diagrams-contrib-1.4.3@sha256:bcfa6c85f8c33b8c48c3a61b7216afdebd51cd793c50da3a2dd358827d25fc76,4693
pantry-tree:
size: 10858
sha256: cadfa25f496761b205a0583bef05ebe84003a64c2d66f86f64ba1f05a79ab916
original:
hackage: diagrams-contrib-1.4.3@sha256:bcfa6c85f8c33b8c48c3a61b7216afdebd51cd793c50da3a2dd358827d25fc76
- completed:
hackage: diagrams-core-1.4.1.1@sha256:6ef6b17785d77997c481eb085570e21b6a00cc91d086fbf49490504130ebc7d1,2999
pantry-tree:
size: 2018
sha256: 1a28c53443667f0a0cf43224e935257973ee234d721e8d2a49a41ab8d8a97a35
original:
hackage: diagrams-core-1.4.1.1@sha256:6ef6b17785d77997c481eb085570e21b6a00cc91d086fbf49490504130ebc7d1
- completed:
hackage: diagrams-lib-1.4.2.3@sha256:47af9bd9249f16e164a0ba014dcc0aaf47a787b5d3ff78a1bf38ed80f5a5c13e,8114
pantry-tree:
size: 11529
sha256: 5520075cab7bcccdbc35f55318a44792df58399a9a085a577d1229ece040770a
original:
hackage: diagrams-lib-1.4.2.3@sha256:47af9bd9249f16e164a0ba014dcc0aaf47a787b5d3ff78a1bf38ed80f5a5c13e
- completed:
hackage: diagrams-svg-1.4.2@sha256:1b0b473c9825f6abda5b1551376dc5c4adcc995cc1705324231c32a57a415697,3197
pantry-tree:
size: 475
sha256: 2ca5139bf68f3a5bce17f48d964e6067c168c3b1d164b8d61537b8d6bf17df06
original:
hackage: diagrams-svg-1.4.2@sha256:1b0b473c9825f6abda5b1551376dc5c4adcc995cc1705324231c32a57a415697
hackage: chiphunk-0.1.2.1
- completed:
hackage: cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3,2243
pantry-tree:
@ -67,27 +25,6 @@ packages:
sha256: 1a6e3a16c3d33c8baac53b4345b29695ff90cf2b559cfed349ab1bc454949632
original:
hackage: cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3
- completed:
hackage: diagrams-solve-0.1.1@sha256:ad04ac1c1b1071f906a9879de00997bd543cc0a46c83c3a38199a87702674afa,1699
pantry-tree:
size: 469
sha256: 1f224951aa3f7ca9e0ec693d9aff30b58368a44aaa83c7764b5f9fe9acf3822e
original:
hackage: diagrams-solve-0.1.1@sha256:ad04ac1c1b1071f906a9879de00997bd543cc0a46c83c3a38199a87702674afa
- completed:
hackage: dual-tree-0.2.2@sha256:b04a53694ad1e8c34f9e2f2b96cd6c3ae59d782caba14b6d523d6205436e1165,2908
pantry-tree:
size: 387
sha256: cad942894995e1c47d4c931562083d93764b5e46b676751038810f92c6b6f360
original:
hackage: dual-tree-0.2.2@sha256:b04a53694ad1e8c34f9e2f2b96cd6c3ae59d782caba14b6d523d6205436e1165
- completed:
hackage: mfsolve-0.3.2.0@sha256:19ce2822b8d3cf906e983a237341698349dcef49d873b1b03d141276b4b0e8dc,1155
pantry-tree:
size: 262
sha256: 194ef83e8a4008c156a76b87fcda74dd3ea5ca3274d3a01b63ca62bd5d8228a2
original:
hackage: mfsolve-0.3.2.0@sha256:19ce2822b8d3cf906e983a237341698349dcef49d873b1b03d141276b4b0e8dc
- completed:
hackage: fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7,1387
pantry-tree:

407
videos/showcase/showcase.hs Normal file
View file

@ -0,0 +1,407 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Main (main) where
import Codec.Picture
import Control.Lens ((^.))
import Control.Monad
import Data.Fixed
import Data.Maybe
import Data.Monoid
import Data.String.Here
import Data.Text (Text)
import Graphics.SvgTree (Number (..), strokeWidth, toUserUnit)
import Reanimate
import Reanimate.Animation
import Reanimate.Builtin.Images
import Reanimate.Effect
import Reanimate.Povray
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg
{- SCRIPT
Some ideas lend themselves well to be illustrated. Take spheres, for example: It
just so happens that the surface of a sphere is exactly 4 times the area of a
circle with the same radius.
Now, this relationship has already been visually explored by others in mucher
greater detail so I'll leave it at this. But there are countless other ideas and
concepts that deserve to be illustrated yet have not. I want to remedy this, in
part, by animating ideas I find interesting, but also by encouraging you to make
your own animations.
Every video on this channel, including this one, will be open-source with the source files
linked in the description. If any of the graphics pique your interest then
please download the code and play with it. The animations are created with a
library called 'reanimate' which, at its core, is a set of tools for generating
individual frames as SVG images. Layered on top of this core are external
components such as:
* LaTeX, for typesetting equations,
* a bitmap tracer, for turning pixel data into vector graphics,
* and a raytracer, for generating 3D graphics,
Raytracing is inherently a pixel-based graphics technique but, with a bit of math,
it and vector graphics can be overlayed with pixel-perfect precision.
Everything is held together by the purely functional language Haskell. Haskell
is particularlly well suited for this task since animations are inherently
immutable.
-}
main :: IO ()
main = reanimate $ animate (const $ mkBackground "black") `parA`
-- animate $ const $ checker 10 10
-- rotateSphere
-- rotateWireSphere
sphereIntro
-- introSVG
-- mapA (scale 2) $ setDuration 20 featLaTeX
playbackTest :: Animation
playbackTest = setDuration 10 feat3D
sphereIntro :: Animation
sphereIntro = sceneAnimation $ do
-- play $ drawSphere
-- # setDuration 15
-- # pauseAtEnd 2
-- play $ rotateWireSphere
-- # setDuration 2
-- # signalA (powerS 2)
fork $ play $ rotateWireSphere
# setDuration 1
# repeatA 10
# takeA (2+5)
# applyE (delayE 2 fadeOutE)
fork $ play $ rotateSphere
# setDuration 1
# repeatA 15
# applyE (overBeginning 2 $ constE $ withGroupOpacity 0)
# applyE (delayE 2 $ overBeginning 5 fadeInE)
wait 7
playZ 1 $ setDuration 3 $ animate $ \t ->
partialSvg t $
withFillOpacity 0 $
rotate 180 $
pathify $
circ
-- playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
-- withFillOpacity t $
-- circ
let scaleFactor = 0.05
fork $ playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
let p = curveS 3 t in
withFillOpacity p $
translate (1*p) (2*p) $
scale (1-scaleFactor*p) $
circ
fork $ playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
let p = curveS 3 t in
withFillOpacity p $
translate (1*p) (-2*p) $
scale (1-scaleFactor*p) $
circ
fork $ playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
let p = curveS 3 t in
withFillOpacity p $
translate (5*p) (2*p) $
scale (1-scaleFactor*p) $
circ
fork $ playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
let p = curveS 3 t in
withFillOpacity p $
translate (5*p) (-2*p) $
scale (1-scaleFactor*p) $
circ
where
circ =
withFillColor "blue" $
withStrokeColor "white" $
mkCircle 2
circEq = mkGroup
[ circ
, withFillColor "white" $
scale 0.5 $
center $ latexAlign "A=\\pi r^2"]
mkFeatSprite :: Double -> Double -> Animation
-> Scene s (Var s Double, Var s Double, Sprite s)
mkFeatSprite xPos yPos ani = do
spriteAt <- newVar 0
spriteTMod <- newVar 0
sprite <- newSprite $ do
genAt <- freezeVar spriteAt
genT <- freezeVar spriteTMod
return $ \real_t d t ->
let i = 1-genAt real_t in
translate (xPos*i) (yPos*i) $
scale (1+0.5*genAt real_t) $
frameAtT (((t+genT real_t)/d) `mod'` 1) ani
return (spriteAt, spriteTMod, sprite)
featSVG :: Animation
featSVG = animate $ const $ scale 0.4 $ svgLogo
feat3D :: Animation
feat3D = rotateSphere
# mapA (scale 0.5)
# repeatA 10
frameAtT :: Double -> Animation -> SVG
frameAtT t (Animation d f) = f t
featLaTeX :: Animation
featLaTeX = animate $ \t ->
translate 0 0.5 $
mkGroup
[ scale 1.5 $
center $
withFillColor "white" $
latex "\\LaTeX"
, frameAtT t $
fadeTransitions 0.2 $ map mkEQ [eq1, eq3, eq4, eq5]
]
where
eq1 = "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}"
eq2 = "e=mc^2"
-- eq3 = "\\int_{a}^{b}f'(x)dx=f(b)-f(a)"
eq3 = "\\Delta \\times E=- \\frac{\\partial B}{\\partial t}"
eq4 = "\\frac{d}{dt}(\\frac{\\partial L}{\\partial \\dot{q}}) = \\frac{\\partial L}{\\partial q}"
eq5 = "\\vec{E} = \\frac{\\sigma}{2\\epsilon_0}\\hat{n}"
mkEQ txt =
drawAnimation $
withStrokeColor "white" $
withFillColor "white" $
withStrokeWidth 0.01 $
translate 0 (-1.5) $
scale 0.5 $
center $
latexAlign txt
fadeTransition :: Double -> Animation -> Animation -> Animation
fadeTransition overlap a b =
(a
# pauseAtEnd overlap
# applyE (overEnding overlap $ fadeOutE)
) `seqA` (
b
# applyE (overBeginning overlap $ fadeInE)
)
fadeTransitions :: Double -> [Animation] -> Animation
fadeTransitions overlap = foldl (fadeTransition overlap) (pause 0)
featWireSphere :: Animation
featWireSphere = rotateWireSphere
# mapA (scale 0.5)
# reverseA
# repeatA 10
introSVG :: Animation
introSVG = sceneAnimation $ do
fork $ play $ animate $ const $
mkBackground "black"
-- Title
title <- newSprite $ do
return $ \_ _d _t ->
translate 0 3.5 $
center $
withFillColor "white" $
latex "reanimate"
spriteZ title 2
-- Shading
shadeOpacity <- newVar 0
shade <- newSprite $ do
opacity <- freezeVar shadeOpacity
return $ \real_t d t ->
withFillOpacity (0.8 * opacity real_t) $
withFillColor "black" $
mkRect screenWidth screenHeight
spriteZ shade 1
-- Modifier
let tweenFeat sp var varT initWait dur = do
wait initWait
spriteZ sp 2
tweenVar shadeOpacity 1 $ \t i -> fromToS i 1 (curveS 2 t)
tweenVar var 1 $ \t i -> fromToS i 1 (curveS 2 t)
tweenVar varT 1 $ \t i -> fromToS i 1 (curveS 2 t)
wait 1
tweenVar varT dur $ \t i -> fromToS i (1+dur) (t/dur)
wait dur
tweenVar var 1 $ \t i -> fromToS i 0 (curveS 2 t)
tweenVar varT dur $ \t i -> fromToS i (1+dur+1) $ curveS 2 (t/dur)
tweenVar shadeOpacity 1 $ \t i -> fromToS i 0 (curveS 2 t)
wait 1
spriteZ sp 0
-- SVG
(svgAt, svgT, svgS) <- mkFeatSprite (-5.5) (1.5) featSVG
fork $ tweenFeat svgS svgAt svgT svgHighlight svgHighlightDur
-- LaTeX
(latexAt, latexT, latexS) <- mkFeatSprite (5.5) (1.5) featLaTeX
fork $ tweenFeat latexS latexAt latexT latexHighlight latexHighlightDur
-- Tracing
(traceAt, traceT, traceS) <- mkFeatSprite (-5.5) (-2.5) featWireSphere
fork $ tweenFeat traceS traceAt traceT traceHighlight traceHighlightDur
-- Raytracing
(rayAt, rayT, rayS) <- mkFeatSprite (5.5) (-2.5) feat3D
fork $ tweenFeat rayS rayAt rayT rayHighlight rayHighlightDur
-- wait
wait $ rayHighlight + rayHighlightDur + 2 + 10
return ()
where
svgHighlight = 1
svgHighlightDur = 3
latexHighlight = svgHighlight+svgHighlightDur+2
latexHighlightDur = 3
traceHighlight = latexHighlight+latexHighlightDur+2
traceHighlightDur = 3
rayHighlight = traceHighlight+traceHighlightDur+2
rayHighlightDur = 3
drawAnimation :: SVG -> Animation
drawAnimation = drawAnimation' 0.5 0.3
drawAnimation' :: Double -> Double -> SVG -> Animation
drawAnimation' fillDur step svg = sceneAnimation $ do
forM_ (zip [0..] $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
let sWidth =
case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
Just (Num d) -> d
_ -> defaultStrokeWidth
fork $ do
wait (n*step)
play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)
# applyE (overEnding fillDur $ fadeLineOutE sWidth))
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t d t ->
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
-- play $ animate (\t -> withStrokeWidth 0 $ fn $ withFillOpacity t tree)
-- # setDuration fillDur
-- # pauseAtEnd ((len-n)*step)
where
len = fromIntegral $ length $ svgGlyphs svg
drawSphere :: Animation
drawSphere = animate $ \t ->
partialSvg t $
withStrokeColor "white" $
withStrokeWidth 0.01 $
withFillOpacity 0 $
lowerTransformations $
flipYAxis $
translate (-screenWidth/2) (-screenHeight/2) $
scale 0.00625 $
mkPath $ extractPath $
vectorize_ ["-i"] $
povraySlow' [] (script (svgAsPngFile texture) 0)
rotateWireSphere :: Animation
rotateWireSphere = animate $ \t ->
withStrokeColor "white" $
withStrokeWidth 0.01 $
withFillOpacity 0 $
lowerTransformations $
flipYAxis $
translate (-screenWidth/2) (-screenHeight/2) $
scale (screenWidth/2560) $
mkPath $ extractPath $
vectorize_ ["-i"] $
povraySlow' [] (script (svgAsPngFile texture) (t*360/10))
rotateSphere :: Animation
rotateSphere = animate $ \t ->
povraySlow [] (script (svgAsPngFile texture) (t*360/10))
texture :: SVG
texture = checker 10 10
script :: FilePath -> Double -> Text
script png s = [iTrim|
//EXAMPLE OF SPHERE
//Files with predefined colors and textures
#include "colors.inc"
#include "glass.inc"
#include "golds.inc"
#include "metals.inc"
#include "stones.inc"
#include "woods.inc"
#include "shapes3.inc"
//Place the camera
camera {
orthographic
// angle 50
location <0,0,-10>
look_at <0,0,0>
//right x*image_width/image_height
up <0,9,0>
right <16,0,0>
}
//Ambient light to "brighten up" darker pictures
global_settings { ambient_light White*3 }
//Set a background color
//background { color White }
//background { color rgbt <0.1, 0, 0, 0> } // red
background { color rgbt <0, 0, 0, 1> } // transparent
//Sphere with specified center point and radius
sphere {
<0,0,0>, 2
texture {
//pigment{ color rgbt <0,0,1,0.1> }
uv_mapping pigment{
image_map{ png ${png} }
//color rgbt <0,0,1,0.1>
}
}
rotate <0,${s'},0>
rotate <-30,0,0>
}
|]
where
precision = 0.1
s' = fromIntegral (round (s / precision)) * precision
checker :: Int -> Int -> SVG
checker w h =
withFillColor "white" $
withStrokeColor "white" $
withStrokeWidth 0.1 $
mkGroup
[ withStrokeWidth 0 $
withFillOpacity 0.8 $ mkBackground "blue"
, mkGroup
[ translate (stepX*x-offsetX + stepX/2) 0 $
mkLine (0, -screenHeight/2*0.9) (0, screenHeight/2*0.9)
| x <- map fromIntegral [0..w-1]
]
,
mkGroup
[ translate 0 (stepY*y-offsetY) $
mkLine (-screenWidth/2, 0) (screenWidth/2, 0)
| y <- map fromIntegral [0..h]
]
]
where
stepX = screenWidth/fromIntegral w
stepY = screenHeight/fromIntegral h
offsetX = screenWidth/2
offsetY = screenHeight/2