home *** CD-ROM | disk | FTP | other *** search
- MCGA Graphics Tutorial
- Lesson #3
- by Jim Cook
-
- As promised, the topic of today's lesson is line drawing. There are
- many ways to approach line drawing, and we will end up with what is
- possibly the fastest non-assembly line drawing routine. But, to get
- there we must start at the beginning.
- Remember: no pain, no gain!
-
- First of all, we will concentrate on the point-slope form of an equation.
- Dust off that algebra book and open to page 137. Where (X1,Y1) and (X2,Y2)
- are the endpoints of a line segment, any point (X,Y) on the line can be
- represented by the equation:
-
- Y - Y1 Y2 - Y1
- ------ = _______
- X - X1 X2 - X1
-
- We are going to turn this into the following procedure:
-
- Procedure LineEqu (X1,Y1,X2,Y2:Integer;Color:Byte);
- var
- Slope : Real;
- D,X,Y : Integer;
- begin
- If (X1 = X2) or (Y1 = Y2) then Exit;
- If X1 > X2 then begin
- D := X1;
- X1 := X2;
- X2 := D;
- D := Y1;
- Y1 := Y2;
- Y2 := D;
- end;
- Slope := (Y2-Y1)/(X2-X1);
- If Abs(Y2-Y1) > Abs(X2-X1) then begin
- Slope := (X2-X1)/(Y2-Y1);
- For Y := Y1 to X2 do
- SetPixel (Trunc(Slope*(Y-Y1)+X1),Y,Color);
- end
- Else begin
- Slope := (Y2-Y1)/(X2-X1);
- For X := X1 to X2 do
- SetPixel (X,Trunc(Slope*(X-X1)+Y1),Color);
- end;
- end;
-
- This is kinda straight forward. I just want to make a couple comments
- on the code. The first line of code checks for a vertical or horizontal
- line. If this case presents itself, we don't draw the line, because of
- the danger of a zero value in the slope formula, and a potential
- division by zero error. We also make the loop draw by X or by Y increments
- to eliminate gaps in our lines. You can make the '>' in the If-Then
- statement into a '<' to see what I mean. These problems are too meaningless
- to fix, because this algorithm produces a measely 165 lines per second
- on my computer (386 33mhz).
-
- Now is the time to Enter stage left, J. E. Bresenham, who in 1965
- produced an elegant solution to avoid all of this computational nonsense.
- Bresenham developed a method of determining what pixel the line was
- closer to. This is achieved by creating a decision variable and two
- incremental/decremental variables which see-saws both sides of the line.
- Trace through the following line procedure to get a full appreciation
- of the algorithm:
-
- Procedure LineIndiv (X1,Y1,X2,Y2:Integer;Color:Byte);
- var
- X,Y,
- YIncr,
- D,DX,DY,
- AIncr,BIncr : Integer;
- Ofs : Word;
- begin
- If X1 > X2 then begin
- D := X1;
- X1 := X2;
- X2 := D;
- D := Y1;
- Y1 := Y2;
- Y2 := D;
- end;
- If Y2 > Y1 then YIncr := 1
- else YIncr := -1;
- DX := X2 - X1;
- DY := Abs (Y2-Y1);
- D := 2 * DY - DX;
- AIncr := 2 * (DY - DX);
- BIncr := 2 * DY;
-
- X := X1;
- Y := Y1;
- SetPixel (X,Y,Color);
-
- For X := X1 + 1 to X2 do begin
- If D >= 0 then begin
- Inc (Y,YIncr);
- Inc (D,AIncr);
- end
- Else Inc (D,BIncr);
- SetPixel (X,Y,Color);
- end;
- end;
-
- Notice that once the variables are set up, the only math taking place
- is subtration and/or addition. There is however, one catch; we are
- calling SetPixel to set each point on the line and this procedure
- contains a multiplication. The speed of an average line with this
- routine is 1000 lines per second. That's six times faster than the
- line equation method.
-
- Let's take a look at the final? evolution:
-
- Procedure Line (X1,Y1,X2,Y2:Integer;Color:Byte);
- var
- I,
- YIncr,
- D,DX,DY,
- AIncr,BIncr : Integer;
- Ofs : Word;
- begin
- If X1 > X2 then begin
- D := X1;
- X1 := X2;
- X2 := D;
- D := Y1;
- Y1 := Y2;
- Y2 := D;
- end;
- If Y2 > Y1 then YIncr := 320
- else YIncr := -320;
- DX := X2 - X1;
- DY := Abs (Y2-Y1);
- D := 2 * DY - DX;
- AIncr := 2 * (DY - DX);
- BIncr := 2 * DY;
-
- Ofs := Word(Y1) * 320 + Word(X1);
-
- Mem [$A000:Ofs] := Color;
-
- For I := X1 + 1 to X2 do begin
- If D >= 0 then begin
- Inc (Ofs,YIncr);
- Inc (D,AIncr);
- end
- Else Inc (D,BIncr);
- Inc (Ofs);
- Mem [$A000:Ofs] := Color;
- end;
- end;
-
- There we have it, an extremely fast line drawing algorithm, all coded in
- Turbo Pascal. We may do this in assembly if the need for speed drives
- us to it. When doing animation, speed is paramount, so we probably will
- implement this is assembly. The benchmark, by the way, is a whopping 1820
- lines per second. Here is a program to test each routine for speed:
-
- Program MCGATest;
- uses
- Crt,Dos,Lib03;
- var
- Stop,
- Start : LongInt;
- Regs : Registers;
- PicBuf : Pointer;
-
- Function Tick : LongInt;
- begin
- Regs.ah := 0;
- Intr ($1A,regs);
- Tick := Regs.cx shl 16 + Regs.dx;
- end;
-
- Procedure ShowAndTell (S:String);
- var
- Ch : Char;
- begin
- Repeat Until Keypressed;
- While Keypressed do Ch := Readkey;
- TextMode (3);
- WriteLn (S);
- WriteLn ('Routine took ',(Stop-Start)/18.2:4:3,' seconds!');
- Write ('or an average of ');
- WriteLn (1000/((Stop-Start)/18.2)):6:1,' lines per second.');
- While KeyPressed do Ch := Readkey;
- Repeat Until Keypressed;
- While Keypressed do Ch := Readkey;
- end;
-
- Procedure Control;
- var
- I : Integer;
- begin
- SetGraphMode ($13);
- Start := Tick;
- For I := 1 to 1000 do
- LineEqu (Random (320),Random (200),
- Random (320),Random(200),Random(256));
- Stop := Tick;
- ShowAndTell ('Equation of a line...');
- SetGraphMode ($13);
- Start := Tick;
- For I := 1 to 1000 do
- LineIndiv (Random (320),Random (200),
- Random (320),Random(200),Random(256));
- Stop := Tick;
- ShowAndTell ('Bresenhan''s algorithm, addressing each pixel individually...');
- SetGraphMode ($13);
- Start := Tick;
- For I := 1 to 1000 do
- Line (Random (320),Random (200),
- Random (320),Random(200),Random(256));
- Stop := Tick;
- ShowAndTell ('Bresenhan''s algorithm, incremental addressing each pixel...');
- end;
-
- Procedure Init;
- begin
- Randomize;
- end;
-
- Begin
- Init;
- Control;
- End.
-
- Hope you are enjoying the series, and I'm looking forward to reading
- your comments. Next installment, we will look at the special case
- lines, vertical and horizontal. These lines can be highly optimized,
- and of course we will do that whenever possible.
-
- hack on
- jim
-