开发者

Free file locked by new Bitmap(filePath)

开发者 https://www.devze.com 2023-02-06 15:43 出处:网络
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:

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开发者_C百科 I unlock the first file?


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);


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.


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.


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);
}


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);
    }
}


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://stackoverflow.com/a/16576471/253938


(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)


I suggest to use PixelMap (available on NuGet) or Github

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

            PixelMap pixelMap = new PixelMap(bild);
            pictureBox1.Image = pixelMap.GetBitmap();


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

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号