Pixmap, Pixbuf and memory lane
So how’s the Haskell project going? So far, so good. When I last wrote, I’d managed to load a “Hello World!” GUI for my application. Since then I’ve managed to parse up some content and render it to the screen. Current revision is 121ed7f2.
Antediluvian file formats
The project, in case you’ve not been following on github, is to resurrect a long-since abandoned game. This game was released as a cover disk on an Amiga magazine back in “the day” and the code has since been made available by the authors (in 68000 assembly), ported to BlitzBasic and (abortively) ported to C++/SDL.
The maps for the BlitzBasic game were stored as tile maps. Each game level combines a tile set and a map to place the tiles on the screen. For additional kicks, some of the files are compressed under a scheme called “BPCK” which involves fairly simple run-length encoding. The code to parse up those files was… err… relatively straightforward to write although I did have some issues with bit-order and palette mapping.
Tiles as Pixbufs
The tiles are stored as sequences of nibbles mapping pixels on to a 16-colour palette. In order to draw these, I extracted the RGB for the nibbles and inserted that 3-byte-per-pixel sequence into a pixmap (this is the source):
buildTile :: [BPCK.PaletteEntry] -> BPCK.Gliph -> IO Pixbuf
buildTile palette g = do
let gData = BPCK.gliphData g
buf <- pixbufNew ColorspaceRgb False 8 gliphX gliphY
pbData <- (pixbufGetPixels buf :: IO (PixbufData Int Word8))
rowStride <- pixbufGetRowstride buf
chan <- pixbufGetNChannels buf -- Hopefully this is 3 (R,G,B)
bits <- pixbufGetBitsPerSample buf -- Hopefully this is 8
doFromTo 0 (gliphX - 1) $ \y ->
doFromTo 0 (gliphY - 1) $ \x -> do
let pixbufoffset = x*chan + y*rowStride
let gliphOffset = fromIntegral $ x + y*gliphX
let paletteIndex = B.index gData gliphOffset
let thiscolor = palette !! fromIntegral paletteIndex
writeArray pbData (pixbufoffset) (fromIntegral $ BPCK.red thiscolor)
writeArray pbData (1 + pixbufoffset) (fromIntegral $ BPCK.green thiscolor)
writeArray pbData (2 + pixbufoffset) (fromIntegral $ BPCK.blue thiscolor)
return buf
where gliphX = BPCK.gliphWidth g
gliphY = BPCK.gliphHeight g
Maps as Pixmaps
The difference between a pixbuf and a pixmap is, as I understand it, that a pixbuf is an X-client side block of data into which you can load pixel information and a pixmap is an X-server side drawable (in the GTK sense) object. Pixbuf is more like a brush for painting, and Pixmap is an off-screen canvas. So to build my map I blatted the tile pixbufs on to a pixmap:
createTiledPixmap :: BPCK.ParsedImage -> BPCK.ParsedTileMap -> IO Pixmap
createTiledPixmap tileSet tileMap = do
putStrLn $ "Building tile pixmaps"
tiles <- tilesFromImageData tileSet
let tileCount = length tiles
putStrLn $ "Creating new pixmap " ++ show totalWidthPixels ++ " x " ++ show totalHeightPixels
pixmap <- pixmapNew (Nothing :: Maybe DrawWindow) totalWidthPixels totalHeightPixels (Just 24)
gc <- gcNew pixmap
doFromTo 0 (tilesHigh - 1) $ \iy ->
doFromTo 0 (tilesAcross - 1) $ \ix -> do
let tileIndex = ix + (iy * tilesAcross)
let tileId = (min (fromIntegral (BPCK.tileMap tileMap !! tileIndex)) tileCount) `mod` tileCount
let curX = ix * tileSizePixels
let curY = iy * tileSizePixels
postGUIAsync $ drawPixbuf pixmap gc (tiles !! tileId) 0 0 curX curY tileSizePixels tileSizePixels RgbDitherNone 0 0
return pixmap
where tileSizePixels = BPCK.gliphSize tileSet
tilesAcross = BPCK.tilesAcross tileMap
tilesHigh = BPCK.tilesHigh tileMap
totalWidthPixels = tileSizePixels * tilesAcross
totalHeightPixels = tileSizePixels * tilesHigh
Results
The finished article looks a bit like this:

or this:

and pressing the space bar cycles through the maps on account of this:
onKeyPress app (\x@(Key { eventKeyName = name,
eventKeyChar = char }) -> do
case char of
Just ' ' -> do
putStrLn $ "Switching map"
currentState <- readIORef mapStateRef
nextState <- nextMapState currentState
writeIORef mapStateRef nextState
drawWin <- widgetGetDrawWindow canvas
gc <- gcNew drawWin
(width, height) <- drawableGetSize drawWin
postGUIAsync $ drawDrawable drawWin gc (renderedMap nextState) 0 0 0 0 width height
Just c -> putStrLn $ "Press " ++ name ++ "('" ++ [c] ++ "')"
Nothing -> putStrLn $ "weird key: " ++ name
return (eventSent x))
Conclusions
I’m now a lot more comfortable with Haskell and think I’ve mastered the “word-for-word translation of imperative to pseudo-functional” style. The file parsing code I’ve written performs like a dog on account of that, but it’s not on a critical path for anything. One thing that has struck me is how easy it is to refactor Haskell code (as long as you use hanging ‘do’s to avoid formatting upsets). So I’ll stick at it and see if I can’t finish this project inside of a year.
Why not SDL?
FYYFDANSEIC, etc.