Бесплатный файл заблокирован новым растровым изображением (filePath) - PullRequest
37 голосов
/ 26 января 2011

У меня есть изображение PictureBox, указывающее на определенный файл «A».Во время выполнения я хочу изменить изображение PictureBox на другое «B», но я получаю следующую ошибку:

«Произошло первое исключение типа« System.IO.IOException »в mscorlib.dll Дополнительная информация: процесс не может получить доступ к файлу «A», потому что он используется другим процессом. "

Я устанавливаю образ следующим образом:

pbAvatar.Image = new Bitmap(filePath);

Как разблокировать первый файл?

Ответы [ 9 ]

49 голосов
/ 12 февраля 2013

Вот мой подход к открытию изображения без блокировки файла ...

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.

Результаты, похоже, имеют смысл, поскольку вам нужно создать изображение дважды, используя метод копирования из растрового изображения.

UPDATE: если вам нужен BitMap, вы можете сделать:

return (Bitmap)Image.FromStream(ms);
39 голосов
/ 02 января 2012

Это распространенный вопрос блокировки, широко обсуждаемый в сети.

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

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

25 голосов
/ 26 января 2011

Использование файлового потока разблокирует файл после того, как он был прочитан и удален:

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

5 голосов
/ 02 ноября 2011

Вы не можете удалить / закрыть поток, пока объект растрового изображения все еще использует его.(То, понадобится ли растровому объекту доступ к нему снова, является только детерминированным, если вы знаете, с каким типом файла вы работаете и какие именно операции вы будете выполнять. - Например, для изображений в формате SOME .gif поток закрывается передконструктор возвращает.)

Clone создает «точную копию» растрового изображения (согласно документации; ILSpy показывает, что он вызывает нативные методы, так что это слишком много для отслеживания прямо сейчас), скорее всего, он копирует эти данные потока какну, иначе это не будет точная копия.

Лучше всего создать точную копию изображения с точностью до пикселя, хотя 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);
}
4 голосов
/ 18 апреля 2013

Вот техника, которую я сейчас использую, и, кажется, работает лучше всего.Он имеет преимущество создания объекта Bitmap с тем же форматом пикселей (24-битный или 32-битный) и разрешением (72 т / д, 96 т / д и т. Д.), Что и в исходном файле.использовать так часто, как это необходимо, следующим образом:

     Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));

Редактировать: Вот обновление вышеупомянутой техники: https://stackoverflow.com/a/16576471/253938

3 голосов
/ 10 января 2014

( Принятый ответ неверен. Когда вы попытаетесь LockBits(...) на клонированном растровом изображении, в конечном итоге вы столкнетесь с ошибками GDI +.)


Я вижу только 3 способавыйдите из этого:
  • скопируйте ваш файл во временный файл и откройте его простым способом new Bitmap(temp_filename)
  • откройте ваш файл, прочитайте изображение, создайте формат pixel-size-pixelformatскопируйте (не Clone()) и удалите первое растровое изображение
  • (принять функцию заблокированного файла)
1 голос
/ 09 января 2018

Насколько я знаю, это на 100% безопасно, поскольку полученное изображение на 100% создано в памяти, без каких-либо связанных ресурсов и без открытых потоков, оставленных в памяти. Он действует как любой другой Bitmap, созданный из конструктора, который не указывает никаких входных источников, и в отличие от некоторых других ответов здесь, он сохраняет оригинальный формат пикселей, то есть может использоваться в индексированных форматах.

На основании этого ответа , но с дополнительными исправлениями и без импорта из внешней библиотеки.

/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackoverflow.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);
    }
}
0 голосов
/ 20 декабря 2013

Три года назад я написал программу для просмотра изображений, чтобы посмотреть, смогу ли я это сделать.На прошлой неделе я добавил код для сканирования изображений.(Я планирую добавить это в программу генеалогии после того, как вытащу ошибки.) Чтобы обрезать неиспользуемую область, у меня есть вызов программы MSPaint с именем файла.Я там редактирую и сохраняю.Когда я закрываю Paint, изображение показывает изменения.
Я получаю ошибку в Paint о блокировке файла, если я что-то сделал с изображением.Я меняю программу для блокировки изображения, используя Image, FromStream ().Я больше не получаю это сообщение в Paint.(Моя программа на VB 2010.)

0 голосов
/ 26 января 2011

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

...