## Tuesday, 1 September 2009

### Generating ASCII Art

ASCII art is a way of presenting graphics through standard characters, without the need for images. For example, you could draw a triangle, using just forward and backwards characters.

`  /\ /__\`

Given an arbitrary picture, how do we convert this to ASCII art? The technique is dead simple - convert the image to gray scale and replace each pixel in the image with a character representing the brightness value. For example, a * character is darker than a ! character.

The following Haskell program does just that. It uses the PGM package to load an image (so it's already converted to gray scale). All it does it map the pixel to a character using a function and amap and then do some jiggery pokery to turn it into an image.

`import Graphics.Pgmimport Text.Parsec.Errorimport Data.Array.Basebrightness =  " .`-_':,;^=+/\"|)\\<>)iv%xclrs{*}I?!][1taeo7zjLu" ++               "nT#JCwfy325Fp6mqSghVd4EgXPGZbYkOA&8U\$@KHDBWNMR0Q";loadImage :: String -> IO (UArray (Int,Int) Int)loadImage path = do                    r <- pgmsFromFile path                    case r of                      Left e -> error "Failed to parse file"                      Right i -> return (head i)brightnessToChar :: Int -> Int -> CharbrightnessToChar m b = brightness !!                        (round ((fromIntegral b) / (fromIntegral m) * (fromIntegral ((length brightness) - 1))))imageToAscii :: UArray (Int,Int) Int -> UArray (Int,Int) CharimageToAscii image = amap (brightnessToChar 255)  image     convertImage :: String -> String -> IO ()convertImage image out = do                       img <- loadImage image                       let ((_,_),(h,w)) = bounds img                       let x = imageToAscii img                       writeFile out (unlines [ [ x ! (i,j) | i <- [0..w] ] | j <- [0..h] ])                       return ()`

Interesting learning exercises for me were the nested list comprehension to go through the array and the use of `Either` to represent a choice of return values. This seems to be used when you want to return more data than simply "it failed" (which you'd use `Maybe`).

Because I can't quite work out how to get a tiny font, it's easier to post a screen shot of some ASCII art (kind of defeats the purpose, I know!). Below is the Ubuntu logo rendered (badly!) as ASCII art. 