0

I'm working on a project in C# windows forms where I'm using pictureboxes to display multiple tachometers.

Each picturebox consists of a background image and an image. The background image is static, but the image (displaying the actual indicator) is rotated and updated a couple of times per second.

Background image: https://i.stack.imgur.com/FBsKI.jpg

Fron image: https://i.stack.imgur.com/T0nJU.jpg

It works well as long as I have one or two tachometers running simultaneously, but when I add more it starts to lag a lot.

Here is the code I use for updating the picturebox:

private void timer4_Tick(object sender, EventArgs e)
    {
        Bitmap image = RotateImage(indicatorImg, i++, false, true, Color.Transparent);
        pictureBox4.Image = image;
    }

The RotateImage function I'm using (Credit to RenniePet at https://stackoverflow.com/a/16027561/3660713):

public static Bitmap RotateImage(Image inputImage, float angleDegrees, bool upsizeOk,
                                     bool clipOk, Color backgroundColor)
    {
        lock (lockObject)
        {
            // Test for zero rotation and return a clone of the input image
            if (angleDegrees == 0f)
                return (Bitmap)inputImage.Clone();

            // Set up old and new image dimensions, assuming upsizing not wanted and clipping OK
            int oldWidth = inputImage.Width;
            int oldHeight = inputImage.Height;
            int newWidth = oldWidth;
            int newHeight = oldHeight;
            float scaleFactor = 1f;

            // If upsizing wanted or clipping not OK calculate the size of the resulting bitmap
            if (upsizeOk || !clipOk)
            {
                double angleRadians = angleDegrees * Math.PI / 180d;

                double cos = Math.Abs(Math.Cos(angleRadians));
                double sin = Math.Abs(Math.Sin(angleRadians));
                newWidth = (int)Math.Round(oldWidth * cos + oldHeight * sin);
                newHeight = (int)Math.Round(oldWidth * sin + oldHeight * cos);
            }

            // If upsizing not wanted and clipping not OK need a scaling factor
            if (!upsizeOk && !clipOk)
            {
                scaleFactor = Math.Min((float)oldWidth / newWidth, (float)oldHeight / newHeight);
                newWidth = oldWidth;
                newHeight = oldHeight;
            }

            // Create the new bitmap object. If background color is transparent it must be 32-bit, 
            //  otherwise 24-bit is good enough.
            Bitmap newBitmap = new Bitmap(newWidth, newHeight, backgroundColor == Color.Transparent ?
                                             PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb);
            newBitmap.SetResolution(inputImage.HorizontalResolution, inputImage.VerticalResolution);

            // Create the Graphics object that does the work
            using (Graphics graphicsObject = Graphics.FromImage(newBitmap))
            {
                graphicsObject.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                graphicsObject.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                graphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

                // Fill in the specified background color if necessary
                if (backgroundColor != Color.Transparent)
                    graphicsObject.Clear(backgroundColor);

                // Set up the built-in transformation matrix to do the rotation and maybe scaling
                graphicsObject.TranslateTransform(newWidth / 2f, newHeight / 2f);

                if (scaleFactor != 1f)
                    graphicsObject.ScaleTransform(scaleFactor, scaleFactor);

                graphicsObject.RotateTransform(angleDegrees);
                graphicsObject.TranslateTransform(-oldWidth / 2f, -oldHeight / 2f);

                // Draw the result 
                graphicsObject.DrawImage(inputImage, 0, 0);
            }

            return newBitmap;
        }
    }

Any ideas on how to improve performance? Is there a better way to do this?

Community
  • 1
  • 1
Jocke
  • 1
  • 2
  • 1
    You could implement it differently (without timer), see [here](http://stackoverflow.com/q/2063178/1997232). Other than that, blitting bitmap is the fastest way. You could prerender frames and then just use them when drawing image. It could be done in a cache way: if frame is missing - it's rendered and then reused, otherwise it stay `null` and don't use memory. – Sinatr May 21 '14 at 13:14
  • You may want to check [**My Answer**](http://stackoverflow.com/a/14711744/643085) to a very similar question, using current, relevant .Net UI technology. – Federico Berasategui May 21 '14 at 13:18
  • FYI, I have modified the sample linked in my previous comment to include 20 gauges randomly moving via Timers, and the UI response is immediate. Something you can never hope to achieve using archaic technologies such as winforms. [Full source code on GitHub](https://github.com/High-Core/WPFSamples/tree/master/MultiGaugeSample) – Federico Berasategui May 21 '14 at 13:43
  • Thank you guys. I will check out your solutions and post my findings! – Jocke May 21 '14 at 13:49

2 Answers2

1

I would create a static list of images for each angle of degrees required on startup.

You could then reference the correct index in the list and use that pre rotated image.

MarkO
  • 2,143
  • 12
  • 14
0

Thank you for your help!

What I did was that I added a WPF control to my Winforms project. The WPF control contains the two images and simply contains code for rotating the front image:

C#:

    public void Rotate(float deg)
    {
        RotateTransform rt = new RotateTransform(deg);

        image2.RenderTransform = rt;
    }

XAML:

<UserControl x:Class="Project.WPF_imgControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Image HorizontalAlignment="Left" Name="image1" Stretch="Fill" VerticalAlignment="Top"  Source="/Project;component/Images/Tachometer.png" />
    <Image RenderTransformOrigin =".5,.5" HorizontalAlignment="Left" Name="image2" Stretch="Fill" VerticalAlignment="Top" Source="/Project;component/Images/Pointer_80%25.png" />
</Grid>

Now I just have to find out why the image looks jagged in the WPF component compared to my standard form.

Edit: RenderOptions.BitmapScalingMode="HighQuality" did the trick :)

//Jocke

Jocke
  • 1
  • 2