Бесплатный файл заблокирован новым растровым изображением (filePath)
У меня есть изображение PictureBox, указывающее на определенный файл "A". Во время выполнения я хочу изменить изображение PictureBox на другое "B", но я получаю следующую ошибку:
"Произошло первое исключение типа" System.IO.IOException "в mscorlib.dll. Дополнительная информация: процесс не может получить доступ к файлу" A ", поскольку он используется другим процессом".
Я устанавливаю изображение следующим образом:
pbAvatar.Image = new Bitmap(filePath);
Как я могу разблокировать первый файл?
9 ответов
Использование файлового потока разблокирует файл, как только он будет прочитан и утилизирован:
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();
}
Редактировать: Обновлено, чтобы разрешить удаление исходного растрового изображения и разрешить закрытие FileStream.
ЭТОТ ОТВЕТ НЕ БЕЗОПАСЕН - смотрите комментарии и смотрите обсуждение в ответе net_prog. Правка для использования Clone
не делает его более безопасным - клонирует все поля, включая ссылку на файловый поток, что при определенных обстоятельствах вызовет проблему.
Вот мой подход к открытию изображения без блокировки файла...
public static Image FromFile(string path)
{
var bytes = File.ReadAllBytes(path);
var ms = new MemoryStream(bytes);
var img = Image.FromStream(ms);
return img;
}
ОБНОВЛЕНИЕ: я сделал некоторые тесты перфоратора, чтобы видеть, какой метод был самым быстрым. Я сравнил его с ответом @net_progs "копировать из растрового изображения" (который кажется наиболее близким к правильному, хотя и имеет некоторые проблемы). Я загрузил изображение 10000 раз для каждого метода и рассчитал среднее время на изображение. Вот результаты:
Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.
Результаты, кажется, имеют смысл, так как вы должны создать изображение дважды, используя метод copy from bitmap.
ОБНОВЛЕНИЕ: если вам нужен BitMap, вы можете сделать:
return (Bitmap)Image.FromStream(ms);
Это распространенный вопрос блокировки, широко обсуждаемый в сети.
Предложенный трюк с потоком не будет работать, на самом деле он работает изначально, но вызывает проблемы позже. Например, он загрузит изображение, и файл останется разблокированным, но если вы попытаетесь сохранить загруженное изображение с помощью метода Save(), он выдаст общее исключение GDI+.
Далее, способ репликации на пиксель не кажется надежным, по крайней мере, шумным.
То, что я нашел работающим, описано здесь: http://www.eggheadcafe.com/microsoft/Csharp/35017279/imagefromfile--locks-file.aspx
Вот как изображение должно быть загружено:
Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
{
img = new Bitmap(bmpTemp);
}
Я искал решение этой проблемы, и этот метод до сих пор отлично работал для меня, поэтому я решил описать его, поскольку обнаружил, что многие люди советуют использовать неправильный потоковый подход здесь и в Интернете.
Вы не можете удалить / закрыть поток, пока объект растрового изображения все еще использует его. (То, будет ли растровому объекту снова нужен доступ к нему, является только детерминированным, если вы знаете, с каким типом файла вы работаете, и какие именно операции вы будете выполнять. - Например, для изображений в формате SOME .gif поток перед этим закрывается. конструктор возвращается.)
Clone создает "точную копию" растрового изображения (согласно документации; ILSpy показывает, что он вызывает нативные методы, поэтому его слишком сложно отследить прямо сейчас), скорее всего, он также копирует данные Stream - иначе это не будет точная копия.
Лучше всего создать точную точную копию изображения - хотя YMMV (с определенными типами изображений может быть больше одного кадра, или вам, возможно, придется копировать данные палитры). Но для большинства изображений это работает:
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;
}
И тогда вы можете вызвать его так:
using (Stream s = ...)
{
Bitmap x = LoadImage(s);
}
Насколько я знаю, это на 100% безопасно, поскольку полученное изображение на 100% создано в памяти, без каких-либо связанных ресурсов и без открытых потоков, оставленных в памяти. Действует как любой другой Bitmap
он создан из конструктора, который не указывает никаких входных источников, и в отличие от некоторых других ответов здесь, он сохраняет оригинальный формат пикселей, то есть его можно использовать в индексированных форматах.
На основании этого ответа, но с дополнительными исправлениями и без импорта из внешней библиотеки.
/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackru.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;
}
Чтобы позвонить, просто используйте:
/// <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);
}
}
Или из байтов:
/// <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);
}
}
Вот техника, которую я сейчас использую, и, кажется, работает лучше всего. Он имеет преимущество в создании объекта Bitmap с тем же форматом пикселей (24-битный или 32-битный) и разрешением (72 т / д, 96 т / д и т. Д.), Что и в исходном файле.
// 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();
Это может использоваться так часто, как необходимо, следующим образом:
Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));
Изменить: Вот обновление вышеупомянутой техники: /questions/23519902/kak-preobrazovat-izobrazhenie-v-bajtovyij-massiv/23519924#23519924
( Принятый ответ неверен. Когда вы пытаетесь LockBits(...)
на клонированном растровом изображении вы в конечном итоге столкнетесь с ошибками GDI+.)
Я вижу только 3 способа выйти из этого:
- скопируйте ваш файл во временный файл и откройте этот простой способ
new Bitmap(temp_filename)
- откройте файл, прочитайте изображение, создайте копию в формате пиксель-размер-пиксель (не
Clone()
) и избавиться от первого растрового изображения - (принять функцию заблокированного файла)
Я предлагаю использовать PixelMap (доступно на NuGet) или Github
Очень прост в использовании и намного быстрее, чем стандартный Bitmap из.NET.
PixelMap pixelMap = new PixelMap(bild);
pictureBox1.Image = pixelMap.GetBitmap();
Три года назад я написал программу для просмотра изображений, чтобы посмотреть, смогу ли я это сделать. На прошлой неделе я добавил код для сканирования изображений. (Я планирую добавить это в программу генеалогии после того, как я получу ошибки.) Чтобы обрезать неиспользуемую область, у меня есть вызов программы MSPaint с именем файла. Я там редактирую и сохраняю. Когда я закрываю Paint, изображение показывает изменения.
Я получаю ошибку в Paint о блокировке файла, если я что-то сделал с изображением. Я меняю программу для блокировки изображения, используя Image,FromStream(). Я больше не получаю это сообщение в Paint. (Моя программа на VB 2010.)
Прочитайте его в поток, создайте растровое изображение, закройте поток.