Free file locked by new Bitmap(filePath)
Asked Answered
B

9

48

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?

Burnedout answered 26/1, 2011 at 11:17 Comment(0)
O
30

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.

Ozonosphere answered 26/1, 2011 at 11:54 Comment(6)
NO! Bad! "You must keep the stream open for the lifetime of the Bitmap." -- MSDN. As posted below by @Computer Linguist.Extravasation
@Extravasation 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 harmfulLungki
@Extravasation Updated the answer with a technique I found elsewhere to get around the bitmap lifetime.Ozonosphere
This answers still fails.Importance
This doesn't work, it changes the PixelFormat and maybe other thingsJoplin
DUBIOUS. As mentioned in BrainSlugs answer, and verified by Anlo, "Clone" does not help the situation. Other answers are safer.Graptolite
M
67

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);
Mariann answered 12/2, 2013 at 16:37 Comment(10)
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.Somersault
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.Outstretched
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.Mariann
Thanks, this helped me a lot.Ingalls
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...!Armored
if you need a Bitmap you can do : return (Bitmap)Image.FromStream(ms);Jobye
@Mariann 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?Dantedanton
@Dantedanton You will definitely want to call Dispose on the image. It holds native GDI objects that must be destroyed.Mariann
I couldn't open a new Form due to an image being opened at runtime from the same directory. This answer solved the issue.Drumbeat
@Jobye 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.Caravette
C
50

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.

Celestina answered 2/1, 2012 at 14:1 Comment(5)
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.Counterintelligence
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.Graptolite
@Graptolite 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.Caravette
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.Graptolite
@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().Rigsby
O
30

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.

Ozonosphere answered 26/1, 2011 at 11:54 Comment(6)
NO! Bad! "You must keep the stream open for the lifetime of the Bitmap." -- MSDN. As posted below by @Computer Linguist.Extravasation
@Extravasation 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 harmfulLungki
@Extravasation Updated the answer with a technique I found elsewhere to get around the bitmap lifetime.Ozonosphere
This answers still fails.Importance
This doesn't work, it changes the PixelFormat and maybe other thingsJoplin
DUBIOUS. As mentioned in BrainSlugs answer, and verified by Anlo, "Clone" does not help the situation. Other answers are safer.Graptolite
E
7

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);
}
Extravasation answered 26/1, 2011 at 11:17 Comment(2)
To preserve dpi, after retval = ...; add: retval.SetResolution(b.HorizontalResolution, b.VerticalResolution);Graptolite
This will crash on indexed images, because the Graphics class can't handle them.Caravette
C
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);
    }
}
Caravette answered 9/1, 2018 at 14:38 Comment(7)
Do note, I have no clue how this would need to be done in case of an animated gif...Caravette
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.Ushijima
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?Caravette
@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.Caravette
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.Ushijima
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.Caravette
I had pretty much copied the code as-is, so not sure. Oh well, I got it working a different way (Graphics.DrawImage).Ushijima
C
4

Here's the technique I'm currently using, and seems to work best. It has the advantage of producing a Bitmap object with the same pixel format (24-bit or 32-bit) and resolution (72 dpi, 96 dpi, whatever) as the source file.

  // ImageConverter object used to convert JPEG byte arrays into Image objects. This is static 
  //  and only gets instantiated once.
  private static readonly ImageConverter _imageConverter = new ImageConverter();

This can be used as often as needed, as follows:

     Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));

Edit: Here's an update of the above technique: https://mcmap.net/q/32838/-how-to-convert-image-to-byte-array-duplicate

Counterintelligence answered 18/4, 2013 at 14:30 Comment(4)
@Caravette I would assume so, but I don't know for sure. Why don't you try it?Counterintelligence
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.Caravette
@Caravette 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.Counterintelligence
Oh, it works. I just don't find it a very clean solution.Caravette
I
3

(The accepted answer is wrong. When you try to LockBits(...) on the cloned bitmap eventually you will encounter GDI+ errors.)


I see only 3 ways to get out of this:
  • copy your file to a temporary file and open that the easy way new Bitmap(temp_filename)
  • open your file, read image, create a pixel-size-pixelformat copy (don't Clone()) and dispose the first bitmap
  • (accept the locked-file-feature)
Importance answered 10/1, 2014 at 10:35 Comment(2)
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.Graptolite
... 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.Graptolite
F
0

I suggest to use PixelMap which is available on NuGet

Very easy to use and much faster than standard Bitmap from .NET

            PixelMap pixelMap = new PixelMap(path);
            pictureBox1.Image = pixelMap.GetBitmap();
Floyd answered 10/8, 2020 at 11:48 Comment(0)
R
-2

Read it into the stream, create bitmap, close the stream.

Regenaregency answered 26/1, 2011 at 11:21 Comment(2)
You have to clone the bitmap - Bitmaps hold the stream open, or else throw exceptions. See msdn.microsoft.com/en-us/library/z7ha67kw.aspxBaluchistan
@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.Graptolite

© 2022 - 2024 — McMap. All rights reserved.