[comp.graphics] 2-D clipping

banks@utah-cs.UUCP (Michael J. Banks) (10/04/88)

I have need of an algorithm to clip 2-D lines to a rectangle.
All of the references I have been able to find give algorithms
for clipping lines in "world coordinates" and rely on floating
point numbers.

Can anyone direct me to an efficient integer algorithm for
clipping lines to rectangles?

mike.

banks@cs.utah.edu		(ARPA)
{ihnp4,decvax}!utah-cs!banks	(UUCP)

erik@cadnetix.COM (Erik Hyypia) (10/04/88)

In article <5760@utah-cs.UUCP> banks@utah-cs.UUCP (Michael J. Banks) writes:
>I have need of an algorithm to clip 2-D lines to a rectangle.
>All of the references I have been able to find give algorithms
>for clipping lines in "world coordinates" and rely on floating
>point numbers.
>
>Can anyone direct me to an efficient integer algorithm for
>clipping lines to rectangles?

Try the Cohen/Sutherland clipping algorithm, described in Principles
of Interactive Computer Graphics, by Newman and Sproull, McGraw Hill.
It is efficient at finding intersection points of lines which cross
the rectangle boundries, and also at trivially rejecting lines which
are wholly outside the clip region (good for operations like 
zooming in, where the majority of lines are rejected).  We 
successfully implemented an integer version of this algorithm.
(Corporate info, can't post it). Good luck,
                         
                          erik@cadnetix.COM (Erik Hyypia)
                          <standard disclaimer, blah, blah>

sa@ttidca.TTI.COM (Steve Alter) (10/06/88)

In article <5760@utah-cs.UUCP> banks@utah-cs.UUCP (Michael J. Banks) writes:
} Can anyone direct me to an efficient integer algorithm for
} clipping lines to rectangles?

The following was written in Turbo Pascal 4.0 for IBM PC but should be
easily convertable to any other structured language.  What's actually
included below is the clipping routine plus an program that I used to
display an impressive test of the routine.

There is some floating point in the clipping routine (variables DX and
DY,) but that's only because the value wouldn't fit into an integer.
A long-integer would probably work just as well.

The algorithm was obtained from a college graphics course
(California State University, Northridge, course COMP 465.)

Procedure ClipLine takes the x and y coordinates of both endpoints of
a line, clips them to the rectangle specified by ClipLeft, ClipRight,
ClipTop and ClipBottom, then returns the new endpoints of the line in
the same four variables; they must be passed by reference (in C,
that's done by passing a pointer to the variables.)  The routine also
returns a boolean value that indicates whether or not *any* portion of
the line lies within the clipping rectangle.  If it comes back false,
there is nothing of that line to draw and the four coordinates come
back with garbage values.

The following describe the Pascal routines needed by the program
(shouldn't be too difficult to port)

	SetColor(n)		obvious
	Line(x1,y1, x2,y2)	obvious
	GetMaxX			function returns highest x-coordinate
	GetMaxY			function returns highest y-coordinate
	Round			round a float to nearest integer
	Rectangle(x1,y1, x2,y2)	obvious
	Readln			read a line of input but don't put it anywhere
				  (I use it as a "Type RETURN to continue")
	InitGraph               switch into specified graphics mode
	DirectVideo             esoterica related to displaying text on screen
				  (This one's a variable, not a routine)
	RestoreCrtMode		switch back to text display mode

Here's the principle behind this test program:

One way to test a clipping routine is to draw the full line in one
color and then draw the same line, after clipping, in another color on
top of the first one.  Do this for a *whole* *bunch* of different
lines and you should see a rectangle start to show itself.  This
rectangle will be in the second color, will be set against a
background of the first color, and the rectangle *is* the clipping
rectangle.  You've got to make sure that most or all of the tested
lines overlap at least one of the rectangle's borders to really give
the clipping routine a workout.

-- Steve Alter
...!{csun,psivax,pyramid,quad1,rdlvax,retix}!ttidca!alter  or  alter@tti.com
Citicorp/TTI, Santa Monica CA  (213) 452-9191 x2541
-----------------------------------------------------------------------------
PROGRAM TestClipLine;

USES Crt, Graph;

VAR
  ClipLeft, ClipRight, ClipTop, ClipBottom: Integer;

PROCEDURE ClipLine (VAR X1,Y1, X2,Y2: Integer; VAR Drawable: Boolean);
  CONST
    ToLeft = 1;  ToRight = 2;
    ToUp   = 4;  ToDown  = 8;

  FUNCTION Code (X,Y: Integer): Integer;
    VAR
      C: Integer;
    BEGIN
      C := 0;
      IF X < ClipLeft THEN C := ToLeft
      ELSE IF X > ClipRight THEN C := ToRight;
      IF Y < ClipTop THEN C := C OR ToUp
      ELSE IF Y > ClipBottom THEN C := C OR ToDown;
      Code := C;
    END;  (* Code *)

  VAR
    C1,C2,C, X,Y: Integer;
    DX,DY: Real;

  BEGIN  (* ClipLine *)
    C1 := Code(X1,Y1);  C2 := Code(X2,Y2);
    Drawable := True;
    WHILE Drawable AND ((C1 <> 0) OR (C2 <> 0)) DO
      IF (C1 AND C2) <> 0 THEN
        Drawable := False
      ELSE
        BEGIN
          IF C1 <> 0 THEN C := C1 ELSE C := C2;
          DX := X2-X1;  DY := Y2-Y1;
          IF (C AND ToLeft) <> 0 THEN
            BEGIN  { Crosses left edge }
              Y := Round(Y1 + DY*(ClipLeft-X1)/DX);
              X := ClipLeft;
            END
          ELSE IF (C AND ToRight) <> 0 THEN
            BEGIN  { Crosses right edge }
              Y := Round(Y1 + DY*(ClipRight-X1)/DX);
              X := ClipRight;
            END
          ELSE IF (C AND ToUp) <> 0 THEN
            BEGIN  { Crosses top edge }
              X := Round(X1 + DX*(ClipTop-Y1)/DY);
              Y := ClipTop;
            END
          ELSE IF (C AND ToDown) <> 0 THEN
            BEGIN  { Crosses bottom edge }
              X := Round(X1 + DX*(ClipBottom-Y1)/DY);
              Y := ClipBottom;
            END;
          IF C = C1 THEN
            BEGIN
              X1 := X; Y1 := Y; C1 := Code(X,Y);
            END
          ELSE
            BEGIN
              X2 := X; Y2 := Y; C2 := Code(X,Y);
            END;
        END;
  END;  (* ClipLine *)

PROCEDURE TestClipLine;
  PROCEDURE ClippedDraw (X1,Y1, X2,Y2: Integer);
    VAR
      Drawable: Boolean;
    BEGIN
      SetColor(3);
      Line(X1,Y1, X2,Y2);
      SetColor(1);
      ClipLine(X1,Y1, X2,Y2, Drawable);
      IF Drawable THEN
        Line(X1,Y1, X2,Y2);
    END;  (* ClippedDraw *)
  CONST
    Steps = 20;
  VAR
    I: Integer;
    X1,Y1, X2,Y2: Real;
  BEGIN
    ClipLeft := 100;  ClipRight := GetMaxX - 100;
    ClipTop := 80;    ClipBottom := GetMaxY - 80;
    X1 := 40;  X2 := GetMaxX - 120;
    Y1 := 40;  Y2 := GetMaxY - 100;
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X1),Round(Y1), Round(X2),Round(Y1+(Y2-Y1)*I/Steps));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X1),Round(Y1), Round(X2-(X2-X1)*I/Steps),Round(Y2));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X1),Round(Y2), Round(X1+(X2-X1)*I/Steps),Round(Y1));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X1),Round(Y2), Round(X2),Round(Y1+(Y2-Y1)*I/Steps));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X2),Round(Y2), Round(X1),Round(Y2-(Y2-Y1)*I/Steps));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X2),Round(Y2), Round(X1+(X2-X1)*I/Steps),Round(Y1));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X2),Round(Y1), Round(X2-(X2-X1)*I/Steps),Round(Y2));
    FOR I := 0 TO Steps DO
      ClippedDraw(Round(X2),Round(Y1), Round(X1),Round(Y2-(Y2-Y1)*I/Steps));
    SetColor(2); Rectangle(ClipLeft-2, ClipTop-2, ClipRight+2, ClipBottom+2);
    SetColor(2); Rectangle(ClipLeft-1, ClipTop-1, ClipRight+1, ClipBottom+1);
    Readln;
  END;  (* TestClipLine *)

VAR
  Driver, Mode: Integer;

BEGIN
  Driver := EGA;
  Mode := EGAHi;
  InitGraph(Driver, Mode, '');
  DirectVideo := False;
  TestClipLine;
  RestoreCrtMode;
END.

hst@mhres.mh.nl (Klaas Hemstra) (10/06/88)

Included is the source of a clip_line routine I wroten recently.

The coordinates may be "int"egers or "long"s, and both positive and negative.
Also the ranges to clip the coordinates in can be in the negative range of
an int or long.

I hope this is useful to you and maybe to others too,

				    Klaas Hemstra


File: clip_line.c
---------------------------------------------------------------------------
/* This file contains routines to clip a line into a specified border   */
/*									*/
/*  Made By  K.Hemstra  it's in the Public Domain  Use at your own risk */

/* All computations are done with integer arithmetic.
/* The only restrictions are that the border to clip the line into can not  */
/* have a height or width larger then the square root of the largest long,  */
/*  and for a line (x1,y1) - (x2,y2) to clip: abs(x2 - x1) * abs(y2 - y1),  */
/*   may not exceed the largest long.					    */

/* So for C-implementations with int = 16 bits and long = 32 bits the       */
/* largest "clip"-box can be 32767x32767 coordinates.		      */

/* The border is defined by the two points (minx,miny) and (maxx,maxy) */
/* All comment if based on the following graph */
/*

			   Above the border
	(minx,miny)  *--------------------------*
		     |			        |
Left of the border   |	Within the border       | Right of the border
		     |			        |
		     *--------------------------* (maxx,maxy)
			   Below the border

*/


#typedef int COOR

/* COORdinate type: This can be int or long.
    The routine will work ok in both cases.
    However it is best too choose "int" if the coordinates allow it, because
    "int"s are faster on some machines, specially 16-bits like MS-DOS.
Note:    For Turbo-C the #typedef will not work.
	Use "#define COOR int" instead, it works fine.
*/



COOR    minx = 0,maxx = 719;	/* initialized to hercules screen */
COOR    miny = 0,maxy = 347;
COOR    imaxx = 719,imaxy = 347;


/* This routine is should be called to initialize the border
    The clip_line routine will "clip" the line into (x1,y1) - (x2,y2)  */

int set_clip_borders(x1,y1,x2,y2)
COOR    x1,y1,x2,y2;
{
    minx = x1, miny = y1, maxx = x2, maxy = y2;
    imaxx = maxx - minx;
    imaxy = maxy - miny;
}


/* The clip_line routine */

int clip_line(x1,y1,x2,y2)
COOR x1,y1,x2,y2;
{
    if (clip_coordinates(&x1,&y1,&x2,&y2) != 0)
    {

/* Here the machine specific line drawing routine is called */

	draw_line(x1,y1,x2,y2);

    }
}



/* The clip_coordinates routine must be called with four addresses to
    COORdinates.
   These COORdinates are changed to fall into the range
	(minx,miny) - (maxx,maxy)
   If the line will not be within the defined border the returned
    integer value will be 1.
*/

int clip_coordinates(ix1,iy1,ix2,iy2)
COOR *ix1,*iy1,*ix2,*iy2;
{
    COOR	x1,y1,x2,y2;
    long	dx,dy,temp;
    long	signdy;

    if (ix2 < ix1)	    /* Always make (x1,y1) the most left point */
	x1 = *ix2 -minx, y1 = *iy2 -miny, x2 = *ix1 -minx, y2 = *iy1 -miny;
    else
	x1 = *ix1 -minx, y1 = *iy1 -miny, x2 = *ix2 -minx, y2 = *iy2 -miny;

	/* Now the coordinates x1,y1,x2,y2 are relatife to (0,0) */
	/*    which is the upper left corner of the border	 */


	/* If line is completely left or right of the border      */

    if ((x2 < 0) || (x1 > imaxx))
	return 1;

	/* If line is completely above or below the border */
    if (((y1 < 0) && (y2 < 0)) || ((y1 > imaxy) && (y2 > imaxy)))
	return 1;

    /* Ok , the line is (maybe partly) in the border, work to do */

    /* Ok, The line is partly above or below border (or both) */
    dx = (x2 - x1);	    /* Distance between two x values */
    dy = abs(y2 - y1);	/* Distance between two y values */
    signdy = (((y2 - y1) < 0) ? -1 : 1);    /* sign of y2-y1 */

	    /* If y1 lies above border then correct */
    if (y1 < 0)
    {    x1 += ((long)dx * (long)(- y1)) / (long) dy;
	y1 = 0;
    }
	/* If y1 lies below border then correct */
    if (y1 > imaxy)
    {    x1 += ((long)dx * (long)(y1 - imaxy)) / (long) dy;
	y1 = imaxy;
    }
	/* If y2 lies above border then correct */
    if (y2 < 0)
    {    x2 -= ((long)dx * (long)(- y2)) / (long) dy;
	y2 = 0;
    }
	/* If y1 lies below border then correct */
    if (y2 > imaxy)
    {    x2 -= ((long)dx * (long)(y2 - imaxy)) / (long) dy;
	y2 = imaxy;
    }

	/* If x1 lies left of border then correct */
    if (x1 < 0)
    {    y1 += signdy * (((long)dy * (long)(- x1)) / (long) dx);
	x1 = 0;
    }
	/* If x2 lies right of border then correct */
    if (x2 > imaxx)
    {    y2 -= signdy * (((long)dy * (long)(x2 - imaxx)) / (long) dx);
	x2 = imaxx;
    }
	/* If line now is completely above or below the border */
    if ((y1 < 0) || (y2 < 0) || (y1 > imaxy) || (y2 > imaxy))
	return 1;

    *ix1 = x1+minx, *iy1 = y1+miny, *ix2 = x2+minx, *iy2 = y2+miny;
    return 0;
}
----------------------------------End of file clip_line.c


-- 
Klaas Hemstra  (hst@mh.nl)                   |    /  / ,~~~  ~~/~~
uucp: ..{uunet!}mcvax!mh.nl!hst              |   /--/  `-,    /  ___  |_/ |__|
Multihouse N.V., Gouda, the Netherlands      |  /  / ___/    /   ---  | \ |  |