Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Algorithm Alley


AUG93: ALGORITHM ALLEY

Until you look below the surface, you never know what you'll find. It was a lesson I learned the hard way a few months ago, when my wife Anne and I chartered a sailboat in the British Virgin Islands for a vacation with my parents. Despite getting us lost three times--not easy to do where the islands are so close and the weather so heavenly--I managed to locate a former pirate cove, now called "The Bight," where we decided to anchor for the evening. By unanimous vote, the crew ordered the skipper (that's me, by virtue of having read Basic Sailing twice) to snorkel out to the anchor and check that the hook was well buried in the sand. Wanting a swim, I happily agreed. But as I paddled away from the boat, I was distracted by a large sea turtle swimming an underwater ballet along with several silvery fish. What a glorious creature that turtle was! I spent many minutes enjoying her beauty and imagining that I truly belonged there in paradise with her.

Finally, remembering my job, I continued searching for the anchor. When I reached it, I saw to my horror that the heavy plow was sitting upside down in the sand and had dragged along the bottom, allowing 38 feet of rented yacht to drift steadily toward the shore, now only a few yards away. Friends, you have never seen anyone swim back to a boat with greater haste. I should probably get an honorary Olympic medal. There I was, wasting my time watching a sea turtle and her fishy companions while unknowingly facing untold calamities in the next few minutes. Put it this way: Running aground definitely would have wrecked dinner.

Eventually, I managed to secure the anchor and avert disaster, but the event reminded me of an important lesson--not just in sailing, but also in other endeavors such as programming and debugging. In the course of investigating a potential problem, I had allowed myself to become sidetracked by other interests, causing me to forget my original goal. The moral is: Stay focused. Get that anchor down before swimming off on new adventures. Look below the surface of your code for potential problems, and resist the urge to implement new features before completing less-interesting chores such as testing and debugging. Boats should float and software should run cleanly. With a little care, neither should ever end up on the rocks.

Bitmaps Down Below

Peering beneath the surface of a Windows bitmap (you don't need a snorkel, just a debugger) reveals several interesting facts about bitmap files and the algorithms for compressing them. The techniques are briefly described in the Microsoft Windows Programmer's Reference, Volume 4: Resources, but to my knowledge, they have never been expressed in algorithm form. Because I had only that book's sketchy descriptions to guide me, my first attempts to lay down the steps failed. Finally, I cracked the puzzle by devising a suite of test programs to compress and decompress make-believe pixel values stored in plain text files. This approach made debugging easier, and because my sample test files were in ASCII, I could easily create and modify them with a text editor--far easier than creating real bitmap files using a drawing program. It also was no trouble to write another program for creating sample bitmaps at random--part of a "Monte Carlo" test that I'll describe next month. All of this advance work paid off in the final compression utility, which successfully packed a bitmap on the program's maiden voyage. (Okay, maybe I got lucky, but anyway, it's a real ego booster to write code that works on the first try.)

This month, I'll describe the Windows bitmap compression and decompression algorithms and list two of the programs in the test suite. Next month, I'll list the remaining test programs and the final bitmap compressor. Although in past columns, I've used pseudocode for algorithms and Pascal for sample listings, this time I'll present the algorithms in Pascal and the programs in C++ (which for other reasons, this project required). The sample programs are not object oriented, so it shouldn't be difficult to convert them to other languages.

A device-independent bitmap (DIB) file begins with various headers that describe the image and its colors. Following this information is an array of pixels in one of several formats. Monochrome bitmaps are stored one bit per pixel; 16-color bitmaps take four bits per pixel; 256-color bitmaps use one 8-bit byte per pixel; and so-called "true-color" bitmaps use a whopping 24 bits for each pixel. Most pixel values are actually indexes into the bitmap's color table--a pixel p's color equals colorTable[p]. In a true-color image, which lacks a color table, pixels directly represent red-green-blue (RGB) colors.

You can find more information on bitmap file formats in many Windows programming books. I'll focus here on a bitmap's array of pixels and ignore the other information in the DIB file. I'll describe the compression method for 8-bit, 256-color images, but the techniques are very nearly the same for 4-bit, 16-color files.

Bitmap Compression

Bitmap pixels are compressed using a combination of three modes. In run-length-encoded mode (RLE), 2-byte "compression units" represent from 1 to 255 runs of pixels, all of the same color. The unit 04 07, for example, packs 4 pixels of color 07. In escape mode, the first byte is 0 and the next byte signifies one of three special instructions: 0 to mark the end of the scan line (the proper term for a horizontal row of pixels), 1 for the end of the bitmap, or 2 for a delta command, sometimes called a "move instruction." Delta commands are followed by two more bytes that represent horizontal and vertical offsets to where the next pixel should appear. For example, the delta unit 00 02 05 08 indicates that the next pixel is to be drawn five positions to the right and eight down from the current location. Deltas are useful for compressing foreground images to be drawn on a fixed background (an important animation technique), but they aren't that valuable for compressing run-of-the-mill bitmaps.

In the third and final compression unit, called absolute mode, the first byte is 0 and the next byte is 3 or greater, representing the number of uncompressed pixels that follow. For instance, the absolute-mode unit 00 03 09 08 07 encodes the three pixel values 09, 08, and 07. Because absolute-mode runs must have three or more pixels--just one of several undocumented quirks in the algorithms--different-color runs of one or two pixels must be expanded using RLE pairs. For example, an absolute run of two adjacent pixels 09 and 07 must be encoded as 01 09 01 07 (one 09 and one 07). This means that compressed files with few same-color runs might actually grow in size. Obviously, in such cases, there would be nothing to gain from compressing the file.

Based on these descriptions, I devised algorithms in Pascal to pack and unpack bitmaps. (I did not implement delta escape codes.) As Listing One (page 150), Algorithm #10, shows, unpacking is the simpler of the two jobs. The method reads bytes from an open file f and requires three subroutines not shown: GetByte(f) returns the next byte from the file, PutByte(p) displays or writes one pixel, and StartNewScanLine performs the graphical equivalent of a carriage return and linefeed.

The algorithm for packing a bitmap is fairly complex, so I decided to implement the parser using a state machine, which helped organize the code into manageable chunks. Several constants at the beginning of Listing Two (page 150), Algorithm #11, describe various states that the machine can assume. The algorithm requires two input values: an Integer np set to the number of pixels per scan line and an array of uncompressed pixel bytes; and sl, representing one scan line of the image. The method uses a While loop and a Case statement to implement the state machine. Each labeled case represents one state: READING examines the current pixel and determines what to do next; ENCODING packs same-color pixels into RLE units; ABSMODE packs different-color pixels as absolute-mode runs; SINGLE handles absolute-mode runs of one or two pixels; and ENDOFLINE marks the end of a compressed scan line.

Test Suite

Like most complex algorithms, the two listed here might seem obscure if you simply read them from top to bottom. To better understand the methods, do as I did: Process sample bitmap data in text files, as illustrated in Example 1. All values are hexadecimal bytes. The file begins with an information line of two bytes representing the number of pixels in a scan line (0A hex here) and the number of scan lines in the image (04). The next several lines give the pixel values for each scan line.

Listing Three, TUNPACK.CPP (page 150), implements Algorithm #10. Listing Four, TPACK.CPP (page 151), implements Algorithm #11. Both programs write to the standard output file. To run the tests, store Example 1 in a text file named TEST.DAT, then enter the DOS command:

TPACK TEST.DAT >PACKED.DAT

To unpack the result, enter:

TUNPACK<I> </I>PACKED.DAT >FINAL.DAT

If the test passes, the values in FINAL.DAT should be the same as those in TEST.DAT, minus the original file's information line, which TUNPACK does not recreate.

Although TPACK and TUNPACK do not operate on real bitmap files, they simplify debugging and testing, and they might also be useful for porting the algorithms to other languages and operating systems. Writing data-compression software is a nerve-wracking business, and it's vital to do everything possible to ensure that compressed files are completely recoverable. You can't perform too many tests of a data-compression utility.

Your Turn

I wrote the sample programs in this column for DOS in order to keep the listings short. If anyone cares to rewrite the programs for Windows, I'd be interested in receiving them in care of DDJ. Or, you can upload files (compressed text only, please) to my CompuServe ID, 73627,3241. Next month, I'll list the rest of the programs in the test suite along with the final utility that can compress real Windows bitmap files.

Meanwhile, I wonder where that sea turtle is right now and whether she noticed my panicked race back to the boat. Call me paranoid, but I can't shake the image of a smart-aleck turtle going around harbors, pulling up anchors, and watching the fun. You don't suppose that's possible, do you? Nah.

Example 1: Sample "fake" bitmap text file.

0A 04
01 01 01 02 02 02 02 02 03 03
01 02 03 04 05 06 07 08 09 0A
01 02 03 04 04 04 01 02 03 04
01 02 01 02 01 02 01 02 01 02



[LISTING ONE]





{ bunpack.txt -- Algorithm #11: Unpack 8-bit Windows bitmap by Tom Swan }

const
  ESCAPE = 0;
  ENDOFLINE = 0;
  ENDOFBITMAP = 1;
  DELTA = 2;

procedure UnpackRLE8(var f: FILE);
var
  done: Boolean;
  count: Integer;
  oldCount: Integer;
  byt: Byte;
begin
  while ((not done) and
    (not eof(f))) do
  begin
    byt := GetByte(f);
    if (byt = ESCAPE) then
    begin { Unpack escape code }
      byt := GetByte(f);
      case byt of
        ENDOFLINE:
          StartNewScanLine;
        ENDOFBITMAP:
          done := True;
        DELTA:
          begin { Not implemented }
            Writeln('Delta escape!');
            Halt
          end;
        else begin
        { Absolute-mode run }
          count := byt;
          oldCount := count;
          while count > 0 do
          begin
            byt := GetByte(f);
            PutByte(byt);
            count := count - 1
          end;
          if Odd(oldCount) { padding }
            then byt := GetByte(f)
        end { else }
      end { case }
    end else
    begin { Unpack RLE unit }
      count := byt;
      byt := GetByte(f);
      while count > 0 do
      begin
        PutByte(byt);
        count := count - 1
      end { while }
    end { else }
  end { while }
end; { UnpackRLE8 }


(*
// --------------------------------------------------------------
// Copyright (c) 1993 by Tom Swan. All rights reserved
// Revision 1.00    Date: 04/27/1993   Time: 08:52 am
*)

[LISTING TWO]
<a name="0248_000c">
{ bpack.txt -- Algorithm #10: Pack 8-bit Windows bitmap by Tom Swan }

const
  READING = 0;
  ENCODING = 1;
  ABSMODE = 2;
  SINGLE = 3;
  ENDOFLINE = 4;
  NOSTATE = 999;

procedure PackRLE8(
  np: Integer; sl: ScanLine);
var
  slx: Integer;
  state: Integer;
  pixel: Integer;
  count: Integer;
  done: Boolean;
  oldcount: Integer;
  oldslx: Integer;
begin
  slx := 0; { Scan line index }
  state := READING;
  done := False;
  while not done do begin
    case state of

    READING:
  (* Input:
     np = # pixels in scan line
     sl = scan line
     sl[slx] = next pixel *)
    begin
      if slx >= np then
        state := ENDOFLINE
      else if slx = np - 1 then
      begin
        count := 1; { 1 pixel left }
        state := SINGLE
      end else
      if sl[slx] = sl[slx + 1] then
        state := ENCODING
      else
        state := ABSMODE
    end;

    ENCODING:
  (* Input:
     slx <= np - 2 (Run of 2+ pixels)
     sl[slx] = first pixel of run
     sl[slx] = sl[slx + 1] *)
    begin
      count := 2;
      pixel := sl[slx];
      slx := slx + 2;
      while ((slx < np) and
        (pixel = sl[slx]) and
        (count < 255)) do
      begin
        count := count + 1;
        slx := slx + 1
      end;
      PutByte(count);  { RLE unit }
      PutByte(pixel);
      state := READING
    end;

    ABSMODE:
  (* Input:
     slx <= np - 2 (Run of 2+ pixels)
     sl[slx] = first pixel of run
     sl[slx] <> sl[slx + 1] *)
    begin
      oldslx := slx;  { Save index }
      count := 2;
      slx := slx + 2;
      { Compute # bytes in run }
      while ((slx < np) and
        (sl[slx] <> sl[slx - 1]) and
        (count < 255)) do
      begin
        count := count + 1;
        slx := slx + 1
      end;
      { Back up on same-color run }
      if ((slx < np) and
          (sl[slx] = sl[slx - 1]))
        then if (count > 1)
          then count := count - 1;
      slx := oldslx;
      if (count < 3 ) then
        state := SINGLE {short run}
      else begin  {normal run}
        PutByte(0);
        PutByte(count);
        oldcount := count;
        while (count > 0) do
        begin
          PutByte(sl[slx]);
          slx := slx + 1;
          count := count - 1
        end;
        if Odd(oldcount) then
          PutByte(0);  {word padding}
        state := READING
      end { else }
    end;

    SINGLE:
  (* Input:
     count = # pixels to output
     slx < np
     sl[slx] = first pixel of run
     sl[slx] <> sl[slx + 1] *)
    begin
      while count > 0 do
      begin
        PutByte(01);
        PutByte(sl[slx]);
        slx := slx + 1;
        count := count - 1
      end;
      state := READING
    end;

    ENDOFLINE:
    begin
      PutByte(0);
      PutByte(0);
      done := TRUE;
      state := NOSTATE
    end;
    else
    begin
      Writeln('Unknown state');
      Halt
    end
   end { case state of }
  end { while }
end; { PackRLE8 }

begin
  Read(np);
  Read(ns);
  while (ns > 0) do
  begin
    GetNextScanLine(sl);
    PackRLE8(np, sl);
    ns := ns - 1
  end;
  PutByte(0);  { Mark bitmap end }
  PutByte(1)
end.


(*
// --------------------------------------------------------------
// Copyright (c) 1993 by Tom Swan. All rights reserved
// Revision 1.00    Date: 04/27/1993   Time: 09:00 am
*)

<a name="0248_000d"><a name="0248_000e">
[LISTING THREE]
 /* ----------------------------------------------------------- *\<a name="0248_000e">
**  tunpack.cpp -- Test unpacking compressed bitmap data       **
** ----------------------------------------------------------- **
**                                                             **
**  Note: Delta escape-code unpacking is not implemented.      **
**                                                             **
** ----------------------------------------------------------- **
**     Copyright (c) 1993 by Tom Swan. All rights reserved.    **
\* ----------------------------------------------------------- */

#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <stdlib.h>

typedef unsigned char Byte;

#define FALSE 0
#define TRUE 1

// Compression escape codes
#define ESCAPE 0

#define ENDOFLINE 0
#define ENDOFBITMAP 1
#define DELTA 2

void Error(const char *msg);
void DecompressFile(const char *fname);
int Odd(int v);
int GetByte(ifstream &ifs);
void PutByte(unsigned char b);

int main(int argc, char *argv[])
{
  if (argc <= 1)
    Error("filename missing");
  DecompressFile(argv[1]);
  return 0;
}

// Display error message and halt
void Error(const char *msg)
{
  cerr << endl << "Error: " << msg << endl;
  exit(1);
}

// Decompress fake bitmap file fname
// Write decompressed results to stdout
void DecompressFile(const char *fname)
{
  int endOfBitmap;  // True if 00 01 found at end of input
  int count;        // Used at various locations in function
  int oldCount;     // Copy of count for absolute-mode units
  unsigned byte;    // Input bytes

  ifstream ifs(fname, ios::in);
  if (!ifs)
    Error("unable to open file");
  endOfBitmap = FALSE;
  while (!endOfBitmap && !ifs.eof()) {
    byte = GetByte(ifs);
    if (byte == ESCAPE) {
    // Unpack escape code unit
      byte = GetByte(ifs);
      switch (byte) {
        case ENDOFLINE:
          cout << endl;
          break;
        case ENDOFBITMAP:
          endOfBitmap = TRUE;
          cout << endl;
          break;
        case DELTA:
          Error("Delta escape codes not implemented");
          break;
        default:  // Absolute-mode run
          count = byte;
          oldCount = count;
          while (count > 0) {
            byte = GetByte(ifs);
            PutByte(byte);
            count--;
          }
          if (Odd(oldCount))
            byte = GetByte(ifs);  // Read word-boundary padding byte
          break;
      }
    } else {
    // Unpack run-length-encoded unit
      count = byte;
      byte = GetByte(ifs);
      while (count > 0) {
        PutByte(byte);
        count--;
      }
    }
  }
  if (!endOfBitmap)
    Error("End of bitmap marker (00 01) not found");
}

// Return next byte from input file ifs
// Count number of bytes read
int GetByte(ifstream &ifs)
{
  int b;
  ifs >> hex >> b;
  return b;
}

// Return true if v is odd
int Odd(int v)
{
  return v & 0x01;
}

// Write byte b in hex in 2 columns with leading 0
// plus one blank to cout
void PutByte(unsigned char b)
{
  cout << setiosflags(ios::uppercase);
  cout << setw(2) << setfill('0') << hex << (int)b << ' ';
  cout << setfill(' ') << dec;
  cout << resetiosflags(ios::uppercase);
}


// --------------------------------------------------------------
// Copyright (c) 1993 by Tom Swan. All rights reserved.
// Revision 1.00    Date: 04/27/1993   Time: 08:55 am

<a name="0248_000f"><a name="0248_0010">
[LISTING FOUR]
<a name="0248_0010">
/* ----------------------------------------------------------- *\
**  tpack.cpp -- Test packing uncompressed bitmap data         **
** ----------------------------------------------------------- **
**                                                             **
**  Data file format (in ASCII hex):                           **
**                                                             **
**  np ns                   # pixels per line; # scan lines    **
**  pp pp pp pp pp ... pp   pixel byte values in hex           **
**  pp pp pp pp pp ... pp      "        "        "             **
**  pp pp pp pp pp ... pp      "        "        "             **
**                                                             **
** ----------------------------------------------------------- **
**     Copyright (c) 1993 by Tom Swan. All rights reserved.    **
\* ----------------------------------------------------------- */

#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <stdlib.h>

typedef unsigned char Byte;

#define FALSE 0
#define TRUE 1

// State-machine definitions
#define READING 0    // General reading mode
#define ENCODING 1   // Encoding same-color pixel runs
#define ABSMODE 2    // Encoding different-color pixel runs
#define SINGLE 3     // Encoding short absolute-mode runs
#define ENDOFLINE 4  // End of scan line detected

void Error(const char *msg);
void CompressFile(const char *fname);
int Odd(int v);
Byte *NextScanLine(ifstream &ifs, int np);
void PutByte(Byte b);
void PackRLE8(int np, const Byte *sl);

int main(int argc, char *argv[])
{
  if (argc <= 1)
    Error("filename missing");
  CompressFile(argv[1]);
  return 0;
}

// Display error message and halt
void Error(const char *msg)
{
  cerr << endl << "Error: " << msg << endl;
  exit(1);
}

// Compress test bitmap file fname
// Write compressed results to stdout
void CompressFile(const char *fname)
{
  int np, ns;  // Number of pixels per line, number of scan lines
  Byte *sl;    // Pointer to scan line

  ifstream ifs(fname, ios::in);
  if (!ifs)
    Error("unable to open file");
  ifs >> hex >> np >> ns;
  if ((np <= 0) || (ns <= 0))
    Error("bad file format");
  while (ns-- > 0) {
    sl = NextScanLine(ifs, np);
    PackRLE8(np, sl);
    delete sl;
  }
  PutByte(0);  // Mark end of bitmap
  PutByte(1);
  cout << endl;
}

// Return true if v is odd
int Odd(int v)
{
  return v & 0x01;
}

// Read next scan line of np pixels from file ifs
Byte *NextScanLine(ifstream &ifs, int np)
{
  if (np <= 0) Error("np zero or less in NextScanLine()");
  Byte *sl = new Byte[np];   // Allocate scan line
  if (!sl) Error("out of memory");
  int j = 0;
  int k;
  while (np-- > 0) {
    ifs >> hex >> k;
    sl[j++] = k;
  }
  return sl;
}

// Write byte b in hex in 2 columns with leading 0
// plus one blank to cout
void PutByte(Byte b)
{
  cout << setiosflags(ios::uppercase);
  cout << setw(2) << setfill('0') << hex << (int)b << ' ';
  cout << setfill(' ') << dec;
  cout << resetiosflags(ios::uppercase);
}

// Compress np pixels in sl
// Write compressed line to stdout
void PackRLE8(int np, const Byte *sl)
{
  int slx = 0;           // Scan line index
  int state = READING;   // State machine control variable
  int pixel, count;      // Used by various states
  int done = FALSE;      // Ends while loop when true
  int oldcount, oldslx;  // Copies of count and slx

  while (!done) {

    switch (state) {

      case READING:
      // Input:
      // np == number of pixels in scan line
      // sl == scan line
      // sl[slx] == next pixel to process

        if (slx > np) Error("READING in PackRLE8()");

        if (slx >= np)                      // No pixels left
          state = ENDOFLINE;
        else if (slx == np - 1) {           // One pixel left
          count = 1;
          state = SINGLE;
        } else if (sl[slx] == sl[slx + 1])  // Next 2 pixels equal
          state = ENCODING;
        else                                // Next 2 pixels differ
          state = ABSMODE;
        break;

      case ENCODING:
      // Input:
      // slx <= np - 2 (at least 2 pixels in run)
      // sl[slx] == first pixel of run
      // sl[slx] == sl[slx + 1]

        count = 2;
        pixel = sl[slx];
        slx += 2;
        while ((slx < np) && (pixel == sl[slx]) && (count < 255)) {
          count++;
          slx++;
        }
        PutByte(count);  // Output run-length-encoded unit
        PutByte(pixel);
        state = READING;
        break;

      case ABSMODE:
      // Input:
      // slx <= np - 2 (at least 2 pixels in run)
      // sl[slx] == first pixel of run
      // sl[slx] != sl[slx + 1]

        oldslx = slx;
        count = 2;
        slx += 2;
        // Compute number of bytes in run
        while ((slx < np) && (sl[slx] != sl[slx - 1]) && (count < 255)) {
          count++;
          slx++;
        }
        // If same-color run found, back up one byte
        if ((slx < np) && (sl[slx] == sl[slx - 1]))
          if (count > 1) count--;
        slx = oldslx;  // Restore scan-line index
        // Output short absolute runs of less than 3 pixels
        if (count < 3 )
          state = SINGLE;
        else {
          // Output absolute-mode run
          PutByte(0);
          PutByte(count);
          oldcount = count;
          while (count > 0) {
            PutByte(sl[slx]);
            slx++;
            count--;
          }
          if (Odd(oldcount))
            PutByte(0);  // End run on word boundary
          state = READING;
        }
        break;

      case SINGLE:
      // Input:
      // count == number of pixels to output
      // slx < np
      // sl[slx] == first pixel of run
      // sl[slx] != sl[slx + 1]

      while (count > 0) {
        PutByte(01);
        PutByte(sl[slx]);
        slx++;
        count--;
      }
      state = READING;
      break;

      case ENDOFLINE:
        PutByte(0);
        PutByte(0);
        done = TRUE;
        break;

      default:
        Error("unknown state in PackRLE8()");
        break;
    }
  }
}


// --------------------------------------------------------------
// Copyright (c) 1993 by Tom Swan. All rights reserved.
// Revision 1.00    Date: 04/27/1993   Time: 09:07 am

Copyright © 1993, 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.