2
votes

In a project of mine (VC++2010, MFC), I want to draw a circle using the CDC::Ellipse. I set two points: the first one is the center of the circle, the second one is a point I want it to be on the circumference.

I pass to the CDC::Ellipse( int x1, int y1, int x2, int y2 ) the coordinates of the upper-left corner and lower-right one.

Briefly: with Pitagora Theorem I calculate the distance between the two points ( radius ), then I subtract this value from the coordinates of the center to obtain the upper-left corner and add to obtain the lower-right one.

When I draw the cirlce and the points, and I zoom in, I see that the second one isn't on the circumference as expected, it is slightly inside unless you set it at 0°, 45°, 90° and so on with respect to the absolute sistem of coordinates.

Then I tried to draw the same circle using CDC::Polyline, I gave to this method the points obtained rotating another point around the center, at the distance equal to the radius. In this case the point is on the circumference every where I set it.

The overlap of these two circles has shown that they perfectly overlap at 0°, 45°, 90° and so on, but the gap is maximum at 22.5°, 67.5° and so on.

Has anyone ever noticed a similar behavior?

Thanks to everybody that can help me!

Code snippet:

this is how I calculate the radius given 2 points:

centerPX = vvFPoint( 1380, 845 );
secondPointPX = vvFPoint( 654,654 );
double radiusPX = (sqrt( (secondPointPX.x - centerPX.x) * (secondPointPX.x - centerPX.x) + (secondPointPX.y - centerPX.y) * (secondPointPX.y - centerPX.y) ));

( vvFPoint is a custom type derived from CPoint )

this is how I draw the "circle" with the CDC::Ellipse:

int up = (int)(((double)(m_p1.y-(double)originY - m_radius) / zoom) + 0.5) + offY;
int left = (int)(((double)(m_p1.x-(double)originX - m_radius) / zoom) + 0.5) + offX;
int down = (int)(((double)(m_p1.y-(double)originY + m_radius) / zoom) + 0.5) + offY;
int right = (int)(((double)(m_p1.x-(double)originX + m_radius) / zoom) + 0.5) + offX;
pDC->Ellipse( left, up, right, down);

(m_p1 is the center of the circle, originX/Y is the origin of the image, m_radius is the radius of the circle, zoom is the scale factor, offX/Y is an offset in the client area of my SW)

this is how I draw the circle "manually" (and quite trivial method) using a custom polyline class:

1) create the array of points:

point.x = centerPX.x + radiusPX;
point.y = centerPX.y;
for ( i=0; i < 3600; i++ )
{
    pt1.RotateDeg ( centerPX, (double)0.1 );
    poly->AddPoint( pt1 );
}

(RotateDeg is a custom method to rotate a point using first argument as a pivot and second argument as angle value in degrees, AddPoint is a custom method to create the array of points, poly is my custom polyline object).

2) draw it:

When I call the Draw( CDC* pDC ) I use the previous array to draw the polyline:

pDC->MoveTo(p);

I hope this can help you to reproduce my weird observations!

code snippet 2:

void vvPoint<Tipo>::RotateDeg(const vvPoint<Tipo> &center, double angle)
{
vvPoint<Tipo> ptB;

angle *= -(M_PI / 180);

*this -= center;
ptB.x = ((this->x * cos(angle)) - (this->y * sin(angle)));
ptB.y = ((this->x * sin(angle)) + (this->y * cos(angle)));
*this = ptB + center;
}

But to let you better understand my observations I would like to add a few images so you can see where my whole question started from... The problem is: I can't add images since I need to have 10 reputation. I uploaded a .zip file on dropbox and if you want I can send you the URL of this file. Let me know if this is the correct (and safe..) way to bypass this problem.

Thanks!

1

1 Answers

2
votes

This might be a possible explanation. As MSDN says about CDC::Ellipse (with my emphasis):

The center of the ellipse is the center of the bounding rectangle specified by x1, y1, x2, and y2, or lpRect. The ellipse is drawn with the current pen, and its interior is filled with the current brush.

The figure drawn by this function extends up to, but does not include, the right and bottom coordinates. This means that the height of the figure is y2 – y1 and the width of the figure is x2 – x1.

The way you described how you calculate the bounding rectangle is not entirely clear (some source code would have helped) but, given the second paragraph quoted above, you possibly need to add 1 to your x2 and y2 values, to make sure you have a circle with the desired radius.

It's also worth noting that there may be slight rounding differences between your two drawing methods where you have an odd-sized bounding box (i.e. so the centre point falls logically on a half-pixel).

UPDATE

Using your code snippets (thanks), and assuming no zoom and zero offsets etc., I get a radius of 750.704 pixels and the following parameters for the ellipse:

pDC->Ellipse(629, 94, 2131, 1596);

According to MSDN, this means that the ellipse will be drawn in a figure of the following dimensions:

width  = (2131 - 629) = 1502
height = (1596 -  94) = 1502

So as far as I can see, this should produce a circle rather than an ellipse.

The next thing to do is to find out how you're drawing the polygon - for that we need to see the implementation of RotateDeg - can you post that code? I'm suspecting some simple rounding error here, that maybe gets magnified when you zoom.

UPDATE 2

Just looking at this code:

for ( i=0; i < 3600; i++ )
{
    pt1.RotateDeg ( centerPX, (double)0.1 );
    poly->AddPoint( pt1 );
}

You are rotating your polygon points incrementally by 0.1 degrees each time. This will possibly accumulate some errors, so it may be worth doing it something like this instead:

for ( i=0; i < 3600; i++ )
{
    vvFPoint ptNew = pt1;
    ptNew.RotateDeg ( centerPX, (double)i * 0.1 );
    poly->AddPoint( ptNew );
}

Maybe this will mean you have to change your RotateDeg function to take care of the correct quadrants.

One other point, you mentioned that you see the problem when you zoom into the image. If this means you are using you zoom variable, it is worth checking in this line ...:

pDC->Ellipse( left, up, right, down);

... that the parameters still form a square shape, so (right - left) == (down - up).

UPDATE 3

I just ran your RotateDeg function, in its current form, to see how the error accumulates (by feeding in the previous result to the next iteration). At each step, I calculated the distance between the new point and the centre and compared this with the required radius.

The chart below shows the result, where you can see an error of 4 pixels by the time the points have been calculated.

errors

I think that this at least explains part of the difference (i.e. your polygon drawing is flawed) and - depending on zoom - you may introduce asymmetry into the ellipse parameters, which you can debug by comparing the width to the height as I described above.