First, please note, that this question is not a duplicate of these: 1st , 2nd , and 3rd.
I am using delphi and openCV, but I am looking for an algorithm, a solution regardless of the language.
For the purpose of a precise image analysis, I need to check for changes in pixel intensity in circular areas. So I read pixel values on a circumference of continuously growing circle. To be able to do that, I of course need to know coordinates of the pixels.
The best solution I found is y:= Round(centerY + radius * sin(angle)), x:= Round(centerX + radius * cos(angle)), while because counting with only 360 degrees is hardly enough, when the radius of the circle is larger, than circa 60px, the angle is being counted like this angle:= angle + (360 / (2 * 3.14 * currentRadius)) -> I sweep through every value from 0 to 360, while the value is being incremented by a fraction of 360/circumference of the circle in pixels. But this approach is not very precise. The larger the circle, the smaller the fraction of the angle needs to be and the precission suffers from the inaccuracy of Pi, plus the rounding.
If I use the mentioned method, and try to draw the counted pixels with this code:
centerX:= 1700;
centerY:= 1200;
maxRadius:= 500;
for currentRadius:= 80 to maxRadius do
begin
angle:= 0;
while angle < 360 do
begin
xI:= Round(centerX + currentRadius * cos(angle));
yI:= Round(centerY + currentRadius * sin(angle));
angle:= angle + (360 / (2 * 3.14 * currentRadius));
//this is openCV function, to test the code, you can use anything, that will draw a dot...
cvLine(image,cvPoint(xI,yI),cvPoint(xI,yI),CV_RGB(0, 255, 0));
end;
end;
the result is this:
It is not bad, but taking into account, that rougly a third of all pixels in the circular area are black, you realize, that a lot of pixels has been "skipped". Plus looking closely on the edge of the last circle, there is clearly visible, that some dots are off the actual circumference - another result of the inaccuracy...
I could possibly use a formula (x - xorig)^2 + (y - yorig)^2 = r^2 to check every possible pixel in a rectangular area around the center, slightly bigger, than a diameter of the circle, if it does, or does't fall onto the cirle's circumference. But that would be very slow to repeat it all the time, as the circle grows.
Is there something, that could be done better? Could anyone help me to improve this? I don't insist on anything from my solution at all, and will accept any other solution, as long as it gives the desired results => let me read values of all (or the vast majority - 95%+) pixels on a circumference of a circle with given center and radius. The faster, the better...
1) Build a list of pixels of the smallest radius circumference. It is enough to
keep the first octant (range 0..Pi/4 in the 1st quadrant of coordinate system) of circle, and get symmetric points with reflections.
You can use, for example, Bresenham circle algorithm or just circle equation.
2) For the next iteration walk through all coordinates in the list (use right one, if there are two points with the same Y value) and check whether right neighbor (or two neighbors!) lies inside the next radius. For the last point check also top, right-top neighbor (at Pi/4 diagonal).
Insert good neighbors (one or two) into the next coordinate list.
Example for Y=5.
R=8 X=5,6 //note that (5,5) point is not inside r=7 circle
R=9 X=7
R=10 X=8
R=11 X=9
R=12 X=10
R=13 X=11,12 //!
R=14 X=13
With this approach you will use all the pixels in maximal radius circle without gaps, and checking process for list generation is rather fast.
Edit:
Code implements slightly another approach, it uses lower line pixel limit to built upper line.
It generates circles in given range, paints them to psychedelic colors. All math is in integers, no floats, no trigonometric functions! Pixels are used only for demonstration purposes.
procedure TForm1.Button16Click(Sender: TObject);
procedure FillCircles(CX, CY, RMin, RMax: Integer);
//control painting, slow due to Pixels using
procedure PaintPixels(XX, YY, rad: Integer);
var
Color: TColor;
r, g, B: Byte;
begin
g := (rad mod 16) * 16;
r := (rad mod 7) * 42;
B := (rad mod 11) * 25;
Color := RGB(r, g, B);
// Memo1.Lines.Add(Format('%d %d %d', [rad, XX, YY]));
Canvas.Pixels[CX + XX, CY + YY] := Color;
Canvas.Pixels[CX - YY, CY + XX] := Color;
Canvas.Pixels[CX - XX, CY - YY] := Color;
Canvas.Pixels[CX + YY, CY - XX] := Color;
if XX <> YY then begin
Canvas.Pixels[CX + YY, CY + XX] := Color;
Canvas.Pixels[CX - XX, CY + YY] := Color;
Canvas.Pixels[CX - YY, CY - XX] := Color;
Canvas.Pixels[CX + XX, CY - YY] := Color;
end;
end;
var
Pts: array of array [0 .. 1] of Integer;
iR, iY, SqD, SqrLast, SqrCurr, MX, LX, cnt: Integer;
begin
SetLength(Pts, RMax);
for iR := RMin to RMax do begin
SqrLast := Sqr(iR - 1) + 1;
SqrCurr := Sqr(iR);
LX := iR; // the most left X to check
for iY := 0 to RMax do begin
cnt := 0;
Pts[iY, 1] := 0; // no second point at this Y-line
for MX := LX to LX + 1 do begin
SqD := MX * MX + iY * iY;
if InRange(SqD, SqrLast, SqrCurr) then begin
Pts[iY, cnt] := MX;
Inc(cnt);
end;
end;
PaintPixels(Pts[iY, 0], iY, iR);
if cnt = 2 then
PaintPixels(Pts[iY, 1], iY, iR);
LX := Pts[iY, 0] - 1; // update left limit
if LX < iY then // angle Pi/4 is reached
Break;
end;
end;
// here Pts contains all point coordinates for current iR radius
//if list is not needed, remove Pts, just use PaintPixels-like output
end;
begin
FillCircles(100, 100, 10, 100);
//enlarge your first quadrant to check for missed points
StretchBlt(Canvas.Handle, 0, 200, 800, 800, Canvas.Handle, 100, 100, 100,
100, SRCCOPY);
end;
If you want to make your code faster, don't call trigonometric functions inside the inner loop, increment sin(angle) and cos(angle) using
sin(n*step)=sin((n-1)*step)*cos(step)+sin(step)*cos((n-1)*step)
cos(n*step)=cos((n-1)*step)*cos(step)-sin(step)*sin((n-1)*step)
that is
...
for currentRadius:= 80 to maxRadius do
begin
sinangle:= 0;
cosangle:= 1;
step:= 1 / currentRadius; // ?
sinstep:= sin(step);
cosstep:= cos(step);
while {? } do
begin
xI:= Round(centerX + currentRadius * cosangle);
yI:= Round(centerY + currentRadius * sinangle);
newsin:= sinangle*cosstep + sinstep*cosangle;
newcos:= cosangle*cosstep - sinstep*sinangle;
sinangle:= newsin;
cosangle:= newcos;
...
end;
end;
First of all: you want all the points on a circle circumference. If you use any (good) algorithm, also some built-in circle function, you get indeed all the points, since the circumference is connected.
What your picture shows, there are holes between neighbour circles, say r=100 and r=101. This is so for circumference drawing functions.
Now if you want that the pixels in your pixel set to cover all the pixels with incrementing radii, you can simply use following approach:
Build a set of filled circle pixels, say r = 101
Build a set of filled circle pixel with r = 100
Exclude set 2 from set 1
Filled circle algorithm is generally more efficient and simpler than connected circumference so you'll not loose much performance.
So you get a circumference which is slightly thicker than 1 px, but this set will surely cover the surface with growing radii without any holes. But it can also happen that the set built in such a way has overlapping pixels with previous set (r-1), so you'd know it better if you test it.
PS: Also it is not clear how any trigonometric functions appear in your code. I don't know any effective circle algorithm which use anything other than square root.
Why don't you simply use more digits for Pi and stop rounding to improve accuracy?
Further I suggest you use subpixel coordinates to get more accurate intensity values if you can afford the interpolation.
It's also very uncommon to use degrees in calculations. I highly recommend using radians. Not sure which functions you use here but Delphi's cos and sin seem to expect radians!
Related
In a 2D grid, we start from the origin (0, 0) and then we can move by one cell at the time with either (x+1, x-1, y+1, y-1).
I have to find the largest area with this constraint: for any point in the area, the sum of the digits of abs(x) plus the sum of the digits of abs(y) should be at most 23.
For example, the point (59,75) isn't valid because 5 + 9 + 7 + 5 = 26.
The point (-51, -7) is valid because 5 + 1 + 7 = 13, which is less than 23.
What could be a way to solve this with great time-complexity ?
Look at this picture. It shows as white those points with digits sum < 23 in the first quadrant reachable from coordinate origin (edit: needed <=23).
Seems it is not hard to make breadth-first search like flood-fill with special border condition sumdigit <=23 (coordinate limit is 699) and count them all.
White filling spreads from origin until border value is met. This process resembles water flooding with level 23. Black islands stay in white sea and solid black border limits it. Count of white pixels is area of continuous region around origin.
.
Moreover, it is possible to determine count of white points in every 100x100 square depending on its coordinates and get mathematical formula.
Scaled fragment:
Primitive implementation in Delphi (there are effective non-recursive Floodfill implementations) gives white pixel count 592597
var
mark: TDictionary<Integer, Integer>;
function digitsum(x, y: integer): integer;
begin
if mark.ContainsKey((x + 1000) * 2000 + y + 1000) then
Exit(9999);
Result := 0;
x := abs(x);
y := abs(y);
while y > 0 do begin
Result := Result + y mod 10;
y := y div 10;
end;
while x > 0 do begin
Result := Result + x mod 10;
x := x div 10;
end;
end;
function flood(x, y: integer): integer;
begin
if digitsum(x,y) > 23 then
Exit(0);
Result := 1;
mark.Add((x + 1000) * 2000 + y + 1000, 0);
Canvas.Pixels[x, y] := clWhite;
Inc(Result, flood(x + 1, y));
Inc(Result, flood(x - 1, y));
Inc(Result, flood(x, y - 1));
Inc(Result, flood(x, y + 1));
end;
begin
Canvas.Brush.Color := clBlack;
Canvas.FillRect(ClientRect);
mark:= TDictionary<Integer, Integer>.Create;
Caption := flood(0, 0).ToString;
I'm using processing, and I'm trying to create a circle from the pixels i have on my display.
I managed to pull the pixels on screen and create a growing circle from them.
However i'm looking for something much more sophisticated, I want to make it seem as if the pixels on the display are moving from their current location and forming a turning circle or something like this.
This is what i have for now:
int c = 0;
int radius = 30;
allPixels = removeBlackP();
void draw {
loadPixels();
for (int alpha = 0; alpha < 360; alpha++)
{
float xf = 350 + radius*cos(alpha);
float yf = 350 + radius*sin(alpha);
int x = (int) xf;
int y = (int) yf;
if (radius > 200) {radius =30;break;}
if (c> allPixels.length) {c= 0;}
pixels[y*700 +x] = allPixels[c];
updatePixels();
}
radius++;
c++;
}
the function removeBlackP return an array with all the pixels except for the black ones.
This code works for me. There is an issue that the circle only has the numbers as int so it seems like some pixels inside the circle won't fill, i can live with that. I'm looking for something a bit more complex like I explained.
Thanks!
Fill all pixels of scanlines belonging to the circle. Using this approach, you will paint all places inside the circle. For every line calculate start coordinate (end one is symmetric). Pseudocode:
for y = center_y - radius; y <= center_y + radius; y++
dx = Sqrt(radius * radius - y * y)
for x = center_x - dx; x <= center_x + dx; x++
fill a[y, x]
When you find places for all pixels, you can make correlation between initial pixels places and calculated ones and move them step-by-step.
For example, if initial coordinates relative to center point for k-th pixel are (x0, y0) and final coordinates are (x1,y1), and you want to make M steps, moving pixel by spiral, calculate intermediate coordinates:
calc values once:
r0 = Sqrt(x0*x0 + y0*y0) //Math.Hypot if available
r1 = Sqrt(x1*x1 + y1*y1)
fi0 = Math.Atan2(y0, x0)
fi1 = Math.Atan2(y1, x1)
if fi1 < fi0 then
fi1 = fi1 + 2 * Pi;
for i = 1; i <=M ; i++
x = (r0 + i / M * (r1 - r0)) * Cos(fi0 + i / M * (fi1 - fi0))
y = (r0 + i / M * (r1 - r0)) * Sin(fi0 + i / M * (fi1 - fi0))
shift by center coordinates
The way you go about drawing circles in Processing looks a little convoluted.
The simplest way is to use the ellipse() function, no pixels involved though:
If you do need to draw an ellipse and use pixels, you can make use of PGraphics which is similar to using a separate buffer/"layer" to draw into using Processing drawing commands but it also has pixels[] you can access.
Let's say you want to draw a low-res pixel circle circle, you can create a small PGraphics, disable smoothing, draw the circle, then render the circle at a higher resolution. The only catch is these drawing commands must be placed within beginDraw()/endDraw() calls:
PGraphics buffer;
void setup(){
//disable sketch's aliasing
noSmooth();
buffer = createGraphics(25,25);
buffer.beginDraw();
//disable buffer's aliasing
buffer.noSmooth();
buffer.noFill();
buffer.stroke(255);
buffer.endDraw();
}
void draw(){
background(255);
//draw small circle
float circleSize = map(sin(frameCount * .01),-1.0,1.0,0.0,20.0);
buffer.beginDraw();
buffer.background(0);
buffer.ellipse(buffer.width / 2,buffer.height / 2, circleSize,circleSize);
buffer.endDraw();
//render small circle at higher resolution (blocky - no aliasing)
image(buffer,0,0,width,height);
}
If you want to manually draw a circle using pixels[] you are on the right using the polar to cartesian conversion formula (x = cos(angle) * radius, y = sin(angle) * radius).Even though it's focusing on drawing a radial gradient, you can find an example of drawing a circle(a lot actually) using pixels in this answer
everyone
I've been trying to solve the following problem in Delphi.
I want to make take an image and to make just one antialiased round corner.
I know how to make all 4 corners round by using RoundRect. However, I don't seem to figure out how to make just one.
I've been trying to solve the problem like this:
procedure RoundCorner(var image: TBitmap; w,h : integer);
//w - width of an image
//h - height of an image
//radius can be set at 150 (rounded rect radius)
// image is the timage object received as var parameter
var
i, j :integer;
x, y :double;
begin
i:= w - Trunc(radius);
x:= 0;
y:= radius - sqrt(sqr(radius) - sqr(x));
while(i < w) do
begin
j:=0;
while(j <= y) do
begin
image.Canvas.Pixels[i-1,j]:=clWhite; //a colour of your choosing or just erase this line
j := j + 1;
end;
y:= radius - sqrt(sqr(radius) - sqr(x));
x := x + 1;
i := i + 1;
end;
end;
This works, however I'm faced with 2 problems:
The corner is not antialiased
I want to fill the cut region with a different colour
Any suggestions are welcomed
Thanks.
First of all in your case "antialiasing" does mean that your image have to have a 32 bit pixel format in order to make a round transparent corner. Second thing is that you cannot access alpha channel via Canvas, you would need to access bitmap pixels directly via Scanline property or maybe to use a library like Graphics32 or AGG.
Another "modern" way to implement round corner is to use FMX and put the image inside a roundrect and clip it by the roundrect.
Can you suggest an algorithm that can draw a sphere in 3D space using only the basic plot(x,y,z) primitive (which would draw a single voxel)?
I was hoping for something similar to Bresenham's circle algorithm, but for 3D instead of 2D.
FYI, I'm working on a hardware project that is a low-res 3D display using a 3-dimensional matrix of LEDs, so I need to actually draw a sphere, not just a 2D projection (i.e. circle).
The project is very similar to this:
... or see it in action here.
One possibility I have in mind is this:
calculate the Y coordinates of the poles (given the radius) (for a sphere centered in the origin, these would be -r and +r)
slice the sphere: for each horizontal plane pi between these coordinates, calculate the radius of the circle obtained by intersecting said plane with the sphere => ri.
draw the actual circle of radius ri on plane pi using Bresenham's algorithm.
FWIW, I'm using a .NET micro-framework microprocessor, so programming is C#, but I don't need answers to be in C#.
The simple, brute force method is to loop over every voxel in the grid and calculate its distance from the sphere center. Then color the voxel if its distance is less than the sphere radius. You can save a lot of instructions by eliminating the square root and comparing the dot product to the radius squared.
Pretty far from optimal, sure. But on an 8x8x8 grid as shown, you'll need to do this operation 512 times per sphere. If the sphere center is on the grid, and its radius is an integer, you only need integer math. The dot product is 3 multiplies and 2 adds. Multiplies are slow; let's say they take 4 instructions each. Plus you need a comparison. Add in the loads and stores, let's say it costs 20 instructions per voxel. That's 10240 instructions per sphere.
An Arduino running at 16 MHz could push 1562 spheres per second. Unless you're doing tons of other math and I/O, this algorithm should be good enough.
I don't believe running the midpoint circle algorithm on each layer will give the desired results once you reach the poles, as you will have gaps in the surface where LEDs are not lit. This may give the result you want, however, so that would be up to aesthetics. This post is based on using the midpoint circle algorithm to determine the radius of the layers through the middle two vertical octants, and then when drawing each of those circles also setting the points for the polar octants.
I think based on #Nick Udall's comment and answer here using the circle algorithm to determine radius of your horizontal slice will work with a modification I proposed in a comment on his answer. The circle algorithm should be modified to take as an input an initial error, and also draw the additional points for the polar octants.
Draw the standard circle algorithm points at y0 + y1 and y0 - y1: x0 +/- x, z0 +/- z, y0 +/- y1, x0 +/- z, z0 +/- x, y0 +/- y1, total 16 points. This forms the bulk of the vertical of the sphere.
Additionally draw the points x0 +/- y1, z0 +/- x, y0 +/- z and x0 +/- x, z0 +/- y1, y0 +/- z, total 16 points, which will form the polar caps for the sphere.
By passing the outer algorithm's error into the circle algorithm, it will allow for sub-voxel adjustment of each layer's circle. Without passing the error into the inner algorithm, the equator of the circle will be approximated to a cylinder, and each approximated sphere face on the x, y, and z axes will form a square. With the error included, each face given a large enough radius will be approximated as a filled circle.
The following code is modified from Wikipedia's Midpoint circle algorithm. The DrawCircle algorithm has the nomenclature changed to operate in the xz-plane, addition of the third initial point y0, the y offset y1, and initial error error0. DrawSphere was modified from the same function to take the third initial point y0 and calls DrawCircle rather than DrawPixel
public static void DrawCircle(int x0, int y0, int z0, int y1, int radius, int error0)
{
int x = radius, z = 0;
int radiusError = error0; // Initial error state passed in, NOT 1-x
while(x >= z)
{
// draw the 32 points here.
z++;
if(radiusError<0)
{
radiusError+=2*z+1;
}
else
{
x--;
radiusError+=2*(z-x+1);
}
}
}
public static void DrawSphere(int x0, int y0, int z0, int radius)
{
int x = radius, y = 0;
int radiusError = 1-x;
while(x >= y)
{
// pass in base point (x0,y0,z0), this algorithm's y as y1,
// this algorithm's x as the radius, and pass along radius error.
DrawCircle(x0, y0, z0, y, x, radiusError);
y++;
if(radiusError<0)
{
radiusError+=2*y+1;
}
else
{
x--;
radiusError+=2*(y-x+1);
}
}
}
For a sphere of radius 4 (which actually requires 9x9x9), this would run three iterations of the DrawCircle routine, with the first drawing a typical radius 4 circle (three steps), the second drawing a radius 4 circle with initial error of 0 (also three steps), and then the third drawing a radius 3 circle with initial error 0 (also three steps). That ends up being nine calculated points, drawing 32 pixels each.
That makes 32 (points per circle) x 3 (add or subtract operations per point) + 6 (add, subtract, shift operations per iteration) = 102 add, subtract, or shift operations per calculated point. In this example, that's 3 points for each circle = 306 operations per layer. The radius algorithm also adds 6 operations per layer and iterates 3 times, so 306 + 6 * 3 = 936 basic arithmetic operations for the example radius of 4.
The cost here is that you will repeatedly set some pixels without additional condition checks (i.e. x = 0, y = 0, or z = 0), so if your I/O is slow you may be better off adding the condition checks. Assuming all LEDs were cleared at the start, the example circle would set 288 LEDs, while there are many fewer LEDs that would actually be lit due to repeat sets.
It looks like this would perform better than the bruteforce method for all spheres that would fit in the 8x8x8 grid, but the bruteforce method would have consistent timing regardless of radius, while this method will slow down when drawing large radius spheres where only part will be displayed. As the display cube increases in resolution, however, this algorithm timing will stay consistent while bruteforce will increase.
Assuming that you already have a plot function like you said:
public static void DrawSphere(double r, int lats, int longs)
{
int i, j;
for (i = 0; i <= lats; i++)
{
double lat0 = Math.PI * (-0.5 + (double)(i - 1) / lats);
double z0 = Math.Sin(lat0) * r;
double zr0 = Math.Cos(lat0) * r;
double lat1 = Math.PI * (-0.5 + (double)i / lats);
double z1 = Math.Sin(lat1) * r;
double zr1 = Math.Cos(lat1) * r;
for (j = 0; j <= longs; j++)
{
double lng = 2 * Math.PI * (double)(j - 1) / longs;
double x = Math.Cos(lng);
double y = Math.Sin(lng);
plot(x * zr0, y * zr0, z0);
plot(x * zr1, y * zr1, z1);
}
}
}
That function should plot a sphere at the origin with specified latitude and longitude resolution (judging by your cube you probably want something around 40 or 50 as a rough guess). This algorithm doesn't "fill" the sphere though, so it will only provide an outline, but playing with the radius should let you fill the interior, probably with decreasing resolution of the lats and longs along the way.
Just found an old q&a about generating a Sphere Mesh, but the top answer actually gives you a short piece of pseudo-code to generate your X, Y and Z :
(x, y, z) = (sin(Pi * m/M) cos(2Pi * n/N), sin(Pi * m/M) sin(2Pi * n/N), cos(Pi * m/M))
Check this Q&A for details :)
procedurally generate a sphere mesh
My solution uses floating point math instead of integer math not ideal but it works.
private static void DrawSphere(float radius, int posX, int poxY, int posZ)
{
// determines how far apart the pixels are
float density = 1;
for (float i = 0; i < 90; i += density)
{
float x1 = radius * Math.Cos(i * Math.PI / 180);
float y1 = radius * Math.Sin(i * Math.PI / 180);
for (float j = 0; j < 45; j += density)
{
float x2 = x1 * Math.Cos(j * Math.PI / 180);
float y2 = x1 * Math.Sin(j * Math.PI / 180);
int x = (int)Math.Round(x2) + posX;
int y = (int)Math.Round(y1) + posY;
int z = (int)Math.Round(y2) + posZ;
DrawPixel(x, y, z);
DrawPixel(x, y, -z);
DrawPixel(-x, y, z);
DrawPixel(-x, y, -z);
DrawPixel(z, y, x);
DrawPixel(z, y, -x);
DrawPixel(-z, y, x);
DrawPixel(-z, y, -x);
DrawPixel(x, -y, z);
DrawPixel(x, -y, -z);
DrawPixel(-x, -y, z);
DrawPixel(-x, -y, -z);
DrawPixel(z, -y, x);
DrawPixel(z, -y, -x);
DrawPixel(-z, -y, x);
DrawPixel(-z, -y, -x);
}
}
}
I have been looking all over the Web for a way to plot an ellipse from rectangle coordinates, that is, top-left corner (x, y) and size (width and height). The only ones I can find everywhere are based on the Midpoint/Bresenham algorithm and I can't use that because when working with integer pixels, I lose precisions because these algorithms use a center point and radials.
The ellipse MUST be limited to the rectangle's coordinates, so if I feed it a rectangle where the width and height are 4 (or any even number), I should get an ellipse that completely fits in a 4x4 rectangle, and not one that will be 5x5 (like what those algorithms are giving me).
Does anyone know of any way to accomplish this?
Thanks!
Can you not get the width and height (divided by 2) and center of the rectangle then plug that into any ellipse drawing routine as its major, minor axis and center? I guess I'm not seeing the problem all the way here.
I had the same need. Here is my solution with code. The error is at most half a pixel.
I based my solution on the McIlroy ellipse algorithm, an integer-only algorithm which McIlroy mathematically proved to be accurate to a half-pixel, without missing or extra points, and correctly drawing degenerate cases such as lines and circles. L. Patrick further analyzed McIlroy's algorithm, including ways to optimize it and how a filled ellipse can be broken up into rectangles.
McIlroy's algorithm traces a path through one quadrant of the ellipse; the remaining quadrants are rendered through symmetry. Each step in the path requires three comparisons. Many of the other ellipse algorithms use octants instead, which require only two comparisons per step. However, octant-based methods have are notoriously inaccurate at the octant boundaries. The slight savings of one comparison is not worth the inaccuracy of the octant methods.
Like virtually every other integer ellipse algorithm, McIlroy's wants the center at integer coordinates, and the lengths of the axes a and b to be integers as well. However, we want to be able to draw an ellipse with a bounding box using any integer coordinates. A bounding box with an even width or even height will have a center on an integer-and-a-half coordinate, and a or b will be an integer-and-a-half.
My solution was to perform calculations using integers that are double of what is needed. Any variable starting with q is calculated from double pixel values. An even q variable is on an integer coordinate, and an odd q variable is at an integer-and-a-half coordinate. I then re-worked McIroy's math to get the correct mathematical expressions with these new doubled values. This includes modifying starting values when the bounding box has even width or height.
Behold, the subroutine/method drawEllipse given below. You provide it with the integer coordinates (x0,y0) and (x1,y1) of the bounding box. It doesn't care if x0 < x1 versus x0 > x1; it will swap them as needed. If you provide x0 == x1, your will get a vertical line. Similarly for the y0 and y1 coordinates. You also provide the boolean fill parameter, which draws only the ellipse outline if false, and draws a filled ellipse if true. You also have to provide the subroutines drawPoint(x, y) which draws a single point and drawRow(xleft, xright, y) which draws a horizontal line from xleft to xright inclusively.
McIlroy and Patrick optimize their code to fold constants, reuse common subexpressions, etc. For clarity, I didn't do that. Most compilers do this automatically today anyway.
void drawEllipse(int x0, int y0, int x1, int y1, boolean fill)
{
int xb, yb, xc, yc;
// Calculate height
yb = yc = (y0 + y1) / 2;
int qb = (y0 < y1) ? (y1 - y0) : (y0 - y1);
int qy = qb;
int dy = qb / 2;
if (qb % 2 != 0)
// Bounding box has even pixel height
yc++;
// Calculate width
xb = xc = (x0 + x1) / 2;
int qa = (x0 < x1) ? (x1 - x0) : (x0 - x1);
int qx = qa % 2;
int dx = 0;
long qt = (long)qa*qa + (long)qb*qb -2L*qa*qa*qb;
if (qx != 0) {
// Bounding box has even pixel width
xc++;
qt += 3L*qb*qb;
}
// Start at (dx, dy) = (0, b) and iterate until (a, 0) is reached
while (qy >= 0 && qx <= qa) {
// Draw the new points
if (!fill) {
drawPoint(xb-dx, yb-dy);
if (dx != 0 || xb != xc) {
drawPoint(xc+dx, yb-dy);
if (dy != 0 || yb != yc)
drawPoint(xc+dx, yc+dy);
}
if (dy != 0 || yb != yc)
drawPoint(xb-dx, yc+dy);
}
// If a (+1, 0) step stays inside the ellipse, do it
if (qt + 2L*qb*qb*qx + 3L*qb*qb <= 0L ||
qt + 2L*qa*qa*qy - (long)qa*qa <= 0L) {
qt += 8L*qb*qb + 4L*qb*qb*qx;
dx++;
qx += 2;
// If a (0, -1) step stays outside the ellipse, do it
} else if (qt - 2L*qa*qa*qy + 3L*qa*qa > 0L) {
if (fill) {
drawRow(xb-dx, xc+dx, yc+dy);
if (dy != 0 || yb != yc)
drawRow(xb-dx, xc+dx, yb-dy);
}
qt += 8L*qa*qa - 4L*qa*qa*qy;
dy--;
qy -= 2;
// Else step (+1, -1)
} else {
if (fill) {
drawRow(xb-dx, xc+dx, yc+dy);
if (dy != 0 || yb != yc)
drawRow(xb-dx, xc+dx, yb-dy);
}
qt += 8L*qb*qb + 4L*qb*qb*qx + 8L*qa*qa - 4L*qa*qa*qy;
dx++;
qx += 2;
dy--;
qy -= 2;
}
} // End of while loop
return;
}
The image above shows the output for all bounding boxes up to size 10x10. I also ran the algorithm for all ellipses up to size 100x100. This produced 384614 points in the first quadrant. The error between where each of these points were plotted and where the actual ellipse occurs was calculated. The maximum error was 0.500000 (half a pixel) and the average error among all of the points was 0.216597.
The solution I found to this problem was to draw the closest smaller ellipse with odd dimensions, but pulled apart by a pixel along the even length dimension, repeating the middle pixels.
This can be done easily by using different middle points for the quadrants when plotting each pixel:
DrawPixel(midX_high + x, midY_high + y);
DrawPixel(midX_low - x, midY_high + y);
DrawPixel(midX_high + x, midY_low - y);
DrawPixel(midX_low - x, midY_low - y);
The high values are the ceil'ed midpoint, and the low values are the floored midpoint.
An image to illustrate, ellipses with width 15 and 16: