Previous | Table of Contents | Next

### An Implementation in C

It’s time to get down and look at some actual working code. Listing 35.1 is a C implementation of Bresenham’s line-drawing algorithm for modes 0EH, 0FH, 10H, and 12H of the VGA, called as function EVGALine. Listing 35.2 is a sample program to demonstrate the use of EVGALine.

LISTING 35.1 L35-1.C

    /*
     * C implementation of Bresenham’s line drawing algorithm
     * for the EGA and VGA. Works in modes 0×E, 0×F, 0×10, and 0×12.
     *
     * Compiled with Borland C++
     *
     * By Michael Abrash
     */

    #include <dos.h>     /* contains MK_FP macro */

    #define EVGA_SCREEN_WIDTH_IN_BYTES     80
                                           /* memory offset from start of
                                              one row to start of next */
    #define EVGA_SCREEN_SEGMENT            0×A000
                                           /* display memory segment */
    #define GC_INDEX                       0×3CE
                                           /* Graphics Controller
                                              Index register port */
    #define GC_DATA                        0×3CF
                                           /* Graphics Controller
                                              Data register port */
    #define SET_RESET_INDEX                0  /* indexes of needed */
    #define ENABLE_SET_RESET_INDEX         1  /* Graphics Controller */
    #define BIT_MASK_INDEX                 8  /* registers */

    /*
     * Draws a dot at (X0,Y0) in whatever color the EGA/VGA hardware is
     * set up for. Leaves the bit mask set to whatever value the
     * dot required.
     */
    void EVGADot(X0, Y0)
    unsigned int X0;     /* coordinates at which to draw dot, with */
    unsigned int Y0;     /* (0,0) at the upper left of the screen */
    {
       unsigned char far *PixelBytePtr;
       unsigned char PixelMask;

       /* Calculate the offset in the screen segment of the byte in
          which the pixel lies */
       PixelBytePtr = MK_FP(EVGA_SCREEN_SEGMENT,
          ( Y0 * EVGA_SCREEN_WIDTH_IN_BYTES ) + ( X0 / 8 ));

       /* Generate a mask with a 1 bit in the pixel’s position within the
          screen byte */
       PixelMask = 0×80 >> ( X0 & 0×07 );

       /* Set up the Graphics Controller’s Bit Mask register to allow
          only the bit corresponding to the pixel being drawn to
          be modified */
       outportb(GC_INDEX, BIT_MASK_INDEX);
       outportb(GC_DATA, PixelMask);

       /* Draw the pixel. Because of the operation of the set/reset
          feature of the EGA/VGA, the value written doesn’t matter.
          The screen byte is ORed in order to perform a read to latch the
          display memory, then perform a write in order to modify it. */
       *PixelBytePtr |= 0×FE;
    }

    /*
     * Draws a line in octant 0 or 3 ( |DeltaX| >= DeltaY ).
     */
    void Octant0(X0, Y0, DeltaX, DeltaY, XDirection)
    unsigned int X0, Y0;          /* coordinates of start of the line */
    unsigned int DeltaX, DeltaY;  /* length of the line (both > 0) */
    int XDirection;               /* 1 if line is drawn left to right,
                                     -1 if drawn right to left */
    {
       int DeltaYx2;
       int DeltaYx2MinusDeltaXx2;
       int ErrorTerm;

       /* Set up initial error term and values used inside drawing loop */
       DeltaYx2 = DeltaY * 2;
       DeltaYx2MinusDeltaXx2 = DeltaYx2 - (int) ( DeltaX * 2 );
       ErrorTerm = DeltaYx2 - (int) DeltaX;

       /* Draw the line */
       EVGADot(X0, Y0);              /* draw the first pixel */
       while ( DeltaX— ) {
          /* See if it’s time to advance the Y coordinate */
          if ( ErrorTerm >= 0 ) {
             /* Advance the Y coordinate & adjust the error term
                back down */
             Y0++;
             ErrorTerm += DeltaYx2MinusDeltaXx2;
          } else {
             /* Add to the error term */
             ErrorTerm += DeltaYx2;
          }
          X0 += XDirection;          /* advance the X coordinate */
          EVGADot(X0, Y0);           /* draw a pixel */
       }
    }

    /*
     * Draws a line in octant 1 or 2 ( |DeltaX| < DeltaY ).
     */
    void Octant1(X0, Y0, DeltaX, DeltaY, XDirection)
    unsigned int X0, Y0;          /* coordinates of start of the line */
    unsigned int DeltaX, DeltaY;  /* length of the line (both > 0) */
    int XDirection;               /* 1 if line is drawn left to right,
                                     -1 if drawn right to left */
    {
       int DeltaXx2;
       int DeltaXx2MinusDeltaYx2;
       int ErrorTerm;

       /* Set up initial error term and values used inside drawing loop */
       DeltaXx2 = DeltaX * 2;
       DeltaXx2MinusDeltaYx2 = DeltaXx2 - (int) ( DeltaY * 2 );
       ErrorTerm = DeltaXx2 - (int) DeltaY;

       EVGADot(X0, Y0);           /* draw the first pixel */
       while ( DeltaY— ) {
          /* See if it’s time to advance the X coordinate */
          if ( ErrorTerm >= 0 ) {
             /* Advance the X coordinate & adjust the error term
                back down */
             X0 += XDirection;
             ErrorTerm += DeltaXx2MinusDeltaYx2;
          } else {
             /* Add to the error term */
             ErrorTerm += DeltaXx2;
          }
          Y0++;                   /* advance the Y coordinate */
          EVGADot(X0, Y0);        /* draw a pixel */
       }
    }

    /*
     * Draws a line on the EGA or VGA.
     */
    void EVGALine(X0, Y0, X1, Y1, Color)
    int X0, Y0;    /* coordinates of one end of the line */
    int X1, Y1;    /* coordinates of the other end of the line */
    char Color;    /* color to draw line in */
    {
       int DeltaX, DeltaY;
       int Temp;

       /* Set the drawing color */

       /* Put the drawing color in the Set/Reset register */
       outportb(GC_INDEX, SET_RESET_INDEX);
       outportb(GC_DATA, Color);
       /* Cause all planes to be forced to the Set/Reset color */
       outportb(GC_INDEX, ENABLE_SET_RESET_INDEX);
       outportb(GC_DATA, 0×F);

       /* Save half the line-drawing cases by swapping Y0 with Y1
          and X0 with X1 if Y0 is greater than Y1. As a result, DeltaY
          is always > 0, and only the octant 0-3 cases need to be
          handled. */
       if ( Y0 > Y1 ) {
          Temp = Y0;
          Y0 = Y1;
          Y1 = Temp;
          Temp = X0;
          X0 = X1;
          X1 = Temp;
       }

       /* Handle as four separate cases, for the four octants in which
          Y1 is greater than Y0 */
       DeltaX = X1 - X0;    /* calculate the length of the line
                               in each coordinate */
       DeltaY = Y1 - Y0;
       if ( DeltaX > 0 ) {
          if ( DeltaX > DeltaY ) {
             Octant0(X0, Y0, DeltaX, DeltaY, 1);
          } else {
             Octant1(X0, Y0, DeltaX, DeltaY, 1);
          }
       } else {
          DeltaX = -DeltaX;             /* absolute value of DeltaX */
          if ( DeltaX > DeltaY ) {
             Octant0(X0, Y0, DeltaX, DeltaY, -1);
          } else {
             Octant1(X0, Y0, DeltaX, DeltaY, -1);
          }
       }

       /* Return the state of the EGA/VGA to normal */
       outportb(GC_INDEX, ENABLE_SET_RESET_INDEX);
       outportb(GC_DATA, 0);
       outportb(GC_INDEX, BIT_MASK_INDEX);
       outportb(GC_DATA, 0×FF);
    }

Previous | Table of Contents | Next

Graphics Programming Black Book © 2001 Michael Abrash