Image.Save (..) lève une exception GDI + car le flux de mémoire est fermé


108

J'ai des données binaires que je veux enregistrer sous forme d'image. Lorsque j'essaie d'enregistrer l'image, cela lève une exception si le flux de mémoire utilisé pour créer l'image a été fermé avant l'enregistrement. La raison pour laquelle je fais cela est parce que je crée dynamiquement des images et en tant que tel .. j'ai besoin d'utiliser un flux de mémoire.

c'est le code:

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

Quelqu'un a-t-il des suggestions sur la façon dont je pourrais enregistrer une image avec le flux fermé? Je ne peux pas compter sur les développeurs pour se souvenir de fermer le flux une fois l'image enregistrée. En fait, le développeur n'aurait AUCUNE IDÉE que l'image a été générée en utilisant un flux mémoire (car cela se produit dans un autre code, ailleurs).

Je suis vraiment confus :(


1
J'ai reçu ce commentaire de @HansPassant dans une autre question . Vous obtiendrez cette exception chaque fois que le codec a du mal à écrire le fichier. Une bonne instruction de débogage à ajouter est System.IO.File.WriteAllText (chemin, "test") avant l'appel Save (), il vérifie la capacité de base à créer le fichier. Vous obtiendrez maintenant une bonne exception qui vous indique ce que vous avez mal fait.
Juan Carlos Oropeza du

Vous devez image2.Save inside usingblock. Je pense que le a originalBinaryDataStream2 été automatiquement supprimé à la fin de l'utilisation. Et cela jetterait l'exception.
taynguyen

Réponses:


172

Comme il s'agit d'un MemoryStream, vous n'avez vraiment pas besoin de fermer le flux - il ne se passera rien de grave si vous ne le faites pas, bien que de toute façon, il soit bon de disposer de tout ce qui est jetable. (Voir cette question pour en savoir plus.)

Cependant, vous devriez supprimer le Bitmap - et cela fermera le flux pour vous. En gros, une fois que vous donnez un flux au constructeur Bitmap, il "possède" le flux et vous ne devriez pas le fermer. Comme le disent les documents de ce constructeur :

Vous devez garder le flux ouvert pendant toute la durée de vie du Bitmap.

Je ne trouve aucun document promettant de fermer le flux lorsque vous supprimez le bitmap, mais vous devriez pouvoir le vérifier assez facilement.


2
impressionnant! c'est une excellente réponse Jon. Fait un sens parfait (et j'ai manqué le peu sur le flux dans la documentation). Deux pouces en l'air! Je ferai un rapport quand
j'aurai essayé

Des commentaires sur la façon de procéder si nous voulons obéir à la règle CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski

@Patrick: Ce n'est tout simplement pas applicable - vous avez transféré la propriété de la ressource, en gros. Le plus proche que vous pourriez venir serait de créer un wrapper "NonClosingStream" qui ignore l'appel Dispose. Je pense que j'en ai peut-être un dans MiscUtil - pas sûr ...
Jon Skeet

Merci pour info @Jon. Pour moi, pour une raison étrange, cela fonctionnait même avec dispose () dans l'environnement de développement local, mais ne fonctionnait pas en production.
Oxon

92

Une erreur générique s'est produite dans GDI +. Peut également résulter d' un chemin de sauvegarde incorrect ! Il m'a fallu une demi-journée pour le remarquer. Assurez-vous donc que vous avez également vérifié le chemin pour enregistrer l'image.


4
Je suis content d'avoir vu cela, mon chemin était C\Users\mason\Desktop\pic.png. Côlon manquant! J'aurais passé une éternité avant de le remarquer.
mason

4
Incorrect signifie également qu'un dossier dans lequel vous souhaitez enregistrer l'image n'existe pas.
Roemer

14

Peut-être vaut-il la peine de mentionner que si le répertoire C: \ Temp n'existe pas, il lèvera également cette exception même si votre flux existe toujours.


+1 Cette exception semble se produire dans une variété de scénarios. Un chemin non valide est celui que j'ai rencontré aujourd'hui.
Kirk Broadhurst

4

J'ai eu le même problème mais en fait la cause était que l'application n'avait pas l'autorisation d'enregistrer des fichiers sur C. Lorsque j'ai changé en "D: \ .." l'image a été enregistrée.


2

Copiez le Bitmap. Vous devez garder le flux ouvert pendant toute la durée de vie du bitmap.

Lors du dessin d'une image: System.Runtime.InteropServices.ExternalException: une erreur générique s'est produite dans 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
Cela ne fonctionne pas exactement; dans votre code dans ToImage (), "image" locale aura correctement un .RawFormat de ce que le fichier d'origine était (jpeg ou png, etc.), alors que la valeur de retour de ToImage () aura de manière inattendue .RawFormat MemoryBmp.
Patrick Szalapski

Je ne sais pas trop combien cela RawFormatcompte. Si vous voulez l'utiliser, récupérez-le à partir de l'objet quelque part en cours de route, mais en général, enregistrez-le sous le type que vous souhaitez réellement avoir .
Nyerguds

2

Vous pouvez essayer de créer une autre copie du 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

Cette erreur m'est apparue lorsque j'essayais de Citrix. Le dossier image a été défini sur C: \ sur le serveur, pour lequel je n'ai pas de privilège. Une fois le dossier d'image déplacé vers un lecteur partagé, l'erreur a disparu.


1

Une erreur générique s'est produite dans GDI +. Cela peut se produire en raison de problèmes de chemins de stockage d'images, j'ai eu cette erreur parce que mon chemin de stockage est trop long, j'ai résolu ce problème en stockant d'abord l'image dans un chemin le plus court et en la déplaçant vers l'emplacement correct avec des techniques de gestion des longs chemins.


1

J'obtenais cette erreur, car le test automatisé que j'exécutais essayait de stocker des instantanés dans un dossier qui n'existait pas. Après avoir créé le dossier, l'erreur a été résolue


0

Une solution étrange qui a fait fonctionner mon code. Ouvrez l'image dans Paint et enregistrez-la en tant que nouveau fichier au même format (.jpg). Maintenant, essayez avec ce nouveau fichier et cela fonctionne. Cela vous explique clairement que le fichier peut être corrompu d'une manière ou d'une autre. Cela ne peut aider que si votre code a tous les autres bogues corrigés


0

Il est également apparu avec moi lorsque j'essayais de sauvegarder une image dans le chemin

C:\Program Files (x86)\some_directory

et le .exen'a pas été exécuté pour fonctionner en tant qu'administrateur, j'espère que cela peut également aider quelqu'un qui a le même problème.


0

Pour moi, le code ci-dessous s'est écrasé A generic error occurred in GDI+sur la ligne qui enregistre dans un fichier MemoryStream. Le code s'exécutait sur un serveur Web et je l'ai résolu en arrêtant et en démarrant le pool d'applications qui exécutait le site.

Il doit y avoir une erreur interne dans 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

Je suis tombé sur cette erreur lorsque j'essayais une simple retouche d'image dans une application WPF.

La définition de la source d'un élément Image sur le bitmap empêche l'enregistrement du fichier. Même la définition de Source = null ne semble pas libérer le fichier.

Maintenant, je n'utilise jamais l'image comme élément Source de l'image, je peux donc l'écraser après l'édition!

ÉDITER

Après avoir entendu parler de la propriété CacheOption (grâce à @Nyerguds), j'ai trouvé la solution: donc au lieu d'utiliser le constructeur Bitmap, je dois définir l'URI après le réglage CacheOption BitmapCacheOption.OnLoad( Image1ci-dessous, l' Imageélément Wpf )

Au lieu de

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

Utilisation:

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

Voir ceci: Mise en cache d'image WPF


1
Les images WPF ont un paramètre spécifique BitmapCacheOption.OnLoadpour les déconnecter de la source de chargement.
Nyerguds

Merci @Nyerguds, jusqu'à votre commentaire je n'ai pas réussi à poser les bonnes questions
mkb

0

Essayez ce code:

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

J'ai utilisé le processeur d'image pour redimensionner les images et un jour j'ai eu l'exception "Une erreur générique s'est produite dans GDI +".

Après avoir regardé un moment, j'ai essayé de recycler le pool d'applications et de bingo, cela fonctionne. Alors je le note ici, j'espère que ça aidera;)

À votre santé

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.