18

The Midpoint circle algorithm can be used rasterize the border of a circle. However, I want the circle to be filled, without drawing pixels multiple times (this is very important).

This answer provides a modification of the algorithm that yields a filled circle, but some pixels are visited several times: fast algorithm for drawing filled circles?

Q: How can I rasterize a circle without drawing pixels multiple times? Note that RAM is very limited!

Update:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CircleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[,] buffer = new byte[50, 50];
            circle(buffer, 25, 25, 20);

            for (int y = 0; y < 50; ++y)
            {
                for (int x = 0; x < 50; ++x)
                    Console.Write(buffer[y, x].ToString());

                Console.WriteLine();
            }
        }

        // 'cx' and 'cy' denote the offset of the circle center from the origin.
        static void circle(byte[,] buffer, int cx, int cy, int radius)
        {
            int error = -radius;
            int x = radius;
            int y = 0;

            // The following while loop may altered to 'while (x > y)' for a
            // performance benefit, as long as a call to 'plot4points' follows
            // the body of the loop. This allows for the elimination of the
            // '(x != y)' test in 'plot8points', providing a further benefit.
            //
            // For the sake of clarity, this is not shown here.
            while (x >= y)
            {
                plot8points(buffer, cx, cy, x, y);

                error += y;
                ++y;
                error += y;

                // The following test may be implemented in assembly language in
                // most machines by testing the carry flag after adding 'y' to
                // the value of 'error' in the previous step, since 'error'
                // nominally has a negative value.
                if (error >= 0)
                {
                    error -= x;
                    --x;
                    error -= x;
                }
            }
        }

        static void plot8points(byte[,] buffer, int cx, int cy, int x, int y)
        {
            plot4points(buffer, cx, cy, x, y);
            if (x != y) plot4points(buffer, cx, cy, y, x);
        }

        // The '(x != 0 && y != 0)' test in the last line of this function
        // may be omitted for a performance benefit if the radius of the
        // circle is known to be non-zero.
        static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
        {
#if false // Outlined circle are indeed plotted correctly!
            setPixel(buffer, cx + x, cy + y);
            if (x != 0) setPixel(buffer, cx - x, cy + y);
            if (y != 0) setPixel(buffer, cx + x, cy - y);
            if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#else // But the filled version plots some pixels multiple times...
            horizontalLine(buffer, cx - x, cy + y, cx + x);
            //if (x != 0) setPixel(buffer, cx - x, cy + y);
            //if (y != 0) setPixel(buffer, cx + x, cy - y);
            //if (x != 0 && y != 0) setPixel(buffer, cx - x, cy - y);
#endif
        }

        static void setPixel(byte[,] buffer, int x, int y)
        {
            buffer[y, x]++;
        }

        static void horizontalLine(byte[,] buffer, int x0, int y0, int x1)
        {
            for (int x = x0; x <= x1; ++x)
                setPixel(buffer, x, y0);
        }
    }
}

Here's the relevant result:

00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000111111111111111111111111111111111111111110000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000011111111111111111111111111111111111111100000
00000001111111111111111111111111111111111111000000
00000001111111111111111111111111111111111111000000
00000000111111111111111111111111111111111110000000
00000000111111111111111111111111111111111110000000
00000000011111111111111111111111111111111100000000
00000000001111111111111111111111111111111000000000
00000000000111111111111111111111111111110000000000
00000000000011111111111111111111111111100000000000
00000000000001111111111111111111111111000000000000
00000000000000122222222222222222222210000000000000
00000000000000001222222222222222221000000000000000
00000000000000000012333333333332100000000000000000
00000000000000000000012345432100000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000

The bottom pixels are plotted too many times. What am I missing here?

Update #2: This solution works:

static void circle(byte[,] buffer, int cx, int cy, int radius)
{
    int error = -radius;
    int x = radius;
    int y = 0;

    while (x >= y)
    {
        int lastY = y;

        error += y;
        ++y;
        error += y;

        plot4points(buffer, cx, cy, x, lastY);

        if (error >= 0)
        {
            if (x != lastY)
                plot4points(buffer, cx, cy, lastY, x);

            error -= x;
            --x;
            error -= x;
        }
    }
}

static void plot4points(byte[,] buffer, int cx, int cy, int x, int y)
{
    horizontalLine(buffer, cx - x, cy + y, cx + x);
    if (y != 0)
        horizontalLine(buffer, cx - x, cy - y, cx + x);
}    
l33t
  • 18,692
  • 16
  • 103
  • 180
  • The answer does not visit any pixels several times. Why do you say that? – Shahbaz Jun 04 '12 at 08:15
  • My implementation keeps doing multi-plotting at the top/bottom of the circle. Maybe I just didn't understand the answer? – l33t Jun 04 '12 at 08:25

5 Answers5

19

The answer to the other question is perfectly fine. However since it is creating confusion, I'm going to explain it a bit.

The algorithm you see in Wikipedia basically finds x and y of 1/8 of a circle (angles 0 to pi/4) and then draws 8 points which are its mirrors. For example:

    (o-y,o+x) x         x (o+y,o+x)

(o-x,o+y) x                  x (o+x,o+y) <-- compute x,y

                   o

(o-x,o-y) x                  x (o+x,o-y)

    (o-y,o-x) x         x (o+y,o-x)

What the other solution suggests, which makes perfect sense if you look closely to this picture, is to instead of drawing 8 points, draw 4 horizontal lines:

    (o-y,o+x) x---------x (o+y,o+x)

(o-x,o+y) x-----------------x (o+x,o+y) <-- compute x,y

                   o

(o-x,o-y) x-----------------x (o+x,o-y)

    (o-y,o-x) x---------x (o+y,o-x)

Now if you compute (x,y) for angles in [0, pi/4] and draw these 4 lines for every computed point, you will have drawn many horizontal lines filling a circle without any line overlapping the other.

Update

The reason you get overlapping lines in the bottom of the circle is that the (x,y) coordinates are rounded, so in those locations the (x,y) move horizontally themselves.

If you take a look at this wikipedia picture:

enter image description here

You will notice that on the top of the circle, some pixels are horizontally aligned. Drawing horizontal lines originating from those points overlap.

If you don't want this, the solution is quite easy. You have to keep the previous x you have drawn with (since the top and bottom are mirrors of the original (x,y), you should keep the previous x which represents the y of those lines) and only draw the horizontal lines if that value changes. If it doesn't, it means that you are on the same line.

Given the fact that you will first encounter the innermost points, you should draw lines for the previous point only if the new point has different x (of course, the last line is drawn always). Alternatively, you can start drawing from angle PI/4 down to 0 instead of 0 to PI/4 and that you will first encounter the outer points, therefore you draw lines every time you see a new x.

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • Thanks. I'll update my question with sample code that demonstrates the issue. There must be a certain condition that I'm missing. – l33t Jun 04 '12 at 09:06
  • Hm. So I need **two** "previous line" variables? One for 'y' and one for 'x' (mirrored)? – l33t Jun 04 '12 at 10:16
  • Well, it's not that simple. To draw a line you need the **outer** pixel. And the algorithm does not guarantee that the most outer pixel is visited first. So if I track visited lines, some pixels will be missed. – l33t Jun 04 '12 at 10:38
  • @NOPslider, you need only 1, which is for `x`. Let's consider the same image from Wikipedia. You start at angle 0, so you have `(x,0)` and you start going up. As you go up, for a few pixels `x` remains the same and `y` changes. 2 of the horizontal lines will be drawn anyway and the other two (mirrored over line y = x) must be checked whether they introduce a horizontal line. If they do, you draw them. – Shahbaz Jun 04 '12 at 11:16
  • Also, you are right, you visit the node from inside out. I'll update the answer – Shahbaz Jun 04 '12 at 11:16
  • Thanks. So I need to edit both the `plot4points()` function and the outer loop? – l33t Jun 04 '12 at 11:32
  • Ok. I obviously need to modify the main loop. Any ideas on how to reverse the "rotation" of this optimized version of Bresenham? I'm getting all dizzy by this algorithm :) – l33t Jun 04 '12 at 11:44
  • In the first method, you need to keep `last_x` and `last_y` and draw lines `(last_x, last_y) -- (-last_x, last_y)` and `(last_x, -last_y) -- (-last_x, -last_y)` always and lines `(last_y, last_x) -- (-last_y, last_x)` and `(last_y, -last_x) -- (-last_y, -last_x)` only if `last_x != x`. So you need to also divide the `plot4points` (or better, make it draw two lines (instead of 4) and give the mirrored x and y in the second call (which is conditioned)). – Shahbaz Jun 04 '12 at 12:09
  • In the second method, instead of starting from `(x=radius,y=0)` while `x >= y`, you need to start from `(radius*sqrt(2)/2, radius*sqrt(2)/2)` while `x >= 0`. To avoid floating point computation, you may want to represent `sqrt(2)` by an equivalent `a/b` number, preferably with a `b` as a power of 2 so it could be replaced with a shift. You then need to reverse all operations. That is all `-`s to `+`s and vice versa. – Shahbaz Jun 04 '12 at 12:13
  • You're being really helpful. Thanks! How many points do you want to modify my sample code? When I apply your changes, I either get weird x/y values or the drawn figure is... something else than a circle :P. I'll update my question with the proposed solution. – l33t Jun 04 '12 at 12:33
  • I saw it. Well, you have also negated the operations on `error`, but not the `if` on `error`. You should either keep `error` as it was, or change the `if` to `if (error <= 0)`. I'd say keep `error` is it has originally been. Honestly though, I'm not sure what the initial value of `error` should be in this case. – Shahbaz Jun 04 '12 at 12:40
  • How about running the original while loop without plotting? Then you get the ending x=17, y=19 and error=0 and could run your reversed loop... – l33t Jun 04 '12 at 13:31
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/12110/discussion-between-nop-slider-and-shahbaz) – l33t Jun 04 '12 at 13:31
  • See my update. I solved it using a quite nice trick. I will accept your answer as you pointed me in the right direction. Thanks! – l33t Jun 05 '12 at 07:16
6

I needed to do this, here's the code I came up with for it. The visual image here shows the pixels drawn where the number is the order in which the pixels are traversed, and the green numbers represent pixels that are drawn using the reflection of the completion of a column using symmetry as shown in the code.

enter image description here

void drawFilledMidpointCircleSinglePixelVisit( int centerX, int centerY, int radius )
{
    int x = radius;
    int y = 0;
    int radiusError = 1 - x;

    while (x >= y)  // iterate to the circle diagonal
    {

        // use symmetry to draw the two horizontal lines at this Y with a special case to draw
        // only one line at the centerY where y == 0
        int startX = -x + centerX;
        int endX = x + centerX;
        drawHorizontalLine( startX, endX, y + centerY );
        if (y != 0)
        {
            drawHorizontalLine( startX, endX, -y + centerY );
        }

        // move Y one line
        y++;

        // calculate or maintain new x
        if (radiusError<0)
        {
            radiusError += 2 * y + 1;
        }
        else 
        {
            // we're about to move x over one, this means we completed a column of X values, use
            // symmetry to draw those complete columns as horizontal lines at the top and bottom of the circle
            // beyond the diagonal of the main loop
            if (x >= y)
            {
                startX = -y + 1 + centerX;
                endX = y - 1 + centerX;
                drawHorizontalLine( startX, endX,  x + centerY );
                drawHorizontalLine( startX, endX, -x + centerY );
            }
            x--;
            radiusError += 2 * (y - x + 1);
        }

    }

}
user2226755
  • 12,494
  • 5
  • 50
  • 73
colinday
  • 71
  • 1
  • 2
4

I came up with an algorithm that draws the circle already filled.
It iterates over the pixels that the circle will be drawn upon and nothing else.
From here on it all about the speed of the draw-pixel function.

Here's a *.gif that demonstrates what the algorithm does !

As for the algorithm here's the code :

    //The center of the circle and its radius.
    int x = 100;
    int y = 100;
    int r = 50;
    //This here is sin(45) but i just hard-coded it.
    float sinus = 0.70710678118;
    //This is the distance on the axis from sin(90) to sin(45). 
    int range = r/(2*sinus);
    for(int i = r ; i >= range ; --i)
    {
        int j = sqrt(r*r - i*i);
        for(int k = -j ; k <= j ; k++)
        {
            //We draw all the 4 sides at the same time.
            PutPixel(x-k,y+i);
            PutPixel(x-k,y-i);
            PutPixel(x+i,y+k);
            PutPixel(x-i,y-k);
        }
    }
    //To fill the circle we draw the circumscribed square.
    range = r*sinus;
    for(int i = x - range + 1 ; i < x + range ; i++)
    {
        for(int j = y - range + 1 ; j < y + range ; j++)
        {
            PutPixel(i,j);
        }
    }

Hope this helps ... some new users ... sorry for necro-posting.
~Shmiggy

Shmiggy
  • 96
  • 5
  • Thanks, but please be aware that the sqrt() call is very expensive. The same goes for multiplication and division. – l33t Dec 13 '14 at 21:06
  • On x86 procs multiplication, division and sqrt are all single instructions. – Aaron Mar 31 '16 at 13:10
  • 1
    A single instruction does not necessarily mean high performance. There are clock cycles to consider, precision issues, platform differences... It makes no sense to use mathematically complex algorithms (e.g. square root approximation) when there is no need to. – l33t Nov 23 '17 at 08:44
2

I wanted to comment on your Update #2: This solution works: (but I guess I need more reputation first...) that there's a small bug in the solution, coincidentally when drawing small circles. If you set the radius to 1 you get

00000
00000
01110
00100
00000

To fix this all you need to do is change the conditional check in plot4points from

if (x != 0 && y != 0)

to

if (y != 0)

I've tested this on small and big circles to make sure each pixel is still assigned only once. Seems to work great. Me thinks the x != 0 was not needed. Save a tiny bit of performance too.

Steven Ackerman
  • 133
  • 1
  • 6
  • This makes sense! And it could actually imply a noticeable performance gain. Branching is usually crazy expensive on GPUs. – l33t Sep 12 '17 at 06:59
0

Update #2

 if (error >= 0)
 {
    if (x != lastY) 
       plot4points(buffer, cx, cy, lastY, x);

to

 if (error >= 0)
 {     
    plot4points(buffer, cx, cy, lastY, x);

Circle And FillCircle version:

Const
  Vypln13:Boolean=False;  // Fill Object


//Draw a circle at (cx,cy)
Procedure Circle(cx: integer; cy: integer; radius: integer );
Var
   error,x,y: integer;
Begin  
   error := -radius;
   x := radius;
   y := 0;

   while (x >= y) do
   Begin

     Draw4Pixel(cx,cy, x, y);
     if ( Not Vypln13 And ( x <> y) ) Then Draw4Pixel(cx,cy, y, x);

     error := error + y;
     y := y + 1;
     error := error + y;

     if (error >= 0) Then
     Begin

       if ( Vypln13) then Draw4Pixel(cx, cy, y - 1, x);

       error := error - x;
       x := x - 1;
       error := error - x;
     End;
   End;
End;


Procedure Draw4Pixel(cx,cy,dx,dy: integer);
Begin
  if ( (dx = 0) And (dy = 0) ) then
  begin
    PutPixel (cx , cy , Color13);
    exit;
  End;

  IF Vypln13 Then
  Begin
    HorizontLine (cx - dx,  cx + dx, cy + dy, Color13);
    if ( dy = 0 ) then exit;
    HorizontLine (cx - dx,  cx + dx, cy - dy, Color13);
    exit;
  end;

  PutPixel (cx + dx, cy + dy, Color13);
  if ( dx <> 0 ) then
  begin
    PutPixel (cx - dx, cy + dy, Color13);
    if ( dy = 0 ) then exit;
    PutPixel (cx + dx, cy - dy, Color13);
  End;
  PutPixel (cx - dx, cy - dy, Color13);

End;
user4906579
  • 1
  • 1
  • 1