Fast Line Draw Method

Many years ago (around 1984) I got my very first Motorola VME-10 development system. It ran a real-time OS named VersaDOS and could multi-task if you wanted to hook several CRT terminals up to it. It was based on the 68010 CPU and had a fairly nice graphics system. I used it for all my program development and had it feeding source code into our Computer Automation LSI-2 Naked Mini to compile programs because the CA only had floppy drives and an OS that didn't support folders. Supporting multiple jobs for many customers on a flat file system is not fun and trying to keep all the source code on a flat directory was impossible. The VME-10 had a multi-user file system to allow separate accounts and could feed the CA to compile code while I worked on other jobs. Of course, this was all due to some custom programs I came up with to automate the systems.

One day I decided to have some fun so I wrote some line and circle drawing routines that directly addressed the pixel locations as Motorola didn't provide any primitive graphic operations. This line drawing method I came up with is similar to Bresenham's line algorithm except it uses only integer math and works for lines in all directions. It is based on the equation for a line, converted to an integer differential analyzer. Such line drawing methods are useful if you need to quickly process pixels along an arbitrary line in an image.

Consider the equation of a line for pixels (x, y) from (0, 0) to (10, 1). We can calculate y from x by using y = y1 + x*dy/dx for x from x1 = 0 to x2 = 10 where dy = 1 and dx = 10. Because pixels are expressed as integer locations, we need to use trunc(y + 0.5) to get the line to increment at the center. This works only for dx>=dy and dx>=0 and dy>=0. In C we would write:

const int x1=0;
const int x2=10; 
const int y1=0; 
const int y2=1; 
const double dx=x2-x1;
const double dy=y2-y1; 
double y; 
int iy;

for (int ix=x1;ix<=x2;ix++)
{
  y=y1+ix*dy/dx;
  iy=trunc(y+0.5);
  plot(ix,iy);
}


This can be converted to simple addition in this way:

const double slope=dy/dx;
ix=x1;
y=y1+0.5;

while (ix<=x2)
{
  plot(ix,trunc(y));
  y=y+slope;
  ix++;
}


Now the trick is to convert this so we don't need to work with y. We do this by introducing a ysum value. Instead of working directly with y, we only increment iy when we accumulate each slope total of 1:

const double s=dy/dx;
ix=x1;
iy=y1;
double ysum=0.5; 

while (ix<=x2)
{
  plot(ix,iy);
  ysum=ysum+s;
  if (ysum>=1.0)
  {
    ysum=ysum-1.0;
    iy++;
  }
  ix++;
}


This still isn't all integer mode, so we see that we can use all integers if we multiply everything related to ysum by dx:

const int idx=x2-x1;
const int idy=y2-y1; 
ix=x1;
iy=y1;
int ysum=idx/2;

while (ix<=x2)
{
  plot(ix,iy);
  ysum=ysum+idy;
  if (ysum>=idx)
  {
    ysum=ysum-idx;
    iy++;
  }
  ix++;
}


Work this out for all line directions and add steering logic and you have my method. For another way of doing this see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm.

Here is a modern C++ class definition for a parametric line with the drawing methods:

//---------------------------------------------------------------------------
#ifndef LineDrawH
#define LineDrawH

//---------------------------------------------------------------------------
// parametric line class
class TVT_ParaLine
{
private:
  bool xInc,pdx,pdy;
  int ix1,iy1,ix2,iy2,idx,idy,adx,ady,IncSum;
protected:
public:
  // this constructs an integer based line from (X1, Y1) to (X2, Y2)
  TVT_ParaLine(int X1, int Y1, int X2, int Y2);

  /*
    fast interpolation of pixels along line
    example use:
      SetFirst(X,Y);
      do {
(process pixel X,Y here)
      } while (NextXY(X,Y));
  */
  void SetFirst(int& X, int& Y);
  bool NextXY(int& X, int& Y);
};
//---------------------------------------------------------------------------

#endif


Here is the implementation of the class:

//---------------------------------------------------------------------------
#include "LineDraw.h"
#include <math.h>
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TVT_ParaLine::TVT_ParaLine(int X1, int Y1, int X2, int Y2)
{
  ix1=X1;
  iy1=Y1;
  ix2=X2;
  iy2=Y2;
  idx=X2-X1;
  idy=Y2-Y1;
  adx=abs(idx);
  ady=abs(idy);
  xInc=adx>=ady;
  pdx=idx>=0;
  pdy=idy>=0;
}
//---------------------------------------------------------------------------
void TVT_ParaLine::SetFirst(int& X, int& Y)
{
  if (xInc)
  {
    IncSum=adx/2;
  }
  else
  {
    IncSum=ady/2;
  }
  X=ix1;
  Y=iy1;
}
//---------------------------------------------------------------------------
bool TVT_ParaLine::NextXY(int& X, int& Y)
{
  bool done;

  if (xInc)
  {
    if ((IncSum+=ady)>=adx)
    {
      if (pdy) Y++; else Y--;
      IncSum-=adx;
    }
    if (pdx)
    {
      X++;
      done=X>ix2;
    }
    else
    {
      X--;
      done=X<ix2;
    }
  }
  else
  {
    if ((IncSum+=adx)>=ady)
    {
      if (pdx) X++; else X--;
      IncSum-=ady;
    }
    if (pdy)
    {
      Y++;
      done=Y>iy2;
    }
    else
    {
      Y--;
      done=Y<iy2;
    }
  }
  return !done;
}
//---------------------------------------------------------------------------

© James S. Gibbons 1987-2015