Beginner’s Haskell: Creating Bitmap Images

8 minute read

Let’s use the library gloss in Haskell to easily display images. We’ll explore the types used by gloss and create a bitmap using ByteString.

Project Setup

We’ll use stack for this project, which I recommend for anyone starting out with haskell.

Download and Install Stack

Run this command to create a directory with your new project:

stack new gloss-bitmaps

Run the following to confirm the project is working. This may take some time when downloading GHC and libraries.

cd gloss-bitmaps/
stack setup
stack build
stack exec gloss-bitmaps-exe

This should print the text someFunc. If this is not working at this point, you will need to troubleshoot your stack installation or setup.

Finding Documentation

Usage examples for Haskell libraries can be hard to find, and we often must rely on library documentation. I would like to explore the process for finding API documentation, using gloss as an example.

Searching for “graphics libraries in Haskell” turns up this list: Applications and libraries/Graphics. The HaskellWiki is often a good source for high level overviews like this.

Through search results and listings, I see gloss is a popular library and has a high-level API with examples.

The top level hackage page for gloss does not have any information about basic usage. This page is what you will usually find when searching for a Haskell library. We need to dig into the module documentation.

There is a Modules section on the hackage page, let’s click the top level module for more information: Graphics.Gloss.

This page works as a starting point. We have a basic example and API documentation for the top level functions. There are many other modules to explore from the main hackage page when we need to use them.

Let’s start with the example in the gloss documentation.

Basic gloss

First we will need to add gloss as a dependency for our project. Edit the gloss-bitmaps.cabal file and add it to the library section:


library
  hs-source-dirs:      src
  exposed-modules:     Lib
  build-depends:       base >= 4.7 && < 5
                     , gloss
  default-language:    Haskell2010

Stack uses hpack in its default project template to specify project settings. We will only use the cabal file in this article, so please delete the hpack config before building:

rm package.yaml

Edit the library source file src/Lib.hs, and add the example code from the gloss documentation:

module Lib
    ( someFunc
    ) where

import Graphics.Gloss

someFunc :: IO ()
someFunc = display (InWindow "Nice Window" (200, 200) (10, 10)) white (Circle 80)

Rebuild and run:

stack build
stack exec gloss-bitmaps-exe

The program should open a new window with a drawing of a circle.

display

Let’s take a look at the type for the display function, found in the module documentation:

display :: Display      -- Display mode.
        -> Color        -- Background color.
        -> Picture      -- The picture to draw.
        -> IO ()

In our example, the first argument to display is:

(InWindow "Nice Window" (200, 200) (10, 10))

This must have the type Display. Sure enough, we can confirm this in the documentation (and in the source code). The data type Display has two constuctors:

data Display
        -- Display in a window with the given name, size and position.
        = InWindow   String (Int, Int) (Int, Int)

        -- Display full screen.
        | FullScreen

Experiment with this and see if you can change the window size or display the image in full screen.

The second parameter of display is very straight forward. It is the background color for our display. In our case we are passing white, which is a value from the gloss library.

The third parameter is of type Picture. We are passing the value (Circle 80). From this we can infer that Circle is a constructor which takes a number (the radius) and returns a Picture of a circle.

Drawing Bitmaps

We’ve got a lot going for us already with just this one line of code. We know if we have a Picture, we have a way to display it. Instead of using drawing functions like circle or line, let’s find out how to make a bitmap Picture.

A bitmap image is basically just a big list of pixels. A 100x100 image would have 10,000 pixels. Each pixel is a 32-bit RGBA value. The value is broken into 4 bytes, one each for red, green, blue, and alpha (transparency).

The RGBA system allows us to specify any color and transparency. For example, in CSS if you wanted a nearly transparent purple, you could say:

rgba(128, 0, 128, 0.25);

Bitmaps in gloss

We need to dig through the documentation again to find out how to create a bitmap Picture. The top level module has a reference to a Bitmap module so let’s check that out: Graphics.Gloss.Data.Bitmap

(Please note if you want to view the source code: this code is imported from another package called gloss-rendering: Graphics.Gloss.Rendering)

There are some promising looking functions here, the best one for us uses ByteString:

bitmapOfByteString :: Int
                   -> Int
                   -> BitmapFormat
                   -> ByteString
                   -> Bool
                   -> Picture

-- Copy a ByteString of RGBA data into a bitmap with the given width and height.

-- The boolean flag controls whether Gloss should cache the data between frames for speed.

ByteString is a basic Haskell type to represent binary data without any assumption of encoding. There are also functions to create bitmaps from a BMP or ForeignPtr Word8. These other functions might be useful if we were loading an image from a file or from memory created by another program.

Since we are just generating one image in Haskell, ByteString should be the easiest to work with. We are not going to worry about performance this time.

Looking at the type and documentation for bitmapOfByteString, we can see the first two Int parameters are for the width and height of the image. Let’s say those will both be 100 for a 100x100 image.

The last parameter is a Bool to turn on caching provided by gloss. Since we are just drawing one image and not changing it, this feature will not matter. Let’s just pass True.

This remaining two parameters have the types BitmapFormat and ByteString. We know the ByteString will hold the binary data for our image, so the BitmapFormat must describe how that data should be formatted.

BitmapFormat

We just need to lookup BitmapFormat in our module documentation:

data BitmapFormat
        = BitmapFormat 
        { rowOrder    :: RowOrder
        , pixelFormat :: PixelFormat }

This type uses Haskell record syntax, we can simplify this for clarity:

data BitmapFormat = BitmapFormat RowOrder PixelFormat

BitmapFormat is just a combination of RowOrder and PixelFormat. Let’s look at these:

data RowOrder
        = TopToBottom 
        | BottomToTop

data PixelFormat
        = PxRGBA | PxABGR

These two settings say how our binary image data will be interpreted. RowOrder determines if the first bytes in our data are for the top or bottom row in our image. PixelFormat similarly determines what order the bytes in each pixel should be.

We can choose any format as long as our image data matches. Let’s use this as our format:

BitmapFormat TopToBottom PxRGBA

Now all we need is our bitmap data in a ByteString. Let’s update our library code to include everything we have looked at.

module Lib
    ( someFunc
    ) where

import Graphics.Gloss

bitmapData = undefined

ourPicture :: Picture
ourPicture = bitmapOfByteString 100 100 (BitmapFormat TopToBottom PxRGBA) bitmapData True

someFunc :: IO ()
someFunc = display (InWindow "Nice Window" (200, 200) (10, 10)) white ourPicture

This code will compile but will not run due to the presence of undefined in the place of actual data. We will fill that in next.

Generating a ByteString

Now we know what format our data should be in, next we need to find out how to create a ByteString. We’ll go to the module documentation like we did before: Data.ByteString

Here is the description of ByteString:

A space-efficient representation of a Word8 vector, supporting many efficient operations.

Word8 is going to be relevant for us, as this is the Haskell type for one byte. If we wanted to represent our purple pixel from earlier (rgba(128, 0, 128, 0.25)) as a list of Word8, it would look like this:

purple :: [Word8]
purple = [128, 0, 128, 64]

A fully opaque red pixel would look like this:

red :: [Word8]
red = [255, 0, 0, 255]

We just need 10,000 of these in a ByteString and we should have a working image. There is a function in the docs which will work for this:

pack :: [Word8] -> ByteString

Our single pixel has 4 bytes in a list, so we’ll need 40,000 bytes in a list to generate the ByteString for a 100x100 image. Let’s first try to make an image of just one color. There is a function that will allow us to turn our pixels above into an infinite list of pixels:

cycle :: [a] -> [a]

-- cycle ties a finite list into a circular one, or equivalently, the infinite repetition of the original list

Laziness in Haskell makes working with infinite lists possible, since the elements of the list are not actually created until asked for. We use the take function to take some values from a list.

take 12 (cycle purple)

-- [128,0,128,64,128,0,128,64,128,0,128,64]

Alright, we are ready to update our code to draw a purple image. First, add the bytestring package to gloss-bitmaps.cabal:

library
  hs-source-dirs:      src
  exposed-modules:     Lib
  build-depends:       base >= 4.7 && < 5
                     , gloss
                     , bytestring
  default-language:    Haskell2010

Add the bitmap data to our library source file:

module Lib
    ( someFunc
    ) where

import Graphics.Gloss
import Data.Word
import Data.ByteString (ByteString, pack)

purple :: [Word8]
purple = [128, 0, 128, 64]

bitmapData :: ByteString
bitmapData = pack $ take 40000 (cycle purple)

ourPicture :: Picture
ourPicture = bitmapOfByteString 100 100 (BitmapFormat TopToBottom PxRGBA) bitmapData True

someFunc :: IO ()
someFunc = display (InWindow "Nice Window" (200, 200) (10, 10)) white ourPicture

Rebuild and run:

stack build
stack exec gloss-bitmaps-exe

purple_square

At this point you have enough info to start playing around with pixel images. Try writing code to generate some different types of pixels.

In a future post, we will use IO in Haskell to produce randomly generated pixels.

The example code can be found on GitHub

Tags:

Categories:

Updated: