File.Delete在之前调用Image.FromFile时失败,尽管复制了加载的图像并破坏了原始图像

时间:2010-09-07 19:03:55

标签: c# system.drawing .net

已更新

我使用了以下解决方案(从流中加载图片),但是遇到了新问题。 img对象绝对正确Image类实例,所有字段都填充了正确的值。但是打电话给

img.Save("path/to/new/image.bmp");

on导致GDI +的新异常(System.Runtime.InteropServices.ExternalException,在GDI +接口中) - 我收到错误消息但是很好,我不知道如何翻译它。

原始问题

我遇到了C#.NET Framework 2.0的问题

基本上我想要实现:

Image img = Image.FromFile("Path/To/Image.bmp");
File.Delete("Path/To/Image.bmp"); // Exception, the file is in use!

删除原始文件时,将图像副本保留在内存中非常重要。我虽然有点奇怪的是,.NET仍然会锁定硬盘上的文件,尽管它不再需要任何操作(整个图像现在都在内存中,不是吗?)

所以我尝试了这个解决方案:

Image img = new Image(Image.FromFile("Path/To/Image.bmp")); // Make a copy
                    // this should immiedietaly destroy original loaded image
File.Delete("Path/To/Image.bmp"); // Still exception: the file is in use!

我能做到:

Image img = null;
using(Image imgTmp = Image.FromFile("Path/To/Image.bmp"))
{
    img = new Bitmap(imgTmp.Width, imgTmp.Height, imgTmp.PixelFormat);
    Graphics gdi = Graphics.FromIage(img);
    gdi.DrawImageUnscaled(imgTmp, 0, 0);
    gdi.Dispose();
    imgTmp.Dispose(); // just to make sure
}
File.Delete("Path/To/Image.bmp"); // Works fine
// So I have img!

但在我看来,这几乎就像使用nuke来杀死bug ......并引发了另一个问题:GDI很难支持将基于调色板的图像相互映射(并且调色板在我的收藏中占多数)。

我做错了吗?有没有更好的方法让内存中的Image和原始文件从硬盘中删除?

5 个答案:

答案 0 :(得分:17)

这应该可以解决问题:

  Image img = null;
  using (var stream = File.OpenRead(path)) {
    img = Image.FromStream(stream);
  }
  File.Delete(path);

更新:请勿使用上述代码!

我找到了相关的知识库文章:http://support.microsoft.com/?id=814675

解决方案是真正复制文章中概述的位图。我已经编写了文章提到的两种方式(第一种是你正在做的那种,第二种是你答案中的那种,但没有使用unsafe):

public static Image CreateNonIndexedImage(string path) { 
  using (var sourceImage = Image.FromFile(path)) { 
    var targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, 
      PixelFormat.Format32bppArgb); 
    using (var canvas = Graphics.FromImage(targetImage)) { 
      canvas.DrawImageUnscaled(sourceImage, 0, 0); 
    } 
    return targetImage; 
  } 
} 

[DllImport("Kernel32.dll", EntryPoint = "CopyMemory")] 
private extern static void CopyMemory(IntPtr dest, IntPtr src, uint length); 

public static Image CreateIndexedImage(string path) { 
  using (var sourceImage = (Bitmap)Image.FromFile(path)) { 
    var targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, 
      sourceImage.PixelFormat); 
    var sourceData = sourceImage.LockBits(
      new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 
      ImageLockMode.ReadOnly, sourceImage.PixelFormat); 
    var targetData = targetImage.LockBits(
      new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 
      ImageLockMode.WriteOnly, targetImage.PixelFormat); 
    CopyMemory(targetData.Scan0, sourceData.Scan0, 
      (uint)sourceData.Stride * (uint)sourceData.Height); 
    sourceImage.UnlockBits(sourceData); 
    targetImage.UnlockBits(targetData); 
    targetImage.Palette = sourceImage.Palette;
    return targetImage; 
  } 
} 

答案 1 :(得分:10)

你的问题是新的Image仍然知道它来自哪里,从旧的Image的复制构造函数中获得了文件句柄,因此运行时仍然知道它有一个打开的文件句柄。

您可以使用Stream来解决此问题:

Image image;
FileStream myStream = new FileStream(path);

try
{
    image = Image.FromStream(myStream);
}
finally
{    
    myStream.Close();
    myStream.Dispose();
}

//test that you have a valid Image and then go to work.

这是一个带有using子句的清洁版:

Image image;
using(FileStream myStream = new FileStream(path))
{
    image = Image.FromStream(myStream);
}
//a using clause calls Dispose() at the end of the block,
//which will call Close() as well
告诫者;我没有对此进行测试,也不能保证它能解决问题,但这似乎是合理的。直接使用Stream可以控制文件句柄,而不是图像实现,因此您可以确保程序在您需要时释放资源。

答案 2 :(得分:1)

刚刚放

GC.Collect();

最后它应该可以正常工作

答案 3 :(得分:0)

这很好用,缺点是它需要“不安全”的编译。

从加载完图片时加载图像的版本导致无法通过经典GDI +将图像保存到光盘

public static unsafe Image LoadImageSafe(string path)
{
    Image ret = null;
    using (Image imgTmp = Image.FromFile(path))
    {
        ret = new Bitmap(imgTmp.Width, imgTmp.Height, imgTmp.PixelFormat);
        if (imgTmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            ColorPalette pal = ret.Palette;
            for (int i = 0; i < imgTmp.Palette.Entries.Length; i++)
                pal.Entries[i] = Color.FromArgb(imgTmp.Palette.Entries[i].A,
                    imgTmp.Palette.Entries[i].R, imgTmp.Palette.Entries[i].G,
                    imgTmp.Palette.Entries[i].B);
            ret.Palette = pal;
            BitmapData bmd = ((Bitmap)ret).LockBits(new Rectangle(0, 0,
                imgTmp.Width, imgTmp.Height), ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);
            BitmapData bmd2 = ((Bitmap)imgTmp).LockBits(new Rectangle(0, 0,
                imgTmp.Width, imgTmp.Height), ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            Byte* pPixel = (Byte*)bmd.Scan0;
            Byte* pPixel2 = (Byte*)bmd2.Scan0;

            for (int Y = 0; Y < imgTmp.Height; Y++)
            {
                for (int X = 0; X < imgTmp.Width; X++)
                {
                    pPixel[X] = pPixel2[X];
                }
                pPixel += bmd.Stride;
                pPixel2 += bmd2.Stride;
            }

            ((Bitmap)ret).UnlockBits(bmd);
            ((Bitmap)imgTmp).UnlockBits(bmd2);
        }
        else
        {
            Graphics gdi = Graphics.FromImage(ret);
            gdi.DrawImageUnscaled(imgTmp, 0, 0);
            gdi.Dispose();
        }
        imgTmp.Dispose(); // just to make sure
    }
    return ret;
}

答案 4 :(得分:0)

以其他方式分享

try
{
var img = Image.FromFile(s);
var bmp = new Bitmap(img);
img.Dispose();
File.Delete(s);
}
catch { }