Ask not what your bitmap can do for you, but what you can do for your
bitmap! Once you know how bitmap files are formatted you'll be able to
do a whole host of things you could never do before. Compress your
bitmaps using your own routines, modify bitmap data/colouring on the
fly, create your own bitmap files from scratch (screencaptures!), and
many more! Excited? I know I am! :)
The first thing you'll find stored in a bitmap file is what's called the File Header structure:
- Private Type BITMAPFILEHEADER
- bfType As Integer
- bfSize As Long
- bfReserved1 As Integer
- bfReserved2 As Integer
- bfOffBits As Long
- End Type
This
UDT (User-Defined Type) will be used to extract the first 14 bytes of
information from the BMP file (an Integer takes up two bytes, a Long
takes up four). bfType will always return "19778" which corresponds to
the two character string, "BM" for bitmap. All bitmaps start with these
two characters. bfSize will describe the entire file's size in bytes,
bfReserved1 and bfReserved2 are reserved spaces and should simply be
set to zero. bfOffBits tells us the byte offset from the beginning of
the file at which the bitmap data starts (yes, I know it's called
bfOffBITS, but it actually describes the number of bytes). So if
bfOffBits = 1078 (as it should for most 8bit bitmaps) then we know that
the header and colour table will be complete by the 1078th byte, and
the picture data has begun.
Now for the Info Header structure:
- Private Type BITMAPINFOHEADER
- biSize As Long
- biWidth As Long
- biHeight As Long
- biPlanes As Integer
- biBitCount As Integer
- biCompression As Long
- biSizeImage As Long
- biXPelsPerMeter As Long
- biYPelsPerMeter As Long
- biClrUsed As Long
- biClrImportant As Long
- End Type
biSize
is the size of the BITMAPINFOHEADER structure given in bytes (usually
equals 40). biWidth and biHeight describe the width and height of the
bitmap in pixels, as you would expect. biPlanes describes the number of
planes contained in the bitmap, this is not normally used, and is set
to one.
Now, biBitCount is an important one. It describes the
"bit-depth" of this bitmap. It can have any of four values: 1, 4, 8,
and 24. A bit depth of one indicates that the bitmap will have only two
colours (monochrome), a bit depth of 4 will allow 16 colours, 8bit
equals 256 colours, and 24bit is 16.8 million colours. Now the bit
depth dictates whether or not a bitmap will use a colour table
(discussed later). 24bit bitmaps DO NOT use a colour table, while the
other bit formats do.
biCompression indicates whether or not RLE
(Run Length Encoding) is used to compress the bitmap data. I won't get
into the algorithm here, but if you're really interested feel free to
email me about it. Simply set this to zero for no compression.
biSizeImage contains the length of the bitmap image data (the actual
pixels) in bytes. You might expect this to simply be equal to the width
multiplied by the height, but it isn't always... more on that later.
biXPelsPerMeter
and biYPelsPerMeter describe the resolution of the bitmap in pixels per
meter. To go with standard resolution, just set these to zero.
biClrUsed indicates how many of the colour table colours are actually
used in the bitmap, set to zero to use all colours. biClrImportant
tells the program which colours are most important, this can increase
the display speed under some circumstances. Simply set this to zero for
standard operation.
Now, we've described the bitmap, all that's
left is to list the colours to be used (if it's not 24bit) and then
store the actual per-pixel data. First, lets look at the structure used
for the colour table (or palette):
- Private Type RGBQUAD
- rgbBlue As Byte
- rgbGreen As Byte
- rgbRed As Byte
- rgbReserved As Byte
- End Type
Each
of these rgb values are a number from 0-255 indicating the intensity of
that particular channel. The rgbReserved byte must be set to zero. The
colour table is comprised of a number of these RGBQUAD structures, the
exact number of which depends on the bit depth of the bitmap. A 1bit
bitmap will have two RGBQUAD's describing its colour table (since a
1bit bitmap can have only two colours). A 4bit bitmap will have 16
RGBQUAD's, and an 8bit bitmap will have 256. These values will be
referred to by the per-pixel data stored later in the BMP file. For
example, to display the colour white in a pixel, we would have to set
up an RGBQUAD structure where each of the values (rgbBlue, rgbGreen,
and rgbWhite) were equal to 255 and then refer to this structure by its
order in the colour table. So if we set this as the first colour
(that's zero in an array) in the table then any pixel referring to the
zeroth colour will show up white. This will become more clear in a
moment (I hope!).
24bit bitmaps, on the other hand, have no
colour table because each pixel is made up of 3bytes, each describing
the intensity of a specific colour channel (red, green, or blue). There
is no need to look up a colour in the table since 16.8million can be
described by each 3byte triplet. An 8bit bitmap, on the other hand,
would only be able to display 256 colours without a colour table, since
you can only store 256 discrete values within an 8bit span. Similarly,
for 4bit bitmaps, only 16 combinations can be stored in the 4bit span.
Therefore, having a colour look-up table at the start of the bitmap
enables these lower bit formats to display a variety of colours, not
simply a fixed set of 256 or 16.
Finally the bitmap data itself can be stored in a simple array of bytes:
- Private Type RGBQUAD
- rgbBlue As Byte
- rgbGreen As Byte
- rgbRed As Byte
- rgbReserved As Byte
- End Type
You'll
have to "redim" this array to the size of the biSizeImage member of the
BITMAPINFOHEADER structure. As noted before, this value is not always
simply the multiplication of the bitmap's height and width. This is due
to 32bit boundary padding. You see, computers these days like things to
be presented to them in 32bit chunks, so in order to ensure optimal
performance, bitmaps are always encoded so that their "scan line
boundaries" end on 32bit edges. That is to say, if your 8bit bitmap is
supposed to be 3 pixels wide (3 pixels at 8bits per pixel = 24bits)
then you'd be 8bits shy of the 32bit boundary. Eight zero padded bits
would then have to be added to make up the difference. So, if your 8bit
bitmap is 3 pixels wide and 3 pixels tall, you'd normally expect the
pixel data to be stored in 72bytes (3pixels * 3pixels * 8bits per
pixel) but as a result of the padding, each 3 pixel row (scan line)
will end up being represented by 32bits rather than 24bits. That's 8
extra bits per row, for 3 rows. Our final total would then come to
96bits (3 rows * 32 bits per row). Understand?
This works just
the same for the scan lines at any bit depth, they all have to end on
32bit boundaries. If a 1bit bitmap was 30 pixels wide, then there'd
have to be 2bits of padding (30 pixels * 1bit per pixel = 30bits, 2
bits short). I'm sure you get the picture now.
The only other
thing you need to know about bitmaps is that the pixel data is stored
from the bottom left hand corner and progressing upward with scan lines
from left to right. So the whole bottom row is stored first, then the
second from the bottom, etc, until the final pixel from the top
right-corner of the bitmap is stored at last. Bear this in mind when
you store your data or read data from a bitmap, otherwise your picture
will come out upside-down.
To open a bitmap and modify or extract the data, simply access it in binary mode:
- Open "SAMPLE.BMP" For Binary Access Read Write Lock Write As #1
You
can then use "get" and "put" statements to extract or place the data.
For example, to extract the BITMAPFILEHEADER and BITMAPINFOHEADER you
can do this:
- Dim BMPFileHeader As BITMAPFILEHEADER
- Dim BMPInfoHeader As BITMAPINFOHEADER
-
- Get #1, 1, BMPFileHeader
- Get #1, , BMPInfoHeader
The
BMPFileHeader variable should extract the data from the very start of
the file, so we pass "1" as the second argument for the "get"
statement. The BMPInfoHeader variable data commences immediately
following the BITMAPFILEHEADER data, and so we omit the second
argument, indicating that we'd like to continue extracting data where
we left off. You can then "get" the RGBQUAD data, according to the
number of entries in the colour table, and finally extract the bitmap
pixel data itself, by "getting" an appropriately sized array of bytes.
See my
Bitmap File Format Project for further information and for an example of how to put these principles to use.