Free file locked by new Bitmap(filePath)

50,041

Solution 1

Using a filestream will unlock the file once it has been read from and disposed:

using (var fs = new System.IO.FileStream("c:\\path to file.bmp", System.IO.FileMode.Open))
{
    var bmp = new Bitmap(fs);
    pct.Image = (Bitmap) bmp.Clone();
}

Edit: Updated to allow the original bitmap to be disposed, and allow the FileStream to be closed.

THIS ANSWER IS NOT SAFE - See comments, and see discussion in net_prog's answer. The Edit to use Clone does not make it any safer - Clone clones all fields, including the filestream reference, which in certain circumstances will cause a problem.

Solution 2

Here is my approach to opening an image without locking the file...

public static Image FromFile(string path)
{
    var bytes = File.ReadAllBytes(path);
    var ms = new MemoryStream(bytes);
    var img = Image.FromStream(ms);
    return img;
}

UPDATE: I did some perf tests to see which method was the fastest. I compared it to @net_progs "copy from bitmap" answer (which seems to be the closest to correct, though does have some issues). I loaded the image 10000 times for each method and calculated the average time per image. Here are the results:

Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.

The results seem to make sense since you have to create the image twice using the copy from bitmap method.

UPDATE: if you need a BitMap you can do:

return (Bitmap)Image.FromStream(ms);

Solution 3

This is a common locking question widely discussed over the web.

The suggested trick with stream will not work, actually it works initially, but causes problems later. For example, it will load the image and the file will remain unlocked, but if you try to save the loaded image via Save() method, it will throw a generic GDI+ exception.

Next, the way with per pixel replication doesn't seem to be solid, at least it is noisy.

What I found working is described here: http://www.eggheadcafe.com/microsoft/Csharp/35017279/imagefromfile--locks-file.aspx

This is how the image should be loaded:

Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
{
    img = new Bitmap(bmpTemp);
}

I was looking for a solution to this problem and this method works fine for me so far, so I decided to describe it, since I found that many people advise the incorrect stream approach here and over the web.

Solution 4

You can't dispose / close a stream while a bitmap object is still using it. (Whether the bitmap object will need access to it again is only deterministic if you know what type of file you are working with and exactly what operations you will be performing. -- for example for SOME .gif format images, the stream is closed before the constructor returns.)

Clone creates an "exact copy" of the bitmap (per documentation; ILSpy shows it calling native methods, so it's too much to track down right now) likely, it copies that Stream data as well -- or else it wouldn't be an exact copy.

Your best bet is creating a pixel-perfect replica of the image -- though YMMV (with certain types of images there may be more than one frame, or you may have to copy palette data as well.) But for most images, this works:

static Bitmap LoadImage(Stream stream)
{
    Bitmap retval = null;

    using (Bitmap b = new Bitmap(stream))
    {
        retval = new Bitmap(b.Width, b.Height, b.PixelFormat);
        using (Graphics g = Graphics.FromImage(retval))
        {
            g.DrawImage(b, Point.Empty);
            g.Flush();
        }
    }

    return retval;
}

And then you can invoke it like such:

using (Stream s = ...)
{
    Bitmap x = LoadImage(s);
}

Solution 5

As far as I know, this is 100% safe, since the resulting image is 100% created in memory, without any linked resources, and with no open streams left behind in memory. It acts like any other Bitmap that's created from a constructor that doesn't specify any input sources, and unlike some of the other answers here, it preserves the original pixel format, meaning it can be used on indexed formats.

Based on this answer, but with extra fixes and without external library import.

/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes.
/// </summary>
/// <param name="sourceImage">The image to clone</param>
/// <returns>The cloned image</returns>
public static Bitmap CloneImage(Bitmap sourceImage)
{
    Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
    Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
    targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
    BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
    BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
    Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
    Int32 h = sourceImage.Height;
    Int32 origStride = sourceData.Stride;
    Boolean isFlipped = origStride < 0;
    origStride = Math.Abs(origStride); // Fix for negative stride in BMP format.
    Int32 targetStride = targetData.Stride;
    Byte[] imageData = new Byte[actualDataWidth];
    IntPtr sourcePos = sourceData.Scan0;
    IntPtr destPos = targetData.Scan0;
    // Copy line by line, skipping by stride but copying actual data width
    for (Int32 y = 0; y < h; y++)
    {
        Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
        Marshal.Copy(imageData, 0, destPos, actualDataWidth);
        sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
        destPos = new IntPtr(destPos.ToInt64() + targetStride);
    }
    targetImage.UnlockBits(targetData);
    sourceImage.UnlockBits(sourceData);
    // Fix for negative stride on BMP format.
    if (isFlipped)
        targetImage.RotateFlip(RotateFlipType.Rotate180FlipX);
    // For indexed images, restore the palette. This is not linking to a referenced
    // object in the original image; the getter of Palette creates a new object when called.
    if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
        targetImage.Palette = sourceImage.Palette;
    // Restore DPI settings
    targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
    return targetImage;
}

To call, simply use:

/// <summary>Loads an image without locking the underlying file.</summary>
/// <param name="path">Path of the image to load</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(String path)
{
    using (Bitmap sourceImage = new Bitmap(path))
    {
        return CloneImage(sourceImage);
    }
}

Or, from bytes:

/// <summary>Loads an image from bytes without leaving open a MemoryStream.</summary>
/// <param name="fileData">Byte array containing the image to load.</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(Byte[] fileData)
{
    using (MemoryStream stream = new MemoryStream(fileData))
    using (Bitmap sourceImage = new Bitmap(stream))    {
    {
        return CloneImage(sourceImage);
    }
}
Share:
50,041

Related videos on Youtube

MrCatacroquer
Author by

MrCatacroquer

Updated on August 11, 2020

Comments

  • MrCatacroquer
    MrCatacroquer almost 4 years

    I have the Image of a PictureBox pointing to a certain file "A". At execution time I want to change the Image of the PictureBox to a different one "B" but I get the following error:

    "A first chance exception of type 'System.IO.IOException' occurred in mscorlib.dll Additional information: The process cannot access the file "A" because it is being used by another process."

    I'm setting the Image as follows:

    pbAvatar.Image = new Bitmap(filePath);
    

    How can I unlock the first file?

  • Lilith River
    Lilith River about 13 years
    You have to clone the bitmap - Bitmaps hold the stream open, or else throw exceptions. See msdn.microsoft.com/en-us/library/z7ha67kw.aspx
  • BrainSlugs83
    BrainSlugs83 over 12 years
    NO! Bad! "You must keep the stream open for the lifetime of the Bitmap." -- MSDN. As posted below by @Computer Linguist.
  • Sam Saffron
    Sam Saffron over 12 years
    @BrainSlugs83 your comment is correct, however we can not control what our users do, you should downvote incorrect answer if you feel they are wrong or actively harmful
  • dbort
    dbort over 12 years
    @BrainSlugs83 Updated the answer with a technique I found elsewhere to get around the bitmap lifetime.
  • RenniePet
    RenniePet about 11 years
    There's a minor problem with this in that the pixel format will always be 32-bit ARGB and the resolution will always be 96 dpi. This may be OK for most applications, but for some applications it's important to try to maintain the pixel format and resolution of the source file.
  • Bitterblue
    Bitterblue over 10 years
    This answers still fails.
  • stricq
    stricq about 10 years
    You still have to keep the stream open for the life of the image, which means the data is in memory twice. This is impracticable for larger images.
  • JochemKempe
    JochemKempe about 9 years
    This doesn't work, it changes the PixelFormat and maybe other things
  • ChrisW
    ChrisW almost 9 years
    In your code example it's not clear whether/when you should (and/or do) call Dispose on the MemoryStream and/or on the Image. Peraps I shouldn't complain however that (i.e. calling Dispose) is part of the essence of this question.
  • Nikwin
    Nikwin almost 9 years
    The image uses the stream after it is created (sorry, I don't recall the scenarios where it does). If you dispose of it, it will throw an exception. It's not actually important to call Dispose on a MemoryStream since it doesn't use any system resources (such as a file lock when using a FileStream). The GC will clean it up properly when it is time.
  • J3soon
    J3soon almost 9 years
    Thanks, this helped me a lot.
  • ToolmakerSteve
    ToolmakerSteve over 7 years
    DrawImage into a Bitmap with matching characteristics seems the simplest way to transfer the Bitmap, without losing pixel format. To preserve dpi, see my comment on that answer.
  • ToolmakerSteve
    ToolmakerSteve over 7 years
    To preserve dpi, after retval = ...; add: retval.SetResolution(b.HorizontalResolution, b.VerticalResolution);
  • ToolmakerSteve
    ToolmakerSteve over 7 years
    DUBIOUS. As mentioned in BrainSlugs answer, and verified by Anlo, "Clone" does not help the situation. Other answers are safer.
  • ToolmakerSteve
    ToolmakerSteve almost 7 years
    @NathanaelJones - to be precise, must Copy the bitmap (see other answers for various ways to do so), not Clone; a clone contains all the same fields, including the stream reference that causes the exception.
  • ToolmakerSteve
    ToolmakerSteve almost 7 years
    Good idea to copy file to a temporary file (that you are willing to lock)! For the second alternative ("create a pixel.. copy"), please note two answers that give code for ways to safely copy: Brian's and Rennie's.
  • ToolmakerSteve
    ToolmakerSteve almost 7 years
    ... also net prog's answer is another safe way to copy. Note that all of these in-memory approaches double memory consumption compared to opening from a file (the bitmap itself uses memory, and those techniques also hold data in a memory stream or array that acts as a backing store in case the bitmap needs to be recreated); for a very large file, the technique of copying to a temporary file is superior, as the file acts as the persistent backing store.
  • Martin Davies
    Martin Davies over 6 years
    This should be the accepted answer in my opinion. This has helped me immensely in a legacy product with recurring file access issues, thank you for this. So simple when you see it...!
  • Nyerguds
    Nyerguds over 6 years
    @ToolmakerSteve This does not work for indexed formats, though. The only way to do it for those is using LockBits to copy of the backing array, and then restore the palette.
  • Nyerguds
    Nyerguds over 6 years
    Do note, I have no clue how this would need to be done in case of an animated gif...
  • RenniePet
    RenniePet over 6 years
    @Nyerguds I would assume so, but I don't know for sure. Why don't you try it?
  • Nyerguds
    Nyerguds over 6 years
    It works... but, I checked the code behind this, and it seems that internally, all it does is make a byte stream and call Image.FromStream() on it, with a not-very-reassuring note in the reference MSDN code note that "hopefully GDI+ knows what to do with this!" So like the other solutions, this leaves an unclosed stream somewhere in memory.
  • RenniePet
    RenniePet over 6 years
    @Nyerguds What you say may be true, but it is an unclosed (or undiscarded) memory stream, not an open file stream and a locked file. And I'm thinking that garbage collection will dispose it and recover the memory when the Image object is no longer referenced anywhere in your program.
  • Nyerguds
    Nyerguds over 6 years
    Oh, it works. I just don't find it a very clean solution.
  • ToolmakerSteve
    ToolmakerSteve over 6 years
    FYI, also see Brian's answer, which avoids creating bitmap twice, so less overhead; instead it uses a byte array as the intermediate to "break" the dependency on the file.
  • Kasey Speakman
    Kasey Speakman about 6 years
    This is broken for me on non-index images. When trying to Marshal.Copy the last row, get an AccessViolationException. I guess it is reading past the end of the source array.
  • Nyerguds
    Nyerguds about 6 years
    The calculations are correct, and I've been using this for ages without any problems, for both indexed and non-indexed images. You sure you didn't do something else wrong, like closing a stream after loading an image from it?
  • Nyerguds
    Nyerguds about 6 years
    @KaseySpeakman FYI: An Image object created from a stream will need the stream to remain open for the entire life cycle of the image object. Unlike with files, there is nothing actively enforcing this, but after the stream is closed, the image will give errors when saved, cloned or otherwise manipulated.
  • Kasey Speakman
    Kasey Speakman about 6 years
    Ah, I thought the purpose was to disconnect the image from the backing resource like the stream by creating a new image. So I am closing the source image and its stream after cloning to the target image.
  • Nyerguds
    Nyerguds about 6 years
    Yeah, but the disconnected one is the newly created image; the image that is fed into this function still needs to be valid during this operation. The usage examples at the bottom show how it should be used.
  • Kasey Speakman
    Kasey Speakman about 6 years
    I had pretty much copied the code as-is, so not sure. Oh well, I got it working a different way (Graphics.DrawImage).
  • elle0087
    elle0087 almost 6 years
    if you need a Bitmap you can do : return (Bitmap)Image.FromStream(ms);
  • JimmyUK1
    JimmyUK1 over 5 years
    @Brian Do you know if is necessary to call Dispose on an Image based on a MemoryStream then? Does Image hold on to any possibly unmanaged resources besides the stream?
  • Nikwin
    Nikwin over 5 years
    @asgerhallas You will definitely want to call Dispose on the image. It holds native GDI objects that must be destroyed.
  • Ctrl S
    Ctrl S over 5 years
    I couldn't open a new Form due to an image being opened at runtime from the same directory. This answer solved the issue.
  • Nyerguds
    Nyerguds almost 5 years
    @elle0087 Image.FromFile() and Image.FromStream() really just call the Bitmap constructor and then lose the specific Bitmap type in their return value. It's cleaner to just call new Bitmap(...) directly.
  • Nyerguds
    Nyerguds over 4 years
    This will crash on indexed images, because the Graphics class can't handle them.
  • Tobias Knauss
    Tobias Knauss almost 3 years
    @Nyerguds: Thanks, that saved me some time. In combination with the hint from @RenniePet, it's a good solution: 1) Load Bitmap #1 from file. 2) Create new Bitmap #2 with same size and pixel format, set resolution, set palette. 3) LockBits on both bitmaps, int copySize = bitmapData1.Stride * bitmapData1.Height Buffer.BlockCopy(bitmapData1.ToPointer(), bitmapData2.ToPointer(), copySize, copySize) from first to second bitmap (this will require unsafe on the method, but you can work around it using 2x Marshal.Copy().