Image.Save (..) löst eine GDI + -Ausnahme aus, da der Speicherstrom geschlossen ist

108

Ich habe einige Binärdaten, die ich als Bild speichern möchte. Wenn ich versuche, das Bild zu speichern, wird eine Ausnahme ausgelöst, wenn der zum Erstellen des Bildes verwendete Speicherstrom vor dem Speichern geschlossen wurde. Der Grund, warum ich das mache, ist, dass ich dynamisch Bilder erstelle und als solches .. Ich muss einen Speicherstrom verwenden.

Das ist der 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.
}

Hat jemand Vorschläge, wie ich ein Bild bei geschlossenem Stream speichern könnte? Ich kann mich nicht darauf verlassen, dass die Entwickler daran denken, den Stream nach dem Speichern des Bildes zu schließen. Tatsächlich hätte der Entwickler KEINE IDEE, dass das Bild unter Verwendung eines Speicherstroms erzeugt wurde (weil es in einem anderen Code an anderer Stelle vorkommt).

Ich bin wirklich verwirrt :(

Pure.Krome
quelle
1
Ich habe diesen Kommentar von @HansPassant in einer anderen Frage erhalten . Diese Ausnahme wird immer dann angezeigt, wenn der Codec Probleme beim Schreiben der Datei hat. Eine gute Debugging-Anweisung zum Hinzufügen ist System.IO.File.WriteAllText (Pfad, "Test") vor dem Save () -Aufruf. Sie überprüft die grundlegende Fähigkeit zum Erstellen der Datei. Sie erhalten jetzt eine gute Ausnahme, die Ihnen sagt, was Sie falsch gemacht haben.
Juan Carlos Oropeza
Sie sollten image2.Save innerhalb des usingBlocks. Ich denke das originalBinaryDataStream2 wurde am Ende der Nutzung automatisch entsorgt. Und das würde die Ausnahme werfen.
Taynguyen

Antworten:

172

Da es ein Memorystream ist, haben Sie wirklich nicht brauchen , um den Strom zu schließen - nichts Schlimmes passieren wird , wenn Sie es nicht tun, obwohl offensichtlich ist es gute Praxis Verfügungs alles , was Einweg ohnehin ist. (Weitere Informationen hierzu finden Sie in dieser Frage .)

Sie sollten jedoch die Bitmap entsorgen - und das schließt den Stream für Sie. Wenn Sie dem Bitmap-Konstruktor einen Stream geben, "besitzt" er den Stream und Sie sollten ihn nicht schließen. Wie die Dokumente für diesen Konstruktor sagen:

Sie müssen den Stream für die Lebensdauer der Bitmap offen halten.

Ich kann keine Dokumente finden, die versprechen, den Stream zu schließen, wenn Sie die Bitmap entsorgen, aber Sie sollten dies ziemlich einfach überprüfen können.

Jon Skeet
quelle
2
genial! Das ist eine großartige Antwort, Jon. Macht den perfekten Sinn (und ich habe das bisschen über den Stream in den Dokumenten verpasst). Zwei Daumen hoch! Ich melde mich zurück, wenn ich es
ausprobiert
Gibt es Kommentare dazu, wie wir vorgehen sollen, wenn wir die Regel CA2000 befolgen wollen? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski
@Patrick: Es ist einfach nicht anwendbar - Sie haben im Grunde das Eigentum an der Ressource übertragen. Am nächsten könnten Sie einen "NonClosingStream" -Wrapper erstellen, der den Dispose-Aufruf ignoriert. Ich denke, ich kann eine in MiscUtil haben - nicht sicher ...
Jon Skeet
Danke für info @Jon. Aus irgendeinem seltsamen Grund funktionierte es sogar mit dispose () in der lokalen Entwicklungsumgebung, aber nicht in der Produktion.
Oxon
92

In GDI + ist ein generischer Fehler aufgetreten. Kann auch aus einem falschen Speicherpfad resultieren ! Ich habe einen halben Tag gebraucht, um das zu bemerken. Stellen Sie daher sicher, dass Sie den Pfad überprüft haben, um das Bild ebenfalls zu speichern.

Houman
quelle
4
Ich bin froh, dass ich das gesehen habe, mein Weg war C\Users\mason\Desktop\pic.png. Fehlender Doppelpunkt! Ich hätte ewig verbracht, bevor ich das bemerkt hätte.
Maurer
4
Falsch bedeutet auch, dass ein Ordner, in dem Sie das Bild speichern möchten, nicht vorhanden ist.
Roemer
14

Vielleicht ist es erwähnenswert, dass wenn das Verzeichnis C: \ Temp nicht existiert, diese Ausnahme auch dann ausgelöst wird, wenn Ihr Stream noch vorhanden ist.

Rojzik
quelle
+1 Diese Ausnahme scheint in verschiedenen Szenarien aufzutreten. Ein ungültiger Pfad ist einer, dem ich heute begegnet bin.
Kirk Broadhurst
4

Ich hatte das gleiche Problem, aber die Ursache war, dass die Anwendung keine Berechtigung zum Speichern von Dateien auf C hatte. Als ich zu "D: \ .." wechselte, wurde das Bild gespeichert.

Morad Aktam
quelle
2

Kopieren Sie die Bitmap. Sie müssen den Stream für die Lebensdauer der Bitmap offen halten.

Beim Zeichnen eines Bildes: System.Runtime.InteropServices.ExternalException: In GDI ist ein generischer Fehler aufgetreten

    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
quelle
1
Das funktioniert nicht genau; In Ihrem Code in ToImage () hat das lokale "Bild" korrekt ein .RawFormat der ursprünglichen Datei (JPEG oder PNG usw.), während der Rückgabewert von ToImage () unerwartet .RawFormat MemoryBmp enthält.
Patrick Szalapski
Ich bin mir jedoch nicht sicher, wie RawFormatwichtig das ist. Wenn Sie , dass verwenden möchten, rufen Sie sie aus dem Objekt irgendwo auf dem Weg, aber im Allgemeinen, speichern als wie auch immer geartete wollen Sie tatsächlich haben .
Nyerguds
2

Sie können versuchen, eine weitere Kopie der Bitmap zu erstellen:

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
quelle
2

Dieser Fehler ist mir beim Versuch von Citrix eingefallen. Der Image-Ordner wurde auf dem Server auf C: \ gesetzt, für den ich keine Berechtigung habe. Sobald der Image-Ordner auf ein freigegebenes Laufwerk verschoben wurde, war der Fehler behoben.

Jay K.
quelle
1

In GDI + ist ein generischer Fehler aufgetreten. Dies kann aufgrund von Problemen mit Bildspeicherpfaden auftreten. Ich habe diesen Fehler erhalten, weil mein Speicherpfad zu lang ist. Ich habe dies behoben, indem ich das Bild zuerst auf einem kürzesten Pfad gespeichert und mit Techniken zur Behandlung langer Pfade an den richtigen Ort verschoben habe.

S.Roshanth
quelle
1

Ich habe diesen Fehler erhalten, weil der automatisierte Test, den ich ausgeführt habe, versucht hat, Snapshots in einem Ordner zu speichern, der nicht vorhanden war. Nachdem ich den Ordner erstellt habe, wurde der Fehler behoben

P. Lisa
quelle
0

Eine seltsame Lösung, die meinen Code zum Laufen brachte. Öffnen Sie das Bild in Farbe und speichern Sie es als neue Datei mit demselben Format (.jpg). Versuchen Sie es jetzt mit dieser neuen Datei und es funktioniert. Es erklärt Ihnen deutlich, dass die Datei auf irgendeine Weise beschädigt sein könnte. Dies kann nur helfen, wenn in Ihrem Code alle anderen Fehler behoben sind

Vinothkumar
quelle
0

Es ist auch bei mir aufgetaucht, als ich versucht habe, ein Bild im Pfad zu speichern

C:\Program Files (x86)\some_directory

und das .exewurde nicht ausgeführt, um als Administrator ausgeführt zu werden. Ich hoffe, dies kann jemandem helfen, der das gleiche Problem hat.

Ali Ezzat Odeh
quelle
0

Für mich stürzte der folgende Code A generic error occurred in GDI+in der Zeile ab, die in a speichert MemoryStream. Der Code wurde auf einem Webserver ausgeführt und ich habe ihn behoben, indem ich den Anwendungspool gestoppt und gestartet habe, auf dem die Site ausgeführt wurde.

Muss ein interner Fehler in GDI + gewesen sein

    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
quelle
0

Ich bin auf diesen Fehler gestoßen, als ich eine einfache Bildbearbeitung in einer WPF-App versuchte.

Durch Festlegen der Quelle eines Bildelements auf die Bitmap wird das Speichern von Dateien verhindert. Selbst das Setzen von Source = null scheint die Datei nicht freizugeben.

Jetzt verwende ich das Bild einfach nie mehr als Bildquelle, sodass ich es nach der Bearbeitung überschreiben kann!

BEARBEITEN

Nachdem ich von der CacheOption-Eigenschaft gehört hatte (Dank an @Nyerguds), fand ich die Lösung: Anstatt den Bitmap-Konstruktor zu verwenden, muss ich den Uri nach dem Festlegen festlegen CacheOption BitmapCacheOption.OnLoad( Image1unten ist das Wpf- ImageElement).

Anstatt

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

Verwenden:

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

Siehe dies: WPF Image Caching

mkb
quelle
1
WPF-Bilder haben einen bestimmten Parameter BitmapCacheOption.OnLoad, um sie von der Ladequelle zu trennen.
Nyerguds
Vielen Dank @Nyerguds, bis zu Ihrem Kommentar habe ich nicht die richtigen Fragen gestellt
mkb
0

Versuchen Sie diesen 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;
}
BogdanRB
quelle
0

Ich habe den Bildprozessor zum Ändern der Bildgröße verwendet und eines Tages die Ausnahme "In GDI + ist ein generischer Fehler aufgetreten" erhalten.

Nachdem ich eine Weile nachgeschlagen hatte, versuchte ich , den Anwendungspool und das Bingo zu recyceln, das funktioniert. Also notiere ich es hier, hoffe es hilft;)

Prost

Hoàng Nghĩa
quelle