[comp.graphics] circles with non-unity aspect ratio

stevens@hsi.UUCP (Richard Stevens) (01/05/88)

I am trying to draw "good" circles on an AT&T 3b1 (aka, UNIX PC).
The problem is the aspect ratio - it is nowhere near unity.
I have gone through McIlroy's paper ("Best Approximate Circles on
Integer Grids") and Foley & Van Dam.  I see two ways to draw the circle:

(1) Utilize "user coordinates" (in this case a 4096 by 4096 square)
	in which case I can use the algorithms as-is, however for each
	point on the 4096x4096 grid that I want to plot, I have to
	convert the point to device coordinates (430 by 288 for a
	square area on the 3b1).  However, I'd guess that this will
	lead to a "messy" circle (i.e., not meeting McIlroy's
	"thinness" or "connectivity" criteria).  Perhaps in this case,
	since I'd be going from a bigger coordinate space to a smaller
	one, there won't be any holes in the circle, but in general,
	I'd think a transformation like this could destroy some of
	the properties that the algorithm tries to preserve.
	Furthermore, this technique will take more time, since I'm
	calculating all the points on a 4096x4096 grid, when I'm really
	plotting on a much smaller area.  Each step of the loop will
	also require a transformation of the x and y coordinates,
	which will take more time.

(2) Utilize "device coordinates" (430 by 288, in this case).
	Here I need convert the circle parameters only once, instead
	of each time through the loop, however what I'm really drawing
	is an ellipse of pixels (that will appear as a circle on the
	screen).  The two references that I've looked at don't go into
	this case.

Am I missing something simple that would make it easy to draw my
circles ??  Can anyone point me to another reference that adequately
covers the drawing of a circle on a screen with a non-unity aspect
ratio (F & VD mention a few, but before I go digging up the articles,
does anyone know if these are what I should be looking at) ??

	Richard Stevens
	Health Systems International, New Haven, CT
           { uunet | ihnp4 } ! hsi ! stevens

mcdonald@uxe.cso.uiuc.edu (01/07/88)

>I am trying to draw "good" circles on an AT&T 3b1 (aka, UNIX PC).
>The problem is the aspect ratio - it is nowhere near unity.
>I have gone through McIlroy's paper ("Best Approximate Circles on
>Integer Grids") and Foley & Van Dam.  I see two ways to draw the circle:

>(1) Utilize "user coordinates" (in this case a 4096 by 4096 square)
       Bad idea.

>(2) Utilize "device coordinates" (430 by 288, in this case).
        The correct way.

>Am I missing something simple that would make it easy to draw my
>circles ??  Can anyone point me to another reference that adequately
>covers the drawing of a circle on a screen with a non-unity aspect
>ratio (F & VD mention a few, but before I go digging up the articles,
>does anyone know if these are what I should be looking at) ??

It's not really easy to generate good looking circles on a pixel grid.
After trying lots of different things, I have adopted the following
ellipse drawer. It draws ellipses in pixel space; to get circles you
feed it the proper height and width in pixels. This thine works really
well for circles with a radius greater than 4. For smaller circles,
it pays in terms of looking nice to draw each size as a special case.

The enclosed program uses the standard Bresnahan algorithm for the
best approximation of a curve. It is optimized for speed.

There was a good reference to this an article in Dr. Dobbs Journal in 1987,
but for circles only, not ellipses.

I hope that this can help you.

Doug McDonald
Department of Chemistry
University of Illinois


/* Draw an ellipse with width irx and height iry                         */
/* from a routine by Tim Hogan in Dr. Dobb's Journal May '85 p.40        */
/* Improved by calculating increments incrementally, thus removing all   */
/* multiplies from the loops. These multiplies were very bad since they  */
/* were (long)*(long).                                                   */
/* Written Sept. 7, 1987 by J.D. McDonald (public domain)                */

static long     alpha, beta, alpha2, alpha4, beta2, beta4, d;
static long     ddx, ddy, alphadx, betady;
static int      dy, dx;

extern void     e_start(int, int, int ,int);
extern void     e_xd();	
extern void     e_xdyu();
extern void     e_yu(); 

ellipse(x, y, irx, iry, c)
    int             x, y, irx, iry;
    unsigned        c;
{

    beta = (long) irx *(long) irx;
    alpha = (long) iry *(long) iry;

    if (alpha == 0L)
	alpha = 1L;
    if (beta == 0L)
	beta = 1L;

    dy = 0;
    dx = irx;
    alpha2 = alpha << 1;
    alpha4 = alpha2 << 1;
    beta2 = beta << 1;
    beta4 = beta2 << 1;
    alphadx = alpha * dx;
    betady = 0;
    ddx = alpha4 * (1 - dx);
    ddy = beta2 * 3;

    d = alpha2 * ((long) (dx - 1) * dx) + alpha + beta2 * (1 - alpha);
    e_start(x - dx, x + dx, y, c);
          /* e_start draws left and rightmost pixels on vertical centerline */
          /* e_yu draws a pixel in right top quadrant one up from previous  */
          /* e_xd draws a pixel in right top quadrant one left from previous*/
          /* e_xdyu draws a pixel in right top quadrant up and left from    */
          /* previous. e_yu, e_xd, and e_xdyu also draw the corresponding   */
          /* pixels in the other three quadrants.                           */
          /* c is the color                                                 */
    do {
	if (d >= 0) {
	    d += ddx;
	    dx--;
	    alphadx -= alpha;
	    ddx += alpha4;
	    e_xdyu();
	} else
	    e_yu();
	d += ddy;
	dy++;
	betady += beta;
	ddy += beta4;
    } while (alphadx > betady);

    d = beta2 * ((long) dy * (dy + 1)) + alpha2 * ((long) dx * (dx - 2) + 1) 
	+ beta * (1 - alpha2);
    ddx = alpha2 * (3 - (dx << 1));
    ddy = beta4 * (1 + dy);

    do {
	if (d <= 0) {
	    d += ddy;
	    ddy += beta4;
	    dy++;
	    e_xdyu();
	} else
	    e_xd();
	d += ddx;
	ddx += alpha4;
	dx--;
    } while (dx > 0);
}

jackm@devvax.JPL.NASA.GOV (Jack Morrison) (01/09/88)

A few requests for ellipses have prompted me to include this code. It
draws ellipses (including circles, of course) of varying line width
on a SUN bitmap display. (It's part of a paint-type program). I don't
know how this compares to the earlier posting of an ellipse algorithm,
but it's fairly fast.

Enjoy or flame....

=====================================================================

/***********************************************/
/* gred_ellipse.c - draw ellipses		*
/* algorithm from IEEE CG&A Sept 1984 p.24 	*
/***********************************************/
#include <stdio.h>
#include <sunwindow/window_hs.h>

gred_ellipse (cx, cy, radius_x, radius_y, line_width, pw, pattern_pr)
int   			cx, cy;
int   			radius_x, radius_y;
int			line_width;
struct pixwin 		*pw;
struct pixrect 		*pattern_pr;
{
    struct pixrect 	*pr;
    int			inner_radius_x;
    int			inner_radius_y;
    int			outer_radius_x;
    int			outer_radius_y;

/*******************************************************************/
/* Calculate the inner, outer radiuses of the ellipse              */
/*******************************************************************/
    /* if one pixel wide, radiuses are same */
    if (line_width < 2)
    {
	inner_radius_x = outer_radius_x = radius_x;
	inner_radius_y = outer_radius_y = radius_y;
    }
    else
    {
	outer_radius_x = radius_x + (line_width >> 1);
	outer_radius_y = radius_y + (line_width >> 1);
	inner_radius_x = outer_radius_x - line_width + 1;
	inner_radius_y = outer_radius_y - line_width + 1;
    }

/*******************************************************************/
/* Create a pixrect to build the ellipse in.                       */
/*******************************************************************/
    pr = mem_create ((outer_radius_x << 1) + 1, (outer_radius_y << 1) + 1, 1);
    /* now clear it */
    pr_rop (pr,	0, 0, pr -> pr_size.x, pr -> pr_size.y,
		PIX_CLR, (struct pixrect *) 0, 0, 0);

/*******************************************************************/
/* If the inner_radius > 0 then outline the inner edge.            */
/* Then if the outer radius of the ellipse is > than the inner     */
/* radius, call the fill ellipse routine with the outer radius.    */
/*******************************************************************/
    if ((inner_radius_x > 0)
    	    && (inner_radius_y > 0))
        outline_ellipse (pr, outer_radius_x, outer_radius_y, 
		inner_radius_x, inner_radius_y);

    if (line_width >=  2)
	fill_ellipse(pr, outer_radius_x, outer_radius_y, 
		outer_radius_x, outer_radius_y);
    
/*******************************************************************/
/* Now write the ellipse out to the window.                        */
/*******************************************************************/
    inner_radius_x = inner_radius_y = 0;	/* normal source offset */
    if ((cx -= outer_radius_x) < 0)	/* ellipse extends beyond left edge? */
    {
	inner_radius_x = -cx;
	cx = 0;
    }
    if ((cy -= outer_radius_y) < 0)	/* above top edge? */
    {
    	inner_radius_y = -cy;
	cy = 0;
    }

    if (pattern_pr == NULL)
        pw_write (pw, cx, cy, pr->pr_size.x-inner_radius_x, pr->pr_size.y-inner_radius_y,
	    PIX_SRC ^ PIX_DST, pr, inner_radius_x, inner_radius_y);
    else
	pw_stencil (pw, cx, cy, pr -> pr_size.x, pr -> pr_size.y, 
	    PIX_SRC, pr, inner_radius_x, inner_radius_y, pattern_pr, 0, 0);

    pr_destroy (pr);
}


/* draw ellipse incrementally */
static outline_ellipse (pr, center_x, center_y, rx, ry)
struct pixrect *pr;
int    center_x, center_y;
int    rx, ry;
{
	/* intermediate terms to speed up loop */
	long t1 = rx*rx, t2 = t1<<1, t3 = t2<<1;
	long t4 = ry*ry, t5 = t4<<1, t6 = t5<<1;
	long t7 = rx*t5, t8 = t7<<1, t9 = 0L;
	long d1 = t2 - t7 + (t4>>1);	/* error terms */
	long d2 = (t1>>1) - t8 + t5;

	register int x = rx, y = 0;	/* ellipse points */

	while (d2 < 0)			/* til slope = -1 */
	{
		/* draw 4 points using symmetry */
	    	pr_put (pr, center_x + x, center_y + y, 1);
	    	pr_put (pr, center_x + x, center_y - y, 1);
	    	pr_put (pr, center_x - x, center_y + y, 1);
	    	pr_put (pr, center_x - x, center_y - y, 1);

		y++;		/* always move up here */
		t9 += t3;	
		if (d1 < 0)	/* move straight up */
		{
			d1 += t9 + t2;
			d2 += t9;
		}
		else		/* move up and left */
		{
			x--;
			t8 -= t6;
			d1 += t9 + t2 - t8;
			d2 += t9 + t5 - t8;
		}
	}

	do 				/* rest of top right quadrant */
	{
		/* draw 4 points using symmetry */
	    	pr_put (pr, center_x + x, center_y + y, 1);
	    	pr_put (pr, center_x + x, center_y - y, 1);
	    	pr_put (pr, center_x - x, center_y + y, 1);
	    	pr_put (pr, center_x - x, center_y - y, 1);

		x--;		/* always move left here */
		t8 -= t6;	
		if (d2 < 0)	/* move up and left */
		{
			y++;
			t9 += t3;
			d2 += t9 + t5 - t8;
		}
		else		/* move straight left */
			d2 += t5 - t8;
	} while (x>=0);
}


static fill_ellipse (pr, center_x, center_y, rx, ry)
struct pixrect *pr;
int    center_x, center_y;
int    rx, ry;
{
	long t1 = rx*rx, t2 = t1<<1, t3 = t2<<1;
	long t4 = ry*ry, t5 = t4<<1, t6 = t5<<1;
	long t7 = rx*t5, t8 = t7<<1, t9 = 0;
	long d1 = t2 - t7 + (t4>>1);	/* error terms */
	long d2 = (t1>>1) - t8 + t5;
	register int x = rx, y = 0;	/* ellipse points */
        register int t;			/* used in fill operation */
	int wid;			/* width of fill */

	while (d2 < 0)			/* til slope = -1 */
	{
	 	/* fill in leftward to inner ellipse */
	    	for (t=x; (!pr_get(pr, center_x+t, center_y+y)) && t; t--);
		wid = x - t + 1;
		pr_rop (pr, center_x+t, center_y+y, wid, 1,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x-x, center_y+y, wid, 1,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x+t, center_y-y, wid, 1,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x-x, center_y-y, wid, 1,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);

		y++;		/* always move up here */
		t9 += t3;	
		if (d1 < 0)	/* move straight up */
		{
			d1 += t9 + t2;
			d2 += t9;
		}
		else		/* move up and left */
		{
			x--;
			t8 -= t6;
			d1 += t9 + t2 - t8;
			d2 += t9 + t5 - t8;
		}
	}

	do 				/* rest of top right quadrant */
	{
		/* fill in downward to inner ellipse */
	    	for (t=y; (!pr_get(pr, center_x+x, center_y+t)) && t; t--);
		wid = y - t + 1;
		pr_rop (pr, center_x+x, center_y+t, 1, wid,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x+x, center_y-y, 1, wid,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x-x, center_y+t, 1, wid,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);
		pr_rop (pr, center_x-x, center_y-y, 1, wid,
			PIX_SET | PIX_DONTCLIP, NULL, 0, 0);

		x--;		/* always move left here */
		t8 -= t6;	
		if (d2 < 0)	/* move up and left */
		{
			y++;
			t9 += t3;
			d2 += t9 + t5 - t8;
		}
		else		/* move straight left */
		{
			d2 += t5 - t8;
		}
	} while (x>=0);
}
-- 
Jack C. Morrison	Jet Propulsion Laboratory
(818)354-1463		jackm@jpl-devvax.jpl.nasa.gov
"The paycheck is part government property, but the opinions are all mine."