You have already mentioned Bresenhams's circle algorithm. That is a good starting point: You could start with the centre pixel and then draw Bresenham circles of increasing size.
The problem is that the Bresenham circle algorithm will miss pixels near the diagonals in a kind of Moiré effect. In another question, I have adopted the Bresenham algorithm for drawing between an inner and outer circle. With that algorithm as base, the strategy of drawing circles in a loop works.
Because the Bresenham algorithm can place pixels only at discrete integer coordinates, the order of visiting pixels will not be strictly in order of increasing distance. But the distance will always be within one pixel of the current circle you are drawing.
An implementation is below. That's in C, but it only uses scalars, so it shouldn't be hard to adapt to C#. The setPixel
is what you do to each pixel when iterating.
void xLinePos(int x1, int x2, int y)
{
x1++;
while (x1 <= x2) setPixel(x1++, y);
}
void yLinePos(int x, int y1, int y2)
{
y1++;
while (y1 <= y2) setPixel(x, y1++);
}
void xLineNeg(int x1, int x2, int y)
{
x1--;
while (x1 >= x2) setPixel(x1--, y);
}
void yLineNeg(int x, int y1, int y2)
{
y1--;
while (y1 >= y2) setPixel(x, y1--);
}
void circle2(int xc, int yc, int inner, int outer)
{
int xo = outer;
int xi = inner;
int y = 0;
int erro = 1 - xo;
int erri = 1 - xi;
int patch = 0;
while (xo >= y) {
if (xi < y) {
xi = y;
patch = 1;
}
xLinePos(xc + xi, xc + xo, yc + y);
yLineNeg(xc + y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc - y);
yLinePos(xc - y, yc + xi, yc + xo);
if (y) {
yLinePos(xc + y, yc + xi, yc + xo);
xLinePos(xc + xi, xc + xo, yc - y);
yLineNeg(xc - y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc + y);
}
y++;
if (erro < 0) {
erro += 2 * y + 1;
} else {
xo--;
erro += 2 * (y - xo + 1);
}
if (y > inner) {
xi = y;
} else {
if (erri < 0) {
erri += 2 * y + 1;
} else {
xi--;
erri += 2 * (y - xi + 1);
}
}
}
if (patch) {
y--;
setPixel(xc + y, yc + y);
setPixel(xc + y, yc - y);
setPixel(xc - y, yc - y);
setPixel(xc - y, yc + y);
}
}
/*
* Scan pixels in circle in order of increasing distance
* from centre
*/
void scan(int xc, int yc, int r)
{
int i;
setPixel(xc, yc);
for (i = 0; i < r; i++) {
circle2(xc, yc, i, i + 1);
}
}
This code takes care of not visiting pixels that are in two octants by skipping coincident picels on alterante octants. (Edit: There was still abug in the original code, but it's fixed now by means of the ´patch` variable.)
There's also room for improvement: The inner circle is basically the outer circle of the previous iteration, so there's no point in calculating it twice; you could keep an array of the outer points of the previous circle.
The xLinePos
functions are also a bit too complicated. There are never more than two pixels drawn in that function, usually only one.
If the roughness of the search order bothers you, you can run a more exact algorithm once at the beginning of the program, where you calculate a traversing order for all circles up to a reasonable maximum radius. You can then keep that data and use it for iterating all circles with smaller radii.