2
votes

so I wrote a Windows Form Application in C# to open a video, display the video in pictureBox_display, and allow users to define (draw) bounding boxes on the picturebox. Only thing is, I can very predictably cause the program to throw a System.AccessViolationException and crash by simply: 1) opening the video (any video) 2) click + dragging my mouse on the picturebox for a few seconds.

Here's some code:

VideoCapture VC;
bool IsMouseDown = false;
bool MouseMoved = false;
string VideoFileName;


private void OpenFileButton_Click(object sender, EventArgs e) {
        OpenFileDialog OFD = new OpenFileDialog();
        if (OFD.ShowDialog() == DialogResult.OK) {
                FrameNumber = 0;
                VideoFileName = OFD.FileName;
                VC = new VideoCapture(VideoFileName);

                if (VC.Width<=0 || VC.Height<=0) {
                    System.Windows.Forms.MessageBox.Show("ERROR: Please load a valid video file");
                    return;
                }


        // Initialize picturebox dimensions according to image
        double W_H_ratio = Convert.ToDouble(VC.Width) / VC.Height;
                if (W_H_ratio >= (double)(5 / 4)) {
                    pictureBox_display.Width = Math.Min(VC.Width, 800);
                    pictureBox_display.Height = Math.Min(VC.Height, (int)((1 / W_H_ratio )* 800));
                }
                else {
                    pictureBox_display.Height = Math.Min(VC.Height, 640);
                    pictureBox_display.Width = Math.Min(VC.Width, (int)(W_H_ratio * 640));
                }

                Mat tempimg = GetMat(FrameNumber);
                Console.WriteLine("Size of tempimg: " + tempimg.Size);
                pictureBox_display.Image = tempimg.Bitmap;
        }
}

private Mat GetMat(int someFrameNumber) {

        try {
                Mat m = new Mat();
                VC.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.PosFrames, someFrameNumber);
                VC.Read(m);

                Mat resized = new Mat();
                CvInvoke.ResizeForFrame(m, resized, pictureBox_display.Size);
                m.Dispose();
                return resized;
        }

        catch {
                Mat m = new Mat();
                System.Windows.Forms.MessageBox.Show("ERROR: Cannot read video frame");
                return m;
        }
}

private void pictureBox_display_MouseDown(object sender, MouseEventArgs e) {
        if (VideoFileName != null && checkBox1.Checked == false && VideoPlaying == false) {
                IsMouseDown = true;
                MouseMoved = false;
                ThereIsNewSelection = true;
                StartLocation = e.Location;
                Console.WriteLine(StartLocation);
        }
}


// Constantly alter the end location of the mouse to provide the coordinates for the rectangle
private void pictureBox_display_MouseMove(object sender, MouseEventArgs e) {
        if (IsMouseDown == true) {
                MouseMoved = true;

                EndLocation = e.Location;
                pictureBox_display.Invalidate();
        }
}

private void pictureBox_display_Paint(object sender, PaintEventArgs e) {
        if (!VideoPlaying && ThereIsNewSelection) {
                e.Graphics.DrawRectangle(Pens.Red, GetRectangle());
        }
}

I know it's definitely to do with Invalidate() as I've commented code out line by line, and it only crashes when there is Invalidate(). Can anyone tell me what might be causing the problem?

Here is the stack trace if it's useful:

at System.Drawing.SafeNativeMethods.Gdip.GdipDrawImageRectI(HandleRef graphics, HandleRef image, Int32 x, Int32 y, Int32 width, Int32 height) at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y, Int32 width, Int32 height) at System.Drawing.Graphics.DrawImage(Image image, Rectangle rect) at System.Windows.Forms.PictureBox.OnPaint(PaintEventArgs pe) at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer) at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

1
you set pictureBox_display.Image = tempimg.Bitmap; and also spamming WinForms with pictureBox_display.Invalidate(); on every mouse move, so my guess will be, that tempimg.Bitmap was disposed in the middle of GDI+ GdipDrawImageRectI operation (or maybe right before), so you have an AccessViolationException. - vasily.sib
Hi @vasily.sib, thanks for your reply! I did consider the possibility of that happening, but I don't see any way I can maintain control of tempimg.Bitmap. How would you recommend I solve the problem? Anyway I did manage to find a fix after you confirmed my suspicions. For those wondering, I basically added another global variable Mat CurrentFrameMat and in the function OpenFileButton_Click(...), assigned Frame 0 to CurrentFrameMat and did a pictureBox_display.Image = CurrentFrameMat.Bitmap just before Invalidate() in the MouseMove function. Thanks a lot! - Ryan T. J.
You simple need to move the tempimg variable out of the method so it becomes a field of the form class. That way the garbage collector always sees a reference to it and prevents it from getting garbage-collected. - Hans Passant

1 Answers

-1
votes

You need to clone (deep copy) the bitmap such as the following:

B.Clone(new Rectangle(0, 0, B.Width, B.Height), B.PixelFormat)