Channels ▼
RSS

The BMP File Format, Part 2


APR95: The BMP File Format, Part 2

The BMP File Format, Part 2

Reading and interpreting the bits

David Charlap

David is a software engineer with Visix Software, makers of the Galaxy Application Framework, a toolkit for cross-platform, object-oriented, distributed software development. He can be contacted via the Internet at [email protected]


The bitmap (BMP) file format is the standard way of storing bitmap images in Windows applications. In last month's installment of this two-part article, I presented four portable data structures that describe all versions of bitmap files on all platforms, including non-Windows environments. This month, I'll explain how to use those structures to read and interpret bits, then examine how the structures fit together.

Reading the Bits

Once the headers BITMAPFILEHEADER, BITMAPARRAYHEADER, BITMAPHEADER, and RGB are read, the only thing left to do is read the pixel data. The offsetToBits field in the BITMAPFILEHEADER structure points to the start of this data. The interpretation of the bits is a function of the image's bit depth and the compressionScheme field of the BITMAPHEADER structure.

For all images with bit depths less than 24 that do not use BITFIELDS encoding, the pixel values are array offsets into the image's color table. For images with bit depths of 24 or those that do use BITFIELDS encoding the pixel values are decoded directly into the color's red, green, and blue values.

Interpreting the Bits: No Compression

Uncompressed bit data is easy to interpret: It is an array of rows of pixels. Each row ends with 0--3 extra bytes, so that each row contains a multiple of four bytes. Each row is a packed array of pixel values. Each pixel's bit width is the image's bit depth.

For a bit depth of 1, each byte represents eight pixels; the most significant bits within a byte are the left-most pixels. For a bit depth of 4, each byte represents two pixels, the most significant four bits representing the left-side pixel, and the least significant four bits, the right-side pixel. For a bit depth of 8, each byte represents one pixel.

For a bit depth of 16, each pixel is represented by two bytes. The bytes must be read as a Big-endian word; that is, the first byte read is the most significant. While this seems backwards (all other structures in BMP files are Little-endian), it makes sense in this context as a logical side effect of a packed array with 16-bit elements. It should be noted that a 16-bit color-table-based bitmap is nonstandard--Windows 16-bit bitmaps must use the BITFIELDS encoding, and OS/2 does not have a standard 16-bit-deep bitmap format.

For a bit depth of 24, each pixel is represented by three bytes, which are interpreted as an RGB value. The first byte is the blue component; the second, green; and the third, red.

Interpreting the Bits: RLE Compression

There are two main forms of compression that bitmaps may use: run-length encoding (RLE) and modified Huffman encoding. Unfortunately, IBM chose not to document the modified Huffman encoding scheme in the OS/2 2.1 toolkit. However, compressed bitmaps are very rare, and most are RLE encoded, since RLE is well documented and Windows does not support Huffman-encoded bitmaps.

I have yet to come across a compressed bitmap in all the time I've been writing Windows and OS/2 programs. This leads me to believe that finding valid test cases for bitmap-decompressing code may be very difficult; therefore, the sample programs will not read compressed bitmaps. The description of RLE that follows is based entirely on the Windows SDK and OS/2 toolkit documentation.

RLE compression comes in three varieties: 4-, 8-, and 24-bit. Windows supports the 4- and 8-bit varieties. OS/2 supports all three. The three varieties are encoded with only slight differences.

RLE-encoded bitmaps are stored as a series of records, of which there are five kinds: RLE data, unencoded data, delta records, end-of-line records, and end-of-RLE records.

An RLE data record consists of a count of pixels followed by a pixel value. The value is repeated for the entire count of pixels. The first byte of an RLE data record is the number of pixels into which the record decodes. This value must be greater than 0. The next byte (or next three bytes for 24-bit RLE) is the value to be repeated; see Table 6. [Editor's Note: Tables 1 through 5 were presented last month.]

An unencoded data record consists of a flag value, a count, and then a string of data. The first byte is always 0, which differentiates it from an RLE data record. The second byte is a count (of bytes for 24-bit RLE, of pixels for 8-bit and 4-bit RLE). The rest of the record is pixel data to be inserted into the bitmap without processing; see Table 7.

A delta record consists of two flag values and two delta values. The first byte is always 0, and the second is always 2. The third and fourth bytes are unsigned byte values representing horizontal and vertical deltas, respectively. A delta record indicates that the next current pixel offset should be moved by the deltas indicated before decoding the next record.

An end-of-line record consists of two flag values. The first and second bytes are both 0. The end-of-line record indicates that the current line is complete and the next record will begin at the start of the next row.

An end-of-RLE record also consists of two flag values, the first of which is always 0 and the second, always 1. This record indicates the end of all RLE data.

Table 8 shows the decoding of the 8-bit RLE data stream 03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01 02 78 00 00 09 1E 00 01 (hexadecimal values). Table 9 shows the decoding of the 4-bit RLE data stream 03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01 04 78 00 00 09 1E 00 01 (hexadecimal values).

Interpreting the Bits: BITFIELDS Encoding

In addition to RLE and modified Huffman encoding, there is one more possible value for the compressionScheme field of a BITMAPHEADER: COMPRESSION_BITFIELDS. In reality, BITFIELDS is not a compression scheme, but an encoding scheme, since data is not compressed. BITFIELDS encoding is only used for images whose bit depth is 16 or 32. Only Windows NT supports BITFIELDS encoding--Windows 3.x and OS/2 do not.

In BITFIELDS encoding, each pixel is represented by 16 or 32 bits, depending on the image's bit depth. Each pixel represents a specific RGB value. The three 32-bit integers immediately following the BITMAPHEADER structure (where a color table normally resides) are used to describe how a pixel's value decodes into red, green, and blue values. The integers are masks applied to a pixel value to isolate its components.

The best way to explain this is with an example. A 32-bit image might be encoded using ten bits of red, ten bits of green, and ten bits of blue per pixel, with two unused bits; see Figure 5. [Editor's Note: Figures 1 through 4 appeared in last month's installment.] Table 10 shows the three integers in the color table for bits encoded this way. An example of a 16-bit BITFIELDS encoding might be a 5-6-5 encoding (commonly used on 16-bit display adapters) like that in Figure 6. Table 11 shows the three integers in the color table for bits encoded this way.

How All These Structures Fit Together

Now I'll examine how the structures combine together to form a bitmap file. The functions readSingleImageBMP, readSingleImageICOPTR, readSingleImageColorICOPTR, and readMultipleImage in readbmp.h and readbmp.c (available electronically; see "Availability," page 3) demonstrate how to use the structure-reading functions to decode a bitmap file.

The simplest structure is the single-image BMP file. All BMP files created on Windows systems are of this kind; likewise, many OS/2 BMP files. The structure of a single-image BMP file is:

BITMAPFILEHEADER
BITMAPHEADER
color table
 ...
bits

The file begins with a BITMAPFILEHEADER structure whose type field contains TYPE_BMP. This is immediately followed by a BITMAPHEADER structure and then a color table. Elsewhere in the file is a block of data that contains the image's bits. A pointer to this location is stored in the BITMAPFILEHEADER structure.

Slightly more complex is the OS/2 monochrome icon/pointer format. Here, too, a single BITMAPFILEHEADER is followed by a BITMAPHEADER, a color table, and a block of bit data. The type field of the BITMAPFILEHEADER contains either TYPE_ICO or TYPE_PTR, indicating either an icon or pointer type image. The structure of the file is the same, but the bits are interpreted differently. In such an image, the bit depth is always 1 (monochrome) and the color table is ignored. Additionally, the height of the image is double the height that will be displayed. Decoding the bits will result in an image whose top half is an XOR mask and whose bottom half is an AND mask. To display the icon/pointer, the bottom half is combined with screen pixels using the AND operator. After applying the AND mask, the top half of the image is combined with screen pixels using the XOR operator. These two masks allow four "colors" to be applied--background, foreground, transparent, and inverse; see Table 12.

The OS/2 color icon/pointer format is a bit more complicated. A color icon is actually the composite of two images: a monochrome icon (containing the mask data) and a bitmap (containing the color data). Color icons and color pointers have the structure shown in Figure 7. For a color icon, both BITMAPFILEHEADER structures have TYPE_ICO_COLOR in the type field. For a color pointer, both BITMAPFILEHEADER structures have TYPE_PTR_COLOR in the type field.

Reading a color icon is rather simple once code for reading bitmaps and monochrome icons/pointers is in place. The first image is read using the same code used for a monochrome icon/pointer. The second image is read using the same code used for a bitmap. This results in two masks and a color bitmap, which are combined with screen pixels; see Table 13.

Once code is in place to read all three types of images, reading an array of images is simple. When the file begins with a BITMAPARRAYHEADER (indicated by a type field of TYPE_ARRAY), you simply traverse the linked list, reading each image using the existing code on each one.

The structure of a multiple-image bitmap file is shown in Figure 8. Each BITMAPARRAYHEADER in the list contains a pointer to the next BITMAPARRAYHEADER, establishing the list. Immediately following each BITMAPARRAYHEADER is the start of a bitmap image (bitmap, icon, or pointer), which is read exactly as it would be read in a single-image file.

Putting it All Together

Once routines are in place to read all four types of files (bitmap, icon/pointer, color icon/pointer, and array), writing a program to read any arbitrary bitmap file is easy. Test.c (Listing Three) shows one way to do this. [Editor's Note: Listings One and Two appeared in last month's installment.]

Test.c opens a file and reads the first two bytes to determine the type of bitmap file being read. The appropriate image-reading function is then called, and the image(s) in the file are read in. The test program then dumps information about the image to an output file; an actual application would probably do something else.

Conclusion

The bitmap file format is large and complex, with many variations. OS/2 and Windows document only their own variants, and almost no documentation is available for other systems (such as workstations). With the information presented here, however, you should have no problem reading any bitmap file you come across--no matter what platform you're developing for.

The sample code provided returns data in a format that is easy for this particular test program to use, but modifying the sample code to produce a different format would not be difficult.

References

Microsoft Corp. Microsoft Win32 Programmer's Reference, vol. 5. Redmond, WA: Microsoft Press, 1993.

Microsoft Corp. Microsoft Windows Programmer's Reference, version 3, vol. 2. Redmond, WA: Microsoft Press, 1990.

IBM Corp. OS/2 2.0 Programmer's Toolkit: Presentation Manager Reference (online manual).

Table 6: RLE data-record encoding.

 1st byte                         Count of pixels.
 2nd--4th bytes(24-bit RLE only)  RGB value to be repeated.
 2nd byte(8-bit RLE only)         Color index to be repeated.
 2nd byte(4-bit RLE only)         Two color indexes to be
                                  alternated for entire count
                                  of pixels.

Table 7: Unencoded data record.

 1st byte                   Must be zero.
 2nd byte (24-bit)          Indicates length (in bytes) of the
                            rest of the record; must be
                            a multiple of 3.
 2nd byte (8-bit)           Indicates length (in bytes) of the rest
                            of the record; must be greater than 2.
 2nd byte (4-bit)           Indicates length (in pixels) of the rest
                            of the record; must be greater than 2.
 Remaining bytes (24-bit)   Every three bytes is another pixel's RGB
                            value; if the total count of pixels
                            is odd, an extra byte is appended
                            for an even length overall.
 Remaining bytes (8-bit)    Every byte is another pixel's color index;
                            if the total count of pixels is odd,
                            an extra byte is appended for an even
                            length overall.
 Remaining bytes (4-bit)    Every byte represents two pixels' color indexes,
                            the most-significant four bits being
                            the first pixel's value and the
                            least significant four bits, the
                            next pixel's value. If the
                            count of pixels is odd, the last four
                            bits will contain 0s; if the count of bytes
                            is odd, an extra byte is appended for
                            an even length overall.

Table 8: Example of 8-bit RLE encoding.

 Bytes               Meaning                              Bytes output                
 03 04               Three bytes of value 04.             04 04 04
 05 06               Five bytes of value 06.              06 06 06 06 06
 00 03 45 56 67 00   Three bytes of unencoded data.       45 56 67
 02 78               Two bytes of value 78.               78 78
 00 02 05 01         Delta record; move cursor forward    none
                     five pixels and up one row.
 02 78               Two bytes of value 78.               78 78
 00 00               End of row; next record begins on    none
                     the next line, at column 0.
 09 1E               Nine bytes of value 1E.              1E 1E 1E 1E 1E 1E 1E 1E 1E
 00 01               End of RLE data.                     none

Table 9: Four-bit RLE encoding.

 Bytes               Meaning                                Pixel-value Output   
 03 04               Three pixels alternating 0 and 4.      0 4 0
 05 06               Five pixels alternating 0 and 6.       0 6 0 6 0
 00 06 45 56 67 00   Six pixels of unencoded data.          4 5 5 6 6 7
 04 78               Four pixels alternating 7 and 8.       7 8 7 8
 00 02 05 01         Delta record; move cursor forward five none
                     pixels and up one row.
 04 78               Four pixels alternating 7 and 8.       7 8 7 8
 00 00               End of row; next record begins on the  none
                     next line, at column 0.
 09 1E               Nine pixels alternating 1 and E.       1 E 1 E 1 E 1 E 1
 00 01               End of RLE data.                       none

Table 10: BITFIELDS description of a 10-10-10 encoding.

    Position    Value (binary)                     Value (hex)   
    1 (Red)     11111111110000000000000000000000   FFC00000
    2 (Green)   00000000001111111111000000000000   003FF000
    3 (Blue)    00000000000000000000111111111100   00000FFC

Table 11: BITFIELDS description of a 5-6-5 encoding.

    Position    Value              Value   
                (binary)           (hex)   
    1 (Red)     1111100000000000   F800
    2 (Green)   0000011111100000   07E0
    3 (Blue)    0000000000011111   001F

Table 12: Mask operations.

    Screen    AND    XOR    Result           
    pixel     mask   mask                    
    x         0      0      0 (background)
    x         0      1      1 (foreground)
    x         1      0      x (transparent)
    x         1      1      ~x (inverse)

Table 13: Color/mask operations.

 Screen pixel   AND mask   XOR mask  Color pixel   Result            
 x              0          0         c             c (color)
 x              0          1         c             c (color)
 x              1          0         c             x (transparent)
 x              1          1         c             ~x (inverse)

Figure 5 Layout of a 32-bit image might be encoded using ten bits of red, ten bits of green, and ten bits of blue per pixel, with two unused bits. Figure 6 16-bit BITFIELDS encoding.

Figure 7: Structure of color icons and color pointers.

BITMAPFILEHEADER (for the monochrome icon part)
BITMAPHEADER (for the monochrome icon part)
color table (for the monochrome icon part)
BITMAPFILEHEADER (for the bitmap part)
BITMAPHEADER (for the bitmap part)
color table (for the bitmap part)
bits (for the monochrome icon part)
...
bits (for the bitmap part)
...

Figure 8: Structure of a multiple-image bitmap file.

BITMAPARRAYHEADER (for first image)
BITMAPFILEHEADER
BITMAPHEADER
color table
BITMAPFILEHEADER (if this image is a color icon or a color pointer)
BITMAPHEADER (if this image is a color icon or a color pointer)
color table (if this image is a color icon or a color pointer)
...
BITMAPARRAYHEADER (for the second image)
BITMAPFILEHEADER (for the second image)
BITMAPHEADER (for the second image)
color table (for the second image)
...
...
bits (for the first image)
...
bits (for the second image)
...
...

Listing Three (Listings One and Two appeared last month.)

/* Test program for reading bitmap files.  It accepts an input file and an
 * output file on the command line.  It will read and process the input file
 * and dump an ASCII representation of the contents to the output file.  The
 * dump will consist of the color image and two masks.  Missing parts will be
 * indicated as such (BMP files have no masks and nonochrome ICO/PTR files
 * have no color data.  In the color image, the dump will be a series of RGB
 * values (in hexadecimal).  In the masks, the dump will be represented by "."
 * symbols representing zeros and "@" symbols representing ones.   */

#include <stdio.h>
#include <stdlib.h>
#include "bmptypes.h"
#include "endian.h"
#include "readbmp.h"

int main (int argc, char *argv[])
{
    FILE *fp;
    RGB **argbs;
    char **xorMasks, **andMasks;
    UINT32 *heights, *widths, row, col;
    UINT16 fileType;
    long filePos;
    int numImages, i;
    int rc;
    
    if (argc < 3)
    {
        printf ("usage: test <infile> <outfile>\n");
        return 1;
    }
    fp = fopen(argv[1], "rb");
    if (fp == NULL)
    {
        perror ("Error opening source file");
        return 2;
    }
    /* Read the first two bytes as little-endian to determine the file type.
     * Preserve the file position. */
    filePos = ftell(fp);
    rc = readUINT16little(fp, &fileType);
    if (rc != 0)
    {
        perror("Error getting file type");
        return 3;
    }
    fseek(fp, filePos, SEEK_SET);

    /* Read the images. */
    switch (fileType) {
    case TYPE_ARRAY:
       /* If this is an array of images, read them. All the arrays we need
       * will be allocated by the reader function. */
       rc = readMultipleImage(fp, &argbs, &xorMasks, &andMasks, &heights,
                                              &widths, &numImages);
       break;
    case TYPE_BMP:
    case TYPE_ICO:
    case TYPE_ICO_COLOR:
    case TYPE_PTR:
    case TYPE_PTR_COLOR:
       /* If this is a single-image file, we've a little more work.  In order
       * to make the output part of this test program easy to write, we're
       * going to allocate dummy arrays that represent what readMultipleImage
       * would have allocated.  We'll read the data into those arrays. */
       argbs = (RGB **)calloc(1, sizeof(RGB *));
       if (argbs == NULL)
       {
          rc = 1005;
          break;
       }
       xorMasks = (char **)calloc(1, sizeof(char *));
       if (xorMasks == NULL)
       {
          free(argbs);
          rc = 1005;
          break;
       }
       andMasks = (char **)calloc(1, sizeof(char *));
       if (andMasks == NULL)
       {
          free(argbs);
          free(xorMasks);
          rc = 1005;
          break;
       }
       heights = (UINT32 *)calloc(1, sizeof(UINT32));
       if (heights == NULL)
       {
          free(argbs);
          free(xorMasks);
          free(andMasks);
          rc = 1005;
          break;
       }
       widths = (UINT32 *)calloc(1, sizeof(UINT32));
       if (widths == NULL)
       {
          free(argbs);
          free(xorMasks);
          free(andMasks);
          free(heights);
          rc = 1005;
          break;
       }
       numImages = 1;
       /* Now that we have our arrays allocted, read the image into them. */
       switch (fileType) {
       case TYPE_BMP:
          rc = readSingleImageBMP(fp, argbs, widths, heights);
          break;
       case TYPE_ICO:
       case TYPE_PTR:
          rc = readSingleImageICOPTR(fp, xorMasks, andMasks, widths, heights);
          break;
       case TYPE_ICO_COLOR:
       case TYPE_PTR_COLOR:
          rc = readSingleImageColorICOPTR(fp, argbs, xorMasks, andMasks,
                                                              widths, heights);
          break;
       }
       break;
    default:
       rc = 1000;
    }
    /* At this point, everything's been read.  Display status messages based
     * on the return values. */
    switch (rc) {
    case 1000:
    case 1006:
       printf ("File is not a valid bitmap file\n");
       break;
    case 1001:
       printf ("Illegal information in an image\n");
       break;
    case 1002:
       printf ("Legal information that I can't handle yet in an image\n");
       break;
    case 1003:
    case 1004:
    case 1005:
       printf ("Ran out of memory\n");
       break;
    case 0:
       printf ("Got good data from file, writing results\n");
       break;
    default:
       printf ("Error reading file rc=%d\n", rc);
       perror ("Errno:");
       break;
    }
    /* If the return value wasn't 0, something went wrong. */
    if (rc != 0)
    {
       if (rc != 1000 && rc != 1005)
       {
           for (i=0; i<numImages; i++)
           {
                if (argbs[i] != NULL)
                   free(argbs[i]);
                if (andMasks[i] != NULL)
                   free(andMasks[i]);
                if (xorMasks[i] != NULL)
                   free(xorMasks[i]);
           }
           free(argbs);
           free(andMasks);
           free(xorMasks);
           free(widths);
           free(heights);
       }
       return rc;
    }
    fclose(fp);
    fp = fopen(argv[2], "wt");
    if (fp == NULL)
    {
       perror ("Error opening target file");
       return 3;
    }
    /* Dump the images. */
    fprintf (fp, "There are %d images in the file\n", numImages);

    for (i=0; i<numImages; i++)
    {
        /* Loop through all the images that were returned. */
        fprintf (fp, "Doing image number %d\n\n", i+1);
        fprintf (fp, "Image dimensions: (%ld,%ld)\n", widths[i], heights[i]);
    
        if (argbs[i] != NULL)
    {
        /* If the image has colors, dump them (BMP, color ICO and color
         * PTR files */
        fprintf(fp, "Colors");
        for (row = 0; row < heights[i]; row++)
        {
             fprintf (fp, "\n\nRow %ld pixels (R,G,B), hex values:\n", row);
             for (col = 0; col < widths[i]; col++)
             {
                  fprintf (fp, "(%2.2x,%2.2x,%2.2x)",
                           argbs[i][row * widths[i] + col].red,
                           argbs[i][row * widths[i] + col].green,
                           argbs[i][row * widths[i] + col].blue);
             }
        }
    }
    else
    {
        /* If image has no colors, say so. (monochrome ICO and PTR files) */
        fprintf (fp, "No color image\n");
    }
    if (xorMasks[i] != NULL)
    {
        /* If the image has an xor mask, dump it.  (ICO and PTR files) */
        fprintf (fp, "\nXOR mask\n");
        for (row = 0; row < heights[i]; row++)
        {
             for (col = 0; col < widths[i]; col++)
        {
                  fprintf (fp, "%c", xorMasks[i][row * widths[i] + 
                                                            col] ? '@' : '.');
        }
        fprintf (fp, "\n");
      }
    }
    else
    {
        /* If the image has no xor mask, say so.  (BMP files). */
        fprintf (fp, "No xor mask\n");
    }

    if (andMasks[i] != NULL)
    {
        /* If the image has an and mask, dump it.  (ICO and PTR files) */
        fprintf (fp, "\nAND mask\n");
        for (row = 0; row < heights[i]; row++)
        {
             for (col = 0; col < widths[i]; col++)
             {
                 fprintf (fp, "%c",
                       andMasks[i][row * widths[i] + col] ? '@' : '.');
             }
             fprintf (fp, "\n");
        }
    }
    else
    {
        /* If the image has noand mask, say so.  (BMP files) */
        fprintf (fp, "No and mask\n");
    }

    if (i != numImages-1)
        fprintf (fp, "\n------------------------------------------\n\n");
    
    }
    fclose(fp);
    /* Dumping is complete.  Free all the arrays and quit */
    for (i=0; i<numImages; i++)
    {
         if (argbs[i] != NULL)
             free(argbs[i]);
         if (andMasks[i] != NULL)
             free(andMasks[i]);
         if (xorMasks[i] != NULL)
            free(xorMasks[i]);
     }
     free(argbs);
     free(andMasks);
     free(xorMasks);
     free(widths);
     free(heights);
    
     return 0;
}
/* Formatting information for emacs in c-mode
 * Local Variables:
 * c-indent-level:4
 * c-continued-statement-offset:4
 * c-brace-offset:-4
 * c-brace-imaginary-offset:0
 * c-argdecl-indent:4
 * c-label-offset:-4
 * End:
 */



Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.