0

I am struggling with a situation, where I want to draw a circle with GDI+ and some points on it (drawn as smaller circles), but the circle seems to be noncircular. By implementing scaling and zero point shifting, I zoom into the points and the circle, and find the points not lying exactly on the circle.

When I add a 'discrete' circle drawn with line segments, this circle does fit the points very well, if enough segments are used. Since the math is the same, I think that roundoff errors in my code can not be the cause of the circle deviations (although probably in the implementation of DrawEllipse).

In fact the deviations are biggest on 45/135/225/315 degrees.

I made a small project reproducing the effect with a slightly different setup: I draw multiple circles with their origins lying on an other circle with its center on the center of the form with the same radius. If everything goes well all circles shoud touch the center of the form. But with big radii like 100'000, they dont pass throug the center anymore, but miss it by a screendistance of maybe 15 pixel.

To reproduce the situation make a new c# project, put on form1 button1, and call the function Draw() from it:

private void Draw()
{
    Graphics g = this.CreateGraphics();
    g.Clear(this.BackColor );

    double hw = this.Width / 2;
    double hh = this.Height / 2;
    double scale = 100000;

    double R = 1;

    for (int i = 0; i < 12; i++)
    {
        double angle = i * 30;

        double cx = R * Math.Cos(angle * Math.PI / 180);
        double cy = R * Math.Sin(angle * Math.PI / 180);

        g.DrawEllipse(new Pen(Color.Blue, 1f), (float)(hw - scale * (cx + R)), (float)(hh + scale * (cy - R)), (float)(2 * R * scale), (float)(2 * R * scale));

        g.DrawLine(Pens.Black, new Point(0, (int)hh), new Point(this.Width, (int)hh));
        g.DrawLine(Pens.Black, new Point((int)hw, 0), new Point((int)hw, this.Height));

        double r = 3;

        g.DrawEllipse(new Pen(Color.Green, 1f), (float)(hw - r), (float)(hh - r), (float)(2 * r), (float)(2 * r));

        //Pen magpen = new Pen(Color.Magenta, 1);
        //double n = 360d / 1000;
        //for (double j = 0; j < 360; j += n)
        //{
        //    double p1x = R * Math.Cos(j * Math.PI / 180) + cx;
        //    double p1y = R * Math.Sin(j * Math.PI / 180) + cy;
        //    double p2x = R * Math.Cos((j + n) * Math.PI / 180) + cx;
        //    double p2y = R * Math.Sin((j + n) * Math.PI / 180) + cy;

        //    g.DrawLine(magpen, (float)(hw - scale * p1x), (float)(hh + scale * p1y), (float)(hw - scale * p2x), (float)(hh + scale * p2y));
        //}
    }            
}

use the variable scale from 100 to 100'000 to see the effect: The circles don't touch the origin anymore, but wobble around it. If you uncomment the commented lines you can see, that the magenta 'discrete' circle performs much better.

Since using 'discrete' circles and arcs is a performance killer, I am looking for a way to draw better circles with GDI+.

Any ideas, suggestions?

Vivek Mahto
  • 194
  • 1
  • 17
man_msu
  • 1
  • 1
  • `DrawEllipse` should be fast, so it's not the right method to draw *perfect circles*. If you want anti-aliasing (which is typically used to smooth edges), then see [this](http://stackoverflow.com/q/5569215/1997232). – Sinatr Mar 05 '15 at 15:16
  • Not sure if this is related, but besides the obvious (`e.Graphics.SmoothingMode = ...someGoodMode;`) I wonder why you draw the circle with `floats` but the lines with `ints`? Maybe the lines are off? – TaW Mar 05 '15 at 18:23

2 Answers2

2

GDI+ (at least in its native version) doesn't draw ellipses: it uses the cubic Bezier approximation, as described here and here.

That is, instead of

   c = cos(angle);
   s = sin(angle);
   x = c * radius;
   y = s * radius;

or, for an ellipse (a & b as semi major and semi minor axes in some order)

   x = c * a;
   y = s * b;

it uses

   K = (sqrt(2) - 1) * 4.0 / 3;
   t = angle * 0.5 / pi;
   u = 1 - t;
   c = (u * u * u) * 1 + (3 * u * u * t) * 1 + (3 * u * t * t) * K + (t * t * t) * 0;
   s = (u * u * u) * 0 + (3 * u * u * t) * K + (3 * u * t * t) * 1 + (t * t * t) * 1;

(for angle in the range [0, pi/2]: other quadrants can be calculated by symmetry).

Alex Jones
  • 21
  • 4
0
g.DrawArc(new Pen(Color.Blue, 1f), (float)((hw) - R * scale), -(float)((2 * R * scale) - hh), (float)(2 * R * scale), (float)(2 * R * scale), 0, 360);

Well I got somewhat precise results with this method.However this is only a sample.

GorillaApe
  • 3,611
  • 10
  • 63
  • 106
  • I forgot to mention, that the error does not show for with the center on one of the coordinate axes. In this situation the angle is zero or a multiple of 90 degrees an my guess is, that the DrawEllipse or DrawArc function is precise at this angles. – man_msu Mar 12 '15 at 07:40
  • And I don't think it matters if you use DrawEllipse or DrawArc. One (DrawEllipse) is just a spezial case of the other. – man_msu Mar 12 '15 at 07:42
  • However you might consinder using a custom circle drawing algorithm that calls drawline . this is what 3d developers do – GorillaApe Mar 12 '15 at 12:59
  • Hi Parhs, thanks for commenting! You will find that the commentet out lines in the above code are a custom circle drawing algorithm. With this the circle can be approximated with good precision, depending on the discretisation. But it has a big drawback on performance ;-( – man_msu Mar 13 '15 at 13:17