Ошибка File.Delete при вызове Image.FromFile до этого, несмотря на создание копии загруженного изображения и уничтожение исходного

ОБНОВЛЕНО

Я использовал приведенные ниже решения (загрузка изображения из потока), но получил новую проблему. Объект img абсолютно корректный экземпляр класса Image со всеми полями, заполненными правильными значениями. Но зовет

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

это приводит к новому исключению для 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 для уничтожения ошибок... и возникает еще одна проблема: GDI плохо поддерживает рисование изображений на основе палитры друг на друге (а палитры составляют большинство в моей коллекции).

Я делаю что-то неправильно? Есть ли лучший способ удалить изображение из памяти и удалить исходный файл с жесткого диска?

5 ответов

Решение

Это должно сделать трюк:

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

Ваша проблема в том, что новое изображение все еще знает, откуда оно взято, получив дескриптор файла от конструктора копирования старого изображения, и поэтому среда выполнения все еще знает, что у него есть открытый дескриптор файла.

Вместо этого вы можете обойти это поведение с помощью 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 дает вам, а не реализацию образа, контроль над дескриптором файла, так что вы можете быть уверены, что ваша программа высвобождает ресурсы, когда вы этого хотите.

Как поделитесь другим способом

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

Просто положи

GC.Collect();

в конце все должно работать нормально

Это прекрасно работает, недостатком является то, что требуется "небезопасная" компиляция.

Версия, когда Image загружается из потока, который завершается при загрузке, приводит к невозможности сохранить образ на диск с помощью классического 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;
}
Другие вопросы по тегам