Haskell で PIL みたいに気軽にお絵かき / mandelbrot
イントロ
最近しばらくごく小さな javascript を除いては基本的に haskell 縛りで生きてみようというようなことを考えついて, 画像についてちょっと調べてみたのですが,どうやらベクタ画像については Diagrams というのがかなりいいっぽい.
一方 python でいう PIL みたいにラスタ画像を手軽に扱えるのないのかなーと思ってたのですが, JuicyPixels: Picture loading/serialization (in png, jpeg, bitmap, gif, tga, tiff and radiance) というのがわりとそれっぽく使えそうな感じなのでご紹介. 何故か僕の環境には入ってたんだけど,これは自分で入れてたのか,なんかのライブラリの依存元だったのか.
Codec.Picture (hoogle)みればだいたい
使い方がわかるのは静的型のありがたいところですね.
どうやら基本的に Data.Word.Word8
をピクセルや RGB 値として使い,画像はその列として
Data.Vector.Storable.Vector
を基盤にできるっぽい.
Gray scale で横幅10px くらいの png を出力するのはこんな感じ:
import qualified Data.Word as W
import qualified Data.Vector.Storable as V
import qualified Codec.Picture as P
main = let dat = V.fromList [255,254,254,254,23,23,24,150,150,250,250] :: V.Vector W.Word8
img = P.Image { P.imageHeight = 1, P.imageWidth = V.length dat, P.imageData = dat } :: P.Image P.Pixel8
in
P.writePng "./out.png" img
Mandelbrot!
人がなぜ png 画像を扱いたいかというと,それはひとえにフラクタル画像を描いてみたいからであります.違うか.
Python とか javascript とかで描いてもう飽きた感もあるけど,haskell で! とかんがえるとやる気も出てくるので
900*600
px で Mandelbrot を描いてみました.結構速い,大きくなった時にどうなるかわからんけど.
ソースファイルは こちら
import Data.Complex
import qualified Data.Word as W
import qualified Data.Vector.Storable as V
import qualified Codec.Picture as P
main :: IO ()
main = P.writePng "./out.png" $ mandel ((-2):+(-1)) (1:+1) 900 600
type CPoint = Complex Double
mandel :: CPoint -> CPoint -> Int -> Int -> P.Image P.Pixel8
mandel topLeft bottomRight width height = P.Image {
P.imageWidth = width,
P.imageHeight = height,
P.imageData = V.generate (width*height)
(\n -> let (y,x) = n `divMod` width in
convergence (topLeft + dx* fromIntegral x + dy* fromIntegral y))
} where
dx = (realPart bottomRight - realPart topLeft) / fromIntegral width :+ 0
dy = 0 :+ (imagPart bottomRight - imagPart topLeft) / fromIntegral height
convergence :: CPoint -> W.Word8
convergence c = convergence' c 5.0 (0:+0) 200
convergence' :: CPoint -> Double -> CPoint -> Int -> W.Word8
convergence' _ _ _ 0 = 0
convergence' c threshold xn n
| magnitude xn' > threshold = 255 - fromIntegral n
| otherwise = convergence' c threshold xn' (n-1)
where
xn' = xn*xn + c
それでこんな感じ.
手軽にちょっと書きたいときとかにはかなりいいかも知れません.