Beginner’s Haskell: Creating Bitmap Images
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
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