Image.Save (..) genera un'eccezione GDI + perché il flusso di memoria viene chiuso


108

ho alcuni dati binari che voglio salvare come immagine. Quando provo a salvare l'immagine, viene generata un'eccezione se il flusso di memoria utilizzato per creare l'immagine è stato chiuso prima del salvataggio. Il motivo per cui lo faccio è perché sto creando immagini dinamicamente e come tale .. ho bisogno di utilizzare un flusso di memoria.

questo è il codice:

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

Qualcuno ha qualche suggerimento su come salvare un'immagine con lo stream chiuso? Non posso fare affidamento sul fatto che gli sviluppatori si ricordino di chiudere lo stream dopo che l'immagine è stata salvata. In effetti, lo sviluppatore NON avrebbe IDEA che l'immagine sia stata generata usando un flusso di memoria (perché accade in qualche altro codice, altrove).

Sono veramente confuso :(


1
Ho ricevuto questo commento da @HansPassant in un'altra domanda . Otterrai questa eccezione ogni volta che il codec ha problemi a scrivere il file. Una buona istruzione di debug da aggiungere è System.IO.File.WriteAllText (path, "test") prima della chiamata Save (), verifica la capacità di base di creare il file. Ora otterrai una buona eccezione che ti dice cosa hai fatto di sbagliato.
Juan Carlos Oropeza

Dovresti immagine2.Salva all'interno del usingblocco. Penso che sia originalBinaryDataStream2 stato automaticamente eliminato alla fine dell'utilizzo. E questo genererebbe l'eccezione.
taynguyen

Risposte:


172

Poiché si tratta di un MemoryStream, non è necessario chiudere lo stream: se non lo fai, non accadrà nulla di male, anche se ovviamente è buona norma smaltire comunque tutto ciò che è usa e getta. (Vedi questa domanda per ulteriori informazioni su questo.)

Tuttavia, dovresti eliminare la bitmap e questo chiuderà il flusso per te. Fondamentalmente una volta che date uno stream al costruttore Bitmap, esso "possiede" lo stream e voi non dovreste chiuderlo. Come dicono i documenti di quel costruttore :

È necessario mantenere il flusso aperto per tutta la durata della bitmap.

Non riesco a trovare alcun documento che prometta di chiudere il flusso quando elimini la bitmap, ma dovresti essere in grado di verificarlo abbastanza facilmente.


2
eccezionale! questa è un'ottima risposta Jon. Ha un senso perfetto (e mi sono perso la parte sullo stream nei documenti). Due pollici in su!
Riferirò di

Qualche commento su come procedere se vogliamo obbedire alla regola CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski

@ Patrick: Semplicemente non è applicabile - hai trasferito la proprietà della risorsa, in pratica. Il massimo che potresti ottenere sarebbe creare un wrapper "NonClosingStream" che ignori la chiamata Dispose. Penso di averne uno in MiscUtil - non sono sicuro ...
Jon Skeet

Grazie per le informazioni @ Jon. Per me, per qualche strana ragione, funzionava anche con dispose () nell'ambiente di sviluppo locale ma non funzionava in produzione.
Oxon

92

Si è verificato un errore generico in GDI +. Può anche derivare da un percorso di salvataggio errato ! Mi ci è voluta mezza giornata per accorgermene. Quindi assicurati di aver ricontrollato il percorso per salvare anche l'immagine.


4
Sono contento di aver visto questo, il mio percorso era C\Users\mason\Desktop\pic.png. Due punti mancanti! Avrei passato un'eternità prima che me ne accorgessi.
muratore

4
Errato significa anche che una cartella in cui si desidera salvare l'immagine non esiste.
Roemer

14

Forse vale la pena ricordare che se la directory C: \ Temp non esiste, verrà anche lanciata questa eccezione anche se il flusso è ancora esistente.


+1 Questa eccezione sembra verificarsi in una varietà di scenari. Il percorso non valido è quello che ho incontrato oggi.
Kirk Broadhurst

4

Ho avuto lo stesso problema ma in realtà la causa era che l'applicazione non aveva il permesso di salvare i file su C. Quando sono passato a "D: \ .." l'immagine è stata salvata.


2

Copia il Bitmap. Devi mantenere il flusso aperto per tutta la durata della bitmap.

Quando si disegna un'immagine: System.Runtime.InteropServices.ExternalException: si è verificato un errore generico in 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
Questo non funziona esattamente; nel codice in ToImage (), "immagine" locale avrà correttamente un .RawFormat di qualunque fosse il file originale (jpeg o png, ecc.), mentre il valore restituito di ToImage () avrà inaspettatamente .RawFormat MemoryBmp.
Patrick Szalapski

Non sono sicuro di quanto sia RawFormatimportante, però. Se vuoi usarlo, recuperalo dall'oggetto da qualche parte lungo la strada, ma in generale, salva come qualsiasi tipo tu voglia effettivamente avere .
Nyerguds

2

Puoi provare a creare un'altra copia di 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

Questo errore si è verificato quando stavo provando da Citrix. La cartella dell'immagine è stata impostata su C: \ nel server, per il quale non ho i privilegi. Una volta che la cartella delle immagini è stata spostata su un Drive condiviso, l'errore è scomparso.


1

Si è verificato un errore generico in GDI +. Può verificarsi a causa di problemi con i percorsi di memorizzazione delle immagini, ho ricevuto questo errore perché il mio percorso di memorizzazione è troppo lungo, ho risolto il problema salvando prima l'immagine in un percorso più breve e spostandola nella posizione corretta con tecniche di gestione del percorso lungo.


1

Ho ricevuto questo errore, perché il test automatizzato che stavo eseguendo, stava cercando di archiviare le istantanee in una cartella che non esisteva. Dopo aver creato la cartella, l'errore è stato risolto


0

Una strana soluzione che ha fatto funzionare il mio codice. Apri l'immagine in Paint e salvala come nuovo file con lo stesso formato (.jpg). Ora prova con questo nuovo file e funziona. Ti spiega chiaramente che il file potrebbe essere danneggiato in qualche modo. Questo può aiutare solo se il tuo codice ha tutti gli altri bug corretti


0

È apparso anche con me quando stavo cercando di salvare un'immagine nel percorso

C:\Program Files (x86)\some_directory

e .exenon è stato eseguito per essere eseguito come amministratore, spero che questo possa aiutare qualcuno che ha lo stesso problema.


0

Per me il codice seguente si è bloccato con A generic error occurred in GDI+sulla riga che salva in un file MemoryStream. Il codice era in esecuzione su un server web e l'ho risolto arrestando e avviando il pool di applicazioni che eseguiva il sito.

Dev'essere stato un errore interno in 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

Mi sono imbattuto in questo errore mentre stavo provando un semplice editing di immagini in un'app WPF.

L'impostazione dell'origine di un elemento immagine sulla bitmap impedisce il salvataggio del file. Anche l'impostazione di Source = null sembra non rilasciare il file.

Ora non uso mai l'immagine come elemento Origine dell'immagine, quindi posso sovrascriverla dopo la modifica!

MODIFICARE

Dopo aver sentito parlare della proprietà CacheOption (grazie a @Nyerguds) ho trovato la soluzione: quindi, invece di usare il costruttore Bitmap, devo impostare Uri dopo l'impostazione CacheOption BitmapCacheOption.OnLoad( Image1sotto è l' Imageelemento Wpf )

Invece di

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

Uso:

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

Vedi questo: WPF Image Caching


1
Le immagini WPF hanno un parametro specifico BitmapCacheOption.OnLoadper disconnetterle dall'origine di caricamento.
Nyerguds

Grazie @Nyerguds, fino al tuo commento non sono riuscito a fare le domande giuste
mkb

0

Prova questo codice:

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

Ho usato imageprocessor per ridimensionare le immagini e un giorno ho ricevuto l'eccezione "Si è verificato un errore generico in GDI +".

Dopo aver guardato un po 'ho provato a riciclare il pool di applicazioni e il bingo funziona. Quindi lo noto qui, spero che aiuti;)

Saluti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.