Image.Save (..) lança uma exceção GDI + porque o fluxo de memória está fechado


108

Eu tenho alguns dados binários que quero salvar como uma imagem. Quando tento salvar a imagem, ele lança uma exceção se o fluxo de memória usado para criar a imagem foi fechado antes de salvar. A razão de eu fazer isso é porque estou criando imagens dinamicamente e, como tal ... preciso usar um fluxo de memória.

este é o código:

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

Alguém tem alguma sugestão de como eu poderia salvar uma imagem com o stream fechado? Não posso contar com os desenvolvedores para lembrar de fechar o fluxo depois que a imagem for salva. Na verdade, o desenvolvedor NÃO teria NENHUMA IDÉIA de que a imagem foi gerada usando um fluxo de memória (porque isso acontece em algum outro código, em outro lugar).

Estou realmente confuso :(


1
Recebi este comentário de @HansPassant em outra pergunta . Você obterá essa exceção sempre que o codec tiver problemas para gravar o arquivo. Uma boa instrução de depuração a ser adicionada é System.IO.File.WriteAllText (path, "test") antes da chamada Save (), ela verifica a capacidade básica de criar o arquivo. Agora você obterá uma boa exceção que informa o que você fez de errado.
— Juan Carlos Oropeza

Você deve image2.Save dentro do usingbloco. Acho que o originalBinaryDataStream2 foi descartado automaticamente no final do uso. E isso lançaria a exceção.
— taynguyen

Respostas:


172

Como é um MemoryStream, você realmente não precisa fechar o stream - nada de ruim acontecerá se você não fizer isso, embora obviamente seja uma boa prática descartar tudo o que é descartável de qualquer maneira. (Veja esta pergunta para saber mais sobre isso.)

No entanto, você deve descartar o Bitmap - e isso fechará o fluxo para você. Basicamente, depois de fornecer ao construtor de bitmap um fluxo, ele "possui" o fluxo e você não deve fechá-lo. Como os documentos desse construtor dizem:

Você deve manter o fluxo aberto durante a vida útil do Bitmap.

Não consigo encontrar nenhum documento prometendo fechar o fluxo quando você descarta o bitmap, mas você deve ser capaz de verificar isso facilmente.


2
impressionante! essa é uma ótima resposta Jon. Faz sentido perfeito (e eu perdi a parte sobre o fluxo nos documentos). Dois polegares para cima! Vou relatar quando eu tentar :)
— Pure.Krome

Algum comentário sobre como fazer isso se quisermos obedecer à regra CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
— Patrick Szalapski

@Patrick: Simplesmente não se aplica - basicamente você transferiu a propriedade do recurso. O mais próximo que você poderia chegar seria criar um wrapper "NonClosingStream" que ignora a chamada Dispose. Acho que posso ter um em MiscUtil - não tenho certeza ...
— Jon Skeet

Obrigado pela informação @Jon. Para mim, por alguma estranha razão, ele estava funcionando até mesmo com dispose () no ambiente de desenvolvimento local, mas não funcionou na produção.
— Oxon

92

Ocorreu um erro genérico no GDI +. Também pode resultar de um caminho de salvamento incorreto ! Levei meio dia para perceber isso. Portanto, certifique-se de ter verificado o caminho para salvar a imagem também.


4
Estou feliz por ter visto isso, meu caminho era C\Users\mason\Desktop\pic.png. Dois pontos faltando! Eu teria passado uma eternidade antes de perceber isso.
— pedreiro de

4
Incorreto também significa que não existe uma pasta na qual você deseja salvar a imagem.
— Roemer

14

Talvez valha a pena mencionar que, se o diretório C: \ Temp não existir, ele também lançará essa exceção mesmo se seu fluxo ainda existir.


+1 Esta exceção parece ocorrer em uma variedade de cenários. Caminho inválido é aquele que encontrei hoje.
— Kirk Broadhurst

4

Eu tive o mesmo problema, mas na verdade a causa era que o aplicativo não tinha permissão para salvar arquivos em C. Quando mudei para "D: \ ..", a imagem foi salva.


2

Copie o bitmap. Você deve manter o fluxo aberto durante toda a vida útil do bitmap.

Ao desenhar uma imagem: System.Runtime.InteropServices.ExternalException: Ocorreu um erro genérico no 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
Isso não funciona exatamente; em seu código em ToImage (), a "imagem" local terá corretamente um .RawFormat de qualquer que seja o arquivo original (jpeg ou png, etc), enquanto o valor de retorno de ToImage () terá inesperadamente .RawFormat MemoryBmp.
— Patrick Szalapski

Não tenho certeza de como isso RawFormatimporta, no entanto. Se você quiser usar isso, recupere-o do objeto em algum lugar ao longo do caminho, mas em geral, salve como o tipo que você realmente deseja ter .
— Nyerguds,

2

Você pode tentar criar outra cópia do bitmap:

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

Este erro me ocorreu quando eu estava tentando no Citrix. A pasta de imagens foi definida como C: \ no servidor, para a qual não tenho privilégios. Depois que a pasta de imagens foi movida para um drive compartilhado, o erro desapareceu.


1

Ocorreu um erro genérico no GDI +. Isso pode ocorrer devido a problemas de caminhos de armazenamento de imagem. Recebi este erro porque meu caminho de armazenamento é muito longo. Corrigi isso armazenando primeiro a imagem em um caminho mais curto e movi-a para o local correto com técnicas de tratamento de caminho longo.


1

Eu estava recebendo este erro porque o teste automatizado que estava executando estava tentando armazenar instantâneos em uma pasta que não existia. Depois de criar a pasta, o erro foi resolvido


0

Uma solução estranha que fez meu código funcionar. Abra a imagem no Paint e salve-a como um novo arquivo com o mesmo formato (.jpg). Agora tente com este novo arquivo e funciona. Isso explica claramente que o arquivo pode estar corrompido de alguma forma. Isso pode ajudar apenas se o seu código tiver todos os outros bugs corrigidos


0

Também apareceu comigo quando eu estava tentando salvar uma imagem no caminho

C:\Program Files (x86)\some_directory

e .exenão foi executado para rodar como administrador, espero que isso ajude alguém com o mesmo problema também.


0

Para mim, o código abaixo travou com A generic error occurred in GDI+a linha que salva em a MemoryStream. O código estava sendo executado em um servidor web e resolvi isso interrompendo e iniciando o pool de aplicativos que estava executando o site.

Deve ter havido algum erro interno no 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

Eu me deparei com esse erro quando estava tentando uma edição de imagem simples em um aplicativo WPF.

Definir a Origem de um elemento de imagem para o bitmap impede o salvamento do arquivo. Mesmo definindo Source = null não parece liberar o arquivo.

Agora, eu simplesmente nunca uso a imagem como o elemento Source of Image, então posso sobrescrever após a edição!

EDITAR

Depois de ouvir sobre a propriedade CacheOption (graças a @Nyerguds), encontrei a solução: Então, em vez de usar o construtor Bitmap, devo definir o Uri após a configuração CacheOption BitmapCacheOption.OnLoad. ( Image1Abaixo está o Imageelemento Wpf )

Ao invés de

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

Usar:

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

Veja isto: Cache de imagem WPF


1
As imagens WPF têm um parâmetro específico BitmapCacheOption.OnLoadpara desconectá-las da fonte de carregamento.
— Nyerguds

Obrigado @Nyerguds, até seu comentário não consegui fazer as perguntas certas
— mkb

0

Experimente este código:

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

Usei o processador de imagens para redimensionar as imagens e um dia recebi a exceção "Ocorreu um erro genérico no GDI +".

Depois de pesquisar um pouco, tentei reciclar o pool de aplicativos e bingo funcionou. Portanto, anoto aqui, espero que ajude;)

Felicidades

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.