packages feed

Xec-0.1.6: Identicon.hs

module Identicon where
import Control.Monad
import Data.List
import Graphics.Rendering.Cairo
import Text.Printf

curry3 f a b c=f (a,b,c)
uncurry3 f (a,b,c)=f a b c

defaultBlocks :: [Block]
defaultBlocks=nub $ concatMap expand
    [Block [0,1,4,3]
    ,Block [0,1,6]
    ,Block [0,1,3]
    ,Block [1,4,3]
    ,Block [0,1,7,6]
    ,Block [0,1,8,3]
    ,Block [0,1,5,8,7,3]
    ,Block [0,1,7,3]
    ,Block [0,1,3,7,6,3]
 --   ,Block [1,5,7,3] -- intentionally omitted for an aesthetic reason
    ]

defaultColors :: [Color]
defaultColors=map (\ix->hsvColor (fromIntegral ix/fromIntegral n) 0.45 1) [0..n-1]
    where n=12


test2 :: FilePath -> Identicon -> IO ()
test2 path ident=withImageSurface FormatRGB24 size size $ \s->renderWith s r >> surfaceWriteToPNG s path
    where r=do
              scale (fromIntegral size) (fromIntegral size)
              render ident
          size=36

testAll :: IO ()
testAll=mapM_ (\(ix,b)->test (printf "prim%03d.png" ix) b) $ zip ([1..] :: [Int]) defaultBlocks

test :: FilePath -> Block -> IO ()
test path block=withImageSurface FormatRGB24 size size $ \s->renderWith s r >> surfaceWriteToPNG s path
    where r=do
              scale (fromIntegral size) (fromIntegral size)
              
              setSourceRGB 0 0 0
              rectangle 0 0 1 1
              fill
            
              setSourceRGB 1 0 0
              renderBlock block
              fill
          size=32


-- | Identicon instance
data Identicon=Identicon Color Block Block Block deriving(Show,Eq)

numIdenticon :: Integer
numIdenticon=(fromIntegral $ length defaultBlocks)^3*(fromIntegral $ length defaultColors)

mapIdenticon :: Integer -> Identicon
mapIdenticon x=Identicon (appC per) (appB corner) (appB edge) (appB mid)
    where
        appB=(defaultBlocks!!)
        appC=(defaultColors!!)
    
        [per,corner,edge,mid]=decompose [m,n,n,n] x
        n=length defaultBlocks
        m=length defaultColors

decompose :: [Int] -> Integer -> [Int]
decompose [] x=[]
decompose (b:bs) x=fromIntegral m:decompose bs q
    where (q,m)=x `divMod` (fromIntegral b)


render :: Identicon -> Render ()
render (Identicon per c e m)=do
    setSourceColor $ RGB 1 1 1
    rectangle 0 0 1 1
    fill
    
    setSourceColor per
    zipWithM_ renderB table ss
    withinRegion 3 3 1 1 $ renderMid m
    fill
    where
        renderB (x,y) block=withinRegion 3 3 x y $ renderBlock block
        
        ss=[r0 c,r0 e,r1 c
           ,r3 e     ,r1 e
           ,r3 c,r2 e,r2 c]
        
        [r0,r1,r2,r3]=take 4 $ iterate (.rotateB) id
        table=[(x,y)|y<-[0,1,2],x<-[0,1,2],x/=1||y/=1]

renderMid m=mapM_ (uncurry renderB) $ zip [(0,0),(1,0),(1,1),(0,1)] $ take 4 $ iterate rotateB m
    where renderB (x,y) block=withinRegion 2 2 x y $ renderBlock block

withinRegion :: Int -> Int -> Int -> Int -> Render a -> Render a
withinRegion nx ny x y f=do
    save
    scale (1/fromIntegral nx) (1/fromIntegral ny)
    translate (fromIntegral x) (fromIntegral y)
    r<-f
    restore
    return r


-- | Unified color
data Color=RGB !Double !Double !Double deriving(Show,Eq)

rgbColor,hsvColor :: Double -> Double -> Double -> Color
rgbColor r g b=RGB r g b
hsvColor h s v=case i `mod` 6 of
    0 -> RGB v t p
    1 -> RGB q v t
    2 -> RGB p v t
    3 -> RGB p q v
    4 -> RGB t p v
    5 -> RGB v p q
    where
        (i,f)=properFraction $ h*6
        
        p=v*(1-s)
        q=v*(1-s*f)
        t=v*(1-s*(1-f))

setSourceColor :: Color -> Render ()
setSourceColor (RGB r g b)=setSourceRGB r g b

setSourceColorA :: Color -> Double -> Render ()
setSourceColorA (RGB r g b) alpha=setSourceRGBA r g b alpha


-- | Primitive shape
--   points' locations are:
--    0  1  2
--    3  4  5
--    6  7  8
newtype Block=Block [Int] deriving(Show)

-- | This is not complete. (ie:0,1,2 and 0,2)
instance Eq Block where
    (Block xs) == (Block ys)
        |length xs/=length ys = False
        |otherwise            = isInfixOf xs (ys++ys) || isInfixOf (reverse xs) (ys++ys)

-- maximum of 8 patterns (rot:4 * rev:2)
expand :: Block -> [Block]
expand block=nub $ map ($block) syms
    where
        syms=rots++map (.reverseB) rots
        rots=take 4 $ iterate (.rotateB) id

renderBlock :: Block -> Render ()
renderBlock (Block (x:xs))=do
    uncurry moveTo (table!!x)
    mapM_ (uncurry lineTo . (table!!)) xs
    closePath
    where table=[(0.5*fromIntegral x,0.5*fromIntegral y)|y<-[0,1,2],x<-[0,1,2]]

rotateB (Block xs)=Block $ map ([2,5,8,1,4,7,0,3,6]!!) xs
reverseB (Block xs)=Block $ map ([2,1,0,5,4,3,8,7,6]!!) xs