Image.Save (..) zgłasza wyjątek GDI +, ponieważ strumień pamięci jest zamknięty

108

Mam pewne dane binarne, które chcę zapisać jako obraz. Kiedy próbuję zapisać obraz, zgłasza wyjątek, jeśli strumień pamięci użyty do utworzenia obrazu został zamknięty przed zapisaniem. Powodem, dla którego to robię, jest to, że dynamicznie tworzę obrazy i jako takie ... muszę użyć strumienia pamięci.

to jest kod:

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

Czy ktoś ma jakieś sugestie, jak mogę zapisać obraz przy zamkniętym strumieniu? Nie mogę liczyć na to, że programiści będą pamiętać o zamknięciu strumienia po zapisaniu obrazu. W rzeczywistości programista nie miałby ŻADNEGO POMYSŁU, że obraz został wygenerowany przy użyciu strumienia pamięci (ponieważ dzieje się to w innym kodzie, gdzie indziej).

Jestem bardzo zmieszany :(

Pure.Krome
źródło
1
Otrzymałem ten komentarz od @HansPassant w innym pytaniu . Ten wyjątek pojawi się, gdy kodek ma problem z zapisaniem pliku. Dobrą instrukcją debugowania do dodania jest System.IO.File.WriteAllText (ścieżka, "test") przed wywołaniem Save (), weryfikuje ona podstawową zdolność do tworzenia pliku. Otrzymasz teraz dobry wyjątek, który mówi, co zrobiłeś źle.
Juan Carlos Oropeza,
Powinieneś wyobrazić sobie 2. Zapisz wewnątrz usingbloku. Myślę, że originalBinaryDataStream2 został automatycznie usunięty po zakończeniu użytkowania. A to spowodowałoby wyjątek.
taynguyen

Odpowiedzi:

172

Ponieważ jest to MemoryStream, naprawdę nie musisz zamykać strumienia - nic złego się nie stanie, jeśli tego nie zrobisz, chociaż oczywiście dobrą praktyką jest pozbycie się wszystkiego, co jest jednorazowego użytku. (Zobacz to pytanie, aby uzyskać więcej informacji.)

Jednak powinieneś pozbyć się mapy bitowej - a to zamknie strumień. Zasadniczo, gdy już nadasz konstruktorowi Bitmap strumień, jest on „właścicielem” strumienia i nie powinieneś go zamykać. Jak mówią dokumenty tego konstruktora :

Musisz pozostawić otwarty strumień przez cały okres istnienia mapy bitowej.

Nie mogę znaleźć żadnych dokumentów obiecujących zamknięcie strumienia, gdy usuniesz bitmapę, ale powinieneś być w stanie to dość łatwo zweryfikować.

Jon Skeet
źródło
2
niesamowite! to świetna odpowiedź, Jon. To ma sens (i przegapiłem fragment o strumieniu w dokumentach). Dwa kciuki w górę! Zgłoszę się, gdy się
sprawdzę
Jakieś komentarze, jak się do tego zabrać, jeśli chcemy przestrzegać reguły CA2000? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski,
@Patrick: To po prostu nie dotyczy - w zasadzie przeniosłeś własność zasobu. Najbliższym możliwym rozwiązaniem byłoby utworzenie opakowania „NonClosingStream”, które ignoruje wywołanie Dispose. Myślę, że mogę mieć jeden w MiscUtil - nie jestem pewien ...
Jon Skeet
Dzięki za informacje @Jon. Dla mnie z jakiegoś dziwnego powodu działał nawet z dispose () w lokalnym środowisku deweloperskim, ale nie działał na produkcji.
Oxon
92

Wystąpił ogólny błąd w GDI +. Może również wynikać z nieprawidłowej ścieżki zapisu ! Zajęło mi to pół dnia, zanim to zauważyłem. Dlatego upewnij się, że dwukrotnie sprawdziłeś ścieżkę, aby zapisać obraz.

Houman
źródło
4
Cieszę się, że to zobaczyłem, moja droga była C\Users\mason\Desktop\pic.png. Brak dwukropka! Spędziłbym wieczność, zanim to zauważyłem.
murarz
4
Niepoprawny oznacza również, że folder, w którym chcesz zapisać obraz, nie istnieje.
Roemer
14

Być może warto wspomnieć, że jeśli katalog C: \ Temp nie istnieje, zgłosi również ten wyjątek, nawet jeśli Twój strumień nadal istnieje.

Rojzik
źródło
+1 Ten wyjątek zdaje się występować w różnych scenariuszach. Nieprawidłowa ścieżka to ta, którą dzisiaj spotkałem.
Kirk Broadhurst
4

Miałem ten sam problem, ale w rzeczywistości przyczyną było to, że aplikacja nie miała uprawnień do zapisywania plików na C. Kiedy zmieniłem na "D: \ .." obraz został zapisany.

Morad Aktam
źródło
2

Skopiuj plik Bitmap. Musisz pozostawić otwarty strumień przez cały okres istnienia mapy bitowej.

Podczas rysowania obrazu: System.Runtime.InteropServices.ExternalException: Wystąpił ogólny błąd w 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
źródło
1
To nie działa dokładnie; w twoim kodzie w ToImage (), lokalny "image" będzie miał poprawnie .RawFormat, niezależnie od tego, jaki był oryginalny plik (jpeg lub png, itp.), podczas gdy wartość zwracana przez ToImage () nieoczekiwanie będzie miała .RawFormat MemoryBmp.
Patrick Szalapski
Nie jestem jednak pewien, jakie to RawFormatma znaczenie. Jeśli chcesz tego użyć, pobierz go z obiektu gdzieś po drodze, ale ogólnie zapisz jako dowolny typ, który chcesz mieć .
Nyerguds
2

Możesz spróbować utworzyć kolejną kopię mapy bitowej:

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
źródło
2

Ten błąd pojawił się podczas próby z Citrix. Folder obrazów został ustawiony na C: \ na serwerze, do którego nie mam uprawnień. Po przeniesieniu folderu obrazów na dysk współdzielony błąd zniknął.

Jay K.
źródło
1

Wystąpił ogólny błąd w GDI +. Może się to zdarzyć z powodu problemów ze ścieżkami przechowywania obrazu. Otrzymałem ten błąd, ponieważ moja ścieżka przechowywania jest zbyt długa. Naprawiłem to, najpierw przechowując obraz w najkrótszej ścieżce i przenosząc go do właściwej lokalizacji za pomocą technik obsługi długich ścieżek.

S.Roshanth
źródło
1

Pojawił się ten błąd, ponieważ wykonywany przeze mnie automatyczny test próbował zapisać migawki w folderze, który nie istniał. Po utworzeniu folderu błąd został rozwiązany

P.Lisa
źródło
0

Dziwne rozwiązanie, które sprawiło, że mój kod działał. Otwórz obraz w farbie i zapisz go jako nowy plik w tym samym formacie (.jpg). Teraz spróbuj z tym nowym plikiem i działa. Wyraźnie wyjaśnia, że ​​plik może być w jakiś sposób uszkodzony. Może to pomóc tylko wtedy, gdy twój kod ma naprawione wszystkie inne błędy

Vinothkumar
źródło
0

Pojawił się również u mnie, gdy próbowałem zapisać obraz na ścieżce

C:\Program Files (x86)\some_directory

i .exenie został uruchomiony jako administrator, mam nadzieję, że może to pomóc komuś, kto ma ten sam problem.

Ali Ezzat Odeh
źródło
0

Dla mnie poniższy kod zawiesił A generic error occurred in GDI+się w linii, która zapisuje do pliku MemoryStream. Kod działał na serwerze internetowym i rozwiązałem go, zatrzymując i uruchamiając pulę aplikacji, na której działała witryna.

To musiał być jakiś wewnętrzny błąd w 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
źródło
0

Natknąłem się na ten błąd, gdy próbowałem prostej edycji obrazu w aplikacji WPF.

Ustawienie źródła elementu obrazu na mapę bitową zapobiega zapisywaniu pliku. Nawet ustawienie Source = null nie wydaje się zwolnić pliku.

Teraz po prostu nigdy nie używam obrazu jako elementu źródła obrazu, więc mogę nadpisać po edycji!

EDYTOWAĆ

Po zapoznaniu się z właściwością CacheOption (podziękowania dla @Nyerguds) znalazłem rozwiązanie: Zamiast więc używać konstruktora Bitmap, muszę ustawić Uri po ustawieniu CacheOption BitmapCacheOption.OnLoad. ( Image1Poniżej Imageelement Wpf )

Zamiast

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

Posługiwać się:

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

Zobacz to: Buforowanie obrazów WPF

mkb
źródło
1
Obrazy WPF mają określony parametr, BitmapCacheOption.OnLoadaby odłączyć je od źródła ładowania.
Nyerguds
Dziękuję @Nyerguds, do czasu twojego komentarza nie zadawałem właściwych pytań
mkb
0

Wypróbuj ten kod:

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
źródło
0

Użyłem procesora obrazu do zmiany rozmiaru obrazów i pewnego dnia otrzymałem wyjątek „Wystąpił ogólny błąd w GDI +”.

Po chwili odszukania próbowałem ponownie wykorzystać pulę aplikacji i bingo działa. Więc zapisuję to tutaj, mam nadzieję, że pomoże;)

Twoje zdrowie

Hoàng Nghĩa
źródło