/**
A tutorial -- including all the code you need to write a bitmap file from an image object
Summary Although Java provides several mechanisms for opening images, saving them is not one of its strengths. This tip will teach you how to save an image in a 24-bit bitmap file. Plus: Jean-Pierre provides all the code necessary to write a bitmap file from an image object. (1,500 words) By Jean-Pierre Dubé
This tip , complements Java Tip 43, which demonstrated the process of loading bitmap files in Java applications. This month, I follow up with a tutorial on how to save images in 24-bit bitmap files and a code snip you can use to write a bitmap file from an image object.
The ability to create a bitmap file opens many doors if you're working in a Microsoft Windows environment. On my last project, for example, I had to interface Java with Microsoft Access. The Java program allowed the user to draw a map on the screen. The map was then printed in a Microsoft Access report. Because Java doesn't support OLE, my only solution was to create a bitmap file of the map and tell the Microsoft Access report where to pick it up. If you've ever had to write an application to send an image to the clipboard, this tip may be of use to you -- especially if this information is being passed to another Windows application.
The format of a bitmap file The bitmap file format supports 4-bit RLE (run length encoding), as well as 8-bit and 24-bit encoding. Because we're only dealing with the 24-bit format, let's take a look at the structure of the file.
The bitmap file is divided into three sections. I've laid them out for you below.
Section 1: Bitmap file header This header contains information about the type size and layout of the bitmap file. The structure is as follows (taken from a C language structure definition):
typedef struct tagBITMAPFILEHEADER { UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits; }BITMAPFILEHEADER;
Here's a description of the code elements from the above listing:
bfType
: Indicates the type of the file and is always
set to BM.
bfSize
: Specifies the size of the whole file in bytes.
bfReserved1
: Reserved -- must be set to 0.
bfReserved2
: Reserved -- must be set to 0.
bfOffBits
: Specifies the byte offset from the
BitmapFileHeader
to the start of the image.
Here you've seen that the purpose of the bitmap header is to identify the bitmap file. Every program that reads bitmap files uses the bitmap header for file validation.
Section 2: Bitmap information header The next header, called the information header, contains all the properties of the image itself.
Here's how you specify information about the dimension and the color format of a Windows 3.0 (or higher) device independent bitmap (DIB):
typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;
Each element of the above code listing is described below:
biSize
: Specifies the number of bytes required by the
BITMAPINFOHEADER
structure.
biWidth
: Specifies the width of the bitmap in
pixels.
biHeight
: Specifies the height of the bitmap in
pixels.
biPlanes
: Specifies the number of planes for the
target device. This member must be set to 1.
biBitCount
: Specifies the number of bits per pixel.
This value must be 1, 4, 8, or 24.
biCompression
: Specifies the type of compression for a
compressed bitmap. In a 24-bit format, the variable is set to 0.
BI_RGB
format.
biBitCount
is
set to 24, biClrUsed
specifies the size of the reference
color table used to optimize performance of Windows color palettes.
Now all the information needed to create the image has been defined.
Section 3: Image
In the 24-bit format, each pixel in the image is represented by a
series of three bytes of RGB stored as BRG. Each scan line is padded to
an even 4-byte boundary. To complicate the process a little bit more,
the image is stored from bottom to top, meaning that the first scan line
is the last scan line in the image. The following figure shows both
headers (BITMAPHEADER
) and (BITMAPINFOHEADER
)
and part of the image. Each section is delimited by a vertical bar:
0000000000 4D42 B536 0002 0000 0000 0036 0000 | 0028 0000000020 0000 0107 0000 00E0 0000 0001 0018 0000 0000000040 0000 B500 0002 0EC4 0000 0EC4 0000 0000 0000000060 0000 0000 0000 | FFFF FFFF FFFF FFFF FFFF 0000000100 FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF *
Now, on to the code Now that we know all about the structure of a 24-bit bitmap file, here's what you've been waiting for: the code to write a bitmap file from an image object.
First, more about compression from this site -- "http://www.webartz.com/fourcc/fccrgb.htm"
These formats are defined below. Click on the FOURCC to be taken to its definition.
Please see "Bitmap Orientation and biHeight" for important additional information.
Label | FOURCC in Hex | Bits per pixel | Description |
BI_RGB | 0x00000000 | 1,4,8,16,24,32 | Basic Windows bitmap format. 1, 4 and 8 bpp versions are palettised. 16, 24 and 32bpp contain raw RGB samples. |
RGB | 0x32424752 | 1,4,8,16,24,32 | Alias for BI_RGB |
BI_RLE8 | 0x00000001 | 8 | Run length encoded 8bpp RGB image. |
RLE8 | 0x38454C52 | 8 | Alias for BI_RLE8 |
BI_RLE4 | 0x00000002 | 4 | Run length encoded 4bpp RGB image. |
BI_BITFIELDS | 0x00000003 | 16,24,32 | Raw RGB with arbitrary sample packing within a pixel. Packing and precision of R, G and B components is determined by bit masks for each. |
RGBA | 0x41424752 | 16,32 | Raw RGB with alpha. Sample precision and packing is arbitrary and determined using bit masks for each component, as for BI_BITFIELDS. |
RGBT | 0x54424752 | 16,32 | Raw RGB with a transparency field. Layout is as for BI_RGB at 16 and 32 bits per pixel but the msb in each pixel indicates whether the pixel is transparent or not. |
This is the basic RBG bitmap format which comes in all the common bits per pixel flavours. 1, 4 and 8 bpp versions of the format are palettised and 16, 24 and 32 bpp contain direct colour information. In all cases, the bitmap comprises a rectangular array of packed pixels.
Each pixel is represented by a single bit, giving 8 pixels per BYTE in memory. A 2 entry palette defines which colours are used to display the pixel if the bit is clear (palette entry 0) or set (palette entry 1). Despite the fact that this definition is apparently pretty clear, many display adapters and graphics applications appear to reverse the definition so, in my experience, you are never entirely sure if a 1bpp bitmap will be displayed as a positive or negative image.
Each pixel here is represented by a nibble, giving 2 pixels per BYTE in memory. The 4 bits used for the pixel give rise to 16 possible values and, hence, a 16 entry colour palette is required to display the image.
Each pixel here is represented by a BYTE, giving 256 possible values per pixel and, hence, requiring a 256 entry palette.
Things were pretty simple up to now but some confusion is introduced by the 16bpp format. It's actually 15bpp since the default format is actually RGB 5:5:5 with the top bit of each u_int16 being unused. In this format, each of the red, green and blue colour components is represented by a 5 bit number giving 32 different levels of each and 32786 possible different colours in total (true 16bpp would be RGB 5:6:5 where there are 65536 possible colours). No palette is used for 16bpp RGB images - the red, green and blue values in the pixel are used to define the colours directly.
As an aside, most display drivers handle both RGB 5:5:5 and 5:6:5 formats but several video codecs get confused when asked to decompress to a 5:6:5 desktop so using 5:5:5 as the default is generally safer.
We get back to predictable ground with 24bpp. Here a pixel is represented by 3 BYTES containing a red, blue and green sample (with blue stored at the lowest address, green next then red). No padding bytes are added between pixels. Although I can't find the information in any spec I have on my machine currently, I get the feeling that 24bpp images should be stored with each line padded to a u_int32 boundary. Information on http://www.mediatel.lu/workshop/graphic/2D_fileformat/h_bmp.html also suggests that this is true.
This is another version of 24bpp where each pixel is padded to a u_int32. Although this is inefficient from a memory standpoint, processing u_int32s is a great deal easier than handling byte triples and the format is used by many graphics accelerators because of this.
The following definition and example are quoted from the Windows 3.1 API online help file.
"When the biCompression member is set to BI_RLE8, the bitmap is compressed using a run-length encoding format for an 8-bit bitmap. This format may be compressed in either of two modes: encoded and absolute. Both modes can occur anywhere throughout a single bitmap.
Encoded mode consists of two bytes: the first byte specifies the number of consecutive pixels to be drawn using the color index contained in the second byte. In addition, the first byte of the pair can be set to zero to indicate an escape that denotes an end of line, end of bitmap, or a delta. The interpretation of the escape depends on the value of the second byte of the pair. The following list shows the meaning of the second byte:
Value Meaning 0 End of line 1 End of bitmap 2 Delta. The two bytes following the escape contain unsigned values indicating the horizontal and vertical offset of the next pixel from the current position. Absolute mode is signaled by the first byte set to zero and the second byte set to a value between 0x03 and 0xFF. In absolute mode, the second byte represents the number of bytes that follow, each of which contains the color index of a single pixel. When the second byte is set to 2 or less, the escape has the same meaning as in encoded mode. In absolute mode, each run must be aligned on a u_int16 boundary.
The following example shows the hexadecimal values of an 8-bit compressed bitmap:
03 04 05 06 00 03 45 56
67 00 02 78 00 02 05 01
02 78 00 00 09 1E 00 01This bitmap would expand as follows (two-digit values represent a color index for a single pixel):
04 04 04
06 06 06 06 06
45 56 67
78 78
move current position 5 right and 1 down
78 78
end of line
1E 1E 1E 1E 1E 1E 1E 1E 1E
end of RLE bitmap"
The following definition and example are quoted from the Windows 3.1 API online help file.
"When the biCompression member is set to BI_RLE4, the bitmap is compressed using a run-length encoding (RLE) format for a 4-bit bitmap, which also uses encoded and absolute modes. In encoded mode, the first byte of the pair contains the number of pixels to be drawn using the color indexes in the second byte. The second byte contains two color indexes, one in its high-order nibble (that is, its low-order four bits) and one in its low-order nibble. The first of the pixels is drawn using the color specified by the high-order nibble, the second is drawn using the color in the low-order nibble, the third is drawn with the color in the high-order nibble, and so on, until all the pixels specified by the first byte have been drawn.
In absolute mode, the first byte contains zero, the second byte contains the number of color indexes that follow, and subsequent bytes contain color indexes in their high- and low-order nibbles, one color index for each pixel. In absolute mode, each run must be aligned on a u_int16 boundary. The end-of-line, end-of-bitmap, and delta escapes also apply to BI_RLE4.
The following example shows the hexadecimal values of a 4-bit compressed bitmap:
03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01
04 78 00 00 09 1E 00 01This bitmap would expand as follows (single-digit values represent a color index for a single pixel):
0 4 0
0 6 0 6 0
4 5 5 6 6 7
7 8 7 8
move current position 5 right and 1 down
7 8 7 8
end of line
1 E 1 E 1 E 1 E 1
end of RLE bitmap"
To allow for arbitrarily packed RGB samples, BI_BITFIELDS specifies a mask field for each of the red, green and blue pixel components. These masks indicate the bit positions occupied by each colour component in a pixel. In general, the masks are passed to a driver or video API using means other than a basic BITMAPINFOHEADER (such as using the appropriate fields in a DirectDraw DDPIXELFORMAT structure) but I have heard that it is valid to append the masks to the end of the BITMAPINFOHEADER in much the same way that a palette is appended for palettised formats.
For example, 16 bit RGB 5:6:5 can be described using BI_BITFIELDS and the following bitmasks:
Red | 0xF800 (5 bits of red) |
Green | 0x07E0 (6 bits of green) |
Blue | 0x001F (5 bits of blue) |
In this case, if used with a BITMAPINFOHEADER, the bitmasks are u_int16s (16 bit) since the biBitFields field is set to 16. For a 32bpp version, the bitmasks are each u_int32s.
This format is an extension of BI_BITFIELDS where a fourth bitmask is used to define bits in the pixel which correspond to an alpha channel. When displayed on top of other images, RGBA pixels are blended with the background pixel according to the value of this alpha component.
For example, a 32bpp RGBA image would likely use the top 8 bits of each u_int32 to store the alpha component (the unused byte in normal 32bpp RGB). In this case, the masks reported would be:
Red | 0x00FF0000 |
Green | 0x0000FF00 |
Blue | 0x000000FF |
Alpha | 0xFF000000 |
giving 256 levels of blending per pixel (8 bits of alpha data).
In general, the masks used for this format are passed using a means other than a BITMAPINFOHEADER (for example, in DirectDraw, the DDPIXELFORMAT structure contains fields specifically for R,G,B and Alpha masks) but I have also heard that it is acceptable to append 4 u_int32s to the end of the BITMAPINFOHEADER structure containing the mask information.
This format can be thought of as a simple extension to the basic 16bpp and 32bpp flavours of BI_RGB. RGBT uses the most significant bit of the pixel (unused in RGB 16bpp and 32bpp) to indicate transparency. If the bit is set, the pixel is visible, otherwise it is transparent. You can also think of this as a version of RGBA where the alpha channel comprises a single bit.
Intro | RGB Formats | YUV
Formats | Compressed
Formats
Chips | Samples | Register | Links | Credits | Help
Now at last the code..
**/ import java.awt.*; import java.io.*; import java.awt.image.*; public class BitmapEncoder { //--- Private constants private final static int BITMAPFILEHEADER_SIZE = 14; private final static int BITMAPINFOHEADER_SIZE = 40; //--- Private variable declaration //--- Bitmap file header private byte bitmapFileHeader [] = new byte [14]; private byte bfType [] = {'B', 'M'}; private int bfSize = 0; private int bfReserved1 = 0; private int bfReserved2 = 0; private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; //--- Bitmap info header private byte bitmapInfoHeader [] = new byte [40]; private int biSize = BITMAPINFOHEADER_SIZE; private int biWidth = 0; private int biHeight = 0; private int biPlanes = 1; private int biBitCount = 24; private int biCompression = 0; private int biSizeImage = 0x030000; private int biXPelsPerMeter = 0x0; private int biYPelsPerMeter = 0x0; private int biClrUsed = 0; private int biClrImportant = 0; //--- Bitmap raw data private int bitmap []; //--- File private OutputStream fo; //--- Default constructor public BitmapEncoder () { } /* This the main method of the process. * It assumes that getWidth () and getHeight () are valid! * convertImage convert the memory image to a byte array; * writeBitmapFileHeader creates and writes the bitmap file header; * writeBitmapInfoHeader creates the information header; * writeBitmap writes the image. */ public boolean encode (Image parImage, OutputStream fos) { this.fo = fos; try { int parWidth = parImage.getWidth (null), parHeight = parImage.getHeight (null); if ((parWidth < 1) || (parHeight < 1)) return false; if (convertImage (parImage, parWidth, parHeight)) { writeBitmapFileHeader (); writeBitmapInfoHeader (); writeBitmap (); return true; } } catch (Exception saveEx) { saveEx.printStackTrace (); } return false; } /* convertImage converts the memory image to the bitmap format (BRG). * It also computes some information for the bitmap info header. */ private boolean convertImage (Image parImage, int parWidth, int parHeight) { int pad; bitmap = new int [parWidth * parHeight]; PixelGrabber pg = new PixelGrabber (parImage, 0, 0, parWidth, parHeight, bitmap, 0, parWidth); try { pg.grabPixels (); } catch (InterruptedException e) { e.printStackTrace (); return (false); } pad = (4 - ((parWidth * 3) % 4)) * parHeight; biSizeImage = ((parWidth * parHeight) * 3) + pad; bfSize = biSizeImage + BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; biWidth = parWidth; biHeight = parHeight; return (true); } /* writeBitmap converts the image returned from the pixel grabber to * the format required. Remember: scan lines are inverted in a bitmap file! * * Each scan line must be padded to an even 4-byte boundary. */ private void writeBitmap () { int size; int value; int j; int i; int rowCount; int rowIndex; int lastRowIndex; int pad; int padCount; byte rgb [] = new byte [3]; size = (biWidth * biHeight) - 1; pad = 4 - ((biWidth * 3) % 4); if (pad == 4) // <==== Bug correction pad = 0; // <==== Bug correction rowCount = 1; padCount = 0; rowIndex = size - biWidth; lastRowIndex = rowIndex; try { for (j = 0; j < size; j++) { value = bitmap [rowIndex]; rgb [0] = (byte) (value & 0xFF); rgb [1] = (byte) ((value >> 8) & 0xFF); rgb [2] = (byte) ((value >> 16) & 0xFF); fo.write (rgb); if (rowCount == biWidth) { padCount += pad; for (i = 1; i <= pad; i++) { fo.write (0x00); } rowCount = 1; rowIndex = lastRowIndex - biWidth; lastRowIndex = rowIndex; } else rowCount++; rowIndex++; } //--- Update the size of the file bfSize += padCount - pad; biSizeImage += padCount - pad; } catch (Exception wb) { wb.printStackTrace (); } } /* writeBitmapFileHeader writes the bitmap file header to the file. */ private void writeBitmapFileHeader () { try { fo.write (bfType); fo.write (intToDWord (bfSize)); fo.write (intToWord (bfReserved1)); fo.write (intToWord (bfReserved2)); fo.write (intToDWord (bfOffBits)); } catch (Exception wbfh) { wbfh.printStackTrace (); } } /* writeBitmapInfoHeader writes the bitmap information header to the file. */ private void writeBitmapInfoHeader () { try { fo.write (intToDWord (biSize)); fo.write (intToDWord (biWidth)); fo.write (intToDWord (biHeight)); fo.write (intToWord (biPlanes)); fo.write (intToWord (biBitCount)); fo.write (intToDWord (biCompression)); fo.write (intToDWord (biSizeImage)); fo.write (intToDWord (biXPelsPerMeter)); fo.write (intToDWord (biYPelsPerMeter)); fo.write (intToDWord (biClrUsed)); fo.write (intToDWord (biClrImportant)); } catch (Exception wbih) { wbih.printStackTrace (); } } /* intToWord converts an int to a word, where the return * value is stored in a 2-byte [least, most] array. */ private byte [] intToWord (int parValue) { byte retValue [] = new byte [2]; retValue [0] = (byte) (parValue & 0x00FF); retValue [1] = (byte) ((parValue >> 8) & 0x00FF); return (retValue); } /* intToDWord converts an int to a double word, where the return * value is stored in a 4-byte [least ... most] array. */ private byte [] intToDWord (int parValue) { byte retValue [] = new byte [4]; retValue [0] = (byte) (parValue & 0x00FF); retValue [1] = (byte) ((parValue >> 8) & 0x000000FF); retValue [2] = (byte) ((parValue >> 16) & 0x000000FF); retValue [3] = (byte) ((parValue >> 24) & 0x000000FF); return (retValue); } } /**/*/ /*