FractalArt-0.2.0.0: src/Main.hs
module Main where
import System.Environment (getArgs)
import System.Random (randomIO)
import System.Random.MWC (Gen, initialize, uniformR)
import System.Directory (getHomeDirectory, createDirectoryIfMissing)
import System.FilePath ((</>))
import Control.Monad (liftM, unless)
import Control.Monad.Primitive
import Data.Word (Word32)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as UV
import qualified Data.Vector.Unboxed.Mutable as MV
import Data.Vector.Unboxed.Mutable (MVector, write)
import Data.Vector.Unboxed (Vector, (!), freeze)
import Codec.Picture
import FractalArt.SimpleCommandParser
import FractalArt.ForeignFunctions
type Position = (Int, Int)
type Size = (Int, Int)
type Color = (Float, Float, Float)
type Grid m = MVector m (Bool, Color)
aOutputDir, aFileName :: Arg String
aOutputDir = Arg "output" 'o' return
aFileName = Arg "file" 'f' return
aWidth, aHeight :: Arg Int
aWidth = Arg "width" 'w' (return . read)
aHeight = Arg "height" 'h' (return . read)
fNoSetWallpaper :: Flag
fNoSetWallpaper = Flag "no-bg" 'n'
main :: IO ()
main = do
args <- getArgs
home <- getHomeDirectory
let workingDir = parseArg aOutputDir args `def` (home </> ".fractalart")
let imageFile = parseArg aFileName args `def` (workingDir </> "wallpaper.bmp")
createDirectoryIfMissing False workingDir
putStrLn $ "Working in: " ++ workingDir
size@(width, height) <- do
(w,h) <- getScreenSize
let nw = parseArg aWidth args `def` w
let nh = parseArg aHeight args `def` h
return (nw, nh)
putStrLn $ "Screen Resolution: " ++ show size
seed <- randomIO :: IO Word32
gen <- initialize (V.singleton seed)
startPosition <- do
x <- uniformR (0, width - 1) gen
y <- uniformR (0, height - 1) gen
return (x, y)
putStrLn "Generating Image..."
-- Generate and force evaluation
grid <- runBG startPosition size gen
putStrLn $ "Saving To: " ++ imageFile
-- Create an immutable copy of the grid
frozen <- freeze grid
let bitmap = UV.map snd frozen
writeBitmap imageFile $ createImage size bitmap
unless (parseFlag fNoSetWallpaper args)
(setWallpaper imageFile)
putStrLn "Done"
-- | Create an image from a vector of colors
createImage :: Size -> Vector Color -> Image PixelRGB8
createImage (width, height) grid = generateImage colorAt width height
where
colorAt x y = toPixel $ grid ! toIndex (width, height) (x, y)
toPixel (r, g, b) = PixelRGB8 (toByte r) (toByte g) (toByte b)
toByte f = floor(f * 255)
-- | Generate the wallpaper
runBG :: PrimMonad m => Position -> Size -> Gen (PrimState m) -> m (Grid (PrimState m))
runBG pos@(x, y) size@(w, h) gen = do
grid <- startGrid
mapM_ (iter pos size gen grid) [1..iterations]
return grid
where
startGrid = do
grid <- MV.replicate (w * h) (False, (0, 0, 0))
col <- startColor
write grid (toIndex size pos) (True, col)
return grid
startColor = do
hue <- uniformR (0, 360) gen
--sat <- uniform gen
return $ hsv hue 0.6 1
iterations = maximum [x, w - 1 - x, y, h - 1 - y]
iter :: PrimMonad m => Position -> Size -> Gen (PrimState m) -> Grid (PrimState m) -> Int -> m ()
iter pos size gen grid n = mapM_ f next
where
f p = do
col <- nextColor grid size gen p
write grid (toIndex size p) (True, col)
next = filter (isInside size) $ ringAt pos n
nextColor :: PrimMonad m => Grid (PrimState m) -> Size -> Gen (PrimState m) -> Position -> m Color
nextColor grid size gen pos = do
c <- colors
i <- uniformR (0, length c - 1) gen
m <- modColor (mkRange $ length c) (c !! i)
return . clampColor $ m
where
colors = liftM (map snd . filter isValid) . mapM (MV.read grid . toIndex size) . filter (isInside size) $ ringAt pos 1
mkRange l = 0.006 * 4 / fromIntegral l
modColor range (r, g, b) = do
mr <- uniformR (-range, range) gen
mg <- uniformR (-range, range) gen
mb <- uniformR (-range, range) gen
return (r + mr, g + mg, b + mb)
foldM' :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM' _ z [] = return z
foldM' f z (x:xs) = do
z' <- f z x
z' `seq` foldM' f z' xs
hsv :: Float -> Float -> Float -> Color
hsv h s v = case hId of
0 -> (v, t, p)
1 -> (q, v, p)
2 -> (p, v, t)
3 -> (p, q, v)
4 -> (t, p, v)
5 -> (v, p, q)
_ -> error $ "invalid hue: " ++ show h
where
hId :: Int
hId = floor (h / 60) `mod` 6
f = h / 60 - fromIntegral hId
p = v * (1 - s)
q = v * (1 - s * f)
t = v * (1 - s * (1 - f))
clampColor :: Color -> Color
clampColor (r, g, b) = (f r, f g, f b)
where
f = min 1 . max 0
ringAt :: Position -> Int -> [Position]
ringAt (x, y) l = sides ++ top ++ bottom
where
top = [(n + x, l + y) | n <- [-l .. l]]
bottom = [(n + x, -l + y) | n <- [-l .. l]]
sides = concat [[(l + x, n + y), (-l + x, n + y)] | n <- [1 - l .. l - 1]]
toIndex :: Size -> Position -> Int
toIndex (w, _) (x, y) = y * w + x
isValid :: (Bool, a) -> Bool
isValid = fst
isInside :: Size -> Position -> Bool
isInside (w, h) (x, y) = inside' x w && inside' y h
where
inside' n s = (n < s) && (n >= 0)