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 :(

Pure.Krome
fonte
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.

Jon Skeet
fonte
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.

Houman
fonte
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.

Rojzik
fonte
+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.

Morad Aktam
fonte
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");
    }
Brian Low
fonte
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;
    }
}
Yuri Perekupko
fonte
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.

Jay K
fonte
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.

S.Roshanth
fonte
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

P.Lisa
fonte
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

Vinothkumar
fonte
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.

Ali Ezzat Odeh
fonte
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);
                }
            }
        }
    }
mortb
fonte
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

mkb
fonte
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;
}
BogdanRB
fonte
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

Hoàng Nghĩa
fonte