Search code examples
c#canvasdrawellipse

How to draw circles faster on windows form?


I need to draw small circles at given X,Y coordinates but could be as high as 6000 circles on a panel on a window. How it is very slow and takes about 2 to 3 seconds for 5000 circles. How could I draw this faster?

 private void drawBGA_Pins(BGAmap PinCordinates, double ExternalZoomFactor, double ExternalOffset_X, double ExternalOffset_Y)
        {
            Graphics g = this.imgBox.CreateGraphics();
            double zoomFactor = (Math.Min(Math.Abs((imgBox.Width) / PinCordinates.width), Math.Abs((imgBox.Height) / PinCordinates.height)))*92/100 * ExternalZoomFactor;
            //g.Clear(Color.Transparent); //you can choose another color for your background here.
            Pen pen = new Pen(Color.Yellow);

            foreach (var p in PinCordinates.pkgCordinates)
            {
                try
                {
                    g.DrawEllipse(pen, (float)(ExternalOffset_X + (p.X* zoomFactor)), (float)(ExternalOffset_Y + (p.Y* zoomFactor)), 10, 10);
                }
                catch
                {

                }
            }
        }

Solution

  • As noted in my comment: The code you post has many issues and one of them is also the reason for the lack of speed..

    Winforms Graphics rule #1 : Never use control.CreateGraphics!

    Also never try to cache a Graphics object! Either draw into a Bitmap bmp using a Graphics g = Graphics.FromImage(bmp) or in the Paint event of a control, using the e.Graphics parameter..

    (The only exception are graphics you actually do not want to persist like drawing rubberband rectangles.. You can test the persistence of your graphics by doing a minimize/maximize sequence..)

    The correct way is to keep a list of things to draw and whenever that list changes Invalidate the control you draw on. All drawing should be in the Paint event, using e.Graphics there!

    This has been discussed quite often here; but what is of even more interest here is that the correct way will be really fast compared to the wrong, non-persistent one.

    Let's see:

    enter image description here

    The missing timings for 50k and 100k circles with the created graphics object are 5.4s (vs 0.18s) and 10.9s (vs 0.41s). (The animation file would get too long for them..)

    So e.Graphics could 'draw' the circles ~30-100x faster.

    How come? - Actually the 'correct' way will only prepare the surface of a double-buffered control internally but only push it to the display once when it is finished and has time to do so, whereas the wrong way will deliver each circle directly. This optimization is done by the system as well as limiting the output area..

    (PicturBox is double-buffered by default; other controls can be made so as well, see here)

    Here is the testbed:

    int count = 5000;
    List<PointF> pinCoordinates = new List<PointF>();
    Random rnd = new Random(9);
    float zoomFactor = 1.5f;
    
    private void Button1_Click(object sender, EventArgs e)
    {
        // init a list of points
        pinCoordinates.Clear();
        Size sz = pictureBox1.ClientSize;
        Cursor = Cursors.WaitCursor;
        for (int i = 0; i < count; i++)
        {
            pinCoordinates.Add(new PointF(rnd.Next(sz.Width), rnd.Next(sz.Height)));
        }
    
        // now draw in one way or the other:
        if (radioButton1.Checked)
        {
            Graphics g = pictureBox1.CreateGraphics();
            DateTime dt0 = DateTime.Now;
            foreach (var p in pinCordinates)  DoDraw(g, p);
            sayTime(dt0);
        }
        else
        {
            pictureBox1.Invalidate();
        }
        Cursor = Cursors.Default;
    }
    
    private void PictureBox1_Paint(object sender, PaintEventArgs e)
    {
        DateTime dt0 = DateTime.Now;
        foreach (var p in pinCoordinates)  DoDraw(e.Graphics, p);
        sayTime(dt0);
    }
    
    void DoDraw(Graphics g, PointF p)
    {
        using (Pen pen = new Pen(Color.FromArgb(rnd.Next(1234567890))))
            g.DrawEllipse(pen, p.X * zoomFactor, p.Y * zoomFactor, 10, 10);
    }
    
    void sayTime(DateTime dt)
    {
        DateTime dt1 = DateTime.Now;
        label1.Text = (dt1 - dt).ToString("s\\.ffff");
    }
    

    Final note: You can expect the same speed if you draw into a Bitmap using Graphics g = Graphics.FromImage(bmp)..