Ошибка 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;
}