Image.Save (..) вызывает исключение GDI +, поскольку поток памяти закрыт


109

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

это код:

[TestMethod]
public void TestMethod1()
{
    // Grab the binary data.
    byte[] data = File.ReadAllBytes("Chick.jpg");

    // Read in the data but do not close, before using the stream.
    Stream originalBinaryDataStream = new MemoryStream(data);
    Bitmap image = new Bitmap(originalBinaryDataStream);
    image.Save(@"c:\test.jpg");
    originalBinaryDataStream.Dispose();

    // Now lets use a nice dispose, etc...
    Bitmap2 image2;
    using (Stream originalBinaryDataStream2 = new MemoryStream(data))
    {
        image2 = new Bitmap(originalBinaryDataStream2);
    }

    image2.Save(@"C:\temp\pewpew.jpg"); // This throws the GDI+ exception.
}

Есть ли у кого-нибудь предложения, как я могу сохранить изображение с закрытым потоком? Я не могу рассчитывать на то, что разработчики не забудут закрыть поток после сохранения изображения. Фактически, у разработчика НЕ ​​БЫЛО ИДЕИ о том, что изображение было создано с использованием потока памяти (потому что это происходит в каком-то другом коде, где-то еще).

Я действительно смущен :(


1
Я получил этот комментарий от @HansPassant в другом вопросе . Вы получите это исключение всякий раз, когда кодек не сможет записать файл. Хороший отладочный оператор для добавления - это System.IO.File.WriteAllText (путь, «тест») перед вызовом Save (), он проверяет базовую возможность создания файла. Теперь вы получите хорошее исключение, которое сообщит вам, что вы сделали не так.
— Хуан Карлос Оропеза,

Вы должны image2.Save внутри usingблока. Я думаю, что он originalBinaryDataStream2 был автоматически утилизирован в конце использования. И это вызовет исключение.
— taynguyen

Ответы:


172

Поскольку это MemoryStream, вам действительно не нужно закрывать поток - ничего плохого не произойдет, если вы этого не сделаете, хотя, очевидно, это хорошая практика в любом случае избавляться от всего одноразового. (Подробнее об этом см. В этом вопросе .)

Однако вы должны удалить Bitmap - и это закроет для вас поток. Обычно, когда вы передаете конструктору Bitmap поток, он «владеет» потоком, и вам не следует его закрывать. Как говорится в документации для этого конструктора :

Вы должны держать поток открытым в течение всего времени существования Bitmap.

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


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

Есть какие-нибудь комментарии о том, как это сделать, если мы хотим подчиняться правилу CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
— Патрик Салапски,

@Patrick: Это просто не применимо - вы фактически передали право собственности на ресурс. Самое близкое, что вы могли бы сделать, это создать оболочку NonClosingStream, которая игнорирует вызов Dispose. Я думаю, что у меня есть один в MiscUtil - не уверен ...
— Джон Скит

Спасибо за информацию @Jon. Для меня по какой-то странной причине он работал даже с dispose () в локальной среде разработки, но не работал в производственной среде.
— Оксон

94

Общая ошибка произошла в GDI +. Также может быть результатом неправильного пути сохранения ! У меня ушло полдня, чтобы это заметить. Поэтому убедитесь, что вы дважды проверили путь, чтобы сохранить изображение.


4
Я рад, что увидел это, мой путь был C\Users\mason\Desktop\pic.png. Отсутствует двоеточие! Я бы потратил вечность, прежде чем заметил бы это.
— Мейсон

4
Неправильно также означает, что папки, в которую вы хотите сохранить изображение, не существует.
— Roemer

14

Возможно, стоит упомянуть, что если каталог C: \ Temp не существует, он также вызовет это исключение, даже если ваш поток все еще существует.


+1 Похоже, что это исключение встречается в самых разных сценариях. Неверный путь - это тот, с которым я столкнулся сегодня.
— Кирк Бродхерст,

4

У меня была та же проблема, но на самом деле причина заключалась в том, что у приложения не было разрешения на сохранение файлов на C. Когда я перешел на «D: \ ..», изображение было сохранено.


2

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

При рисовании изображения: System.Runtime.InteropServices.ExternalException: в GDI произошла общая ошибка

    public static Image ToImage(this byte[] bytes)
    {
        using (var stream = new MemoryStream(bytes))
        using (var image = Image.FromStream(stream, false, true))
        {
            return new Bitmap(image);
        }
    }

    [Test]
    public void ShouldCreateImageThatCanBeSavedWithoutOpenStream()
    {
        var imageBytes = File.ReadAllBytes("bitmap.bmp");

        var image = imageBytes.ToImage();

        image.Save("output.bmp");
    }

1
Это не работает точно; в вашем коде в ToImage () локальный «образ» будет правильно иметь .RawFormat того, чем был исходный файл (jpeg, png и т. д.), тогда как возвращаемое значение ToImage () неожиданно будет иметь .RawFormat MemoryBmp.
— Патрик Салапски

RawFormatХотя не уверен, насколько это важно. Если вы хотите использовать это, извлеките его из объекта где-нибудь по пути, но в целом сохраните его как тот тип, который вы действительно хотите иметь .
— Nyerguds

2

Вы можете попробовать создать еще одну копию растрового изображения:

using (var memoryStream = new MemoryStream())
{
    // write to memory stream here

    memoryStream.Position = 0;
    using (var bitmap = new Bitmap(memoryStream))
    {
        var bitmap2 = new Bitmap(bitmap);
        return bitmap2;
    }
}

2

Эта ошибка возникла у меня, когда я пытался из Citrix. Папка изображений была установлена ​​на C: \ на сервере, для которого у меня нет прав. Как только папка с изображениями была перемещена на общий диск, ошибка исчезла.


1

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


1

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


0

Одно странное решение, которое заставило мой код работать. Откройте изображение в краске и сохраните его как новый файл в том же формате (.jpg). Теперь попробуйте с этим новым файлом, и он работает. Это ясно объясняет вам, что файл может быть каким-то образом поврежден. Это может помочь, только если в вашем коде исправлены все остальные ошибки.


0

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

C:\Program Files (x86)\some_directory

и .exe не был запущен для запуска от имени администратора, я надеюсь, что это может помочь тем, у кого такая же проблема.


0

Для меня код ниже разбился A generic error occurred in GDI+в строке, которая сохраняет вMemoryStream . Код выполнялся на веб-сервере, и я решил его, остановив и запустив пул приложений, на котором работал сайт.

Должна быть какая-то внутренняя ошибка в GDI +

    private static string GetThumbnailImageAsBase64String(string path)
    {
        if (path == null || !File.Exists(path))
        {
            var log = ContainerResolver.Container.GetInstance<ILog>();
            log.Info($"No file was found at path: {path}");
            return null;
        }

        var width = LibraryItemFileSettings.Instance.ThumbnailImageWidth;

        using (var image = Image.FromFile(path))
        {
            using (var thumbnail = image.GetThumbnailImage(width, width * image.Height / image.Width, null, IntPtr.Zero))
            {
                using (var memoryStream = new MemoryStream())
                {
                    thumbnail.Save(memoryStream, ImageFormat.Png); // <= crash here 
                    var bytes = new byte[memoryStream.Length];
                    memoryStream.Position = 0;
                    memoryStream.Read(bytes, 0, bytes.Length);
                    return Convert.ToBase64String(bytes, 0, bytes.Length);
                }
            }
        }
    }

0

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

Установка источника элемента изображения на растровое изображение предотвращает сохранение файла. Даже установка Source = null, похоже, не освобождает файл.

Теперь я просто никогда не использую изображение в качестве элемента Source of Image, поэтому я могу перезаписать его после редактирования!

РЕДАКТИРОВАТЬ

Услышав о свойстве CacheOption (благодаря @Nyerguds), я нашел решение: поэтому вместо использования конструктора Bitmap я должен установить Uri после настройки CacheOption BitmapCacheOption.OnLoad. ( Image1Ниже Imageэлемент Wpf )

Вместо того

Image1.Source = new BitmapImage(new Uri(filepath));

Использование:

var image = new BitmapImage();
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(filepath);
image.EndInit();
Image1.Source = image;

См. Это: Кэширование изображений WPF


1
У изображений WPF есть специальный параметр, BitmapCacheOption.OnLoadчтобы отключить их от источника загрузки.
— Nyerguds

Спасибо @Nyerguds, до вашего комментария я не смог задать правильные вопросы
— mkb

0

Попробуйте этот код:

static void Main(string[] args)
{
    byte[] data = null;
    string fullPath = @"c:\testimage.jpg";

    using (MemoryStream ms = new MemoryStream())
    using (Bitmap tmp = (Bitmap)Bitmap.FromFile(fullPath))
    using (Bitmap bm = new Bitmap(tmp))
    {
        bm.SetResolution(96, 96);
        using (EncoderParameters eps = new EncoderParameters(1))
        {   
            eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
            bm.Save(ms, GetEncoderInfo("image/jpeg"), eps);
        }

        data = ms.ToArray();
    }

    File.WriteAllBytes(fullPath, data);
}

private static ImageCodecInfo GetEncoderInfo(string mimeType)
{
        ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();

        for (int j = 0; j < encoders.Length; ++j)
        {
            if (String.Equals(encoders[j].MimeType, mimeType, StringComparison.InvariantCultureIgnoreCase))
                return encoders[j];
        }
    return null;
}

0

Я использовал обработчик изображений для изменения размера изображений, и однажды я получил исключение «Общая ошибка произошла в GDI +».

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

Ура

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.