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.
}

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

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

Pure.Krome
источник
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 +. Также может быть результатом неправильного пути сохранения ! У меня ушло полдня, чтобы это заметить. Поэтому убедитесь, что вы дважды проверили путь, чтобы сохранить изображение.

Houman
источник
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);
                }
            }
        }
    }
Mortb
источник
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 +».

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

Ура

Хоанг Нгха
источник