Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Showing animated gif in Winforms without locking the file

I'm trying to display images of various file types (including animated .gif files) in my Winforms application. I also have to be able to modify the files that are shown. (change the file name, delete them).

The problem is that a Picturebox locks the image file until the application is closed when using the normal way.

That means I can't do this:

private void Form1_Load(object sender, EventArgs e)
    {

        PictureBox pic = new PictureBox();
        pic.Size = new Size(250, 250);
        pic.Image = Image.FromFile("someImage.gif");
        this.Controls.Add(pic);

                    //No use to call pic.Image = null or .Dispose of it
        File.Delete("someImage.gif"); //throws exception
    }

The workaround in the link above is as follows:

    private void Form1_Load2(object sender, EventArgs e)
    {
        PictureBox pic = new PictureBox();
        pic.Size = new Size(250, 250);
                    //using a FileStream
        var fs = new System.IO.FileStream("someImage.gif", System.IO.FileMode.Open, System.IO.FileAccess.Read);
        pic.Image = System.Drawing.Image.FromStream(fs);
        fs.Close();

        this.Controls.Add(pic);

        pic.MouseClick += pic_MouseClick;
    }

That works fine for normal image types, but it won't load animated .gifs, which is important to me. Trying to load one will make it look like this.

I've found a few other topics about it (this and this) but they're all about WPF and use BitmapImage. I've searched about how to use BitmapImage in a Winforms application, but haven't found anything apart from that it is supposed to somehow work.

I would like to stay with Winforms because I'm just getting used to it, but that's not a necessity.

To summarize: I need a way to show common image types (png, jpg, bmp, and animated gif) while still being able modify the file on the HDD. It is OK if that means unloading->modifying->reloading the file. I'd prefer Winforms, but other Frameworks would do.

Thanks for your help.

Edit: Another way I've tried

using (System.IO.FileStream fs = new System.IO.FileStream("E:\\Pics\\small.gif", System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            fs.CopyTo(ms);
            pic.Image = Image.FromStream(ms);
        } 

But shows the same problem as the second example. The gif doesn't load.

like image 808
s3rius Avatar asked Dec 16 '22 01:12

s3rius


2 Answers

Using a MemoryStream is indeed the right way to avoid the file lock. Which is a strong optimization btw, the lock is created by the memory-mapped file that the Image class uses to keep the pixel data out of the paging file. That matters a great deal when the bitmap is large. Hopefully not on an animated gif :)

A small mistake in your code snippet, you forgot to reset the stream back to the start of the data. Fix:

 using (var fs = new System.IO.FileStream(...)) {
     var ms = new System.IO.MemoryStream();
     fs.CopyTo(ms);
     ms.Position = 0;                               // <=== here
     if (pic.Image != null) pic.Image.Dispose(); 
     pic.Image = Image.FromStream(ms);
 } 

In case it needs to be said: do not dispose the memory stream. That causes very hard to diagnose random crashes later, pixel data is read lazily.

like image 156
Hans Passant Avatar answered Jan 03 '23 18:01

Hans Passant


Essentialy you'll have to make a copy of the image file in your memory.

Pre .Net 4.0 (2.0,3.0,3.5) you'd have to create a FileStream and copy it to a MemoryStream and rewind it, as seen in another answer.

Since .Net 4.0 (4.0,4.5,...) Image.FromFile supports animated GIF's


If you work with .Net 4.0 or later following method will suffice:

Using System.IO, System.Drawing and System.Drawing.Imaging

 private void Form1_Load(object sender, EventArgs e)
    {
        string szTarget = "C:\\someImage.gif";

        PictureBox pic = new PictureBox();
        pic.Dock = DockStyle.Fill;

        Image img = Image.FromFile(szTarget);   // Load image fromFile into Image object

        MemoryStream mstr = new MemoryStream(); // Create a new MemoryStream
        img.Save(mstr, ImageFormat.Gif);        // Save Image to MemoryStream from Image object

        pic.Image = Image.FromStream(mstr); // Load Image from MemoryStream into PictureBox
        this.Controls.Add(pic); 

        img.Dispose(); // Dispose original Image object (fromFile)
        // after this you should be able to delete/manipulate the file

        File.Delete(szTarget);
    }
like image 34
MrPaulch Avatar answered Jan 03 '23 17:01

MrPaulch