Question Picturebox.BackgroundImage rendering speed huge difference from 32bit to 64bit

Regmaster

Member
Joined
Oct 25, 2024
Messages
6
Programming Experience
10+
Hi All, I'm Daniele and have a problem during showing an image using the backgroundImage property of a Windows Form picture box object.
A camera send me a frame each 100 milliseconds, and the same code compiled 32bit, take about 120 millisecond to show the image into the picturebox; simply compile same code with 64bit, the frame arrived in the same way every 100 milliseconds, but takes about 1700 milliseconds to be showed into the picture box...Why this huge difference?? Exist a work around?
Thanks in advance for help!
 
That definitely doesn't sound right.

In general, if you are accessing anything that is 32-bit on a 64-bit OS, Windows need to thunk down from 64 to 32 bit and then back up again. I would have expected the 32 bit to be slower, but you are reporting that the 64 bit is over 10 times slower.

Show us your code for getting and transforming from raw camera data to bitmap for use as the background in the picture box.
 
Is this running on .NET Framework, or on .NET 8.0?
 
It's a .NET Framework 4.8 Windows Form Application.

Maybe I explained myself badly, the code that decodes the raw frame that comes from the camera is stable at 100 milliseconds in both compilations (32bit and 64bit), so the problem is not the conversion of the data from byte[] to image. The problem is in the management of the image display on the video; I measured the time using the Stopwatch timer, I start it before associating the image (system.drawing.image) to the BackgroundImage property of a normal picturebox and at the end of the Paint event on the picturebox I stop the timer and see how long it took to display the image in the picturebox.

This is the part of code, but is not mine, is a part of SDK.

C#:
Stopwatch timerRedraw = new Stopwatch();

private void PicCameraView_Paint(object sender, PaintEventArgs e)
{
    VimbaHelper.ImageInUse = true;
    timerRedraw.Stop();

    System.Diagnostics.Debug.WriteLine($"Elapsed time frame refresh -> {timerRedraw.ElapsedMilliseconds} ms.");

}

/// <summary>
/// Handles the FrameReceived event
/// </summary>
/// <param name="sender">The Sender</param>
/// <param name="args">The FrameEventArgs</param>
private void OnFrameReceived(object sender, FrameEventArgs args)
{
    // Start an async invoke in case this method was not
    // called by the GUI thread.
    if (InvokeRequired == true)
    {
        BeginInvoke(new FrameReceivedHandler(this.OnFrameReceived), sender, args);
        return;
    }

    if (true == m_Acquiring)
    {
        // Display image
        Image image = args.Image;
        if (null != image)
        {
            timerRedraw.Restart();
            picCameraView.BackgroundImage = image;
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("An acquisition error occurred. Reason: " + args.Exception.Message);

            try
            {
                try
                {
                    // Start asynchronous image acquisition (grab) in selected camera
                    m_VimbaHelper.StopContinuousImageAcquisition();
                }
                finally
                {
                    m_Acquiring = false;
                    //UpdateControls();
                    //m_CameraList.Enabled = true;
                }

                System.Diagnostics.Debug.WriteLine("Asynchronous image acquisition stopped.");
            }
            catch (Exception exception)
            {
                System.Diagnostics.Debug.WriteLine("Error while stopping asynchronous image acquisition. Reason: " + exception.Message);
            }
        }
    }
}
 
Paint messages have lower priority.

Try adding in extra tracing showing current elapsed time to right after assignment of the image on line 35, as well as when the paint starts before line 5.
 
Paint messages have lower priority.

Try adding in extra tracing showing current elapsed time to right after assignment of the image on line 35, as well as when the paint starts before line 5.

If I understand correctly, this is the code modification but timing results are very close...

Debug out >

RX RAW DATA : 102 ms.
Start of paint event ----->>> 1425 ms.
Elapsed time frame refresh -> 1426 ms.


C#:
Stopwatch timerRedraw = new Stopwatch();
Stopwatch timerCheck = new Stopwatch();

private void PicCameraView_Paint(object sender, PaintEventArgs e)
{
    timerCheck.Stop();
    System.Diagnostics.Debug.WriteLine($"Start of paint event ----->>> {timerCheck.ElapsedMilliseconds} ms.");
    timerRedraw.Stop();
    System.Diagnostics.Debug.WriteLine($"Elapsed time frame refresh -> {timerRedraw.ElapsedMilliseconds} ms.");
    VimbaHelper.ImageInUse = true;

}

/// <summary>
/// Handles the FrameReceived event
/// </summary>
/// <param name="sender">The Sender</param>
/// <param name="args">The FrameEventArgs</param>
private void OnFrameReceived(object sender, FrameEventArgs args)
{
    // Start an async invoke in case this method was not
    // called by the GUI thread.
    if (InvokeRequired == true)
    {
        BeginInvoke(new FrameReceivedHandler(this.OnFrameReceived), sender, args);
        return;
    }

    if (true == m_Acquiring)
    {
        // Display image
        Image image = args.Image;
        if (null != image)
        {
            timerRedraw.Restart();
            picCameraView.BackgroundImage = image;
            timerCheck.Restart();
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("An acquisition error occurred. Reason: " + args.Exception.Message);

            try
            {
                try
                {
                    // Start asynchronous image acquisition (grab) in selected camera
                    m_VimbaHelper.StopContinuousImageAcquisition();
                }
                finally
                {
                    m_Acquiring = false;
                    //UpdateControls();
                    //m_CameraList.Enabled = true;
                }

                System.Diagnostics.Debug.WriteLine("Asynchronous image acquisition stopped.");
            }
            catch (Exception exception)
            {
                System.Diagnostics.Debug.WriteLine("Error while stopping asynchronous image acquisition. Reason: " + exception.Message);
            }
        }
    }
}
 
Sort off...

I was thinking more of this:

C#:
Stopwatch timerRedraw = new Stopwatch();
Stopwatch timerCheck = new Stopwatch();

private void PicCameraView_Paint(object sender, PaintEventArgs e)
{
    timerRedraw.Stop();
    System.Diagnostics.Debug.WriteLine($"Elapsed time frame refresh -> {timerRedraw.ElapsedMilliseconds} ms.");
    VimbaHelper.ImageInUse = true;
}

/// <summary>
/// Handles the FrameReceived event
/// </summary>
/// <param name="sender">The Sender</param>
/// <param name="args">The FrameEventArgs</param>
private void OnFrameReceived(object sender, FrameEventArgs args)
{
    // Start an async invoke in case this method was not
    // called by the GUI thread.
    if (InvokeRequired == true)
    {
        BeginInvoke(new FrameReceivedHandler(this.OnFrameReceived), sender, args);
        return;
    }

    if (true == m_Acquiring)
    {
        // Display image
        Image image = args.Image;
        if (null != image)
        {
            timerRedraw.Restart();
            timerCheck.Restart();
            picCameraView.BackgroundImage = image;
            timerCheck.Stop();
            System.Diagnostics.Debug.WriteLine($"Image transfer ----->>> {timerCheck.ElapsedMilliseconds} ms.");
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("An acquisition error occurred. Reason: " + args.Exception.Message);

            try
            {
                try
                {
                    // Start asynchronous image acquisition (grab) in selected camera
                    m_VimbaHelper.StopContinuousImageAcquisition();
                }
                finally
                {
                    m_Acquiring = false;
                    //UpdateControls();
                    //m_CameraList.Enabled = true;
                }

                System.Diagnostics.Debug.WriteLine("Asynchronous image acquisition stopped.");
            }
            catch (Exception exception)
            {
                System.Diagnostics.Debug.WriteLine("Error while stopping asynchronous image acquisition. Reason: " + exception.Message);
            }
        }
    }
}
 
Sort off...

I was thinking more of this:

C#:
Stopwatch timerRedraw = new Stopwatch();
Stopwatch timerCheck = new Stopwatch();

private void PicCameraView_Paint(object sender, PaintEventArgs e)
{
    timerRedraw.Stop();
    System.Diagnostics.Debug.WriteLine($"Elapsed time frame refresh -> {timerRedraw.ElapsedMilliseconds} ms.");
    VimbaHelper.ImageInUse = true;
}

/// <summary>
/// Handles the FrameReceived event
/// </summary>
/// <param name="sender">The Sender</param>
/// <param name="args">The FrameEventArgs</param>
private void OnFrameReceived(object sender, FrameEventArgs args)
{
    // Start an async invoke in case this method was not
    // called by the GUI thread.
    if (InvokeRequired == true)
    {
        BeginInvoke(new FrameReceivedHandler(this.OnFrameReceived), sender, args);
        return;
    }

    if (true == m_Acquiring)
    {
        // Display image
        Image image = args.Image;
        if (null != image)
        {
            timerRedraw.Restart();
            timerCheck.Restart();
            picCameraView.BackgroundImage = image;
            timerCheck.Stop();
            System.Diagnostics.Debug.WriteLine($"Image transfer ----->>> {timerCheck.ElapsedMilliseconds} ms.");
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("An acquisition error occurred. Reason: " + args.Exception.Message);

            try
            {
                try
                {
                    // Start asynchronous image acquisition (grab) in selected camera
                    m_VimbaHelper.StopContinuousImageAcquisition();
                }
                finally
                {
                    m_Acquiring = false;
                    //UpdateControls();
                    //m_CameraList.Enabled = true;
                }

                System.Diagnostics.Debug.WriteLine("Asynchronous image acquisition stopped.");
            }
            catch (Exception exception)
            {
                System.Diagnostics.Debug.WriteLine("Error while stopping asynchronous image acquisition. Reason: " + exception.Message);
            }
        }
    }
}
ok, results from 0 to 1 ms.
But this time is related only to the object property setting, not to the actual rendering on the screen. Instead the picture box paint event is fired when the control is redrawn on the screen as described here
paint event microsoft learn

in practice it seems that by enabling the "prefer 32bit" project compilation option the paint event is called more efficiently than what happens for the same program compiled in 64bit, all this happens after installing the Win11 updates released on 3/10/2024... I'm really going crazy trying to understand what is causing this slowdown.
 
Try adding a picCameraView.Invalidate() call between lines 34-35.
 
Elapsed time frame refresh -> 1425 ms.
RX RAW DATA : 100 ms.
Image transfer ----->>> 0 ms.
RAW DATA CONVERSION TIME : 4 ms.
RX RAW DATA : 102 ms.
Elapsed time frame refresh -> 1429 ms.
 
I'd pull my hair out too if it takes that long for the repaint message to get to the control.
 
I have very little hair now :ROFLMAO::ROFLMAO::ROFLMAO:

However, I am comforted to see that I am not the only one surprised by this malfunction, especially since I would have expected a performance increase with 64 bit and not such a drastic deterioration. I also tried to create a new .NET 8.0 windows form project but the slowness is even worse...

thanks anyway in the meantime
 
I recall making a Matrix-like screen saver where I rendered to a bitmap, and then had the bitmap get put on the screen automatically by a panel (rather than a picture box), and it seemed to draw at the same rate for both 32-bit and 64-bit .NET Framework 4.8. But that was with Windows 10 a few years ago. I've lost the code since then. Perhaps things are different with Windows 11...

Also granted, my target frame rate at that time was just 12 FPS so it wasn't any more demanding than the 10 PFS that you seem to be seeking.
 
I sort of managed to reproduce the problem where x64 was about 2-3 times slower getting to the paint message as compared to x86:
C#:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

namespace ImageRenderTest
{
    class Program : Form
    {
        const int StripeHeight = 20;
        int m_offset = 0;
        readonly Stopwatch m_paintStopWatch = new Stopwatch();
        readonly Stopwatch m_renderStopWatch = new Stopwatch();

        Bitmap RenderBitmap()
        {
            int width = ClientRectangle.Width;
            int height = ClientRectangle.Height;
            var bitmap = new Bitmap(width, height);
            using (var g = Graphics.FromImage(bitmap))
            {
                int red = m_offset;
                int white = m_offset + StripeHeight + 1;
                while (white <= height)
                {
                    g.FillRectangle(Brushes.Red, 0, red, width, StripeHeight);
                    g.FillRectangle(Brushes.White, 0, white, width, StripeHeight);
                    red += StripeHeight * 2;
                    white += StripeHeight * 2;
                }
            }
            return bitmap;
        }

        Program()
        {
            Size = new Size(800, 600);

            var pictureBox = new PictureBox()
            {
                Dock = DockStyle.Fill,
                BackColor = Color.Black,
                BackgroundImageLayout = ImageLayout.None
            };
            pictureBox.Paint += (o, e) =>
            {
                m_paintStopWatch.Stop();
                var paintTime = $"Paint time: {m_paintStopWatch.ElapsedMilliseconds}";
                Trace.WriteLine(paintTime);
                Text = paintTime;
            };

            Controls.Add(pictureBox);

            var timer = new Timer()
            {
                Interval = 100,
            };
            timer.Tick += (o, e) =>
            {
                m_paintStopWatch.Restart();
                m_renderStopWatch.Restart();
                m_offset = (m_offset + 1) % StripeHeight;
                pictureBox.BackgroundImage = RenderBitmap();
                m_renderStopWatch.Stop();
                Trace.WriteLine($"Render time: {m_renderStopWatch.ElapsedMilliseconds}");
            };
            timer.Start();
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Program());
        }
    }
}

What is curious is that if I comment out line 43 BackgroundImageLayout = ImageLayout.None, then suddenly the x64 performs at the same speed as x86.

Alternatively, another way to get the same paint speed is to change line 64 to the foreground image instead of the background image: pictureBox.Image = RenderBitmap().
 
Anyway, if you can do the painting yourself instead of relying on the picture box, it runs much faster:
C#:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

namespace ImageRenderTest
{
    class Program : Form
    {
        const int StripeHeight = 20;
        int m_offset = 0;
        readonly Stopwatch m_paintStopWatch = new Stopwatch();
        readonly Stopwatch m_renderStopWatch = new Stopwatch();

        Bitmap RenderBitmap()
        {
            int width = ClientRectangle.Width;
            int height = ClientRectangle.Height;
            var bitmap = new Bitmap(width, height);
            using (var g = Graphics.FromImage(bitmap))
            {
                int red = m_offset;
                int white = m_offset + StripeHeight + 1;
                while (white <= height)
                {
                    g.FillRectangle(Brushes.Red, 0, red, width, StripeHeight);
                    g.FillRectangle(Brushes.White, 0, white, width, StripeHeight);
                    red += StripeHeight * 2;
                    white += StripeHeight * 2;
                }
            }
            return bitmap;
        }

        Program()
        {
            Size = new Size(800, 600);
            BackColor = Color.Black;
            DoubleBuffered = true;

            Paint += (o, e) =>
            {
                m_paintStopWatch.Stop();
                var paintTime = $"Paint time: {m_paintStopWatch.ElapsedMilliseconds}";
                Trace.WriteLine(paintTime);
                Text = paintTime;

                m_renderStopWatch.Restart();
                using (var image = RenderBitmap())
                    e.Graphics.DrawImageUnscaled(image, 0, 0);
                m_renderStopWatch.Stop();
                Trace.WriteLine($"Render time: {m_renderStopWatch.ElapsedMilliseconds}");
            };

            var timer = new Timer()
            {
                Interval = 100,
            };
            timer.Tick += (o, e) =>
            {
                m_paintStopWatch.Restart();
                m_offset = (m_offset + 1) % StripeHeight;
                Invalidate();
            };
            timer.Start();
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Program());
        }
    }
}
 
Back
Top Bottom