WPF Out of memory error in a QR Code Scanner

71 views Asked by At

I have attempted making a WPF QR Code Scanner Page using AForge.Video.DirectShow package to implement the video capture which uses an Image in the XAML side to display it.

The memory usage of the application increases rapidly and reaches 3.6GB+ in 15 seconds before crashing and throwing an out of memory exception at these lines of code:

private ImageSource BitmapToImageSource(Bitmap bitmap)
{
    return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        bitmap.GetHbitmap(),
        IntPtr.Zero,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
}

The entire code of the WPF Scanner Page is:

using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ZXing;
using System.IO;

namespace QRCodeApp
{
    /// <summary>
    /// Interaction logic for Scanner.xaml
    /// </summary>
    public partial class Scanner : Page
    {
        private FilterInfoCollection videoDevices;
        private VideoCaptureDevice videoSource;
        public Scanner()
        {
            InitializeComponent();
            InitializeCamera();
        }
        private void InitializeCamera()
        {
            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (videoDevices.Count > 0)
            {
                videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
                videoSource.NewFrame += VideoSource_NewFrame;
                videoSource.Start();
            }
            else
            {
                MessageBox.Show("No video capture devices found.","No Camera");
            }
        }

        private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            try
            {
                using (Bitmap bitmap = (Bitmap)eventArgs.Frame.Clone())
                {
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        cameraImage.Source = BitmapToImageSource(bitmap);
                    });

                    BarcodeReader barcodeReader = new BarcodeReader();
                    Result result = barcodeReader.Decode(bitmap);

                    if (result != null)
                    {
                        string downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                        string initialDirectory = Path.Combine(downloadsPath, "QR Codes");

                        if (!Directory.Exists(initialDirectory))
                        {
                            Directory.CreateDirectory(initialDirectory);
                        }

                        DateTime currentDateTime = DateTime.Now;
                        string fdt = currentDateTime.ToString("yyyy-MM-dd HH_mm_ss");
                        string fileName = "QRCode " + fdt + ".png";

                        fileName = CleanFileName(fileName);

                        string path = Path.Combine(initialDirectory, fileName);
                        bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
                        videoSource.NewFrame -= VideoSource_NewFrame;
                        videoSource.SignalToStop();

                        System.Threading.Tasks.Task.Run(() =>
                        {
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                cameraImage.Source = null;
                                videoSource = null;
                                videoDevices = null;
                                myframe.frame.Content = new Scanned(bitmap, path, false, false);
                            });
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error: {ex.Message}", "An Exception occured");
            }
        }

        private ImageSource BitmapToImageSource(Bitmap bitmap)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                bitmap.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (videoSource != null && videoSource.IsRunning)
            {
                videoSource.SignalToStop();
                videoSource.WaitForStop();
            }
        }

        private string CleanFileName(string fileName)
        {
            foreach (char c in System.IO.Path.GetInvalidFileNameChars())
            {
                fileName = fileName.Replace(c, '_');
            }
            return fileName;
        }



        private void Back(object sender, RoutedEventArgs e)
        {
            myframe.frame.Content = new ScanSelection();
        }
    }
}

I tried bitmap.Dispose() when the scanning was done but it did not make a difference. Also stopped the camera and set the UI image source, video source and video devices to null. Keep in mind when the QR Code scans perfectly before app crashes the memory usage stop at just what it was before the scan got completed, so if memory usage was 1.5GB before QR Code scanned it will stop increasing and stay at 1.5GB while the app moves to the page I set.

1

There are 1 answers

3
Rushaan On

The memory usage was being increased so much and so rapidly only because the older Bitmaps were not disposed and deleting hBitmap from BitmapToImageSource() every time entirely fixed the issue.

The Previous BitmapToImageSource() function:

private ImageSource BitmapToImageSource(Bitmap bitmap)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                bitmap.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }

The updated BitmapToImageSource() function along with the external function to delete the bitmap which fixed the issue:

private ImageSource BitmapToImageSource(Bitmap bitmap)
        {
            IntPtr hBitmap = bitmap.GetHbitmap();
            try
            {
                return Imaging.CreateBitmapSourceFromHBitmap(
                    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(hBitmap);
            }
        }

        [DllImport("gdi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeleteObject(IntPtr hObject);