Harry
Harry

Reputation: 5006

What is the fastest way to draw thousands of lines in WinForms application

I have WinForms application. I made an user control, which draws a map from coordinates of ca 10k lines. Actualy, not all lines are straight ones, but when the map is zoomed out fully - Bezier curves are irrelevant and are replaced with straight lines.

When the map is zoomed, I have smaller number of lines and curves, so the drawing is fast enough (below 15ms). But when it's zoomed out fully - I need to draw all lines (because all fit into viewport). This is painfully slow. On my very fast machine it takes about 1000ms, so on slower machines it would be an overkill.

Is there a simple way to speed up the drawing? I use Graphics object for drawing and I set Graphics.Scale property to my map fit into my control. Does this slow things down? I use Graphics.TranslateTransform() to ensure the whole map is visible. Both scale and translate is set only once in OnPaint() event handler.

Then there is a loop which draws ca 10k lines. And I just see them drawing on the screen.

Maybe WPF container would help?

Well, I could probably simplify the map to merge some lines, but I wonder if it's worth the effort. It would complicate the code greatly, would introduce much more calculations, use extra memory and I don't know if at the end of the day it would be considerably faster.

BTW, I tested that processing of all lines (converting from one structure to another with some aditional calculations) takes ca 10ms on my machine. So - the drawing alone costs 100x more time.

EDIT: Now here's the new problem. I've turned double buffering on with:

SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);

Here's my messy OnPaint() handler:

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    if (Splines == null) return;

    var pens = new[] {
        new Pen(TrackColor),
        new Pen(TrackColor),
        new Pen(RoadColor),
        new Pen(RiverColor),
        new Pen(CrossColor)
    };

    var b = Splines.Bounds;
    Graphics g = e.Graphics;

    g.PageScale = _CurrentScale;
    g.TranslateTransform(-b.Left, -b.Top);
    int i = 0;
    foreach (var s in Splines) {
        g.DrawLine(pens[s.T], s.A, s.D);
        if (++i > 100) break;
        //if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
        //else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
    }

    foreach (var p in pens) p.Dispose();
}

Take my word the code works, if I only remove OptimizedDoubleBuffer from styles. When double buffering is on the handler executes properly, each DrawLine is executed with correct params. But the graphics is not displayed. CPU usage during resizing is next to zero. Like all DrawLine calls were ignored. What's happening here?

Upvotes: 2

Views: 6734

Answers (4)

Umar Hassan
Umar Hassan

Reputation: 312

Or you could use lower level of drawing, outside GDI+. one such example is SlimDX. This wrapper allows you to create a directX device write from your windows controls and forms. Once DirectX is in action, the speed can increase up to several times.

2ndly, when drawing on win panel even with DoubleBuffered enabled, you always have to Invalidate the panel which asks the Environment to call the OnPaint event which actual draws using the system provided Graphics object. This invalidation usually requires a timer with fire rate more than 30 to five you a feeling of smooth playback. Now, when the load increases, the subsequent timer event is delayed since everything is happening under a single thread. And the timer must Yield the thread for around 25ms after every fire (windows OS limitation). Cross Thread access ia not allowed, using which a System.Threading.Timer could have prevent this jitter.

See this link for an example where I have tried to transfer my existing GDI code to DirectX. The code uses a lot of graphics attributes which i have incorporated in the wrapper which can draw on both GDI and DirectX.

https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk

Upvotes: 0

Octopoid
Octopoid

Reputation: 3698

The feasibility of this really depends on if you're using anti-aliasing, if the thing can rotate, if the thickness has to be very accurate, etc.

However you can always draw all the lines into a bitmap, then simply redraw the bitmap unless the map data itself has actually changed. Of course then you get into having different bitmaps for different zoom levels, hiding and showing them, multiple bitmaps in a grid for the high details etc.

It's definitely not ideal, but if you really do need to draw thousands of lines on a 20ms refresh though.. it might be your only real option.

Upvotes: 1

Emond
Emond

Reputation: 50692

Try double buffering as a possible solution or try to reduce the number of lines. Only testing will give you an answer for your application.

Winforms Double Buffering

Double buffering with Panel

Upvotes: 2

adv12
adv12

Reputation: 8551

In a related post I've seen recently but can't find, the OP claimed to have seen a large speed-up when switching his control to use double-buffering. Apparently there's a substantial hit for drawing stuff to the screen.

Another thing you could try is decimating the point lists in the lines you draw when zoomed out. Instead of doing the decimation each frame, you could do it only once each time the zoom is changed.

Upvotes: 2

Related Questions