1

Simplifying a bit, I have a custom control that draws an LED bargraph sound level meter at 20 frames per second (as data is received). My current implementation works okay, but with 16 controls on the screen it consumes too much CPU on older machines... I know it can be better.

Currently I'm overriding onDraw() to draw the following layers in order:

  1. A cached background bitmap that doesn't change
  2. An arrow in a variable position with e.Graphics.DrawImageUnscaled()
  3. e.Graphics.FillRectangle() to fill Green/Yellow/Red based on data
  4. e.Graphics.DrawString() to draw value at the top

Here's a mockup of the final product:

Bargraph Example

Is this the best way to do that? I could conceivably pre-render everything into bitmaps and simply clip them as needed... and maybe still use DrawString for the number(?)... but what is the best approach? Should I even be doing that in onDraw()? Graphics functions in c# are not my strongest area.

Thank you for any advice you can provide.

Edit: Sounds like I'm doing about as good as WinForms will do and it's actually doing just fine. On my development machine it's taking just 0.32ms to paint the control. I'm reacting to a complaint of 100% CPU usage and dropped frames, but the customer is using 10-year-old hardware and I suspect the graphics adapter is not great.

Still, this post was very helpful for general improvements, and did shave a few microseconds off my painting time:

https://stackoverflow.com/a/11025428/1195740

Community
  • 1
  • 1
Adamlive
  • 139
  • 1
  • 2
  • 9
  • If there is limited cases of values (and no animation required), pre-rendering the results into images are actually easier. – Raptor Dec 28 '15 at 03:15
  • Perhaps. There are 96 possible values although I could scale that down to say 32 visibly-different values... I'd hate to go less. Would I still do that in onDraw() using DrawImageUnscaled() or is there some kind of buffer approach that's better? – Adamlive Dec 28 '15 at 03:23
  • 2
    Pay attention to the pixel format of the bitmap, *very* important to painting overhead. There is only one format that is fast and it is never the default, use 32bppPArgb. Ten times as fast as all the other ones. And make sure it never gets rescaled, that's very expensive as well. – Hans Passant Dec 28 '15 at 07:14
  • 1
    What are you targetting: Winforms? WPF? ASP? ...?? __Always__ tag your question accordingly! – TaW Dec 28 '15 at 10:10
  • _There are 96 possible values although I could scale that down to say 32 visibly-different values... I'd hate to go less_ Can you explain the numbers? LEDs do always light fully, so there are four values (off, green yellow and red), no? Actually all you need is drawing the off value in one piece of varying height over the fixed background.. Do the number and the arrow change/move at all? – TaW Dec 28 '15 at 15:38

2 Answers2

0

Assuming your project is WPF code. I would think 20 updates per second is more than a person could watch, especially with the top number updating. I would think a few updates a second would suffice, but I do not know the requirements of your app of course.

I whipped up a sample to see how a WPF UserControl might perform, and the perf seemed fine without using graphics classes, and just building the control from Ellipse, TextBox, and Slider. The slider is not hard to re-style, and I did not do that in my sample.

Design Mode

Design Mode

When Running

When Running

Source Code

You can find my project on GitHub at LightBar

Conclusion

If this helps, great. You asked for Advice and if this was the best way. Not sure if my sample code is the best, but rather one of the many ways any such problem could be solved.

Happy coding, Kory

Kory Gill
  • 6,993
  • 1
  • 25
  • 33
  • I spent a lot of time last night profiling the app and creating some alternatives and I think you're right... I'm already doing about the best WinForms can do. Or rather, the control itself isn't the problem at all. I created a test app similar to yours above that sent random data to 16 objects and I think I was getting 50 fps.... either way it became obvious that graphics performance is not the problem. – Adamlive Dec 28 '15 at 18:49
0

Assuming Winforms, this is a rather quick and pretty dirty example.

It uses two fixed images:

enter image description here enter image description here

  • It does not display the yellow triangle nor the number.

  • And it doesn't even draw the graphics in a persistent way. But this may be unnecessary anyway for this situation..!?

So it leaves some room for improvement. But it does work and it extremly simple:

public Form1()
{
    InitializeComponent();

    Bitmap level = (Bitmap)Image.FromFile("D:\\LEDmeter0.png");
    bmpL0 = level.Clone(new Rectangle(Point.Empty, level.Size), 
                        PixelFormat.Format32bppPArgb);
    level = (Bitmap)Image.FromFile("D:\\LEDmeter1.png");
    bmpL1 = level.Clone(new Rectangle(Point.Empty, level.Size), 
                        PixelFormat.Format32bppPArgb);
}

Bitmap bmpL0 = null;
Bitmap bmpL1 = null;


Random R = new Random(0);

private void timer1_Tick(object sender, EventArgs e)
{
    Size sz = pictureBox2.ClientSize;
    int level = R.Next(10) + R.Next(5) + R.Next(3) ;  // 0-17
    level = 27 * level + 50;
    using (Graphics G = pictureBox2.CreateGraphics())
    {
        G.DrawImage(bmpL1, new Rectangle(0, 0, sz.Width, sz.Height),
            new Rectangle(0, 0, sz.Width, sz.Height), GraphicsUnit.Pixel);
        G.DrawImage(bmpL0, new Rectangle(0, 0, sz.Width, level),
            new Rectangle(0, 0, sz.Width, level), GraphicsUnit.Pixel);
    }
}
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thank you for this... I'm starting to think my existing implementation is just fine, but if I re-did it, I would probably go this route. – Adamlive Dec 28 '15 at 18:52