Бесплатный файл заблокирован новым растровым изображением (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.)

Прочитайте его в поток, создайте растровое изображение, закройте поток.

Другие вопросы по тегам