mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-11 16:12:20 +00:00
Update stylish preferences.
This commit is contained in:
parent
0ea12c58f9
commit
aa4656d77d
12 changed files with 132 additions and 34 deletions
|
|
@ -81,7 +81,7 @@ steps:
|
||||||
# > init, last, length)
|
# > init, last, length)
|
||||||
#
|
#
|
||||||
# Default: true
|
# Default: true
|
||||||
pad_module_names: false
|
pad_module_names: true
|
||||||
|
|
||||||
# Long list align style takes effect when import is too long. This is
|
# Long list align style takes effect when import is too long. This is
|
||||||
# determined by 'columns' setting.
|
# determined by 'columns' setting.
|
||||||
|
|
|
||||||
BIN
docs/gifs/doc_rotationalTrajectory.gif
Normal file
BIN
docs/gifs/doc_rotationalTrajectory.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 451 KiB |
18
examples/doc_rotationalTrajectory.hs
Executable file
18
examples/doc_rotationalTrajectory.hs
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env stack
|
||||||
|
-- stack runghc --package reanimate
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Main(main) where
|
||||||
|
|
||||||
|
import Reanimate
|
||||||
|
import Reanimate.Builtin.Documentation
|
||||||
|
import Reanimate.Morph.Common
|
||||||
|
import Reanimate.Morph.Linear
|
||||||
|
import Reanimate.Morph.Rotational
|
||||||
|
import Graphics.SvgTree
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = reanimate $ docEnv $ playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->
|
||||||
|
withStrokeLineJoin JoinRound $
|
||||||
|
let src = scale 8 $ center $ latex "X"
|
||||||
|
dst = scale 8 $ center $ latex "H"
|
||||||
|
in morph linear{morphTrajectory=rotationalTrajectory (0.5,0.5)} src dst t
|
||||||
|
|
@ -27,16 +27,19 @@ traceBuffer :: IORef [Animation]
|
||||||
traceBuffer = unsafePerformIO (newIORef [])
|
traceBuffer = unsafePerformIO (newIORef [])
|
||||||
|
|
||||||
{-# NOINLINE traceSVG #-}
|
{-# NOINLINE traceSVG #-}
|
||||||
|
-- | Add SVG image to trace stack.
|
||||||
traceSVG :: SVG -> a -> a
|
traceSVG :: SVG -> a -> a
|
||||||
traceSVG = traceA . staticFrame (recip 60)
|
traceSVG = traceA . staticFrame (recip 60)
|
||||||
|
|
||||||
{-# NOINLINE traceA #-}
|
{-# NOINLINE traceA #-}
|
||||||
|
-- | Add animation to trace stack.
|
||||||
traceA :: Animation -> a -> a
|
traceA :: Animation -> a -> a
|
||||||
traceA a v = unsafePerformIO $ do
|
traceA a v = unsafePerformIO $ do
|
||||||
modifyIORef' traceBuffer (a :)
|
modifyIORef' traceBuffer (a :)
|
||||||
evaluate v
|
evaluate v
|
||||||
|
|
||||||
{-# NOINLINE playTraces #-}
|
{-# NOINLINE playTraces #-}
|
||||||
|
-- | Evaluate argument and play back the trace stack.
|
||||||
playTraces :: a -> Animation
|
playTraces :: a -> Animation
|
||||||
playTraces v = unsafePerformIO $ do
|
playTraces v = unsafePerformIO $ do
|
||||||
_ <- evaluate v
|
_ <- evaluate v
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import System.IO.Unsafe ( unsafePerformIO )
|
||||||
latex :: T.Text -> Tree
|
latex :: T.Text -> Tree
|
||||||
latex = latexWithHeaders []
|
latex = latexWithHeaders []
|
||||||
|
|
||||||
|
-- | Invoke latex with extra script headers.
|
||||||
latexWithHeaders :: [T.Text] -> T.Text -> Tree
|
latexWithHeaders :: [T.Text] -> T.Text -> Tree
|
||||||
latexWithHeaders = someTexWithHeaders "latex" "dvi" []
|
latexWithHeaders = someTexWithHeaders "latex" "dvi" []
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@ someTexWithHeaders exec dvi args headers tex =
|
||||||
where
|
where
|
||||||
script = mkTexScript exec args headers tex
|
script = mkTexScript exec args headers tex
|
||||||
|
|
||||||
|
-- | Invoke latex and separate results.
|
||||||
latexChunks :: [T.Text] -> [Tree]
|
latexChunks :: [T.Text] -> [Tree]
|
||||||
latexChunks chunks | pNoExternals = map mkText chunks
|
latexChunks chunks | pNoExternals = map mkText chunks
|
||||||
latexChunks chunks = worker (svgGlyphs $ latex $ T.concat chunks) chunks
|
latexChunks chunks = worker (svgGlyphs $ latex $ T.concat chunks) chunks
|
||||||
|
|
@ -74,6 +76,7 @@ latexChunks chunks = worker (svgGlyphs $ latex $ T.concat chunks)
|
||||||
xelatex :: Text -> Tree
|
xelatex :: Text -> Tree
|
||||||
xelatex = xelatexWithHeaders []
|
xelatex = xelatexWithHeaders []
|
||||||
|
|
||||||
|
-- | Invoke xelatex with extra script headers.
|
||||||
xelatexWithHeaders :: [T.Text] -> T.Text -> Tree
|
xelatexWithHeaders :: [T.Text] -> T.Text -> Tree
|
||||||
xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"]
|
xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"]
|
||||||
|
|
||||||
|
|
@ -89,6 +92,7 @@ xelatexWithHeaders = someTexWithHeaders "xelatex" "xdv" ["-no-pdf"]
|
||||||
ctex :: T.Text -> Tree
|
ctex :: T.Text -> Tree
|
||||||
ctex = ctexWithHeaders []
|
ctex = ctexWithHeaders []
|
||||||
|
|
||||||
|
-- | Invoke xelatex with extra script headers + ctex headers.
|
||||||
ctexWithHeaders :: [T.Text] -> T.Text -> Tree
|
ctexWithHeaders :: [T.Text] -> T.Text -> Tree
|
||||||
ctexWithHeaders headers = xelatexWithHeaders ("\\usepackage[UTF8]{ctex}" : headers)
|
ctexWithHeaders headers = xelatexWithHeaders ("\\usepackage[UTF8]{ctex}" : headers)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
{-# LANGUAGE BangPatterns #-}
|
{-# LANGUAGE BangPatterns #-}
|
||||||
{-# LANGUAGE ExistentialQuantification #-}
|
{-# LANGUAGE ExistentialQuantification #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-|
|
||||||
|
Copyright : Written by David Himmelstrup
|
||||||
|
License : Unlicense
|
||||||
|
Maintainer : lemmih@gmail.com
|
||||||
|
Stability : experimental
|
||||||
|
Portability : POSIX
|
||||||
|
|
||||||
|
With animations defined as SVG images over time, it is unfortunately
|
||||||
|
quite easy to write inefficient code where expensive expressions are
|
||||||
|
re-evaluated for every frame even if nothing has changed. This get
|
||||||
|
around this issue, this module defines a global key-value table that
|
||||||
|
can store expensive expressions such that they are evaluated only once.
|
||||||
|
|
||||||
|
There is currently no way to clear values from the table and it is your
|
||||||
|
own responsibility to not consume all available memory.
|
||||||
|
|
||||||
|
-}
|
||||||
module Reanimate.Memo
|
module Reanimate.Memo
|
||||||
( Key(..)
|
( Key(..)
|
||||||
, memo
|
, memo
|
||||||
|
|
@ -57,15 +74,20 @@ cacheMapInsert (k:ks) v (CacheMap sub vals) =
|
||||||
fn = Just . cacheMapInsert ks v . fromMaybe emptyCacheMap
|
fn = Just . cacheMapInsert ks v . fromMaybe emptyCacheMap
|
||||||
|
|
||||||
{-# NOINLINE cacheMap #-}
|
{-# NOINLINE cacheMap #-}
|
||||||
|
-- FIXME: There should be a way to clear the cache.
|
||||||
cacheMap :: IORef CacheMap
|
cacheMap :: IORef CacheMap
|
||||||
cacheMap = unsafePerformIO (newIORef emptyCacheMap)
|
cacheMap = unsafePerformIO (newIORef emptyCacheMap)
|
||||||
|
|
||||||
|
-- | Keys can either by any boxed object with identity (to be compared with
|
||||||
|
-- StableNames) or a primitive type with an Eq instance.
|
||||||
data Key = forall a. Key !a | forall a. (Typeable a, Eq a, Ord a) => KeyPrim !a
|
data Key = forall a. Key !a | forall a. (Typeable a, Eq a, Ord a) => KeyPrim !a
|
||||||
|
|
||||||
fromKey :: Key -> IO DynamicName
|
fromKey :: Key -> IO DynamicName
|
||||||
fromKey (Key val) = DynamicName <$> makeStableName val
|
fromKey (Key val) = DynamicName <$> makeStableName val
|
||||||
fromKey (KeyPrim val) = pure (DynamicKey val)
|
fromKey (KeyPrim val) = pure (DynamicKey val)
|
||||||
|
|
||||||
|
-- | Cache expensive value in global store. You must guarantee that the
|
||||||
|
-- key uniquely determines the value.
|
||||||
memo :: Typeable a => [Key] -> a -> a
|
memo :: Typeable a => [Key] -> a -> a
|
||||||
memo !k v = unsafePerformIO $ do
|
memo !k v = unsafePerformIO $ do
|
||||||
keys <- mapM fromKey k
|
keys <- mapM fromKey k
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
-}
|
-}
|
||||||
module Reanimate.Morph.Rotational
|
module Reanimate.Morph.Rotational
|
||||||
( rotationalTrajectory
|
( Origin
|
||||||
|
, rotationalTrajectory
|
||||||
, polygonOrigin
|
, polygonOrigin
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
|
@ -19,8 +20,22 @@ import Reanimate.Ease
|
||||||
import Reanimate.Morph.Common
|
import Reanimate.Morph.Common
|
||||||
import Reanimate.Math.Polygon
|
import Reanimate.Math.Polygon
|
||||||
|
|
||||||
|
-- | Rotational origin relative to polygon center.
|
||||||
|
-- (0.5, 0.5) is center of polygon. Top right is (1,1) and
|
||||||
|
-- bottom left is (0,0)
|
||||||
type Origin = (Double, Double)
|
type Origin = (Double, Double)
|
||||||
|
|
||||||
|
-- | Interpolation by rotating around an origin point.
|
||||||
|
--
|
||||||
|
-- Example:
|
||||||
|
--
|
||||||
|
-- > playThenReverseA $ pauseAround 0.5 0.5 $ mkAnimation 3 $ \t ->
|
||||||
|
-- > withStrokeLineJoin JoinRound $
|
||||||
|
-- > let src = scale 8 $ center $ latex "X"
|
||||||
|
-- > dst = scale 8 $ center $ latex "H"
|
||||||
|
-- > in morph linear{morphTrajectory=rotationalTrajectory (0.5,0.5)} src dst t
|
||||||
|
--
|
||||||
|
-- <<docs/gifs/doc_rotationalTrajectory.gif>>
|
||||||
rotationalTrajectory :: Origin -> Trajectory
|
rotationalTrajectory :: Origin -> Trajectory
|
||||||
rotationalTrajectory origin (src,dst) =
|
rotationalTrajectory origin (src,dst) =
|
||||||
\t ->
|
\t ->
|
||||||
|
|
@ -41,6 +56,7 @@ rotationalTrajectory origin (src,dst) =
|
||||||
|
|
||||||
originAngle o = lineAngle (o + V2 1 0) o
|
originAngle o = lineAngle (o + V2 1 0) o
|
||||||
|
|
||||||
|
-- | Compute the absolute position of rotational origin point in polygon.
|
||||||
polygonOrigin :: Polygon -> Origin -> V2 Double
|
polygonOrigin :: Polygon -> Origin -> V2 Double
|
||||||
polygonOrigin poly (originX, originY) =
|
polygonOrigin poly (originX, originY) =
|
||||||
case pBoundingBox poly of
|
case pBoundingBox poly of
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ License : Unlicense
|
||||||
Maintainer : lemmih@gmail.com
|
Maintainer : lemmih@gmail.com
|
||||||
Stability : experimental
|
Stability : experimental
|
||||||
Portability : POSIX
|
Portability : POSIX
|
||||||
|
|
||||||
|
Internal tools for rastering SVGs and rendering videos. You are unlikely
|
||||||
|
to ever directly use the functions in this module.
|
||||||
|
|
||||||
-}
|
-}
|
||||||
module Reanimate.Render
|
module Reanimate.Render
|
||||||
( render
|
( render
|
||||||
|
|
@ -54,6 +58,7 @@ idempotentFile path action = do
|
||||||
where
|
where
|
||||||
lockFile = path <.> "lock"
|
lockFile = path <.> "lock"
|
||||||
|
|
||||||
|
-- | Generate SVGs at 60fps and put them in a folder.
|
||||||
renderSvgs :: FilePath -> Int -> Bool -> Animation -> IO ()
|
renderSvgs :: FilePath -> Int -> Bool -> Animation -> IO ()
|
||||||
renderSvgs folder offset _prettyPrint ani = do
|
renderSvgs folder offset _prettyPrint ani = do
|
||||||
print frameCount
|
print frameCount
|
||||||
|
|
@ -77,6 +82,8 @@ renderSvgs folder offset _prettyPrint ani = do
|
||||||
hPutStrLn stderr msg
|
hPutStrLn stderr msg
|
||||||
exitWith (ExitFailure 1)
|
exitWith (ExitFailure 1)
|
||||||
|
|
||||||
|
-- | Select a single frame that doesn't already exist in the output
|
||||||
|
-- folder and render it. If all frames have been rendered, print "Done".
|
||||||
renderOneFrame :: FilePath -> Int -> Bool -> Int -> Animation -> IO ()
|
renderOneFrame :: FilePath -> Int -> Bool -> Int -> Animation -> IO ()
|
||||||
renderOneFrame folder offset _prettyPrint rate ani =
|
renderOneFrame folder offset _prettyPrint rate ani =
|
||||||
worker (frameOrder rate frameCount)
|
worker (frameOrder rate frameCount)
|
||||||
|
|
@ -99,6 +106,9 @@ renderOneFrame folder offset _prettyPrint rate ani =
|
||||||
frameCount = round (duration ani * fromIntegral rate) :: Int
|
frameCount = round (duration ani * fromIntegral rate) :: Int
|
||||||
|
|
||||||
-- XXX: Merge with 'renderSvgs'
|
-- XXX: Merge with 'renderSvgs'
|
||||||
|
-- | Render 10 frames and print them to stdout. Used for testing.
|
||||||
|
--
|
||||||
|
-- XXX: Not related to the snippets in the playground.
|
||||||
renderSnippets :: Animation -> IO ()
|
renderSnippets :: Animation -> IO ()
|
||||||
renderSnippets ani = forM_ [0 .. frameCount - 1] $ \nth -> do
|
renderSnippets ani = forM_ [0 .. frameCount - 1] $ \nth -> do
|
||||||
let now = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth
|
let now = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth
|
||||||
|
|
@ -120,6 +130,7 @@ filterFrameList seen nthFrame nFrames = filter (not . isSeen)
|
||||||
[0, nthFrame .. nFrames - 1]
|
[0, nthFrame .. nFrames - 1]
|
||||||
where isSeen x = any (\y -> x `mod` y == 0) seen
|
where isSeen x = any (\y -> x `mod` y == 0) seen
|
||||||
|
|
||||||
|
-- | Video formats supported by reanimate.
|
||||||
data Format = RenderMp4 | RenderGif | RenderWebm
|
data Format = RenderMp4 | RenderGif | RenderWebm
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
|
@ -150,6 +161,7 @@ mp4Arguments fps progress template target =
|
||||||
-- gifArguments :: FPS -> FilePath -> FilePath -> FilePath -> [String]
|
-- gifArguments :: FPS -> FilePath -> FilePath -> FilePath -> [String]
|
||||||
-- gifArguments fps progress template target =
|
-- gifArguments fps progress template target =
|
||||||
|
|
||||||
|
-- | Render animation to a video file with given parameters.
|
||||||
render
|
render
|
||||||
:: Animation
|
:: Animation
|
||||||
-> FilePath
|
-> FilePath
|
||||||
|
|
@ -334,6 +346,8 @@ rasterTemplate RasterNone = "render-%05d.svg"
|
||||||
rasterTemplate RasterAuto = "render-%05d.svg"
|
rasterTemplate RasterAuto = "render-%05d.svg"
|
||||||
rasterTemplate _ = "render-%05d.png"
|
rasterTemplate _ = "render-%05d.png"
|
||||||
|
|
||||||
|
-- | Resolve RasterNone and RasterAuto. If no valid raster can
|
||||||
|
-- be found, exit with an error message.
|
||||||
requireRaster :: Raster -> IO Raster
|
requireRaster :: Raster -> IO Raster
|
||||||
requireRaster raster = do
|
requireRaster raster = do
|
||||||
raster' <- selectRaster (if raster == RasterNone then RasterAuto else raster)
|
raster' <- selectRaster (if raster == RasterNone then RasterAuto else raster)
|
||||||
|
|
@ -346,6 +360,8 @@ requireRaster raster = do
|
||||||
exitWith (ExitFailure 1)
|
exitWith (ExitFailure 1)
|
||||||
_ -> pure raster'
|
_ -> pure raster'
|
||||||
|
|
||||||
|
-- | Resolve RasterNone and RasterAuto. If no valid raster can
|
||||||
|
-- be found, return RasterNone.
|
||||||
selectRaster :: Raster -> IO Raster
|
selectRaster :: Raster -> IO Raster
|
||||||
selectRaster RasterAuto = do
|
selectRaster RasterAuto = do
|
||||||
rsvg <- hasRSvg
|
rsvg <- hasRSvg
|
||||||
|
|
@ -358,6 +374,8 @@ selectRaster RasterAuto = do
|
||||||
| otherwise -> pure RasterNone
|
| otherwise -> pure RasterNone
|
||||||
selectRaster r = pure r
|
selectRaster r = pure r
|
||||||
|
|
||||||
|
-- | Convert SVG file to a PNG file with selected raster engine. If
|
||||||
|
-- raster engine is RasterAuto or RasterNone, do nothing.
|
||||||
applyRaster :: Raster -> FilePath -> IO ()
|
applyRaster :: Raster -> FilePath -> IO ()
|
||||||
applyRaster RasterNone _ = return ()
|
applyRaster RasterNone _ = return ()
|
||||||
applyRaster RasterAuto _ = return ()
|
applyRaster RasterAuto _ = return ()
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,18 @@ module Reanimate.Svg.BoundingBox
|
||||||
, svgWidth
|
, svgWidth
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
import Control.Lens ((^.))
|
import Control.Lens ((^.))
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe (mapMaybe)
|
import Data.Maybe (mapMaybe)
|
||||||
import Graphics.SvgTree hiding (height, line, path, use,
|
import qualified Data.Vector.Unboxed as V
|
||||||
width)
|
import qualified Geom2D.CubicBezier.Linear as Bezier
|
||||||
import Linear.V2 hiding (angle)
|
import Graphics.SvgTree hiding (height, line, path, use, width)
|
||||||
|
import Linear.V2 hiding (angle)
|
||||||
import Linear.Vector
|
import Linear.Vector
|
||||||
import Reanimate.Constants
|
import Reanimate.Constants
|
||||||
import Reanimate.Svg.LineCommand
|
import Reanimate.Svg.LineCommand
|
||||||
import qualified Reanimate.Transform as Transform
|
import qualified Reanimate.Transform as Transform
|
||||||
-- import qualified Geom2D.CubicBezier as Bezier
|
|
||||||
|
|
||||||
-- | Return bounding box of SVG tree.
|
-- | Return bounding box of SVG tree.
|
||||||
-- The four numbers returned are (minimal X-coordinate, minimal Y-coordinate, width, height)
|
-- The four numbers returned are (minimal X-coordinate, minimal Y-coordinate, width, height)
|
||||||
|
|
@ -67,7 +67,8 @@ linePoints = worker zero
|
||||||
LineBezier [p] ->
|
LineBezier [p] ->
|
||||||
p : worker p xs
|
p : worker p xs
|
||||||
LineBezier ctrl -> -- approximation
|
LineBezier ctrl -> -- approximation
|
||||||
[ last (partialBezierPoints (from:ctrl) 0 (recip chunks*i)) | i <- [0..chunks]] ++
|
let bezier = Bezier.AnyBezier (V.fromList (from:ctrl))
|
||||||
|
in [ Bezier.evalBezier bezier (recip chunks*i) | i <- [0..chunks]] ++
|
||||||
worker (last ctrl) xs
|
worker (last ctrl) xs
|
||||||
LineEnd p -> p : worker p xs
|
LineEnd p -> p : worker p xs
|
||||||
chunks = 10
|
chunks = 10
|
||||||
|
|
@ -106,7 +107,7 @@ svgBoundingPoints t = map (Transform.transformPoint m) $
|
||||||
pointToRPoint p =
|
pointToRPoint p =
|
||||||
case mapTuple (toUserUnit defaultDPI) p of
|
case mapTuple (toUserUnit defaultDPI) p of
|
||||||
(Num x, Num y) -> V2 x y
|
(Num x, Num y) -> V2 x y
|
||||||
_ -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format."
|
_ -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format."
|
||||||
|
|
||||||
circleBoundingPoints circ =
|
circleBoundingPoints circ =
|
||||||
let (xnum, ynum) = circ ^. circleCenter
|
let (xnum, ynum) = circ ^. circleCenter
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,33 @@
|
||||||
module Reanimate.Svg.LineCommand where
|
{-|
|
||||||
|
Copyright : Written by David Himmelstrup
|
||||||
|
License : Unlicense
|
||||||
|
Maintainer : lemmih@gmail.com
|
||||||
|
Stability : experimental
|
||||||
|
Portability : POSIX
|
||||||
|
-}
|
||||||
|
module Reanimate.Svg.LineCommand
|
||||||
|
( LineCommand(..)
|
||||||
|
, lineLength
|
||||||
|
, toLineCommands
|
||||||
|
, lineToPath
|
||||||
|
, lineToPoints
|
||||||
|
, partialSvg
|
||||||
|
) where
|
||||||
|
|
||||||
import Control.Lens ((%~), (&), (.~))
|
import Control.Lens ((%~), (&), (.~))
|
||||||
import Control.Monad.Fix
|
import Control.Monad.Fix
|
||||||
import Control.Monad.State
|
import Control.Monad.State
|
||||||
import Data.Functor
|
import Data.Functor
|
||||||
import qualified Data.Vector.Unboxed as V
|
import qualified Data.Vector.Unboxed as V
|
||||||
import qualified Geom2D.CubicBezier.Linear as Bezier
|
import qualified Geom2D.CubicBezier.Linear as Bezier
|
||||||
import Graphics.SvgTree hiding (height, line, path, use, width)
|
import Graphics.SvgTree hiding (height, line, path, use, width)
|
||||||
import Linear.Metric
|
import Linear.Metric
|
||||||
import Linear.V2 hiding (angle)
|
import Linear.V2 hiding (angle)
|
||||||
import Linear.Vector
|
import Linear.Vector
|
||||||
|
|
||||||
type CmdM a = State RPoint a
|
type CmdM a = State RPoint a
|
||||||
|
|
||||||
|
-- | Simplified version of a PathCommand where all points are absolute.
|
||||||
data LineCommand
|
data LineCommand
|
||||||
= LineMove RPoint
|
= LineMove RPoint
|
||||||
-- | LineDraw RPoint
|
-- | LineDraw RPoint
|
||||||
|
|
@ -20,6 +35,7 @@ data LineCommand
|
||||||
| LineEnd RPoint
|
| LineEnd RPoint
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
-- | Convert from line commands to path commands.
|
||||||
lineToPath :: [LineCommand] -> [PathCommand]
|
lineToPath :: [LineCommand] -> [PathCommand]
|
||||||
lineToPath = map worker
|
lineToPath = map worker
|
||||||
where
|
where
|
||||||
|
|
@ -31,6 +47,7 @@ lineToPath = map worker
|
||||||
worker LineBezier{} = error "Reanimate.Svg.lineToPath: invalid bezier curve"
|
worker LineBezier{} = error "Reanimate.Svg.lineToPath: invalid bezier curve"
|
||||||
worker LineEnd{} = EndPath
|
worker LineEnd{} = EndPath
|
||||||
|
|
||||||
|
-- | Using @n@ control points, approximate the path of the curves.
|
||||||
lineToPoints :: Int -> [LineCommand] -> [RPoint]
|
lineToPoints :: Int -> [LineCommand] -> [RPoint]
|
||||||
lineToPoints nPoints cmds =
|
lineToPoints nPoints cmds =
|
||||||
map lineEnd lineSegments
|
map lineEnd lineSegments
|
||||||
|
|
@ -58,10 +75,11 @@ adjustLineLength :: Double -> RPoint -> LineCommand -> LineCommand
|
||||||
adjustLineLength alpha from cmd =
|
adjustLineLength alpha from cmd =
|
||||||
case cmd of
|
case cmd of
|
||||||
LineBezier points -> LineBezier $ drop 1 $ partialBezierPoints (from:points) 0 alpha
|
LineBezier points -> LineBezier $ drop 1 $ partialBezierPoints (from:points) 0 alpha
|
||||||
LineMove p -> LineMove p
|
LineMove p -> LineMove p
|
||||||
-- LineDraw t -> LineDraw (lerp alpha t from)
|
-- LineDraw t -> LineDraw (lerp alpha t from)
|
||||||
LineEnd p -> LineBezier [lerp alpha p from]
|
LineEnd p -> LineBezier [lerp alpha p from]
|
||||||
|
|
||||||
|
-- | Estimated length of all segments in a line.
|
||||||
lineLength :: LineCommand -> CmdM Double
|
lineLength :: LineCommand -> CmdM Double
|
||||||
lineLength cmd =
|
lineLength cmd =
|
||||||
case cmd of
|
case cmd of
|
||||||
|
|
@ -80,11 +98,12 @@ lineLength cmd =
|
||||||
rpointsToBezier :: [RPoint] -> Bezier.CubicBezier Double
|
rpointsToBezier :: [RPoint] -> Bezier.CubicBezier Double
|
||||||
rpointsToBezier lst =
|
rpointsToBezier lst =
|
||||||
case lst of
|
case lst of
|
||||||
[a,b] -> Bezier.CubicBezier a a b b
|
[a,b] -> Bezier.CubicBezier a a b b
|
||||||
[a,b,c] -> Bezier.quadToCubic (Bezier.QuadBezier a b c)
|
[a,b,c] -> Bezier.quadToCubic (Bezier.QuadBezier a b c)
|
||||||
[a,b,c,d] -> Bezier.CubicBezier a b c d
|
[a,b,c,d] -> Bezier.CubicBezier a b c d
|
||||||
_ -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst
|
_ -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst
|
||||||
|
|
||||||
|
-- | Convert from path commands to line commands.
|
||||||
toLineCommands :: [PathCommand] -> [LineCommand]
|
toLineCommands :: [PathCommand] -> [LineCommand]
|
||||||
toLineCommands ps = evalState (worker zero Nothing ps) zero
|
toLineCommands ps = evalState (worker zero Nothing ps) zero
|
||||||
where
|
where
|
||||||
|
|
@ -239,9 +258,6 @@ partialBezierPoints ps a b =
|
||||||
Bezier.AnyBezier os = Bezier.bezierSubsegment c1 a b
|
Bezier.AnyBezier os = Bezier.bezierSubsegment c1 a b
|
||||||
in V.toList os
|
in V.toList os
|
||||||
|
|
||||||
interpolatePathCommands :: Double -> [PathCommand] -> [PathCommand]
|
|
||||||
interpolatePathCommands alpha = lineToPath . partialLine alpha . toLineCommands
|
|
||||||
|
|
||||||
{- | Create an image showing portion of a path.
|
{- | Create an image showing portion of a path.
|
||||||
Note that this only affects paths (see 'Reanimate.Svg.Constructors.mkPath').
|
Note that this only affects paths (see 'Reanimate.Svg.Constructors.mkPath').
|
||||||
You can also use this with other SVG shapes if you convert them to path first (see 'Reanimate.Svg.pathify').
|
You can also use this with other SVG shapes if you convert them to path first (see 'Reanimate.Svg.pathify').
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,18 @@ import Data.Maybe
|
||||||
import Graphics.SvgTree
|
import Graphics.SvgTree
|
||||||
import Linear.V2
|
import Linear.V2
|
||||||
|
|
||||||
type TMatrix = Matrix Coord
|
|
||||||
|
|
||||||
-- | Identity matrix.
|
-- | Identity matrix.
|
||||||
--
|
--
|
||||||
-- @transformPoints identity x = x@
|
-- @transformPoints identity x = x@
|
||||||
identity :: TMatrix
|
identity :: Matrix Coord
|
||||||
identity = M.identity 3
|
identity = M.identity 3
|
||||||
|
|
||||||
fromList :: [Coord] -> TMatrix
|
fromList :: [Coord] -> Matrix Coord
|
||||||
fromList [a,b,c,d,e,f] = M.fromList 3 3 [a,c,e,b,d,f,0,0,1]
|
fromList [a,b,c,d,e,f] = M.fromList 3 3 [a,c,e,b,d,f,0,0,1]
|
||||||
fromList _ = error "Reanimate.Transform.fromList: bad input"
|
fromList _ = error "Reanimate.Transform.fromList: bad input"
|
||||||
|
|
||||||
-- | Apply a transformation matrix to a 2D point.
|
-- | Apply a transformation matrix to a 2D point.
|
||||||
transformPoint :: TMatrix -> RPoint -> RPoint
|
transformPoint :: Matrix Coord -> RPoint -> RPoint
|
||||||
transformPoint m (V2 x y) = V2 (a*x +c*y + e) (b*x + d*y +f)
|
transformPoint m (V2 x y) = V2 (a*x +c*y + e) (b*x + d*y +f)
|
||||||
where
|
where
|
||||||
!a = M.unsafeGet 1 1 m
|
!a = M.unsafeGet 1 1 m
|
||||||
|
|
@ -43,12 +41,12 @@ transformPoint m (V2 x y) = V2 (a*x +c*y + e) (b*x + d*y +f)
|
||||||
-- (a:c:e:b:d:f:_) = M.toList m
|
-- (a:c:e:b:d:f:_) = M.toList m
|
||||||
|
|
||||||
-- | Convert multiple SVG transformations into a single transformation matrix.
|
-- | Convert multiple SVG transformations into a single transformation matrix.
|
||||||
mkMatrix :: Maybe [Transformation] -> TMatrix
|
mkMatrix :: Maybe [Transformation] -> Matrix Coord
|
||||||
mkMatrix Nothing = identity
|
mkMatrix Nothing = identity
|
||||||
mkMatrix (Just ts) = foldl' (*) identity (map transformationMatrix ts)
|
mkMatrix (Just ts) = foldl' (*) identity (map transformationMatrix ts)
|
||||||
|
|
||||||
-- | Convert an SVG transformation into a transformation matrix.
|
-- | Convert an SVG transformation into a transformation matrix.
|
||||||
transformationMatrix :: Transformation -> TMatrix
|
transformationMatrix :: Transformation -> Matrix Coord
|
||||||
transformationMatrix transformation =
|
transformationMatrix transformation =
|
||||||
case transformation of
|
case transformation of
|
||||||
TransformMatrix a b c d e f -> fromList [a,b,c,d,e,f]
|
TransformMatrix a b c d e f -> fromList [a,b,c,d,e,f]
|
||||||
|
|
@ -65,7 +63,7 @@ transformationMatrix transformation =
|
||||||
where r = a * pi / 180
|
where r = a * pi / 180
|
||||||
|
|
||||||
-- | Convert a transformation matrix back into an SVG transformation.
|
-- | Convert a transformation matrix back into an SVG transformation.
|
||||||
toTransformation :: TMatrix -> Transformation
|
toTransformation :: Matrix Coord -> Transformation
|
||||||
toTransformation m = TransformMatrix a b c d e f
|
toTransformation m = TransformMatrix a b c d e f
|
||||||
where
|
where
|
||||||
[a,c,e,b,d,f,_,_,_] = M.toList m
|
[a,c,e,b,d,f,_,_,_] = M.toList m
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
module Reanimate.Voice
|
module Reanimate.Voice
|
||||||
( Transcript(..)
|
( Transcript(..)
|
||||||
, TWord(..)
|
, TWord(..)
|
||||||
|
, Phone(..)
|
||||||
, findWord -- :: Transcript -> [Text] -> Text -> TWord
|
, findWord -- :: Transcript -> [Text] -> Text -> TWord
|
||||||
, findWords -- :: Transcript -> [Text] -> Text -> [TWord]
|
, findWords -- :: Transcript -> [Text] -> Text -> [TWord]
|
||||||
, loadTranscript -- :: FilePath -> Transcript
|
, loadTranscript -- :: FilePath -> Transcript
|
||||||
|
|
@ -90,6 +91,7 @@ instance FromJSON TWord where
|
||||||
<*> o
|
<*> o
|
||||||
.: "word"
|
.: "word"
|
||||||
|
|
||||||
|
-- | Phoneme type
|
||||||
data Phone = Phone
|
data Phone = Phone
|
||||||
{ phoneDuration :: Double
|
{ phoneDuration :: Double
|
||||||
, phoneType :: Text
|
, phoneType :: Text
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue