how to paint circles and ellipses (part 4)


contents

part1modifying pixels in a bitmap
part2drawing dots and lines: the XBitmap class
part3flicker free painting
part4drawing circles and ellipses


Introduction

This article describes a way to paint circles and ellipses in the Delphi programming language.
Please look at the picture below:
The general equation of an ellipse is:
2
æ
x
a
ö
­­
èø
 + 
2
æ
y
b
ö
­­
èø
 = 1


In case of a circle, where a = b = r (radius), the equation becomes x2 + y2 = r2

For the convienience of calculations we assume the center of the ellipse at (0,0).

The ellipse is painted point by point.
To avoid unpainted "holes" between pixels, the slope of the tangent must be observed.
If the tangent is < 1 (and > -1) , a horizontal oriented line , the x coordinate steps by 1
and the corresponding y is calculated.
However, if the tangent is > 1 (or < -1) , a vertical oriented line, then y must be stepped by 1 and x is calculated.
So we first calculate the point on the ellipse where the tangent = 1 (or -1):
The derivative of the equation is: .....
2 x
a 2
 + 
2 y y'
b 2
 = 0


If y' = -1 we get......
x
a 2
 − 
y
b 2
 = 0
.....................y = 
b 2 x
a 2


Substitution of y in the original equation of the ellipse:..............
x 2
a 2
 + 
b 2 x 2
a 4
 = 1

a 2 x 2 + b 2 x 2 = a 4
x = 
a 2
\a 2 + b 2
.......and similar...............
y = 
b 2
\a 2 + b 2


So these are the coordinates of point A.
In case of a = 3, b = 2...........x = 2.496..........y = 1.109

Because of the symmetry we also know points B,C,D.

The ellipse is painted while walking (stepping pixel by pixel) road AB, BC,CD,DA while calculating
and painting the corresponding point of the ellipse.
On AB or CD, x is stepped by 1 and Y is calculated.
On BC or DA, y is stepped and x is calculated.

The program

The procedure presented below is part of the XBitmap class and it allows for painting an ellipse arc.
The ellipse is defined by it's fitting rectangle with left top (x1,y1) and right bottom (x2,y2).
Calculated center point is M.
Starting point of the arc is the intersection of the line through M and (x3,y3) with the ellipse.
Ending point is the intersection of the line through M and (x4,y4) with the ellipse.

Acode is the arrowcode. If zero, no arrows are painted.
I have removed the code for the arrows.

Also removed is the code to paint dash-dot patterns.

Initially, (x1,y1) and (x2,y2) are the coordinates of the fitting rectangle, but later in the program
(x1,y1) ....(x2,y2) become coordinates of points B and D.
Rectangle ABCD is called the "inner rectangle" in the program comments.
Line AB is coded as side 0, BC is side 1, CD is side 2 and DA is side 3

The Xarc procedure has some local functions and procedures:

procedure nextpoint(var p : integer; var side : byte);
Variable side has a value of 0..3 for AB ..DA.
p is the x or y position on the side.
To know the next point, p is decremented on sides 0 and 3 and incremented on sides 1 and 2.
nextpoint calls procedure nextside.

procedure nextside(var p : integer; var side : byte);//p : point, s : side
If the position p on a side exceeds the length of the side, the side and position must be updated.
Then nextpoint calls itself, because a side may be crossed again in case of a very flat ellipse.

function WtoP(p : integer; side : byte) : TPoint;
This procedure calculates the point on the ellipse to be painted from the side and
position p on the side.

Painting starts at beginside at point sp.
Painting ends at endside at point ep.

XBitmap procedure Dot(.. ,..) paints the point of the ellipse.
Refer to the XBitmap source code for the Dot( ) procedure.
Here also mx and my are added to the point. (pt.x + mx, pt.y + my).

Source code listing

procedure TXBitmap.Arc1(x1,y1,x2,y2,x3,y3,x4,y4 : integer; acode : byte);
//common code for Xellipse,Xarc
//acode = 0 for  ellipse, = FXArrowcode for Arcs
//draw arc inside rect(x1,y1)..(x2,y2)
//from intersection with line M..(x3,y3)
//to intersection with line M..(x4,y4)  ...counterclockwise
//use penwidth, pencolor, penlevel
var a,b,a2,b2,mx,my,x,y,pa : integer;
    beginside, endside : byte;
    ab2,s,v1,v2,dab,dba : single;
    sp,ep : integer;                   //start-,endpoint
    pt : TPoint;
    
   procedure nextside(var p : integer; var side : byte);//p : point, s : side
    //use points x1,x2,y1,y2 as reference ,
    //walk (x2,y1)..(x1,y1)..(x1,y2)..(x2,y2)..(x2,y1) side 0..3
    begin
     case side of
      0 : if p = x1 then begin
                          p := y1; side := 1; nextside(p,side);
                         end;
      1 : if p = y2 then begin
                          p := x1; side := 2; nextside(p,side);
                         end;
      2 : if p = x2 then begin
                          p := y2; side := 3; nextside(p,side);
                         end;
      3 : if p = y1 then begin
                          p := x2; side := 0; nextside(p,side);
                         end;
     end;//case
    end;

    procedure nextpoint(var p : integer; var side : byte);
    //calculate next point and side
    begin
     if (side = 0) or (side = 3) then dec(p) else inc(p);
     nextside(p,side);
    end;
   
// -------------------------

    function WtoP(p : integer; side : byte) : TPoint;
    //point p on side s to point of ellipse
    //use a2,b2,dba,dab
    begin
     case side of
      0 : begin
           result.x := p;
           result.y := -trunc(dba*sqrt(a2-p*p)+0.25);
          end;
      1 : begin
           result.x := -trunc(dab*sqrt(b2-p*p)+0.25);
           result.y := p;
          end;
      2 : begin
           result.x := p;
           result.y := trunc(dba*sqrt(a2-p*p)+0.25);
          end;
      3 : begin
           result.x := trunc(dab*sqrt(b2-p*p)+0.25);
           result.y := p;
          end;
     end;//case
    end;

// ------------------------- main ------

begin
 mx := (x1+x2) div 2;       //sort x,y
 my := (y1+y2) div 2;
 a := abs(x2-x1) shr 1;
 b := abs(y2-y1) shr 1;
 if (a = 0) or (b = 0) or (a > 1000) or (b > 1000) then exit;
//--
 
//--- calculate (x1,y1)....(x2,y2) of inner rectangle

 a2 := a*a;
 b2 := b*b;
 ab2 := sqrt(a2 + b2);
 x2 := trunc(a2/ab2 + 0.5);
 x1 := -x2;
 y2 := trunc(b2/ab2 + 0.5);
 y1 := -y2;
//
 if (x1 = 0) and (y1 = 0) then exit;
//
 x3 := x3 - mx; x4 := x4 - mx;
 y3 := y3 - my; y4 := y4 - my;
 if ((x3=0) and (y3=0)) then x3 := x2;
 if ((x4=0) and (y4=0)) then x4 := x2;
//
 v1 := b*x3;
 v2 := a*y3;
 s := a*b/sqrt(v1*v1 + v2*v2);
 x := trunc(x3*s + 0.5);
 y := trunc(y3*s + 0.5);
//
 dba := b/a;       //for WtoP procedure
 dab := a/b;
//
 if (abs(x) <= x2) then                  //startpoint
  begin
   if (y3<=0) then beginside := 0 else beginside := 2;
   sp := x;
  end
 else begin
       if (x3<0) then beginside := 1 else beginside := 3;
       sp := trunc(y3*s+0.5);
      end;
 v1 := b*x4;                             //endpoint
 v2 := a*y4;
 s := a*b/sqrt(v1*v1 + v2*v2);
 x := trunc(x4*s + 0.5);
 y := trunc(y4*s + 0.5);
 if abs(x) <= x2 then
  begin
   if (y4<=0) then endside := 0 else endside := 2;
   ep := x;
  end
 else  begin
        if (x4<0) then endside := 1 else endside := 3;
        ep := trunc(y4*s+0.5);
       end;
 nextside(sp,beginside);
 nextside(ep,endside);

// ------- draw --------------

 repeat
  pt := WtoP(sp,beginside);
  Dot(mx+pt.x,my+pt.y);
  nextPoint(sp,beginside);
 until (beginside = endside) and (sp = ep);
end;