module Pictures
( Picture
, horse
, flipV
, flipH
, above
, beside
, superimpose
, invertColour
, scale
, black
, white
)
where
import Test.SmallCheck
import Test.SmallCheck.Series
import Data.List
import Control.Monad
newtype Picture = Picture [[Char]]
deriving Eq
-- The example used in Craft2e: a polygon which looks like a horse. Here
-- taken to be a 16 by 12 rectangle.
horse :: Picture
horse = Picture
[" ## ",
" ## # ",
" ## # ",
" # # ",
" # # # ",
" # ### # ",
" # # ## ",
" # # ",
" # # ",
" # # ",
" # # ",
" ## "]
-- Completely white and black pictures.
white :: Picture
white = Picture [" "]
black = Picture ["#"]
-- Getting a picture onto the screen.
instance Show Picture where
show (Picture p) = "\n" ++ concatMap (\x -> "|" ++ x ++ "|\n") p
-- SmallCheck generator
instance Serial Picture where
series d = do
size <- [0..d]
map (fromList size) $ replicateM (size^2) [' ', '#']
coseries = error "Picture.coseries"
fromList :: Int -> [Char] -> Picture
fromList size cs = Picture $ unfoldr f cs
where
f [] = Nothing
f xs = Just $ splitAt size xs
-- Transformations of pictures.
pic :: ([[Char]] -> [[Char]]) -> Picture -> Picture
pic f (Picture p) = Picture $ f p
pic2 :: ([[Char]] -> [[Char]] -> [[Char]]) -> Picture -> Picture -> Picture
pic2 f (Picture p1) (Picture p2) = Picture $ f p1 p2
-- Reflection in a vertical mirror.
flipV :: Picture -> Picture
flipV = pic $ map reverse
-- Reflection in a horizontal mirror.
flipH :: Picture -> Picture
flipH = pic reverse
-- Rotation through 180 degrees, by composing vertical and horizontal
-- reflection. Note that it can also be done by flipV.flipH, and that we
-- can prove equality of the two functions.
rotate :: Picture -> Picture
rotate = flipH . flipV
-- One picture above another. To maintain the rectangular property,
-- the pictures need to have the same width.
above :: Picture -> Picture -> Picture
above = pic2 (++)
-- One picture next to another. To maintain the rectangular property,
-- the pictures need to have the same height.
beside :: Picture -> Picture -> Picture
beside = pic2 $ zipWith (++)
-- Superimose one picture above another. Assume the pictures to be the same
-- size. The individual characters are combined using the combine function.
superimpose :: Picture -> Picture -> Picture
superimpose = pic2 $ zipWith (zipWith combine)
-- For the result to be '.' both components have to the '.'; otherwise
-- get the '#' character.
combine :: Char -> Char -> Char
combine topCh bottomCh
= if (topCh == ' ' && bottomCh == ' ')
then ' '
else '#'
-- Inverting the colours in a picture; done pointwise by invert...
invertColour :: Picture -> Picture
invertColour = pic $ map (map invert)
-- ... which works by making the result '.' unless the input is '.'.
invert :: Char -> Char
invert ch = if ch == ' ' then '#' else ' '
scale :: Int -> Picture -> Picture
scale n = pic $ \ls ->
map (>>= replicate n) ls >>= replicate n