Color-theory updates and related API changes. (#50)

Rewamp the scene api, add circle plots, add blender flips, fix caching issue in driver, fix bug in partialSvg.


Former-commit-id: 5cb900521044fee13766dd7474fd9b72c7691686
This commit is contained in:
David Himmelstrup 2020-02-02 09:17:29 +08:00 committed by GitHub
commit 4bfafa4d8e
25 changed files with 1441 additions and 338 deletions

16
examples/doc_circlePlot.hs Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main(main) where
import Reanimate hiding (raster, hsv)
import Reanimate.Builtin.Documentation
import Reanimate.Builtin.CirclePlot
import Reanimate.Interpolate
import Data.Colour.RGBSpace.HSV
import Data.Colour.RGBSpace
import Data.Colour.SRGB
import Codec.Picture.Types
main :: IO ()
main = reanimate $ docEnv $ animate $ const $ circlePlot 500 $ \ang r ->
promotePixel $ toRGB8 $ uncurryRGB sRGB $ hsv (ang/pi*180) r 1

View file

@ -2,6 +2,7 @@
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ApplicativeDo #-}
module Main (main) where
import Reanimate
@ -23,12 +24,14 @@ main = seq texture $ reanimate $ pauseAtEnd 1 $ parA bg $ sceneAnimation $ do
rotX <- newVar 0
rotY <- newVar 0
_ <- newSprite $ do
getBend <- freezeVar bend
getTrans <- freezeVar trans
getRotX <- freezeVar rotX
getRotY <- freezeVar rotY
return $ \real_t dur t -> seq (texture (t/dur)) $
blender (script (texture (t/dur)) (getBend real_t) (getTrans real_t) (getRotX real_t) (getRotY real_t))
getBend <- unVar bend
getTrans <- unVar trans
getRotX <- unVar rotX
getRotY <- unVar rotY
t <- spriteT
dur <- spriteDuration
return $ seq (texture (t/dur)) $
blender (script (texture (t/dur)) getBend getTrans getRotX getRotY)
wait 2
tweenVar trans 5 (\t v -> fromToS v (-2) $ curveS 2 (t/5))
tweenVar bend 5 (\t v -> fromToS v 1 $ curveS 2 (t/5))
@ -234,7 +237,8 @@ drawAnimation' mbSeed fillDur step svg = sceneAnimation $ do
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t _d t ->
t <- spriteT
pure $
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
where
shuf lst =

View file

@ -1,6 +1,7 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ApplicativeDo #-}
module Main (main) where
import Reanimate
@ -83,7 +84,8 @@ drawAnimation' mbSeed fillDur step svg = sceneAnimation $ do
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t _d t ->
t <- spriteT
return $
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
where
shuf lst =

View file

@ -17,11 +17,7 @@ main = reanimate $ parA bg $ sceneAnimation $ do
play $ mkAnimation drawDuration $ \t -> partialSvg t (wireframe (-45) 220)
xRot <- newVar (-45)
yRot <- newVar 220
wf <- newSprite $ do
getX <- freezeVar xRot
getY <- freezeVar yRot
return $ \real_t _dur _t ->
wireframe (getX real_t) (getY real_t)
wf <- newSprite $ wireframe <$> unVar xRot <*> unVar yRot
tweenVar yRot spinDur (\t v -> fromToS v (v+60*3) $ curveS 2 (t/spinDur))
replicateM_ wobbles $ do
tweenVar xRot (wobbleDur/2) (\t v -> fromToS v (v+90) $ curveS 2 (t/(wobbleDur/2)))

View file

@ -2,6 +2,7 @@
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ApplicativeDo #-}
module Main (main) where
import Reanimate
@ -25,12 +26,14 @@ main = reanimate $ parA bg $ sceneAnimation $ do
xRot <- newVar 0
zRot <- newVar 0
_ <- newSprite $ do
transZ <- freezeVar zPos
getX <- freezeVar xRot
getZ <- freezeVar zRot
return $ \real_t dur t ->
transZ <- unVar zPos
getX <- unVar xRot
getZ <- unVar zRot
t <- spriteT
dur <- spriteDuration
pure $
povraySlow [] $
script (svgAsPngFile (texture (t/dur))) (transZ real_t) (getX real_t) (getZ real_t)
script (svgAsPngFile (texture (t/dur))) transZ getX getZ
wait 2
tweenVar zPos 9 (\t v -> fromToS v 8 (t/9))
tweenVar xRot 9 (\t v -> fromToS v 360 $ curveS 2 (t/9))
@ -148,7 +151,8 @@ drawAnimation' mbSeed fillDur step svg = sceneAnimation $ do
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t _d t ->
t <- spriteT
return $
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
where
shuf lst =

View file

@ -2,6 +2,7 @@
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ApplicativeDo #-}
module Main (main) where
import Reanimate
@ -25,12 +26,14 @@ main = reanimate $ parA bg $ sceneAnimation $ do
yRot <- newVar 180
zRot <- newVar 0
_ <- newSprite $ do
getX <- freezeVar xRot
getY <- freezeVar yRot
getZ <- freezeVar zRot
return $ \real_t dur t ->
getX <- unVar xRot
getY <- unVar yRot
getZ <- unVar zRot
t <- spriteT
dur <- spriteDuration
return $
povraySlow [] $
script (svgAsPngFile (texture (t/dur))) (getX real_t) (getY real_t) (getZ real_t)
script (svgAsPngFile (texture (t/dur))) getX getY getZ
wait 2
let tDuration = 10
tweenVar yRot tDuration (\t v -> fromToS v (v+180) $ curveS 2 (t/tDuration))
@ -184,7 +187,8 @@ drawAnimation' mbSeed fillDur step svg = sceneAnimation $ do
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t _d t ->
t <- spriteT
return $
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
where
shuf lst =

View file

@ -63,6 +63,8 @@ library
Reanimate.Blender
Reanimate.Effect
Reanimate.Builtin.TernaryPlot
Reanimate.Builtin.CirclePlot
Reanimate.Builtin.Flip
Reanimate.Constants
Reanimate.Parameters
Reanimate.Chiphunk

View file

@ -67,7 +67,6 @@ module Reanimate
sceneAnimation,
fork,
play,
playZ,
queryNow,
waitAll,
waitUntil,
@ -76,11 +75,14 @@ module Reanimate
withSceneDuration,
newSprite,
newSpriteA,
newSpriteSVG,
destroySprite,
spriteE,
newVar,
tweenVar,
freezeVar,
unVar,
spriteT,
spriteDuration,
-- ** Effects
Effect,

View file

@ -47,4 +47,3 @@ mkBlenderImage' script = cacheFile template $ \target -> do
, "--render-output", target, "--python", py_file]
where
template = show (hash script) <.> "png"

View file

@ -0,0 +1,23 @@
module Reanimate.Builtin.CirclePlot where
import Codec.Picture
import Graphics.SvgTree (Tree)
import Reanimate.Raster
import Reanimate.Svg
import Reanimate.Constants
circlePlot :: Int -- ^ Pixels in the X-axis.
-> (Double -- ^ Angle in radians
-> Double -- ^ Radius in percent
-> PixelRGBA8) -> Tree
circlePlot density fn =
scaleToHeight screenHeight $ flipYAxis $
embedImage $ generateImage gen density density
where
cN = fromIntegral $ density `div` 2 - 1
gen x y =
let radius = sqrt ((fromIntegral x-cN)**2 + (fromIntegral y-cN)**2)
ang = atan2 (fromIntegral y-cN) (fromIntegral x-cN)
in if radius > cN
then PixelRGBA8 0 0 0 0
else fn ang (radius / cN)

200
src/Reanimate/Builtin/Flip.hs Executable file
View file

@ -0,0 +1,200 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ApplicativeDo #-}
module Reanimate.Builtin.Flip
( FlipSprite(..)
, flipSprite
, Transition
, signalT
, flipTransition
, flipTransitionOpts
, overlapTransition
) where
import Reanimate.Animation
import Reanimate.Blender
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg.Constructors
import Data.String.Here
import qualified Data.Text as T
data FlipSprite s = FlipSprite
{ fsSprite :: Sprite s
, fsBend :: Var s Double
, fsZoom :: Var s Double
, fsWobble :: Var s Double
}
flipSprite :: Animation -> Animation -> Scene s (FlipSprite s)
flipSprite front back = do
bend <- newVar 0
trans <- newVar 0
rotX <- newVar 0
s <- newSprite $ do
getBend <- unVar bend
getTrans <- unVar trans
getRotX <- unVar rotX
t <- spriteT
dur <- spriteDuration
return $
let rotY = fromToS 0 pi (t/dur)
frontTexture = svgAsPngFile (frameAt t $ setDuration dur front)
backTexture = svgAsPngFile (flipXAxis $ frameAt t $ setDuration dur back)
-- seq'ing frontTexture and backTexture is required to avoid segfaults. :(
in frontTexture `seq` backTexture `seq`
blender (script frontTexture backTexture getBend getTrans getRotX rotY)
return FlipSprite
{ fsSprite = s
, fsBend = bend
, fsZoom = trans
, fsWobble = rotX }
type Transition = Animation -> Animation -> Animation
signalT :: Signal -> Transition -> Transition
signalT s t = \a b -> signalA s (t a b)
overlapTransition :: Double -> Transition -> Transition
overlapTransition overlap t a b =
aBefore `seqA` t aOverlap bOverlap `seqA` bAfter
where
aBefore = takeA (duration a - overlap) a
aOverlap = dropA (duration a - overlap) a
bOverlap = takeA overlap b
bAfter = dropA overlap b
flipTransitionOpts :: Double -> Double -> Double -> Transition
flipTransitionOpts bend zoom wobble a b = sceneAnimation $ do
FlipSprite{..} <- flipSprite a b
fork $ tweenVar fsZoom dur $ \v -> fromToS v zoom . oscillateS
fork $ tweenVar fsBend dur $ \v -> fromToS v bend . oscillateS
fork $ tweenVar fsWobble dur $ \v -> fromToS v wobble . oscillateS
where
dur = max (duration a) (duration b)
flipTransition :: Transition
flipTransition = flipTransitionOpts bend zoom wobble
where
bend = 1/3
zoom = 3
wobble = -pi*0.10
script :: FilePath -> FilePath -> Double -> Double -> Double -> Double -> T.Text
script frontImage backImage bend transZ rotX rotY = [iTrim|
import os
import math
import bpy
light = bpy.data.objects['Light']
bpy.ops.object.select_all(action='DESELECT')
light.select_set(True)
bpy.ops.object.delete()
cam = bpy.data.objects['Camera']
cam.location = (0,0,22.22 + ${transZ})
cam.rotation_euler = (0, 0, 0)
bpy.ops.object.empty_add(location=(0.0, 0, 0))
focus_target = bpy.context.object
bpy.ops.object.select_all(action='DESELECT')
cam.select_set(True)
focus_target.select_set(True)
bpy.ops.object.parent_set()
focus_target.rotation_euler = (${rotX}, 0, 0)
origin = bpy.data.objects['Cube']
bpy.ops.object.select_all(action='DESELECT')
origin.select_set(True)
bpy.ops.object.delete()
x = ${bend}
bpy.ops.mesh.primitive_plane_add()
plane = bpy.context.object
plane.scale = (16/2,${fromToS (9/2) 4 bend},1)
bpy.ops.object.shade_smooth()
bpy.context.object.active_material = bpy.data.materials['Material']
mat = bpy.context.object.active_material
mix = mat.node_tree.nodes.new('ShaderNodeMixShader')
geo = mat.node_tree.nodes.new('ShaderNodeNewGeometry')
mat.blend_method = 'HASHED'
image_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
gh_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
output = mat.node_tree.nodes['Material Output']
gh_mix = mat.node_tree.nodes.new('ShaderNodeMixShader')
transparent = mat.node_tree.nodes.new('ShaderNodeBsdfTransparent')
mat.node_tree.links.new(geo.outputs['Backfacing'], mix.inputs['Fac'])
mat.node_tree.links.new(mix.outputs['Shader'], output.inputs['Surface'])
mat.node_tree.links.new(image_node.outputs['Color'], mix.inputs[1])
#mat.node_tree.links.new(gh_node.outputs['Color'], mix.inputs[2])
mat.node_tree.links.new(gh_node.outputs['Color'], gh_mix.inputs[2])
mat.node_tree.links.new(gh_node.outputs['Alpha'], gh_mix.inputs['Fac'])
mat.node_tree.links.new(transparent.outputs['BSDF'], gh_mix.inputs[1])
mat.node_tree.links.new(gh_mix.outputs['Shader'], mix.inputs[2])
image_node.image = bpy.data.images.load('${T.pack frontImage}')
image_node.interpolation = 'Closest'
gh_node.image = bpy.data.images.load('${T.pack backImage}')
gh_node.interpolation = 'Closest'
modifier = plane.modifiers.new(name='Subsurf', type='SUBSURF')
modifier.levels = 7
modifier.render_levels = 7
modifier.subdivision_type = 'SIMPLE'
bpy.ops.object.empty_add(type='ARROWS',rotation=(math.pi/2,0,0))
empty = bpy.context.object
bendUp = plane.modifiers.new(name='Bend up', type='SIMPLE_DEFORM')
bendUp.deform_method = 'BEND'
bendUp.origin = empty
bendUp.deform_axis = 'X'
bendUp.factor = -math.pi*x
bendAround = plane.modifiers.new(name='Bend around', type='SIMPLE_DEFORM')
bendAround.deform_method = 'BEND'
bendAround.origin = empty
bendAround.deform_axis = 'Z'
bendAround.factor = -math.pi*2*x
bpy.context.view_layer.objects.active = plane
bpy.ops.object.modifier_apply(modifier='Subsurf')
bpy.ops.object.modifier_apply(modifier='Bend up')
bpy.ops.object.modifier_apply(modifier='Bend around')
bpy.ops.object.select_all(action='DESELECT')
plane.select_set(True);
bpy.ops.object.origin_clear()
bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
plane.rotation_euler = (0, ${rotY}, 0)
scn = bpy.context.scene
#scn.render.engine = 'CYCLES'
#scn.render.resolution_percentage = 10
scn.view_settings.view_transform = 'Standard'
scn.render.resolution_x = 2560
scn.render.resolution_y = 1440
scn.render.film_transparent = True
bpy.ops.render.render( write_still=True )
|]

View file

@ -33,6 +33,11 @@ bigXYZCoordinates = unsafePerformIO $ do
Right vec -> return $ Map.fromList
[ (nm, (x,y,z)) | (nm,x,y,z) <- V.toList vec, nm <= 700 ]
nmToColor :: Nanometer -> Maybe (Colour Double)
nmToColor nm = do
(x, y, z) <- Map.lookup nm bigXYZCoordinates
return $ cieXYZ x y z
renderXYZCoordinates :: Tree
renderXYZCoordinates =
withFillOpacity 0 $

View file

@ -21,6 +21,7 @@ presetFormat ExampleGif = RenderGif
presetFormat Quick = RenderMp4
presetFormat MediumQ = RenderMp4
presetFormat HighQ = RenderMp4
presetFormat LowFPS = RenderMp4
presetFPS :: Preset -> FPS
presetFPS Youtube = 60
@ -28,6 +29,7 @@ presetFPS ExampleGif = 24
presetFPS Quick = 15
presetFPS MediumQ = 30
presetFPS HighQ = 30
presetFPS LowFPS = 10
presetWidth :: Preset -> Width
presetWidth Youtube = 2560
@ -35,6 +37,7 @@ presetWidth ExampleGif = 320
presetWidth Quick = 320
presetWidth MediumQ = 800
presetWidth HighQ = 1920
presetWidth LowFPS = presetWidth HighQ
presetHeight :: Preset -> Height
presetHeight preset = presetWidth preset * 9 `div` 16
@ -163,7 +166,7 @@ guessParameter a b def = fromMaybe def (a <|> b)
-- If user specifies exactly one dimension explicitly, calculate the other
userPreferredDimensions :: Maybe Width -> Maybe Height -> Maybe (Width, Height)
userPreferredDimensions (Just width) (Just height) = Just (width, height)
userPreferredDimensions (Just width) Nothing = Just (width, makeEven $ width * 9 `div` 16)
userPreferredDimensions (Just width) Nothing = Just (width, makeEven $ width * 9 `div` 16)
userPreferredDimensions Nothing (Just height) = Just (makeEven $ height * 16 `div` 9, height)
userPreferredDimensions Nothing Nothing = Nothing

View file

@ -34,7 +34,7 @@ data Command
}
deriving (Show)
data Preset = Youtube | ExampleGif | Quick | MediumQ | HighQ
data Preset = Youtube | ExampleGif | Quick | MediumQ | HighQ | LowFPS
deriving (Show)
readFormat :: String -> Maybe Format
@ -58,6 +58,7 @@ readPreset preset =
"quick" -> Just Quick
"medium" -> Just MediumQ
"high" -> Just HighQ
"lowfps" -> Just LowFPS
_ -> Nothing
showPreset :: Preset -> String
@ -66,6 +67,7 @@ showPreset ExampleGif = "gif"
showPreset Quick = "quick"
showPreset MediumQ = "medium"
showPreset HighQ = "high"
showPreset LowFPS = "lowfps"
options :: Parser Options
options = Options <$> commandP

View file

@ -4,12 +4,11 @@ module Reanimate.Driver.Server
, findOwnSource
) where
import Control.Concurrent
import Control.Concurrent (forkIO, killThread, threadDelay)
import Control.Concurrent.MVar
import Control.Exception (SomeException, catch, finally)
import Control.Monad
import Control.Monad.Fix (fix)
import Data.Hashable (hash)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
@ -94,31 +93,47 @@ slaveHandler conn self svgDir =
withCurrentDirectory (takeDirectory self) $
withSystemTempDirectory "reanimate" $ \tmpDir ->
withTempFile tmpDir "reanimate.exe" $ \tmpExecutable handle -> do
-- cap <- getNumCapabilities
let n = 25
sem <- newQSemN n
hClose handle
lock <- newMVar ()
sendTextData conn (T.pack "status\nCompiling")
ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions tmpDir ++ [takeFileName self, "-o", tmpExecutable]
case ret of
Left err ->
sendTextData conn $ T.pack $ "error\n" ++ unlines (drop 3 (lines err))
Right{} -> runCmdLazy tmpExecutable execOpts $ \getFrame -> do
(frameCount,_) <- expectFrame =<< getFrame
(frameCount,_) <- expectFrame sem =<< getFrame
sendTextData conn (T.pack $ "frame_count\n" ++ show frameCount)
fix $ \loop -> do
(frameIdx, frame) <- expectFrame =<< getFrame
let fileName = svgDir </> show (hash frame) <.> "svg"
T.writeFile fileName frame
sendTextData conn (T.pack $ "frame\n" ++ show frameIdx ++ "\n" ++ fileName)
(frameIdx, frame) <- expectFrame sem =<< getFrame
-- putStrLn $ "Got frame: " ++ show frameIdx
let fileName = svgDir </> takeBaseName tmpExecutable <.> show frameIdx <.> "svg"
-- pngName = replaceExtension fileName "png"
_ <- forkIO $ do
waitQSemN sem 1
T.writeFile fileName frame
-- runCmd "rsvg-convert"
-- [ fileName
-- , "--width=256" -- "--width=1024"
-- , "--height=144" -- "--height=576"
-- , "--output", pngName ]
withMVar lock $ \_ ->
sendTextData conn (T.pack $ "frame\n" ++ show frameIdx ++ "\n" ++ fileName)
signalQSemN sem 1
loop
where
execOpts = ["raw", "+RTS", "-N", "-M1G", "-RTS"]
expectFrame :: Either String Text -> IO (Integer, Text)
expectFrame (Left "") = do
execOpts = ["raw", "+RTS", "-N", "-M2G", "-RTS"]
expectFrame :: QSemN -> Either String Text -> IO (Integer, Text)
expectFrame sem (Left "") = do
waitQSemN sem 25 -- =<< getNumCapabilities
sendTextData conn (T.pack "status\nDone")
exitSuccess
expectFrame (Left err) = do
expectFrame _ (Left err) = do
sendTextData conn $ T.pack $ "Error" ++ err
exitWith (ExitFailure 1)
expectFrame (Right frame) =
expectFrame _ (Right frame) =
case T.decimal frame of
Left err -> do
hPutStrLn stderr (T.unpack frame)

View file

@ -1,4 +1,5 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ApplicativeDo #-}
module Reanimate.Scene where
import Control.Monad.Fix
@ -6,7 +7,6 @@ import Control.Monad.ST
import Data.List
import Data.STRef
import Reanimate.Animation
import Reanimate.Signal
import Reanimate.Effect
import Reanimate.Svg.Constructors
import Graphics.SvgTree (Tree(None))
@ -19,53 +19,41 @@ o # f = f o
-- (seq duration, par duration)
-- [(Time, Animation, ZIndex)]
-- Map Time [(Animation, ZIndex)]
type Timeline = [(Time, Animation, ZIndex)]
type Gen s = ST s (Duration -> Time -> (SVG, ZIndex))
newtype Scene s a = M { unM :: Time -> ST s (a, Duration, Duration, Timeline, [Gen s]) }
unionTimeline :: Timeline -> Timeline -> Timeline
unionTimeline = (++)
emptyTimeline :: Timeline
emptyTimeline = []
newtype Scene s a = M { unM :: Time -> ST s (a, Duration, Duration, [Gen s]) }
instance Functor (Scene s) where
fmap f action = M $ \t -> do
(a, d1, d2, tl, gens) <- unM action t
return (f a, d1, d2, tl, gens)
(a, d1, d2, gens) <- unM action t
return (f a, d1, d2, gens)
instance Applicative (Scene s) where
pure a = M $ \_ -> return (a, 0, 0, emptyTimeline, [])
pure a = M $ \_ -> return (a, 0, 0, [])
f <*> g = M $ \t -> do
(f', s1, p1, tl1, gen1) <- unM f t
(g', s2, p2, tl2, gen2) <- unM g (t+s1)
return (f' g', s1+s2, max p1 (s1+p2), unionTimeline tl1 tl2, gen1++gen2)
(f', s1, p1, gen1) <- unM f t
(g', s2, p2, gen2) <- unM g (t+s1)
return (f' g', s1+s2, max p1 (s1+p2), gen1++gen2)
instance Monad (Scene s) where
return = pure
f >>= g = M $ \t -> do
(a, s1, p1, tl1, gen1) <- unM f t
(b, s2, p2, tl2, gen2) <- unM (g a) (t+s1)
return (b, s1+s2, max p1 (s1+p2), unionTimeline tl1 tl2, gen1++gen2)
(a, s1, p1, gen1) <- unM f t
(b, s2, p2, gen2) <- unM (g a) (t+s1)
return (b, s1+s2, max p1 (s1+p2), gen1++gen2)
instance MonadFix (Scene s) where
mfix fn = M $ \t -> mfix (\v -> let (a,_s,_p,_tl,_gens) = v in unM (fn a) t)
mfix fn = M $ \t -> mfix (\v -> let (a,_s,_p,_gens) = v in unM (fn a) t)
liftST :: ST s a -> Scene s a
liftST action = M $ \_ -> action >>= \a -> return (a, 0, 0, emptyTimeline, [])
liftST action = M $ \_ -> action >>= \a -> return (a, 0, 0, [])
sceneAnimation :: (forall s. Scene s a) -> Animation
sceneAnimation action =
runST (do
(_, s, p, tl, gens) <- unM action 0
(_, s, p, gens) <- unM action 0
let dur = max s p
anis = foldl' parDropA (pause 0) $
map snd $ sortOn fst
[ (z, pause startT `seqA` a)
| (startT, a, z) <- tl
]
genFns <- sequence gens
return $ anis `parDropA` mkAnimation dur (\t ->
return $ mkAnimation dur (\t ->
mkGroup $
map fst $
sortOn snd
@ -75,25 +63,20 @@ sceneAnimation action =
fork :: Scene s a -> Scene s a
fork (M action) = M $ \t -> do
(a, s, p, tl, gens) <- action t
return (a, 0, max s p, tl, gens)
(a, s, p, gens) <- action t
return (a, 0, max s p, gens)
play :: Animation -> Scene s ()
play = playZ 0
playZ :: ZIndex -> Animation -> Scene s ()
playZ z ani = M $ \t -> do
let d = duration ani
return ((), d, 0, [(t, ani, z)], [])
play ani = newSpriteA ani >>= destroySprite
queryNow :: Scene s Time
queryNow = M $ \t -> return (t, 0, 0, emptyTimeline, [])
queryNow = M $ \t -> return (t, 0, 0, [])
-- Wait until all forked and sequential animations have finished.
waitAll :: Scene s a -> Scene s a
waitAll (M action) = M $ \t -> do
(a, s, p, tl, gens) <- action t
return (a, max s p, 0, tl, gens)
(a, s, p, gens) <- action t
return (a, max s p, 0, gens)
waitUntil :: Time -> Scene s ()
waitUntil tNew = do
@ -102,12 +85,18 @@ waitUntil tNew = do
wait :: Duration -> Scene s ()
wait d = M $ \_ ->
return ((), d, 0, emptyTimeline, [])
return ((), d, 0, [])
adjustZ :: (ZIndex -> ZIndex) -> Scene s a -> Scene s a
adjustZ fn (M action) = M $ \t -> do
(a, s, p, tl, gens) <- action t
return (a, s, p, [ (startT, ani, fn z) | (startT, ani, z) <- tl ], gens)
(a, s, p, gens) <- action t
return (a, s, p, map genFn gens)
where
genFn gen = do
frameGen <- gen
return $ \d t ->
let (svg, z) = frameGen d t
in (svg, fn z)
withSceneDuration :: Scene s () -> Scene s Duration
withSceneDuration s = do
@ -116,52 +105,13 @@ withSceneDuration s = do
t2 <- queryNow
return (t2-t1)
newtype Object s = Object (STRef s (Maybe Timeline))
newObject :: Scene s (Object s)
newObject = Object <$> liftST (newSTRef Nothing)
stretchTimeline :: Timeline -> Scene s ()
stretchTimeline = mapM_ worker
where
worker (t, a, z) = M $ \tNow -> -- 3
let tNew = t + duration a -- 1+1=2
dNew = tNow - tNew -- 3-2=1
aNew = setDuration dNew (signalA (constantS 1) a) in
if (dNew > 0)
then return ((), 0, 0, [(tNew, aNew, z)], [])
else return ((), 0, 0, emptyTimeline, [])
dropObject :: Object s -> Scene s ()
dropObject (Object ref) = do
mbTimeline <- liftST $ readSTRef ref
case mbTimeline of
Nothing -> return ()
Just timeline -> do
liftST $ writeSTRef ref Nothing
stretchTimeline timeline
listen :: Scene s a -> Scene s (a, Timeline)
listen scene = M $ \t -> do
(a, s, p, tl, gens) <- unM scene t
return ((a,tl), s, p, tl, gens)
withObject :: Object s -> Scene s a -> Scene s a
withObject obj@(Object ref) scene = do
dropObject obj
(a, tl) <- listen scene
liftST $ writeSTRef ref (Just tl)
return a
fromParams :: Gen s -> Scene s ()
fromParams gen = M $ \_ -> return ((), 0, 0, emptyTimeline, [gen])
fromParams gen = M $ \_ -> return ((), 0, 0, [gen])
simpleParam :: (a -> SVG) -> a -> Scene s (Var s a)
simpleParam render def = do
v <- newVar def
_ <- newSprite $ do
getV <- freezeVar v
return $ \real_t _d _t -> render (getV real_t)
_ <- newSprite $ render <$> unVar v
return v
newtype Var s a = Var (STRef s (Time -> a))
@ -190,8 +140,10 @@ tweenVar (Var ref) dur fn = do
fn (prev t) ((max 0 $ min dur $ t-now)/dur)
wait dur
freezeVar :: Var s a -> ST s (Time -> a)
freezeVar (Var ref) = readSTRef ref
unVar :: Var s a -> Frame s a
unVar (Var ref) = Frame $ do
fn <- readSTRef ref
return $ \real_t _d _t -> fn real_t
findVar :: (a -> Bool) -> [Var s a] -> Scene s (Var s a)
findVar _cond [] = error "Variable not found."
@ -202,22 +154,44 @@ findVar cond (v:vs) = do
applyVar :: Var s a -> Sprite s -> (a -> SVG -> SVG) -> Scene s ()
applyVar var sprite fn = do
spriteModify sprite $ do
varFn <- freezeVar var
return $ \absT _relD _relT (svg, zindex) ->
(fn (varFn absT) svg, zindex)
varFn <- unVar var
return $ \(svg, zindex) ->
(fn varFn svg, zindex)
data Sprite s = Sprite Time (STRef s (Duration, ST s (Duration -> Time -> SVG -> (SVG, ZIndex))))
newSprite :: ST s (Time -> Duration -> Time -> SVG) -> Scene s (Sprite s)
newtype Frame s a = Frame { unFrame :: ST s (Time -> Duration -> Time -> a) }
instance Functor (Frame s) where
fmap fn (Frame gen) = Frame $ do
m <- gen
return (\real_t d t -> fn $ m real_t d t)
instance Applicative (Frame s) where
pure v = Frame $ return (\_ _ _ -> v)
Frame f <*> Frame g = Frame $ do
m1 <- f
m2 <- g
return $ \real_t d t ->
m1 real_t d t (m2 real_t d t)
-- Time in seconds.
spriteT :: Frame s Time
spriteT = Frame $ return (\_real_t _d t -> t)
spriteDuration :: Frame s Duration
spriteDuration = Frame $ return (\_real_t d _t -> d)
newSprite :: Frame s SVG -> Scene s (Sprite s)
newSprite render = do
now <- queryNow
ref <- liftST $ newSTRef (-1, return $ \_d _t svg -> (svg, 0))
fromParams $ do
fn <- render
(spriteDuration, spriteEffectGen) <- readSTRef ref
fn <- unFrame render
(spriteDur, spriteEffectGen) <- readSTRef ref
spriteEffect <- spriteEffectGen
return $ \d absT ->
let relD = (if spriteDuration < 0 then d else spriteDuration)-now
let relD = (if spriteDur < 0 then d else spriteDur)-now
relT = absT-now in
if relT < 0 || relD < relT
then (None, 0)
@ -228,20 +202,12 @@ newSpriteA :: Animation -> Scene s (Sprite s)
newSpriteA = newSpriteA' SyncStretch
newSpriteA' :: Sync -> Animation -> Scene s (Sprite s)
newSpriteA' sync animation = do
now <- queryNow
ref <- liftST $ newSTRef (-1, return $ \_d _t svg -> (svg, 0))
fromParams $ do
(spriteDuration, spriteEffectGen) <- readSTRef ref
spriteEffect <- spriteEffectGen
return $ \d absT ->
let relD = (if spriteDuration < 0 then d else spriteDuration)-now
relT = absT-now in
if relT < 0 || relT > relD
then (None, 0)
else spriteEffect relD relT (getAnimationFrame sync animation relT relD)
wait (duration animation)
return $ Sprite now ref
newSpriteA' sync animation =
newSprite (getAnimationFrame sync animation <$> spriteT <*> spriteDuration)
<* wait (duration animation)
newSpriteSVG :: SVG -> Scene s (Sprite s)
newSpriteSVG = newSprite . pure
getAnimationFrame :: Sync -> Animation -> Time -> Duration -> SVG
getAnimationFrame sync (Animation aDur aGen) t d =
@ -265,24 +231,28 @@ destroySprite (Sprite _ ref) = do
liftST $ modifySTRef ref $ \(ttl, render) ->
(if ttl < 0 then now else min ttl now, render)
spriteModify :: Sprite s -> ST s (Time -> Duration -> Time -> (SVG, ZIndex) -> (SVG, ZIndex)) -> Scene s ()
spriteModify :: Sprite s -> Frame s ((SVG,ZIndex) -> (SVG, ZIndex)) -> Scene s ()
spriteModify (Sprite born ref) modFn =
liftST $ modifySTRef ref $ \(ttl, renderGen) ->
(ttl, do
render <- renderGen
modRender <- modFn
modRender <- unFrame modFn
return $ \relD relT ->
let absT = relT + born
in modRender absT relD relT . render relD relT)
spriteMap :: Sprite s -> (SVG -> SVG) -> Scene s ()
spriteMap sprite fn = spriteModify sprite $ pure $ \(svg, zindex) -> (fn svg, zindex)
spriteTween :: Sprite s -> Duration -> (Double -> SVG -> SVG) -> Scene s ()
spriteTween sprite@(Sprite born _) dur fn = do
now <- queryNow
let tDelta = now - born
spriteModify sprite $ do
return $ \_real_t _d t (svg, zindex) ->
(fn (clamp 0 1 $ (t-tDelta)/dur) svg, zindex)
wait dur
now <- queryNow
let tDelta = now - born
spriteModify sprite $ do
t <- spriteT
return $ \(svg, zindex) ->
(fn (clamp 0 1 $ (t-tDelta)/dur) svg, zindex)
wait dur
where
clamp a b v
| v < a = a
@ -293,9 +263,9 @@ spriteVar :: Sprite s -> a -> (a -> SVG -> SVG) -> Scene s (Var s a)
spriteVar sprite def fn = do
v <- newVar def
spriteModify sprite $ do
getV <- freezeVar v
return $ \real_t _d _t (svg, zindex) ->
(fn (getV real_t) svg, zindex)
getV <- unVar v
return $ \(svg, zindex) ->
(fn getV svg, zindex)
return v
spriteE :: Sprite s -> Effect -> Scene s ()
@ -306,7 +276,7 @@ spriteE (Sprite born ref) effect = do
render <- renderGen
return $ \d t svg ->
let (svg', z) = render d t svg
in (delayE (now-born) effect d t svg', z))
in (delayE (max 0 $ now-born) effect d t svg', z))
spriteZ :: Sprite s -> ZIndex -> Scene s ()
spriteZ (Sprite born ref) zindex = do

View file

@ -251,7 +251,7 @@ interpolatePathCommands alpha = lineToPath . partialLine alpha . toLineCommands
partialSvg :: Double -- ^ number between 0 and 1 inclusively, determining what portion of the path to show
-> Tree -- ^ Image representing a path, of which we only want to display a portion determined by the first argument
-> Tree
-- partialSvg alpha | alpha >= 1 = id
partialSvg alpha | alpha >= 1 = id
partialSvg alpha = mapTree worker
where
worker (PathTree path) =

View file

@ -0,0 +1,39 @@
#!/usr/bin/env stack
-- stack --resolver lts-13.14 runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module EndScene (endScene) where
import Control.Lens ()
import Control.Monad
import qualified Data.ByteString as BS
import qualified Data.Map as Map
import Data.Monoid
import qualified Data.Text as T
import Codec.Picture
import Codec.Picture.Jpg
import Codec.Picture.Types
import Data.Maybe
import Data.Word
import Graphics.SvgTree hiding (Image, imageHeight, imageWidth)
import Graphics.SvgTree.Memo
import Numeric
import Reanimate
import Reanimate.Animation
import Reanimate.ColorMap
import Reanimate.ColorSpace
import Reanimate.Builtin.Images
import Reanimate.Constants
import Reanimate.Driver (reanimate)
import Reanimate.Effect
import Reanimate.LaTeX
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg
import System.IO.Unsafe
endScene :: Animation
endScene = mkAnimation 5 $ const $
scale 0.5 $ githubIcon

151
videos/color-theory/Grid.hs Executable file
View file

@ -0,0 +1,151 @@
#!/usr/bin/env stack
-- stack --resolver lts-13.14 runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ApplicativeDo #-}
module Grid (gridScene) where
import Control.Lens ()
import Control.Monad
import qualified Data.ByteString as BS
import qualified Data.Map as Map
import Data.Monoid
import qualified Data.Text as T
import Codec.Picture
import Codec.Picture.Jpg
import Codec.Picture.Types
import Data.Maybe
import Data.Word
import Graphics.SvgTree hiding (Image, imageHeight, imageWidth)
import Graphics.SvgTree.Memo
import Numeric
import Reanimate
import Reanimate.Animation
import Reanimate.ColorMap
import Reanimate.ColorSpace
import Reanimate.Constants
import Reanimate.Driver (reanimate)
import Reanimate.Effect
import Reanimate.LaTeX
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg
import System.IO.Unsafe
gridScene :: Animation
gridScene = sceneAnimation $ do
sViridis <- monalisaSprite (-1) 1 "viridis"
sCividis <- monalisaSprite 0 1 "cividis"
sParula <- monalisaSprite 1 1 "parula"
sJet <- monalisaSprite (-1) 0 "jet"
sInferno <- monalisaSprite 0 0 "inferno"
sSinebow <- monalisaSprite 1 0 "sinebow"
sTurbo <- monalisaSprite (-1) (-1) "turbo"
sPlasma <- monalisaSprite 0 (-1) "plasma"
sHSV <- monalisaSprite 1 (-1) "hsv"
showMap sViridis
showMap sCividis
showMap sParula
wait 2
showMap sJet
showMap sInferno
showMap sSinebow
wait 2
showMap sTurbo
showMap sPlasma
showMap sHSV
wait 2
return ()
where
showMap hd = do
spriteZ (fst hd) 1
tweenVar (snd hd) 1 $ \v -> fromToS v 1 . curveS 3
wait 1
tweenVar (snd hd) 1 $ \v -> fromToS v 0 . curveS 3
spriteZ (fst hd) 0
maps =
[ ("viridis", viridis)
, ("cividis", cividis)
, ("parula", parula)
, ("jet", jet)
, ("inferno", inferno)
, ("sinebow", sinebow)
, ("turbo", turbo)
, ("plasma", plasma)
, ("hsv", hsv)
]
monalisaSprite :: Double -> Double -> T.Text -> Scene s (Sprite s, Var s Double)
monalisaSprite x y txt = do
highlight <- newVar 0
s <- newSprite $ do
getHighlight <- unVar highlight
return $
translate (screenWidth/3 * x * (1-getHighlight))
(screenHeight/3 * y * (1-getHighlight)) $
scale (fromToS 1 2 (getHighlight)) $
mkGroup
[ scaleToSize (screenWidth/3) (screenHeight/3) $
embedImage $
applyColorMap (fromMaybe jet $ lookup txt maps) monalisa
, translate (-screenWidth/6 + screenWidth*0.005) (screenHeight/6 - screenHeight*0.005) $
scale 0.5 $
withStrokeColor "black" $
withStrokeWidth (defaultStrokeWidth*0.5) $
withFillColor "white" $
latex ("\\texttt{" <> txt <> "}")
]
return (s, highlight)
monalisaPoster :: SVG
monalisaPoster =
mkGroup
[ mkPic (-1) 1 viridis "viridis", mkPic 0 1 cividis "cividis", mkPic 1 1 parula "parula"
, mkPic (-1) 0 jet "jet", mkPic 0 0 inferno "inferno", mkPic 1 0 sinebow "sinebow"
, mkPic (-1) (-1) turbo "turbo", mkPic 0 (-1) plasma "plasma", mkPic 1 (-1) hsv "hsv" ]
where
mkPic x y cm txt =
translate (screenWidth/3 * x) (screenHeight/3 * y) $
mkGroup
[ scaleToSize (screenWidth/3) (screenHeight/3) $ embedImage $
applyColorMap cm monalisa
, translate (-screenWidth/6) (screenHeight/6) $
scale 0.5 $
withStrokeColor "black" $
withStrokeWidth (defaultStrokeWidth*0.5) $
withFillColor "white" $
latex ("\\texttt{" <> txt <> "}")
]
monalisa :: Image PixelRGB8
monalisa = unsafePerformIO $ do
dat <- BS.readFile "monalisa.jpg"
case decodeJpeg dat of
Left err -> error err
Right img -> return $ convertRGB8 img
monalisaLarge :: Image PixelRGB8
monalisaLarge = scaleImage 15 monalisa
scaleImage :: Pixel a => Int -> Image a -> Image a
scaleImage factor img =
generateImage fn (imageWidth img * factor) (imageHeight img * factor)
where
fn x y = pixelAt img (x `div` factor) (y `div` factor)
applyColorMap :: (Double -> PixelRGB8) -> Image PixelRGB8 -> Image PixelRGB8
applyColorMap cmap img =
generateImage fn (imageWidth img) (imageHeight img)
where
fn x y =
case pixelAt img x y of
PixelRGB8 r _ _ -> cmap (fromIntegral r/255)

View file

@ -0,0 +1,29 @@
On its own, data doesn't look like much.
Often it is merely a wall of unintelligible numbers.
However, if we take each number and assign it a shade of grey, suddenly
the data becomes understandable and the face of the monalisa stares out at us.
Using shades of grey is not the only possible color palette, though,
and many colorful alternatives exists. Some of these colormaps have been around
for a while, and Jet, one of the oldest, first appeared in the 1970s. However,
in recent years, a lot of attention has gone into addressing the shortcomings
of the early colormaps and create better standards for the future.
Particularly, Matlab replaced Jet by Parula in 2014, Viridis became the default
in Matplotlib by 2015, the research paper describing Cividis was published in
2018, and Google created Turbo as a spiritual successor to Jet in 2019.
So, why were so many new colormaps invented in this 5-year period? How are these
colormaps created? Are they merely the favorite colors of their creators? How
can a colormap be interesting enough to merit a research publication?
To answer these questions, first we have to explore a bit of color theory.
Visible light roughly ranges from a wavelength of 400nm to 700nm. If each
combination of wavelengths gave rise to a unique color then creating a color
space would be nigh impossible. Fortunately, most human eyes have just three
types of light-sensitive cells that respond to ranges of wavelengths, and the
space of colors is therefore reduced to three dimensions.
The axes are called S, M and L because the corrosponding cones are sensitive to
short, medium, and lone wavelengths respectively.

View file

@ -1,32 +1,33 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ApplicativeDo #-}
module Spectrum
( colorSpacesScene
, xyzTernaryPlot
, interpolation
, spacesA
, scene2
, scene3
( scene2
) where
import Control.Lens ((&), (.~))
import Codec.Picture
import Codec.Picture.Types
import Control.Monad
import qualified Data.ByteString as BS
import Data.Colour
import Data.Colour.CIE
import Data.Colour.CIE.Illuminant
import Data.Colour.RGBSpace
import Data.Colour.RGBSpace.HSV (hsvView)
import Data.Colour.SRGB
import Data.Colour.SRGB.Linear
import Data.List
import qualified Data.Map as Map
import Data.Maybe
import Data.Ord
import Data.Text (Text)
import Graphics.SvgTree hiding (Text)
import Linear.V2
import Reanimate
import Reanimate.Animation
import Reanimate.Builtin.CirclePlot
import Reanimate.Builtin.Documentation
import qualified Reanimate.Builtin.TernaryPlot as Ternary
import Reanimate.ColorMap
@ -41,6 +42,7 @@ import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg
import Reanimate.Svg.BoundingBox
import System.IO.Unsafe
{- STORYBOARD
- Wavelength scene
@ -58,8 +60,8 @@ xCoords = [ (nm, x/2) | (nm, (x, y, z)) <- Map.toList bigXYZCoordinates ]
yCoords = [ (nm, y/2) | (nm, (x, y, z)) <- Map.toList bigXYZCoordinates ]
zCoords = [ (nm, z/2) | (nm, (x, y, z)) <- Map.toList bigXYZCoordinates ]
labScaleX = 128
labScaleY = 128
labScaleX = 110 -- 100 -- 128
labScaleY = 110 -- 100 -- 128
blueName = "royalblue"
greenName = "green"
@ -67,7 +69,7 @@ redName = "maroon"
drawSensitivities :: Animation
drawSensitivities = sceneAnimation $ do
bg <- newSpriteA $ staticFrame 0 spectrumGrid
bg <- newSpriteSVG $ spectrumGrid True
spriteZ bg 1
forM_ [(short, blueName), (medium, greenName), (long, redName)] $
@ -81,7 +83,7 @@ drawSensitivities = sceneAnimation $ do
drawMorphingSensitivities :: Animation
drawMorphingSensitivities = sceneAnimation $ do
bg <- newSpriteA $ staticFrame 0 spectrumGrid
bg <- newSpriteSVG $ spectrumGrid True
spriteZ bg 1
forM_ keys $ \(datA, datB, name) -> do
@ -92,7 +94,7 @@ drawMorphingSensitivities = sceneAnimation $ do
, (medium, yCoords, greenName)
, (long, xCoords, redName)]
drawDur = 3
{-
colorSpacesScene :: Animation
colorSpacesScene = sceneAnimation $ mdo
beginT <- queryNow
@ -167,7 +169,7 @@ colorSpacesScene = sceneAnimation $ mdo
drawDur = 3
drawStagger = 1
drawPause = 4
-}
drawLabelS = drawLabel "S" blueName
drawLabelM = drawLabel "M" greenName
drawLabelL = drawLabel "L" redName
@ -176,25 +178,63 @@ drawLabelZ = drawLabel "Z" blueName
drawLabelY = drawLabel "Y" greenName
drawLabelX = drawLabel "X" redName
scene2Intro :: Animation
scene2Intro = staticFrame 10 (spectrumGrid False)
illustrateSpectrum :: Animation
illustrateSpectrum = sceneAnimation $ do
grid <- newSpriteSVG $ spectrumGrid False
spriteZ grid 1
forM_ (zip [0 ..] spectrum) $ \(nth, intensity) -> do
let nm = fromIntegral nth * (300/23/2) + 400
fork $ drawLine nm intensity
wait 0.05
wait 5
return ()
where
spectrum =
[ 0.02, 0.03, 0.07, 0.1, 0.2, 0.35, 0.7, 0.8, 0.9 ,0.75, 0.5 ] ++
[ 0.3, 0.27, 0.31, 0.25, 0.3, 0.35, 0.4, 0.5] ++
[ 0.65, 0.8, 0.95, 1.0, 0.95, 0.98, 0.93, 0.97] ++
[ 0.85, 0.75, 0.65, 0.60, 0.55, 0.52, 0.48, 0.45] ++
[ 0.45, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.15, 0.15, 0.13, 0.11, 0.08]
drawLine nm intensity = do
let Just c = fmap (promotePixel.toRGB8) (nmToColor (round nm))
s <- newSpriteSVG $
pathify $
withStrokeColorPixel c $
translate (-spectrumWidth/2) (-spectrumHeight/2) $
translate (fromToS 0 spectrumWidth ((nm-400)/(700-400))) 0 $
mkLine (0,0) (0, spectrumHeight*intensity)
spriteE s $ overEnding 0.3 fadeOutE
-- spriteTween s intensity $ partialSvg . curveS 2
spriteTween s 0.8 $ partialSvg . curveS 2
scene2 :: Animation
scene2 = {- dropA 29 $-} sceneAnimation $ do
scene2 = seqA scene2Intro $ seqA illustrateSpectrum $ sceneAnimation $ do
oldGrid <- newSpriteSVG $ spectrumGrid False
newGrid <- newSpriteSVG $ spectrumGrid True
spriteTween newGrid 0.5 withGroupOpacity
destroySprite newGrid
destroySprite oldGrid
-- SML labels and timings.
labelS <- fork $ newSpriteA $ drawLabelS
# applyE (overBeginning 0.3 fadeInE)
# applyE (overEnding 0.2 fadeOutE)
# mapA (uncurry translate (labelPosition short))
labelS <- newSpriteSVG $ drawLabelSVG "S" blueName
spriteMap labelS $ uncurry translate (labelPosition short)
labelM <- fork $ do
wait 2
wait 2.1
newSpriteA $ drawLabelM
# applyE (overBeginning 0.3 fadeInE)
# applyE (overEnding 0.2 fadeOutE)
# mapA (uncurry translate (labelPosition medium))
spriteMap labelM $ uncurry translate (labelPosition medium)
labelL <- fork $ do
wait 3.5
newSpriteA $ drawLabelL
# applyE (overBeginning 0.3 fadeInE)
# applyE (overEnding 0.2 fadeOutE)
# mapA (uncurry translate (labelPosition long))
spriteMap labelL $ uncurry translate (labelPosition long)
forM_ [labelS, labelM, labelL] $ \label -> do
spriteE label $ overBeginning 0.3 fadeInE
spriteE label $ overEnding 0.3 fadeOutE
play $ drawSensitivities
# pauseAtEnd 1
@ -207,15 +247,15 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
-- XYZ labels and timings
wait (duration drawMorphingSensitivities)
labelZ <- fork $ newSpriteA $ drawLabelZ
# applyE (overBeginning 0.3 fadeInE)
spriteE labelZ $ overBeginning 0.3 fadeInE
labelZPos <- spriteVar labelZ (labelPosition zCoords) $ uncurry translate
labelY <- fork $ newSpriteA $ drawLabelY
# applyE (overBeginning 0.3 fadeInE)
spriteE labelY $ overBeginning 0.3 fadeInE
labelYPos <- spriteVar labelY (labelPosition yCoords) $ uncurry translate
labelX <- fork $ newSpriteA $ drawLabelX
# applyE (overBeginning 0.3 fadeInE)
spriteE labelX $ overBeginning 0.3 fadeInE
labelXPos <- spriteVar labelX (labelPosition xCoords) $ uncurry translate
wait 4
@ -243,15 +283,17 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
redFactor <- newVar 0
greenFactor <- newVar 0
blueFactor <- newVar 0
xyzSpace <- newSprite $ do
getRed <- freezeVar redFactor
getGreen <- freezeVar greenFactor
getBlue <- freezeVar blueFactor
return $ \real_t d t ->
cieXYImage (getRed real_t) (getGreen real_t) (getBlue real_t) imgSize
-- xyzSpace <- newSparite $ cieXYImage <$> unVar redFactor <*> unVar greenFactor <*> unVar blueFactor
spriteE xyzSpace $ constE $ translate (-screenWidth/4) 0
xyzSpace <- newSprite $
cieXYImage
<$> unVar redFactor
<*> unVar greenFactor
<*> unVar blueFactor
<*> pure imgSize
spriteMap xyzSpace $ translate (-screenWidth/4) 0
spriteZ xyzSpace (-1)
spriteTween xyzSpace 0.5 $ aroundCenter . scale . curveS 2
wait 1
@ -265,21 +307,49 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
tweenVar blueFactor 1 $ \v -> fromToS v 1 . curveS 2
wait 3
visLine <- fork $ newSpriteA' SyncFreeze $ mkAnimation 3 $ \t ->
withStrokeWidth 0.03 $ withStrokeColor "white" $ morphXYZCoordinates t
let downShift = 1
visCurve <- newVar 0
waveLine <- fork $ newSpriteSVG $
translate (screenWidth/4) 0 $
scale 0.5 $
translate 0 (-spectrumHeight/2) $
wavelengthAxis
spriteTween waveLine 1 $ \d ->
translate 0 (fromToS 0 (-downShift) $ curveS 2 d)
-- wait 1
visLine <- newSprite $
withStrokeWidth 0.03 . withStrokeColor "white" . morphXYZCoordinates
<$> unVar visCurve
spriteZ visLine (1)
spriteTween waveLine 0.5 $ \t -> withGroupOpacity (1-t)
destroySprite waveLine
visSide <- spriteVar visLine 0 $ \t ->
translate 0 (-downShift) .
translate (fromToS (screenWidth/4) (-screenWidth/4) $ curveS 2 t) 0 .
scale (fromToS 0.5 1 $ curveS 2 t) .
translate 0 (fromToS (-spectrumHeight/2) 0 $ curveS 2 t)
fork $ tweenVar visSide 3 $ \v -> fromToS v 1
fork $ tweenVar visSide 3 $ \v -> fromToS v 1 . curveS 2
fork $ tweenVar visCurve 3 $ \v -> fromToS v 1 . curveS 2
fork $ spriteTween visLine 3 $ \d ->
translate 0 (fromToS 0 downShift $ curveS 2 d)
obsVisible <- newVar 1
gamut <- newVar 0
reorient <- newVar 0
cmOpacity <- newVar 1
cmDelta <- newVar 0
cmName <- newVar "sinebow"
cmFunc <- newVar sinebow
visSpace <- fork $ newSprite $ do
getObs <- freezeVar obsVisible
getGamut <- freezeVar gamut
return $ \real_t d t ->
getObs <- unVar obsVisible
getGamut <- unVar gamut
getReorient <- unVar reorient
getOpacity <- unVar cmOpacity
getDelta <- unVar cmDelta
getFunc <- unVar cmFunc
return $
translate (-screenWidth/4) 0 $
mkGroup
[ mkClipPath "visible"
@ -288,16 +358,43 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
]
, mkClipPath "sRGB"
[ simplify $
sRGBTriangle (getGamut real_t)
sRGBTriangle getGamut
]
, withGroupOpacity (getObs real_t) $
, withGroupOpacity getObs $
withClipPathRef (Ref "visible") $
mkGroup [cieXYImage 1 1 1 imgSize]
, withClipPathRef (Ref "sRGB") $
mkGroup [cieXYImageGamut (getGamut real_t) imgSize]
, translate 0 (1*getReorient) $
rotate (-gamutSlope sRGBGamut * getReorient) $
mkGroup
[ scale (1+0.5*getReorient) $
withClipPathRef (Ref "sRGB") $
mkGroup [cieXYImageGamut getGamut imgSize]
, lowerTransformations $ scale (1+0.5*getReorient) $
withGroupOpacity getOpacity $
cmToTernary getDelta getFunc
]
, withGroupOpacity getReorient $
translate 0 3.5 $ scale 0.5 $
center $ withFillColor "white" $
latex "sRGB"
]
colorMap <- newSprite $ do
getOpacity <- unVar cmOpacity
getDelta <- unVar cmDelta
getFunc <- unVar cmFunc
getName <- unVar cmName
return $
withGroupOpacity getOpacity $
mkGroup
[ renderColorMap getDelta (screenWidth*0.75) (screenHeight*0.15) getFunc
, withGroupOpacity (getDelta * 2) $
withFillColor "white" $
translate 0 1.2 $
scale 0.7 $
center $ latex getName
]
spriteTween colorMap 0 $ const $ translate 0 (-3)
wait 4
fork $ spriteTween visLine 1 $ \t -> withGroupOpacity (1-t)
@ -306,29 +403,167 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
fork $ spriteTween xyzGraph 1 $ \t -> withGroupOpacity (1-t)
fork $ spriteTween labelX 1 $ \t -> withGroupOpacity (1-t)
fork $ spriteTween labelY 1 $ \t -> withGroupOpacity (1-t)
fork $ spriteTween labelZ 1 $ \t -> withGroupOpacity (1-t)
fork $ tweenVar labelXPos 1 $ \(x,y) t ->
let (newX, newY) = (-1,3)
s = curveS 2 t
in (fromToS x newX s, fromToS y newY s)
fork $ tweenVar labelYPos 1 $ \(x,y) t ->
let (newX, newY) = (0,3)
s = curveS 2 t
in (fromToS x newX s, fromToS y newY s)
fork $ tweenVar labelZPos 1 $ \(x,y) t ->
let (newX, newY) = (1,3)
s = curveS 2 t
in (fromToS x newX s, fromToS y newY s)
-- fork $ spriteTween labelX 1 $ \t -> withGroupOpacity (1-t)
-- fork $ spriteTween labelY 1 $ \t -> withGroupOpacity (1-t)
-- fork $ spriteTween labelZ 1 $ \t -> withGroupOpacity (1-t)
wait 1
-- wait 1
spriteZ visSpace (-1)
spriteTween visSpace 1 $ \t ->
translate (fromToS 0 (screenWidth/4) $ curveS 2 t) 0
wait 2
rgb <- newSprite $ do
getGamut <- freezeVar gamut
return $ \real_t d t ->
withStrokeColor "white" $ sRGBTriangle (getGamut real_t)
rgb <- newSprite $ withStrokeColor "white" . sRGBTriangle <$> unVar gamut
spriteTween rgb 1 $ partialSvg
wait 1
tweenVar obsVisible 1 $ \v -> fromToS v 0 . curveS 2
wait 1
fork $ spriteTween rgb 1 $ \t -> withGroupOpacity (1-t)
tweenVar gamut 1 $ \v -> fromToS v 1 . curveS 2
fork $ spriteTween labelX 1 $ \t -> withGroupOpacity (1-t)
fork $ spriteTween labelY 1 $ \t -> withGroupOpacity (1-t)
fork $ spriteTween labelZ 1 $ \t -> withGroupOpacity (1-t)
tweenVar obsVisible 1 $ \v -> fromToS v 0 . curveS 2
wait 1
-- tweenVar gamut 1 $ \v -> fromToS v 1 . curveS 2
tweenVar reorient 1 $ \v -> fromToS v 1 . curveS 2
wait 1
writeVar cmName "jet"
writeVar cmFunc jet
tweenVar cmDelta 1 $ \d -> fromToS d 1
wait 2
spriteTween visSpace 1 $ \t -> translate (-2.5*curveS 2 t) 0
hsv <- newSprite $ do
getOpacity <- unVar cmOpacity
getDelta <- unVar cmDelta
getFunc <- unVar cmFunc
return $
mkGroup
[ lowerTransformations $
scaleToWidth (screenWidth*0.20) $
mkGroup [ hsvColorSpace 100
, withGroupOpacity getOpacity $
cmToHSV getDelta getFunc]
, translate 0 2 $ scale 0.5 $
center $ withFillColor "white" $
latex "HSV" ]
-- lchColorSpace 100
-- cieLABImage 100 50
-- cieLABImage 2000 2000
spriteTween hsv 0 $ const $ translate 3 1.5
spriteTween hsv 1 withGroupOpacity
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "sinebow"
writeVar cmFunc sinebow
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "parula"
writeVar cmFunc parula
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
fork $ spriteTween hsv 1 $ \t -> translate (2*curveS 2 t) 0
spriteTween visSpace 1 $ \t -> translate (-2*curveS 2 t) 0
lab <- newSprite $ do
getOpacity <- unVar cmOpacity
getDelta <- unVar cmDelta
getFunc <- unVar cmFunc
return $
mkGroup
[ lowerTransformations $
scaleToWidth (screenWidth/4) $
mkGroup [ cieLABImagePixels
, withGroupOpacity getOpacity $
cmToLAB getDelta getFunc]
, translate 0 2 $ scale 0.5 $
center $ withFillColor "white" $
latex "LAB" ]
spriteTween lab 0 $ const $ translate 0 1.5
spriteTween lab 1 withGroupOpacity
-- tweenVar rgbCM 1 $ \(cm,d) t -> (parula ,fromToS 0 1 t)
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "viridis"
writeVar cmFunc viridis
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "plasma"
writeVar cmFunc plasma
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "cividis"
writeVar cmFunc cividis
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "jet"
writeVar cmFunc jet
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
tweenVar cmOpacity 0.3 $ \o t -> fromToS o 0 t
writeVar cmName "turbo"
writeVar cmFunc turbo
writeVar cmOpacity 1
writeVar cmDelta 0
tweenVar cmDelta 5 $ \d -> fromToS d 1
wait 1
return ()
where
imgSize = 50
@ -337,6 +572,18 @@ scene2 = {- dropA 29 $-} sceneAnimation $ do
scale 5 $
renderXYZCoordinatesTernary
renderColorMap :: Double -> Double -> Double -> (Double -> PixelRGB8) -> Tree
renderColorMap delta width height cmap =
translate (-width/2 * (1-delta)) 0 $
mkGroup
[ scaleToSize (width*delta) height $ showColorMap (\t -> cmap (t*delta))
, withStrokeWidth (defaultStrokeWidth*0.7) $
withStrokeColor "white" $ withFillOpacity 0 $
mkRect (width*delta) height
]
{-
scene3 :: Animation
scene3 = sceneAnimation $ do
play $ mkAnimation 1 $ \t -> cieXYImage 0 0 t imgSize
@ -355,7 +602,9 @@ scene3 = sceneAnimation $ do
lowerTransformations $
scale (100/2) $
renderLABCoordinates
-}
{-
frame = mkAnimation 2 $ \t ->
-- emit $ mkBackground "black"
-- emit $ spectrumGrid
@ -399,7 +648,7 @@ frame = mkAnimation 2 $ \t ->
imgSize :: Num a => a
imgSize = 1000
img1 = cieXYImage 1 1 1 imgSize
img2 = cieLABImage imgSize imgSize
img2 = cieLABImage imgSize 50
obsColors =
lowerTransformations $
scale 5 $
@ -408,7 +657,9 @@ frame = mkAnimation 2 $ \t ->
lowerTransformations $
scale (100/2) $
renderLABCoordinates
-}
{-
xyzTernaryPlot = mkAnimation 2 $ \t ->
-- emit $ mkBackground "black"
-- emit $ spectrumGrid
@ -451,7 +702,7 @@ xyzTernaryPlot = mkAnimation 2 $ \t ->
imgSize :: Num a => a
imgSize = 50
img1 t = cieXYImage t t t imgSize
img2 = cieLABImage imgSize imgSize
img2 = cieLABImage imgSize 50
obsColors =
lowerTransformations $
scale 5 $
@ -460,7 +711,7 @@ xyzTernaryPlot = mkAnimation 2 $ \t ->
lowerTransformations $
scale (100/2) $
renderLABCoordinates
-}
renderXYZCoordinatesTernary :: Tree
renderXYZCoordinatesTernary =
@ -496,7 +747,7 @@ cieXYImageGamut t density = Ternary.ternaryPlot density $ \aCoord bCoord cCoord
aCoord' = fromToS aCoord (rX * aCoord + gX * bCoord + bX * cCoord) t
bCoord' = fromToS bCoord (rY * aCoord + gY * bCoord + bY * cCoord) t
cCoord' = fromToS cCoord (rZ * aCoord + gZ * bCoord + bZ * cCoord) t
RGB r g b = toSRGBBounded (cieXYZ aCoord' bCoord' cCoord)
RGB r g b = toSRGBBounded (cieXYZ aCoord' bCoord' cCoord')
in PixelRGBA8 r g b 0xFF
where
RGB r g b = primaries sRGBGamut
@ -504,6 +755,80 @@ cieXYImageGamut t density = Ternary.ternaryPlot density $ \aCoord bCoord cCoord
(gX, gY, gZ) = chromaCoords $ chromaConvert g
(bX, bY, bZ) = chromaCoords $ chromaConvert b
-- slope in degrees
gamutSlope :: RGBGamut -> Double
gamutSlope gamut = atan2 (y1/y2) (x1/x2) / pi * 180
where
RGB r g b = primaries sRGBGamut
(gX, gY, _gZ) = chromaCoords $ chromaConvert g
(bX, bY, _bZ) = chromaCoords $ chromaConvert b
(x1, y1) = Ternary.toCartesianCoords gX gY
(x2, y2) = Ternary.toCartesianCoords bX bY
strokeLine :: Double -> [(Double, Double)] -> SVG
strokeLine t points = mkGroup
[ withStrokeWidth (defaultStrokeWidth*1) $
withFillOpacity 0 $ withStrokeColor "black" $
partialSvg t $
mkLinePath points
& strokeLineCap .~ pure CapRound
, withStrokeWidth (defaultStrokeWidth*0.5) $
withFillOpacity 0 $ withStrokeColor "white" $
partialSvg t $ mkLinePath points
& strokeLineCap .~ pure CapRound
]
-- 0.15 0.06 -> 0 0
cmToTernary :: Double -> (Double -> PixelRGB8) -> Tree
cmToTernary 0 _ = mkGroup []
cmToTernary t cm =
lowerTransformations $ scale 5 $
strokeLine t points
where
steps = 100
points =
[ Ternary.toOffsetCartesianCoords (cieY/s) (cieX/s)
| n <- [0..steps]
, let PixelRGB8 red green blue = cm (fromIntegral n / fromIntegral steps)
(cieX,cieY,cieZ) = cieXYZView (sRGB24 red green blue)
s = cieX + cieY + cieZ
]
cmToHSV :: Double -> (Double -> PixelRGB8) -> Tree
cmToHSV t cm =
lowerTransformations $ scale (screenHeight/2) $
strokeLine t points
where
steps = 100
points =
[ (cos radian * s, sin radian * s)
| n <- [0..steps]
, let PixelRGB8 red green blue = cm (fromIntegral n / fromIntegral steps)
(h,s,_v) = hsvView (toSRGB $ sRGB24 red green blue)
radian = h/180*pi
]
-- dim = 100
-- -labScaleX = 0
-- 0 = dim/2
-- +labScaleX = dim
-- -labScaleX to +labScaleX
cmToLAB :: Double -> (Double -> PixelRGB8) -> Tree
cmToLAB t cm =
lowerTransformations $
translate (-screenHeight/2) (-screenHeight/2) $
strokeLine t points
where
steps = 100
points =
[ ( (a+labScaleX)/(labScaleX*2)*screenHeight,
(b+labScaleY)/(labScaleY*2)*screenHeight )
| n <- [0..steps]
, let PixelRGB8 red green blue = cm (fromIntegral n / fromIntegral steps)
(_l,a,b) = cieLABView d65 (sRGB24 red green blue)
]
-- aCoord = red
-- bCoord = green
-- cCoord = blue
@ -524,17 +849,54 @@ cieXYImage redFactor greenFactor blueFactor density =
-- RGB r g b = toSRGBBounded (chromaColour d65 1)
in PixelRGBA8 r g b 0xFF
cieLABImage :: Int -> Int -> Tree
cieLABImage width height = embedImage $ generateImage gen width height
cieLABImagePixels :: SVG
cieLABImagePixels = scaleToHeight screenHeight $ embedImage $
unsafePerformIO $ do
dat <- BS.readFile "lab.png"
case decodePng dat of
Left err -> error err
Right img -> return $ convertRGBA8 img
cieLABImages :: Int -> Tree
cieLABImages dim = mkGroup
[ cieLABImage dim lStar
| lStar <- [40..95]
]
cieLABImage :: Int -> Double -> Tree
cieLABImage dim = embedImage . cieLABImage' dim
cieLABImage' dim lStar = generateImage gen dim dim
where
gen x y =
let
aStar = (fromIntegral x / fromIntegral width) * labScaleX*2 - labScaleX
bStar = (1-(fromIntegral y / fromIntegral height)) * labScaleY*2 - labScaleY
lStar = findLStar aStar bStar
aStar = (fromIntegral x / fromIntegral dim) * labScaleX*2 - labScaleX
bStar = (1-(fromIntegral y / fromIntegral dim)) * labScaleY*2 - labScaleY
-- lStar = 50 -- findLStar aStar bStar
color = cieLAB d65 lStar aStar bStar
RGB r g b = toSRGBBounded (cieLAB d65 lStar aStar bStar)
-- RGB r g b = RGB (round $ lStar/100 * 255) (round $ lStar/100 * 255) (round $ lStar/100 * 255)
in PixelRGB8 r g b
in if inGamut sRGBGamut color
then PixelRGBA8 r g b 0xFF
else PixelRGBA8 0xFF 0xFF 0xFF 0x00
cieLABImage_ :: Int -> Tree
cieLABImage_ = embedImage . cieLABImage_'
cieLABImage_' dim = generateImage gen dim dim
where
gen x y =
let
aStar = (fromIntegral x / fromIntegral dim) * labScaleX*2 - labScaleX
bStar = (1-(fromIntegral y / fromIntegral dim)) * labScaleY*2 - labScaleY
-- lStar = 50 -- findLStar aStar bStar
colors = [ toSRGBBounded color
| lStar <- reverse [40 .. 95]
, let color = cieLAB d65 lStar aStar bStar
, inGamut sRGBGamut color ]
in case listToMaybe colors of
Nothing -> PixelRGBA8 0xFF 0xFF 0xFF 0x00
Just (RGB r g b) -> PixelRGBA8 r g b 0xFF
findLStar :: Double -> Double -> Double
findLStar aStar bStar = worker 0 100 10
@ -589,6 +951,7 @@ mkClosedLinePath :: [(Double, Double)] -> Tree
mkClosedLinePath [] = mkGroup []
mkClosedLinePath ((startX, startY):rest) =
PathTree $ defaultSvg & pathDefinition .~ cmds
& strokeLineJoin .~ pure JoinRound
where
cmds = [ MoveTo OriginAbsolute [V2 startX startY]
, LineTo OriginAbsolute [ V2 x y | (x, y) <- rest ]
@ -597,8 +960,32 @@ mkClosedLinePath ((startX, startY):rest) =
spectrumHeight = screenHeight * 0.5
spectrumWidth = screenWidth * 0.7
spectrumGrid :: Tree
spectrumGrid =
wavelengthAxis :: Tree
wavelengthAxis =
withStrokeWidth strokeWidth $
mkGroup
[ translate (-spectrumWidth/2) 0 $
withFillOpacity 0 $ withStrokeColor "white" $ mkPath $
[ MoveTo OriginAbsolute [V2 0 0]
, VerticalTo OriginRelative [tickLength,-tickLength]
, HorizontalTo OriginRelative [spectrumWidth]
, VerticalTo OriginRelative [tickLength,-tickLength]
]
++ concat
[ [ MoveTo OriginAbsolute [V2 (n / (nTicksX-1) * spectrumWidth) 0]
, VerticalTo OriginRelative [tickLength]]
| n <- [0..nTicksX-1]
]
]
where
strokeWidth = 0.03
nTicksX = 24
nTicksY = fromIntegral (round (spectrumHeight/spectrumWidth * nTicksX))
tickLength = spectrumHeight*0.02
spectrumGrid :: Bool -> Tree
spectrumGrid includeSensitivity =
withStrokeWidth strokeWidth $
mkGroup
[ --center $
@ -620,20 +1007,22 @@ spectrumGrid =
, HorizontalTo OriginRelative [tickLength]]
| n <- [0..nTicksY-1]
]
, withFillColor "white" $
translate (-spectrumWidth*0.5 + svgWidth sensitivity*1.2) 0 $
sensitivity
, if includeSensitivity
then withFillColor "white" $
translate (-spectrumWidth*0.5 + svgWidth sensitivity*1.2) 0 $
sensitivity
else None
, withFillColor "white" $
translate 0 (-spectrumHeight*0.5 + svgHeight wavelength*1.2) $
wavelength
-- , withFillColor "white" $
-- translate (-spectrumWidth*0.5 + svgWidth shortWaves/2)
-- (-spectrumHeight*0.5 + svgHeight shortWaves) $
-- shortWaves
-- , withFillColor "white" $
-- translate (spectrumWidth*0.5 - svgWidth shortWaves/2)
-- (-spectrumHeight*0.5 + svgHeight shortWaves) $
-- longWaves
, withFillColor "white" $
translate (-spectrumWidth*0.5 - svgWidth shortWaves)
(-spectrumHeight*0.5 + svgHeight shortWaves*1.2) $
shortWaves
, withFillColor "white" $
translate (spectrumWidth*0.5 - svgWidth longWaves)
(-spectrumHeight*0.5 + svgHeight longWaves*1.2) $
longWaves
]
where
strokeWidth = 0.03
@ -649,12 +1038,12 @@ spectrumGrid =
scale 0.8 $
latex "Wavelength"
shortWaves =
center $
scale 0.6 $
rotate (-45) $ center $
scale 0.3 $
latex "400 nm"
longWaves =
center $
scale 0.6 $
rotate (-45) $ center $
scale 0.3 $
latex "700 nm"
@ -679,6 +1068,15 @@ morphSensitivity datA datB c = animate $ \t ->
-- mkBackground "green"
sensitivitySVG :: Double -> Double -> [(Nanometer, Double)] -> String -> Tree
sensitivitySVG maxHeight limit dat c =
mkGroup
[ mkClipPath "spectrum" $
let margin = 1 in
[ simplify $ lowerTransformations $
translate 0 (margin/2) $
pathify $
mkRect spectrumWidth (spectrumHeight+margin)
]
,
withClipPathRef (Ref "spectrum") $
simplify $ lowerTransformations $
withStrokeColor c $
@ -692,7 +1090,7 @@ sensitivitySVG maxHeight limit dat c =
x = percent * spectrumWidth
y = n/maxHeight * spectrumHeight
, percent <= limit
]
] ]
where
initNM = fromIntegral $ fst (head dat)
lastNM = 700 -- fromIntegral $ fst (last dat)
@ -719,6 +1117,17 @@ drawLabel label c = animate $ const $
scale 1 $
latex label
drawLabelSVG :: Text -> String -> SVG
drawLabelSVG label c =
translate (0) (-svgHeight labelSVG * 1.5) $
withFillColor c $
labelSVG
where
labelSVG =
center $
scale 1 $
latex label
moveUp :: SVG -> SVG
moveUp svg = translate 0 (-svgHeight svg * 1.5) svg
@ -750,41 +1159,44 @@ interpolation = mkAnimation 2 $ \t ->
cyan = PixelRGB8 0x00 0xFF 0xFF
red = PixelRGB8 0xFF 0x00 0x00
lchColorSpace :: Int -> Int -> Tree
lchColorSpace width height = embedImage $ generateImage gen width height
where
toRad deg = deg/180 * pi
gen x y =
let
h = fromToS 0 360 (fromIntegral x / fromIntegral width)
aStar = (cos (toRad h) * c)
bStar = (sin (toRad h) * c)
l = findLStar aStar bStar
c = fromToS 0 (sqrt (labScaleX^2 + labScaleY^2)) (fromIntegral y / fromIntegral height)
-- RGB r g b = toSRGBBounded (colorPack lchComponents l c h)
RGB r g b = toSRGBBounded (cieLAB d65 l aStar bStar)
in PixelRGB8 r g b
lchColorSpace :: Int -> Tree
lchColorSpace width =
circlePlot width $ \ang radius ->
let
toRad deg = deg/180 * pi
h = ang/pi*180
aStar = (cos (toRad h) * c)
bStar = (sin (toRad h) * c)
l = 50 -- findLStar aStar bStar
c = fromToS 0 (sqrt (labScaleX^2 + labScaleY^2)) radius
-- RGB r g b = toSRGBBounded (colorPack lchComponents l c h)
color = cieLAB d65 l aStar bStar
RGB r g b = toSRGBBounded color
in if inGamut sRGBGamut color
then PixelRGBA8 r g b 0xFF
else PixelRGBA8 0 0 0 0x00
hsvColorSpace :: Int -> Int -> Tree
hsvColorSpace width height = embedImage $ generateImage gen width height
where
toRad deg = deg/180 * pi
gen x y =
let
h = fromToS 0 360 (fromIntegral x / fromIntegral width)
v = 1
s = fromToS 0 1 (fromIntegral y / fromIntegral height)
RGB r g b = toSRGBBounded (colorPack hsvComponents h s v)
in PixelRGB8 r g b
hsvColorSpace :: Int -> Tree
hsvColorSpace width =
circlePlot width $ \ang radius ->
let
h = ang/pi*180
v = 1
s = radius
color = colorPack hsvComponents h s v
RGB r g b = toSRGBBounded color
in if inGamut sRGBGamut color
then PixelRGBA8 r g b 0xFF
else PixelRGBA8 0 0 0 0x00
spacesA :: Animation
spacesA = mkAnimation 10 $ \t ->
scaleToSize screenWidth screenHeight $
if t < 0.3
then cieLABImage 100 100
then cieLABImage 100 50
else if t < 0.6
then lchColorSpace 100 100
else hsvColorSpace 100 100
then lchColorSpace 100
else hsvColorSpace 100
highlightE :: Effect
highlightE d t =

View file

@ -18,22 +18,25 @@ import Data.Word
import Graphics.SvgTree hiding (Image, imageHeight, imageWidth)
import Graphics.SvgTree.Memo
import Numeric
import Reanimate.ColorMap
import Reanimate.Driver (reanimate)
import Reanimate.LaTeX
import Reanimate
import Reanimate.Animation
import Reanimate.ColorMap
import Reanimate.ColorSpace
import Reanimate.Builtin.Flip
import Reanimate.Constants
import Reanimate.Driver (reanimate)
import Reanimate.Effect
import Reanimate.LaTeX
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Effect
import Reanimate.Interpolate
import Reanimate.Svg
import Reanimate.ColorSpace
import Reanimate.Constants
import System.IO.Unsafe
import Grid
import Spectrum
import EndScene
{- Scene sequence
- black
@ -63,11 +66,15 @@ import Spectrum
highdef = True
main :: IO ()
main = reanimate $
(animate $ const $ mkBackground "black") `parA`
main = reanimate $ -- takeA 10 $ dropA 55 $
parA (staticFrame 1 $ mkBackground "black") $
monalisaScene `seqA`
-- colorSpacesScene
scene2
falseColorScene `seqA`
scene2 `seqA`
(parA (staticFrame 1 $ mkBackground "aliceblue") $
overlapTransition 1.5 (signalT (curveS 2) flipTransition)
(parA (staticFrame 1 $ mkBackground "black") $ gridScene)
(parA (staticFrame 1 $ mkBackground "black") $ endScene))
-- scene3
-- xyzTernaryPlot
-- interpolation
@ -82,7 +89,7 @@ monalisaScene =
-- Draw numbers
fork $
play $ drawHexPixels
# setDuration (drawPixelDelay+toGrayScaleTime)
# setDuration (drawPixelDelay+toGrayScaleTime+3)
# pauseAtBeginning beginPause
# fadeIn stdFade
wait beginPause
@ -90,19 +97,20 @@ monalisaScene =
let PixelRGB8 minR _ _ = minPixel monalisa
PixelRGB8 maxR _ _ = maxPixel monalisa
waitAll $ do
fork $ do
play $ drawPixelImage (fromIntegral minR/255) ((fromIntegral maxR+1)/255)
# setDuration toGrayScaleTime
# pauseAround drawPixelDelay 5
-- Move monalisa to the side of the screen
play $ sceneFalseColorIntro
-- Show colormap as monalisa fades in
-- play $ showColorMap (fromIntegral minR/255) ((fromIntegral maxR+1)/255)
-- # setDuration toGrayScaleTime
-- # pauseAround 1 1
-- # fadeIn stdFade
-- # fadeOut stdFade
play $ showColorMap (fromIntegral minR/255) ((fromIntegral maxR+1)/255)
# setDuration 2
# pauseAround 1 1
# fadeIn stdFade
# fadeOut stdFade
play $ drawPixelImage (fromIntegral minR/255) ((fromIntegral maxR+1)/255)
# setDuration toGrayScaleTime
# pauseAround drawPixelDelay 3
-- Move monalisa to the side of the screen
play $ sceneFalseColorIntro
-- Cycle through colormaps for monalisa
-- play $ sceneColorMaps `sim` (sceneFalseColorChain $ map snd
@ -123,6 +131,52 @@ monalisaScene =
stdFade = 0.3
toGrayScaleTime = 3
falseColorScene :: Animation
falseColorScene = sceneAnimation $ do
delta <- newVar 0
cms <- newVar (greyscale, greyscale)
let total = 7
nth <- newVar 0
let pushCM label cm = do
(_, prevCM) <- readVar cms
writeVar delta 0
writeVar cms (prevCM, cm)
this <- readVar nth
writeVar nth (this+1)
s <- fork $ newSpriteA $ drawColorMap label cm
spriteE s (overBeginning 0.3 fadeInE)
spriteTween s 0 $ \_ -> positionColorMap total this
fork $ tweenVar delta 0.3 $ \v -> fromToS v 1 . curveS 2
wait 3
let cmSprite delta (cmap1, cmap2) t =
let s = curveS 3 t
cm x = interpolateRGB8 labComponents (cmap1 x) (cmap2 x) delta
in translate (screenWidth/4 - 0.75) 0 $
scaleToSize (screenWidth/2) (screenHeight/2) $
embedImage $ applyColorMap cm monalisa
s <- newSprite $
cmSprite
<$> unVar delta
<*> unVar cms
<*> spriteT
pushCM "greyscale" greyscale
pushCM "jet" jet
pushCM "turbo" turbo
pushCM "sinebow" sinebow
pushCM "parula" parula
pushCM "viridis" viridis
pushCM "cividis" cividis
wait 2
return ()
monalisa :: Image PixelRGB8
monalisa = unsafePerformIO $ do
@ -132,7 +186,7 @@ monalisa = unsafePerformIO $ do
Right img -> return $ convertRGB8 img
monalisaLarge :: Image PixelRGB8
monalisaLarge = scaleImage 15 monalisa
monalisaLarge = scaleImage (if highdef then 15 else 1) monalisa
maxPixel :: Image PixelRGB8 -> PixelRGB8
maxPixel img = pixelFold (\acc _ _ pix -> max acc pix) (pixelAt img 0 0) img
@ -213,14 +267,30 @@ sceneColorMaps = mkAnimation 5 $ const $
width = screenWidth*0.4
height = screenHeight*0.06
drawColorMap :: T.Text -> (Double -> PixelRGB8) -> Animation
drawColorMap label cmap = animate $ const $
mkGroup
[ renderColorMap width height cmap
, withFillColor "white" $ translate (-width/2 + screenWidth*0.01) (height*1.1) $
scale 0.3 $ latex label
]
where
width = screenWidth*0.4
height = screenHeight*0.06
positionColorMap :: Int -> Int -> SVG -> SVG
positionColorMap total nth =
translate xOffset (yInit - fromIntegral nth*yStep)
where
xOffset = -screenWidth*0.3
yInit = yStep*fromIntegral (total `div` 2)
yStep = height * 2.2
height = screenHeight*0.06
limitGreyPixels :: Word8 -> Image PixelRGB8 -> Image PixelRGBA8
limitGreyPixels limit img =
generateImage fn (imageWidth img) (imageHeight img)
where
fn x y =
let pixel@(PixelRGB8 r _ _) = pixelAt img x y
in if r < limit then promotePixel pixel else PixelRGBA8 0 0 0 1 --limit limit limit
limitGreyPixels limit = pixelMap $ \pixel@(PixelRGB8 r _ _) ->
if r < limit then PixelRGBA8 r r r 255 else PixelRGBA8 0 0 0 0
renderColorMap :: Double -> Double -> (Double -> PixelRGB8) -> Tree
renderColorMap width height cmap = mkGroup
@ -281,27 +351,33 @@ mkColorMap f = center $ embedImage img
drawPixelImage :: Double -> Double -> Animation
drawPixelImage start end = mkAnimation 2 $ \t ->
let limit = fromToS start end $ curveS 2 t
in scaleToSize screenWidth screenHeight $ center $ embedImage $
limitGreyPixels (floor (limit*255)) monalisaLarge
let limit = fromToS start end $ curveS 2 t
in scaleToSize screenWidth screenHeight $ center $ embedImage $
cache !! floor (limit*255)
where
cache =
[ limitGreyPixels n monalisaLarge
| n <- [0..255] ]
drawHexPixels :: Animation
drawHexPixels = mkAnimation 1 $ \_ -> simplify $ simplify $ simplify $
mkGroup
[ if highdef then defs else None
, withFillOpacity 1 $ withStrokeWidth 0 $ withFillColor "white" $
mkGroup
[ translate ((fromIntegral x+0.5)/fromIntegral width*screenWidth - screenWidth/2)
(screenHeight/2 - (fromIntegral y+0.5)/fromIntegral height*screenHeight) $
if highdef
then mkUse ("tag" ++ show r)
else mkCircle 0.5
| x <- [0..width-1]
, y <- [0..height-1]
, let pixel@(PixelRGB8 r _ _) = pixelAt monalisa x y
]
]
drawHexPixels = mkAnimation 1 $ \_ -> svg
where
svg = -- scaleToSize screenWidth screenHeight $ embedDynamicImage $ raster $
simplify $ simplify $ simplify $
mkGroup
[ if highdef then defs else None
, withFillOpacity 1 $ withStrokeWidth 0 $ withFillColor "white" $
mkGroup
[ translate ((fromIntegral x+0.5)/fromIntegral width*screenWidth - screenWidth/2)
(screenHeight/2 - (fromIntegral y+0.5)/fromIntegral height*screenHeight) $
if highdef
then mkUse ("tag" ++ show r)
else mkCircle 0.5
| x <- [0..width-1]
, y <- [0..height-1]
, let pixel@(PixelRGB8 r _ _) = pixelAt monalisa x y
]
]
defs = preRender $ mkDefinitions images
getNthSet n = centerX $ snd (splitGlyphs [n*2,n*2+1] allGlyphs)
allGlyphs = lowerTransformations $ scale 0.15 $ center $ latex $

View file

@ -0,0 +1,60 @@
module Main where
import Control.Lens ((&), (.~))
import Codec.Picture
import Control.Monad
import Data.Colour
import Data.Colour.CIE
import Data.Colour.CIE.Illuminant
import Data.Colour.RGBSpace
import Data.Colour.RGBSpace.HSV (hsvView)
import Data.Colour.SRGB
import Data.Colour.SRGB.Linear
import Data.List
import Data.Maybe
import qualified Data.Map as Map
import Data.Ord
import Data.Text (Text)
import Graphics.SvgTree hiding (Text)
import Linear.V2
import Reanimate
import Reanimate.Animation
import Reanimate.Builtin.CirclePlot
import Reanimate.Builtin.Documentation
import qualified Reanimate.Builtin.TernaryPlot as Ternary
import Reanimate.ColorMap
import Reanimate.ColorSpace
import Reanimate.Constants
import Reanimate.Driver (reanimate)
import Reanimate.Effect
import Reanimate.Interpolate
import Reanimate.LaTeX
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Svg
import Reanimate.Svg.BoundingBox
import Control.Parallel.Strategies
labScaleX = 110 -- 100 -- 128
labScaleY = 110 -- 100 -- 128
main :: IO ()
main = writePng "lab.png" (cieLABImage_' 1000)
cieLABImage_' dim = generateImage gen dim dim
where
gen x y =
let
aStar = (fromIntegral x / fromIntegral dim) * labScaleX*2 - labScaleX
bStar = (1-(fromIntegral y / fromIntegral dim)) * labScaleY*2 - labScaleY
-- lStar = 50 -- findLStar aStar bStar
colors = {-withStrategy (parList rpar)-} [ toSRGBBounded color
| lStar <- reverse [0, 0.1 .. 100]
, let color = cieLAB d65 lStar aStar bStar
, inGamut sRGBGamut color ]
in case listToMaybe colors of
Nothing -> PixelRGBA8 0xFF 0xFF 0xFF 0x00
Just (RGB r g b) -> PixelRGBA8 r g b 0xFF

85
videos/color-theory/posters.hs Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env stack
-- stack --resolver lts-13.14 runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Main (main) where
import Control.Lens ()
import Control.Monad
import qualified Data.ByteString as BS
import qualified Data.Map as Map
import Data.Monoid
import qualified Data.Text as T
import Codec.Picture
import Codec.Picture.Jpg
import Codec.Picture.Types
import Data.Word
import Graphics.SvgTree hiding (Image, imageHeight, imageWidth)
import Graphics.SvgTree.Memo
import Numeric
import Reanimate.ColorMap
import Reanimate.Driver (reanimate)
import Reanimate.LaTeX
import Reanimate
import Reanimate.Animation
import Reanimate.Raster
import Reanimate.Scene
import Reanimate.Signal
import Reanimate.Effect
import Reanimate.Svg
import Reanimate.ColorSpace
import Reanimate.Constants
import System.IO.Unsafe
main :: IO ()
main = reanimate $
mkAnimation (1/60) (const monalisaPoster)
monalisaPoster :: SVG
monalisaPoster =
mkGroup
--[ mkPic (-1) 1 jet, mkPic 0 1 turbo, mkPic 1 1 parula
--, mkPic (-1) 0 viridis, mkPic 0 0 inferno, mkPic 1 0 sinebow
--, mkPic (-1) (-1) plasma, mkPic 0 (-1) cividis, mkPic 1 (-1) hsv ]
[ mkPic (-1) 1 viridis "viridis", mkPic 0 1 cividis "cividis", mkPic 1 1 parula "parula"
, mkPic (-1) 0 jet "jet", mkPic 0 0 inferno "inferno", mkPic 1 0 sinebow "sinebow"
, mkPic (-1) (-1) turbo "turbo", mkPic 0 (-1) plasma "plasma", mkPic 1 (-1) hsv "hsv" ]
where
mkPic x y cm txt =
translate (screenWidth/3 * x) (screenHeight/3 * y) $
mkGroup
[ scaleToSize (screenWidth/3) (screenHeight/3) $ embedImage $
applyColorMap cm monalisa
, translate (-screenWidth/6) (screenHeight/6) $
scale 0.5 $
withStrokeColor "black" $
withStrokeWidth (defaultStrokeWidth*0.5) $
withFillColor "white" $
latex ("\\texttt{" <> txt <> "}")
]
monalisa :: Image PixelRGB8
monalisa = unsafePerformIO $ do
dat <- BS.readFile "monalisa.jpg"
case decodeJpeg dat of
Left err -> error err
Right img -> return $ convertRGB8 img
monalisaLarge :: Image PixelRGB8
monalisaLarge = scaleImage 15 monalisa
scaleImage :: Pixel a => Int -> Image a -> Image a
scaleImage factor img =
generateImage fn (imageWidth img * factor) (imageHeight img * factor)
where
fn x y = pixelAt img (x `div` factor) (y `div` factor)
applyColorMap :: (Double -> PixelRGB8) -> Image PixelRGB8 -> Image PixelRGB8
applyColorMap cmap img =
generateImage fn (imageWidth img) (imageHeight img)
where
fn x y =
case pixelAt img x y of
PixelRGB8 r _ _ -> cmap (fromIntegral r/255)

View file

@ -2,6 +2,7 @@
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ApplicativeDo #-}
module Main (main) where
import Codec.Picture
@ -103,7 +104,7 @@ sphereIntro = sceneAnimation $ do
-- # applyE (delayE 1 $ overBeginning 2 fadeInE)
-- # applyE (delayE 8 $ translateE (-3) 0)
wait 5
playZ 1 $ setDuration 3 $ animate $ \t ->
adjustZ (+1) $ play $ setDuration 3 $ animate $ \t ->
partialSvg t $
withFillOpacity 0 $
rotate 180 $
@ -114,25 +115,25 @@ sphereIntro = sceneAnimation $ do
-- circ
let scaleFactor = 0.05
tweenVar sphereX 1 $ \t x -> fromToS x (-3) (curveS 3 t)
fork $ playZ 1 $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
fork $ adjustZ (+1) $ play $ 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 ->
fork $ adjustZ (+1) $ play $ 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 ->
fork $ adjustZ (+1) $ play $ 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 ->
fork $ adjustZ (+1) $ play $ pauseAtEnd 2 $ setDuration 1 $ animate $ \t ->
let p = curveS 3 t in
withFillOpacity p $
translate (5*p) (-2*p) $
@ -155,13 +156,15 @@ 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
genAt <- unVar spriteAt
genT <- unVar spriteTMod
t <- spriteT
d <- spriteDuration
return $
let i = 1-genAt in
translate (xPos*i) (yPos*i) $
scale (1+0.5*genAt real_t) $
frameAtT (((t+genT real_t)/d) `mod'` 1) ani
scale (1+0.5*genAt) $
frameAtT (((t+genT)/d) `mod'` 1) ani
return (spriteAt, spriteTMod, sprite)
featSVG :: Animation
@ -227,8 +230,8 @@ introSVG = sceneAnimation $ do
fork $ play $ animate $ const $
mkBackground "black"
-- Title
title <- newSprite $ do
return $ \_ _d _t ->
title <- newSprite $
pure $
translate 0 3.5 $
center $
withFillColor "white" $
@ -237,9 +240,9 @@ introSVG = sceneAnimation $ do
-- Shading
shadeOpacity <- newVar 0
shade <- newSprite $ do
opacity <- freezeVar shadeOpacity
return $ \real_t d t ->
withFillOpacity (0.8 * opacity real_t) $
opacity <- unVar shadeOpacity
return $
withFillOpacity (0.8 * opacity) $
withFillColor "black" $
mkRect screenWidth screenHeight
spriteZ shade 1
@ -300,7 +303,8 @@ drawAnimation' fillDur step svg = sceneAnimation $ do
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t d t ->
t <- spriteT
return $
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
-- play $ animate (\t -> withStrokeWidth 0 $ fn $ withFillOpacity t tree)
-- # setDuration fillDur