I took a break from arguing with our GIF decoder to take a quick
look at the BBM format as I have a few files in that format
containing colour palettes I wished to extract. When I looked
into this, I found a BBM file is essentially an LBM file without
any image data, so I set to work at writing a new palette
serializer for reading and writing the palette files. This
article describes how to read the palettes from BBM and LBM
files.
Note: I only cover loading of color palette data in this
article. The image data I don't even look at - this article
does not represent a full LBM decoder.
Caveat Emptor
The sample code presented in this article took all of an hour to
write and has been tested on a pretty small selection of images.
It only handles 8bit LBM files (possibly lower depths too, but I
have not tested this). And, who knows, I might be
misinterpreting the specification or missing a chunk of vital
information.
Overview of BBM/LBM Files
Information is sketchy so I may well be wrong in particulars,
but a BBM file essentially seems to be a LBM without a full
image - in the files I've experimented with, there are header
chunks describing a bitmap, but no real data. A LBM file is of
course a full graphic, most popular (I think) by DeluxePaint on
both the Amiga and MS DOS. As I said though, this information is
my understanding and might be totally wrong. Luckily enough, or
our purposes it doesn't matter. However, to keep things simple,
for the rest of this article I'm going to refer to LBM, but you
can consider this interchangeable with BBM.
The LBM format is more formally known as ILBM - IFF Interleaved
Bitmap. It is built upon "EA IFF 85" Standard for Interchange
Format Files. Both formats were devised by Electronic Arts as a
standard means of sharing data between systems, their example of
writing a theme song with a Macintosh score editor and
incorporating it into an Amiga game summing it up neatly.
More information on these formats can be found in specification
documents here and here.
Reading an LBM file
An IFF file is comprised of chunks of data. Each chunk is
prefixed with a four character ID, the size of the data in the
chunk, and then the chunk data itself.
There is one oddity in that if the size of the chunk is odd, an
extra padding byte is added to the chunk data to make it even.
This padding byte is not included in the size field, so if it's
odd you must make sure you read (and discard) the padding byte.
Oh yes, and there's one other important detail. I don't know if
this is specific to all IFF format files, or just LBM, but
integers and longs are in big-endian format, so we need to
convert these when we read them.
The CMAP chunk
The only section of the LBM file we are interested in is the
CMAP chunk, which describes an 8bit colour palette. According
to the specification however, it's optional so it's entirely
possible that not all LBM files contain a CMAP. Also, only
8bit (or lower?) LBM files will contain a CMAP, as they only
support RGB channels. 24bit or 32bit images won't have one as
there's no scope for storing alpha channels.
The data section of a CMAP chunk is as simple as it gets - one
set of 3 bytes for each colour describing the red, green and
blue channels. The size attribute is the number of colours * 3.
Other Chunks
Although I'm not reading other chunks, I still have to pay
attention to some of them.
Firstly, the FORM chunk describes an IFF document. So, if the
file we read doesn't start with this, it's not a valid IFF file
and we shouldn't continue reading.
The second chunk we at least want to identify is the chunk that
states if this is an actual image. The specification states that
this should be ILBM, but the sample images I've worked with
use a different header which is PBM for Planar BitMap. Note
there's a trailing space on this ID as the specification states
ID's are four ASCII characters long. As in both cases the CMAP
section is the same, I look for either of these.
Anything else will be discarded.
Reading the file
After opening the file, the first thing we do is read the first
four bytes and convert these to an ASCII string. If the string
reads FORM, we know we have an IFF document and continue
reading. Otherwise, we throw an InvalidDataException
exception.
Next we read the size of the data contained within the FORM
chunk. As we aren't checking for nested chunks nor reading all
the data, we can safely ignore this.
Time for another sanity check, this time to verify we are
reading an image, be it Planar (PBM ) or Interleaved (ILBM).
For some reason this chunk doesn't include a size, so we don't
attempt to read any more bytes as the next byte is the start of
a new chunk.
The reset of the routine is going to load one chunk of data from
the file at a time, and either discard it or process it.
First, we read 4 bytes that will be the ID of the chunk. We also
need the size of the chunk, regardless of whether we use it or
not, so we'll read that too.
As we are only interested in CMAP chunks, if the pending chunk
has any other type of ID, we skip the remainder of the chunk, as
identified by chunkLength read earlier. If we can, we just
move the current position in the stream ahead, but if we can't
(can't think why not!) then we just read and discard bytes until
done.
Aha! We finally found the CMAP chunk. Now it's just a
straightforward reading of colours. chunkLength is the number
of colours / 3 (as each colour is represented by 3 bytes), so a
simple loop to read each triplet and add them to our results
collection is all we need.
Then, we exit out of the while loop - no pointing reading the
entire file now that we have what we wanted.
If we are still in the loop however, then we need to check our
chunkLength value. If it's odd, we read and discard the
padding byte - otherwise you'll be out of alignment and won't
hit any more chunk headers, except by accident.
Converting big-endian to little-endian
At the start of the article I mentioned that the numeric data
types in an LBM image are stored as big-endian. On the Windows
platform, we use little-endian. So when we try to read the chunk
length from the file... well, it's just not going to work.
As bit shifting still jellies my brain, I took to Stack Overflow
which provided me with a function for converting four bytes
of big-endian data into a little-endian integer.
So in our sample, we read our 4 bytes, shift their bits around
and return the result.
That was easy
That was fairly straightforward! Well, if I ignore the endian
conversion. And I'm sure if I decided to read the BODY chunk
and actually start decoding the image itself I'd be tearing out
my hair, but the bit I actually wanted could hardly have been
easier.
As usual, a fully working sample is attached to this post.
Further Thoughts
As I wrap up this post it occurred to me I forgot to add
anything in for if it's a valid LBM image, but doesn't contain a
CMAP section. Although the clue would be in the empty list
that's returned.
I also didn't even begin to look at writing a BBM file... this
will probably be the next thing I take a look at. Unless I get
distracted by Microsoft's (old?) palette format which I
discovered is also a variant of IFF and should be just as easy
to read.
Update History
2014-01-11 - First published
2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?
The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game - but until that time comes, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.
I took a break from arguing with our GIF decoder to take a quick look at the BBM format as I have a few files in that format containing colour palettes I wished to extract. When I looked into this, I found a BBM file is essentially an LBM file without any image data, so I set to work at writing a new palette serializer for reading and writing the palette files. This article describes how to read the palettes from BBM and LBM files.