{-# LANGUAGE CPP #-} -- | A module providing a couple of Piet-specific types -- and simple associated functions needed throughout the library. module Language.Piet.Types ( -- * Piet Interpreter -- ** Direction Pointer and Codel Chooser DirectionPointer(..), addCoordinates, rotate , CodelChooser(..), toggle -- ** Piet's type system , PietType(..) -- ** Runtime program representation , Program(..), isBlocked -- * Colour system , Colour(..), rgba2Colour, rgb2Colour , HueColour(..), hueChange , Lightness(..), lightnessChange -- * Images , Image, imgWidth, imgHeight, imgInRange, imgNew, imgPixel, imgSetPixel , LabelKey, LabelInfo(..), labelSize, addPixel, LabelBorder(..) ) where import Data.Array.IArray import Data.IntMap hiding ((!)) import Data.Monoid import Data.Ord -- | The Direction Pointer (DP). data DirectionPointer = DPRight | DPDown | DPLeft | DPUp deriving (Show, Read, Eq, Ord, Enum) -- | Move coordinates by one in the direction of the 'DirectionPointer'. addCoordinates :: DirectionPointer -- ^ Direction to move to -> Int -- ^ x-coordinate -> Int -- ^ y-coordinate -> (Int, Int) -- ^ New x-/y-coordinates addCoordinates DPRight x y = (x + 1, y) addCoordinates DPDown x y = (x, y + 1) addCoordinates DPLeft x y = (x - 1, y) addCoordinates DPUp x y = (x, y - 1) -- | Rotate a 'DirectionPointer' clockwise (counter clockwise if the 'Int' is -- negative) a given number of times. rotate :: Int -> DirectionPointer -> DirectionPointer rotate n dp = let n' = (n + fromEnum dp) `rem` 4 in toEnum $ if n' < 0 then n' + 4 else n' -- | The Codel Chooser (CC). data CodelChooser = CCLeft | CCRight deriving (Show, Read, Eq, Ord, Enum) -- | Toggle a 'CodelChooser' a given number of times. toggle :: Int -> CodelChooser -> CodelChooser toggle n cc = let n' = (n + fromEnum cc) `rem` 2 in toEnum $ if n' < 0 then n' + 2 else n' -- | Piet types. Relevant to distinguish in-/output strategies. data PietType = PietNumber | PietChar deriving (Show, Read, Eq, Ord) -- | Runtime program representation. data Program = Program { image :: Image Colour -- ^ Original image , mask :: Image LabelKey -- ^ Labelled image , info :: IntMap LabelInfo -- ^ Information about the labels } -- | Returns if a given codel in a program is blocked in the Piet -- sense (which is the case when it is out of the image's range or -- 'Black'). isBlocked :: Int -> Int -> Program -> Bool isBlocked x y program = (not (imgInRange x y (image program))) || (Black == imgPixel x y (image program)) -- | The colours that make up a Piet program text. data Colour = Black | White | Hue {-# UNPACK #-} !Lightness {-# UNPACK #-} !HueColour deriving (Show, Read, Eq, Ord) -- | Converts red\/green\/blue\/alpha values to a 'Colour'. The alpha channel -- is ignored for now, but may be used in future implementations or -- dialects, so please use this function instead of 'rgb2Colour' whenever -- an alpha channel is available. rgba2Colour :: Num w => w -- ^ red -> w -- ^ green -> w -- ^ blue -> w -- ^ alpha -> Colour rgba2Colour r g b _ = rgb2Colour r g b -- | Converts red\/green\/blue values to a 'Colour'. If the supplied -- arguments do not form a proper Piet 'Colour', 'White' is returned. rgb2Colour :: Num w => w -- ^ red -> w -- ^ green -> w -- ^ blue -> Colour rgb2Colour 0x00 0x00 0x00 = Black rgb2Colour 0xff 0xff 0xff = White rgb2Colour 0xff 0xc0 0xc0 = Hue Light Red rgb2Colour 0xff 0x00 0x00 = Hue Normal Red rgb2Colour 0xc0 0x00 0x00 = Hue Dark Red rgb2Colour 0xff 0xff 0xc0 = Hue Light Yellow rgb2Colour 0xff 0xff 0x00 = Hue Normal Yellow rgb2Colour 0xc0 0xc0 0x00 = Hue Dark Yellow rgb2Colour 0xc0 0xff 0xc0 = Hue Light Green rgb2Colour 0x00 0xff 0x00 = Hue Normal Green rgb2Colour 0x00 0xc0 0x00 = Hue Dark Green rgb2Colour 0xc0 0xff 0xff = Hue Light Cyan rgb2Colour 0x00 0xff 0xff = Hue Normal Cyan rgb2Colour 0x00 0xc0 0xc0 = Hue Dark Cyan rgb2Colour 0xc0 0xc0 0xff = Hue Light Blue rgb2Colour 0x00 0x00 0xff = Hue Normal Blue rgb2Colour 0x00 0x00 0xc0 = Hue Dark Blue rgb2Colour 0xff 0xc0 0xff = Hue Light Magenta rgb2Colour 0xff 0x00 0xff = Hue Normal Magenta rgb2Colour 0xc0 0x00 0xc0 = Hue Dark Magenta rgb2Colour _ _ _ = White -- | Piet colours in the hue cycle. data HueColour = Red | Yellow | Green | Cyan | Blue | Magenta deriving (Show, Read, Eq, Ord, Enum) -- | Hue difference between two 'HueColour's. 'Red' means no change, -- 'Yellow' one step and so forth. hueChange :: HueColour -> HueColour -> HueColour hueChange c1 c2 = toEnum $ (fromEnum c2 - fromEnum c1) `mod` 6 -- | Hue lightness values supported by Piet. data Lightness = Light | Normal | Dark deriving (Show, Read, Eq, Ord, Enum) -- | Lightness difference between Piet lightness values. 'Light' -- represents no change, 'Normal' one step darker and 'Dark' -- two steps darker. lightnessChange :: Lightness -> Lightness -> Lightness lightnessChange l1 l2 = toEnum $ (fromEnum l2 - fromEnum l1) `mod` 3 -- | An image. Its coordinates will be @(0, 0) .. (width-1, height-1)@ data Image a = Image { imgWidth :: {-# UNPACK #-} !Int -- ^ Width of an 'Image' in pixels. , imgHeight :: {-# UNPACK #-} !Int -- ^ Height of an 'Image' in pixels. , imgPixels :: !(Array (Int, Int) a) -- ^ An 'Array' storing the pixels of an 'Image'. } #ifndef __HADDOCK__ deriving (Show, Eq, Ord) #else instance (Show a) => Show (Image a) instance (Eq a) => Eq (Image a) instance (Ord a) => Ord (Image a) #endif instance Functor Image where fmap f img = img { imgPixels = amap f (imgPixels img) } -- | Build a new image. imgNew :: Int -- ^ Width -> Int -- ^ Height -> [((Int, Int), a)] -- ^ Coordinate-value list -> Image a imgNew width height entries = Image { imgWidth = width , imgHeight = height , imgPixels = array ((0, 0), (width - 1, height - 1)) entries } -- | Find out, if the given coordinates are within the 'Image' -- borders (which are @ (0, 0) .. (width-1, height-1)@). imgInRange :: Int -- ^ x-coordinate -> Int -- ^ y-coordinate -> Image a -- ^ An 'Image' -> Bool -- ^ If @(x, y)@ is within the 'Image' imgInRange x y img = 0 <= x && x < imgWidth img && 0 <= y && y < imgHeight img -- | Access a pixel at given x/y-coordinates. imgPixel :: Int -- ^ x-coordinate -> Int -- ^ y-coordinate -> Image a -> a imgPixel x y img = (imgPixels img) ! (x, y) -- | Set a pixel at given x/y-coordinates. imgSetPixel :: Int -> Int -> a -> Image a -> Image a imgSetPixel x y pixel img = img { imgPixels = (imgPixels img) // [((x, y), pixel)] } -- | We'll just use 'Int's to identifiy labels. type LabelKey = Int -- | Stores compiler-relevant information about a label. This type -- implements an instance of 'Monoid' to merge labels. data LabelInfo = EmptyInfo -- ^ The empty label | LabelInfo { _labelSize :: {-# UNPACK #-} !Int -- ^ Number of pixels , labelTop :: {-# UNPACK #-} !LabelBorder -- ^ Top border , labelLeft :: {-# UNPACK #-} !LabelBorder -- ^ left border , labelBottom :: {-# UNPACK #-} !LabelBorder -- ^ Bottom border , labelRight :: {-# UNPACK #-} !LabelBorder -- ^ Right border } -- ^ Label with a size and four borders deriving (Show, Eq, Ord) -- | Number of pixels in a label. This function is defined for all -- constructors of 'LabelInfo' so, in contrast to '_labelSize', it -- won't fail on 'EmptyInfo' . labelSize :: LabelInfo -> Int labelSize EmptyInfo = 0 labelSize _info@(LabelInfo { }) = _labelSize _info instance Monoid LabelInfo where mempty = EmptyInfo mappend EmptyInfo i = i mappend i EmptyInfo = i mappend i1 i2 = LabelInfo { _labelSize = labelSize i1 + labelSize i2 , labelTop = mergeMin (labelTop i1) (labelTop i2) , labelLeft = mergeMin (labelLeft i1) (labelLeft i2) , labelBottom = mergeMax (labelBottom i1) (labelBottom i2) , labelRight = mergeMax (labelRight i1) (labelRight i2) } -- | Holds information of a label (coloured area) relevant for the Piet -- language, i. e. information about where the program flow will be -- directed regarding a Direction Pointer. -- -- Holds a border position (e. g. an x-coordinate) and the minimum -- or maximum associated \"other\" coordinates (e. g. y-coordinates). data LabelBorder = LabelBorder { borderCoord :: {-# UNPACK #-} !Int -- ^ Where the border is located , borderMin :: {-# UNPACK #-} !Int -- ^ Minimum \"other\" coordinate of the border , borderMax :: {-# UNPACK #-} !Int -- ^ Maximum \"other\" coordinate of the border } deriving (Show, Eq, Ord) -- | Merge two 'LabelBorder's holding a /maximum/ coordinate. mergeMin :: LabelBorder -> LabelBorder -> LabelBorder mergeMin = merge (comparing borderCoord) -- | Merge two 'LabelBorder's holding a /minimum/ coordinate. mergeMax :: LabelBorder -> LabelBorder -> LabelBorder mergeMax = merge (comparing (negate . borderCoord)) -- | General merge, see 'mergeMin' and 'mergeMax' merge :: (LabelBorder -> LabelBorder -> Ordering) -> LabelBorder -> LabelBorder -> LabelBorder merge comp b1 b2 = case comp b1 b2 of EQ -> b1 { borderMin = min (borderMin b1) (borderMin b2) , borderMax = max (borderMax b1) (borderMax b2) } LT -> b1 GT -> b2 -- | Add a pixel to a 'LabelInfo'. addPixel :: Int -> Int -> LabelInfo -> LabelInfo addPixel x y EmptyInfo = LabelInfo { _labelSize = 1 , labelTop = LabelBorder y x x , labelLeft = LabelBorder x y y , labelBottom = LabelBorder y x x , labelRight = LabelBorder x y y } addPixel x y nonEmpty = nonEmpty { _labelSize = 1 + labelSize nonEmpty , labelTop = mergeMin (labelTop nonEmpty) (LabelBorder y x x) , labelLeft = mergeMin (labelLeft nonEmpty) (LabelBorder x y y) , labelBottom = mergeMax (labelBottom nonEmpty) (LabelBorder y x x) , labelRight = mergeMax (labelRight nonEmpty) (LabelBorder x y y) }