tags:

views:

826

answers:

7

I have an dynamic List of Point, new Point can be added at any time. I want to draw lines to connect them using different color. Color is based on the index of those points. Here is the code:

    private List<Point> _points;
    private static Pen pen1 = new Pen(Color.Red, 10);
    private static Pen pen2 = new Pen(Color.Yellow, 10);
    private static Pen pen3 = new Pen(Color.Blue, 10);
    private static Pen pen4 = new Pen(Color.Green, 10);

    private void Init()
    {
        // use fixed 80 for simpicity
        _points = new List<Point>(80);

        for (int i = 0; i < 80; i++)
        {
            _points.Add(new Point(30 + i * 10, 30));
        }
    }

    private void DrawLinesNormal(PaintEventArgs e)
    {
        for (int i = 0; i < _points.Count-1; i++)
        {
            if (i < 20)
                e.Graphics.DrawLine(pen1, _points[i], _points[i + 1]);
            else if (i < 40)
                e.Graphics.DrawLine(pen2, _points[i], _points[i + 1]);
            else if (i < 60)
                e.Graphics.DrawLine(pen3, _points[i], _points[i + 1]);
            else
                e.Graphics.DrawLine(pen4, _points[i], _points[i + 1]);
        }
    }

I find this method is not fast enough when I have new points coming in at a high speed. Is there any way to make it faster? I did some research and someone said using GraphicsPath could be faster, but how?

[UPDATE] I collect some possible optimizations:

  1. Using GrahpicsPath, Original Question
  2. Change Graphics quality ( such as SmoothingMode/PixelOffsetMode...), also call SetClip to specify the only necessary region to render.
A: 

This is about as fast as you're going to get with System.Drawing. You might see a bit of gain using Graphics.DrawLines(), but you'd need to format your data differently to get the advantage of drawing a bunch of lines at once with the same pen. I seriously doubt GraphicsPath will be faster.

One sure way to improve speed is to reduce the quality of the output. Set Graphics.InterpolationMode to InterpolationMode.Low, Graphics.CompositingQuality to CompositingQuality.HighSpeed, Graphics.SmoothingMode to SmoothingMode.HighSpeed, Graphics.PixelOffsetMode to PixelOffsetMode.HighSpeed and Graphics.CompositingMode to CompositingMode.SourceCopy.

I remember a speed test once where someone compared Graphics to P/Invoke into GDI routines, and was quite surprised by the much faster P/Invoke speeds. You might check that out. I'll see if I can find that comparison... Apparently this was for the Compact Framework, so it likely doesn't hold for a PC.

The other way to go is to use Direct2D, which can be faster yet than GDI, if you have the right hardware.

codekaizen
+2  A: 

You won't be able to squeeze much more speed out of that code without losing quality or changing to a faster renderer (GDI, OpenGL, DirectX). But GDI will often be quite a bit faster (maybe 2x), and DirectX/OpenGL can be much faster (maybe 10x), depending on what you're drawing.

The idea of using a Path is that you batch many (in your example, 20) lines into a single method call, rather than calling DrawLine 20 times. This will only benefit you if you can arrange the incoming data into the correct list-of-points format for the drawing routine. Otherwise, you will have to copy the points into the correct data structure and this will waste a lot of the time that you are gaining by batching into a path. In the case of DrawPath, you may have to create a GraphicsPath from an array of points, which may result in no time saved. But if you have to draw the same path more than once, you can cache it, and you may then see a net benefit.

If new points are added to the list, but old ones are not removed (i.e. you are always just adding new lines to the display) then you would be able to use an offscreen bitmap to store the lines rendered so far. That way each time a point is added, you draw one line, rather than drawing all 80 lines every time.

It all depends on exactly what you're trying to do.

Jason Williams
caching old points is a good idea, however, can I simply change the color of already drawn lines? If not, I still have to draw all 80 lines every time.
AZ
If you have fewer than 256 line colours, then you may be able to use palette cycling on an 8bpp bitmap (so you draw each line in a unique colour, and then you just update the palette to redisplay the lines in changing colours).
Jason Williams
Alternatively, you may be able to re-render only the lines that are changing colour in each frame. Note that this will cause them to overdraw all other lines, so you may get unwanted "depth" glitches where they overlap (the wrong line appears "in front") - but you may be able to get away with it. It may also generate odd colours if you render the lines with anti-aliasing, as each line will blend against the colours behind it (including the old colour of the same line). But you could try it and see if you are happy with the results...
Jason Williams
"then you may be able to use palette cycling on an 8bpp bitmap (so you draw each line in a unique colour, and then you just update the palette to redisplay the lines in changing colours)" I don't understand, do you have an example?
AZ
"Palette animation" or "Palette cycling" involves drawing to a paletted (e.g. 256-colour) image. These images use a pixel value that is simpy an index into a list of colours (e.g. "index 5 is red"). This means you can change the colour of all "index 5" pixels by simply changing the palette entry for colour 5 to a different RGB value (rather than having to re-draw all those pixels). It's a fairly simple technique, but you'd be best searching for information on it rather than me trying to explain it in one paragraph! Note: it doesn't change the displayed image, only the colours used for pixels.
Jason Williams
A: 

You might wanna look into the Brush object, and it's true that you won't get near real-time performance out of a GDI+ program, but you can easily maintain a decent fps as long as the geometry and number of objects stay within reasonable bounds. As for line drawing, I don't see why not.

But if you reach the point where you doing what you think is optimal, and all that is, drawing lines.. you should consider a different graphics stack, and if you like .NET but have issues with unmanaged APIs like OpenGL and DirectX, go with WPF or Silverlight, it's quite powerful.

Anyway, you could try setting up a System.Drawing.Drawing2D.GraphicsPath and then using a System.Drawing.Drawing2D.PathGradientBrush to a apply the colors this way. That's a single buffered draw call and if you can't get enough performance out of that. You'll have to go with something other entirely than GDI+

John Leidegren
I don't think you can use Brush to "fill" a line
AZ
A: 

Not GDI(+) at all, but a completely different way to tackle this could be to work with a block of memory, draw your lines into there, convert it to a Bitmap object to instantly paint where you need to show your lines.

Of course this hinges in the extreme on fast ways to

  • draw lines of given color in the memory representation of choice and
  • convert that to the Bitmap to show.

Not in the .NET Framework, I think, but perhaps in a third party library? Isn't there a bitmap writer of sorts in Silverlight for stuff like this? (Not into Silverlight myself that much yet...)

At least it might be an out of the box way to approach this. Hope it helps.

peSHIr
There's nothing in .NET 2.0 that works well with GDI+ for this (you can do it, but it's not going to be particularly fast). However, WPF or Silverlight, depending on if you wanna go the desktop or web approach, has that. A writable bitmap http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx
John Leidegren
Isn't that what I said, John? ;-)
peSHIr
Can I use double buffer to draw on an off-screen image and then draw to Graphics?
AZ
@AZ: Of course, but when your drawing to the off-screen image using GDI+ I don't expect it to be that much more quicker than switching to another way of rendering, except for the overhead of not rendering the intermediate states of your image-in-progress to the screen. I interpreted your question as "how can I get GDI+ to draw faster, given the number of Pens I need for the different colors?" and there it wouldn't really help, I guess. Best of course is to actually try and time.
peSHIr
A: 

Doesn't really help to improve performance, but i would put the pens also into a list and writing all this lines in this way:

int ratio = _points.Count / _pens.Count;

for (int i = 0; i < _points.Count - 1; i++)
{
    e.Graphics.DrawLine(_pens[i / ratio], _points[i], _points[i + 1]);
}
Oliver
A: 

Hi friends, I think you have to dispose pen object and e.Graphics object after drawing. One more thing it is better if you write your drawLine code inside onPaint().

 just override onPaint() method it support better drawing and fast too.
prashant
Yep, I have to dispose those pens but this code is just for demo purpose so I didn't write that. -I don't think I need to dispose e.Graphics object, I didn't create it and why I need to dispose it? - Can you explain why override OnPaint will be faster? Do you mean the overhead of invoking Paint event?
AZ
A: 

Too late, but possibly somebody still need a solution.

I've created small library GLGDI+ with similiar (but not full/equal) GDI+ syntax, which run upon OpenTK: http://code.google.com/p/glgdiplus/

I'm not sure about stability, it has some issues with DrawString (problem with TextPrint from OpenTK). But if you need performance boost for your utility (like level editor in my case) it can be solution.