Image.Save (..) bellek akışı kapalı olduğundan bir GDI + istisnası atar


109

Resim olarak kaydetmek istediğim bazı ikili verilerim var. Görüntüyü kaydetmeye çalıştığımda, görüntüyü oluşturmak için kullanılan bellek akışı kaydetmeden önce kapatılmışsa bir istisna atıyor. Bunu yapmamın nedeni, dinamik olarak imajlar oluşturmam ve bu nedenle .. bir bellek akışı kullanmam gerekiyor.

kod bu:

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

Akış kapalıyken bir resmi nasıl kaydedebileceğime dair herhangi bir önerisi olan var mı? Görüntü kaydedildikten sonra geliştiricilerin akışı kapatmayı hatırlamalarına güvenemiyorum. Aslında, geliştiricinin görüntünün bir bellek akışı kullanılarak oluşturulduğuna dair HİÇBİR FİKİRİ olmazdı (çünkü başka bir kodda, başka bir yerde gerçekleşir).

Gerçekten kafam karıştı :(


1
Bu yorumu @HansPassant'tan başka bir soruda aldım . Codec, dosyayı yazarken sorun yaşadığında bu istisnayı alırsınız. Eklenecek iyi bir hata ayıklama ifadesi, Save () çağrısından önce System.IO.File.WriteAllTexttir (yol, "test"), dosyayı oluşturmak için temel yeteneği doğrular. Şimdi size neyi yanlış yaptığınızı söyleyen iyi bir istisna elde edeceksiniz.
Juan Carlos Oropeza

Image2.Save usingbloğun içine kaydetmelisiniz . Kullanımın originalBinaryDataStream2 sonunda otomatik olarak atıldığını düşünüyorum . Ve bu istisnayı ortaya çıkarır.
taynguyen

Yanıtlar:


172

Bir MemoryStream olduğu için , akışı gerçekten kapatmanıza gerek yoktur - kapatmazsanız kötü bir şey olmayacaktır, ancak yine de tek kullanımlık herhangi bir şeyi atmak iyi bir uygulamadır. ( Bununla ilgili daha fazla bilgi için bu soruya bakın .)

Ancak, Bitmap'i atmanız gerekir - bu sizin için akışı kapatacaktır. Temel olarak, Bitmap yapıcısına bir akış verdiğinizde, akışa "sahip" olur ve onu kapatmamalısınız. Bu kurucu için belgelerin dediği gibi:

Akışı Bitmap'in ömrü boyunca açık tutmalısınız.

Bitmap'i attığınızda akışı kapatmayı vaat eden herhangi bir belge bulamıyorum, ancak bunu oldukça kolay bir şekilde doğrulayabilmelisiniz.


2
müthiş! bu harika bir cevap Jon. Mükemmel bir fikir veriyor (ve belgelerde akışla ilgili kısmı kaçırdım). Çift beğeni!
Denediğimde

CA2000 kuralına uymak istiyorsak, bunun nasıl yapılacağına dair herhangi bir yorumunuz var mı? (msdn.microsoft.com/en-us/library/ms182289.aspx)
Patrick Szalapski

@Patrick: Bu kesinlikle uygulanabilir değil - temelde kaynağın sahipliğini devretmişsiniz. Gelebileceğiniz en yakın şey, Dispose çağrısını yok sayan bir "NonClosingStream" sarmalayıcısı oluşturmak olacaktır. Sanırım MiscUtil'de bir tane olabilir - emin değilim ...
Jon Skeet

@ Jon bilgisi için teşekkürler. Benim için garip bir nedenden ötürü yerel geliştirme ortamında dispose () ile bile çalışıyordu ama üretimde çalışmıyordu.
Oxon

93

GDI + 'da genel bir hata oluştu. Yanlış kaydetme yolundan da kaynaklanabilir ! Bunu fark etmem yarım günümü aldı. Bu nedenle, görüntüyü kaydetmek için yolu iki kez kontrol ettiğinizden emin olun.


4
Bunu gördüğüme sevindim, yolum buydu C\Users\mason\Desktop\pic.png. Eksik kolon! Bunu fark etmeden önce sonsuza kadar harcardım.
mason

4
Yanlış, görüntüyü kaydetmek istediğiniz bir klasörün olmadığı anlamına da gelir.
Roemer

14

Belki de C: \ Temp dizini yoksa, akışınız hala mevcut olsa bile bu istisnayı atacağını belirtmekte fayda var.


+1 Bu istisna, çeşitli senaryolarda görülüyor. Geçersiz yol, bugün karşılaştığım bir yol.
Kirk Broadhurst

4

Ben de aynı sorunu yaşadım ama aslında nedeni uygulamanın C'ye dosya kaydetme izni olmamasıydı. "D: \ .." olarak değiştirdiğimde resim kaydedildi.


2

Bitmap'i kopyalayın. Bit eşlemin ömrü boyunca akışı açık tutmanız gerekir.

Bir görüntü çizerken: System.Runtime.InteropServices.ExternalException: GDI'da genel bir hata oluştu

    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
Bu tam olarak çalışmıyor; ToImage () içindeki kodunuzda, yerel "resim" doğru bir şekilde orijinal dosyanın (jpeg veya png, vb.) bir .RawFormat'ına sahip olurken, ToImage () 'ın dönüş değeri beklenmedik bir şekilde .RawFormat MemoryBmp olacaktır.
Patrick Szalapski

RawFormatYine de ne kadar önemli olduğundan emin değilim . Bunu kullanmak istiyorsanız, onu yol boyunca bir yerde nesneden alın, ancak genel olarak, gerçekten sahip olmak istediğiniz tür olarak kaydedin .
Nyerguds

2

Bitmap'in başka bir kopyasını oluşturmayı deneyebilirsiniz:

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

Bu hata Citrix'ten denerken başıma geldi. Görüntü klasörü, ayrıcalığım olmayan sunucuda C: \ olarak ayarlandı. Görüntü klasörü bir ortak drive'a taşındığında hata ortadan kalktı.


1

GDI + 'da genel bir hata oluştu. Görüntü depolama yolları sorunları nedeniyle ortaya çıkabilir, depolama yolum çok uzun olduğu için bu hatayı aldım, bunu önce görüntüyü en kısa yoldan depolayarak ve uzun yol işleme teknikleri ile doğru yere taşıyarak düzelttim.


1

Bu hatayı alıyordum çünkü yürüttüğüm otomatik test anlık görüntüleri var olmayan bir klasöre depolamaya çalışıyordu. Klasörü oluşturduktan sonra hata çözüldü


0

Kodumun çalışmasını sağlayan garip bir çözüm. Resmi boyayla açın ve aynı formatta (.jpg) yeni bir dosya olarak kaydedin. Şimdi bu yeni dosyayı deneyin ve işe yarıyor. Dosyanın bir şekilde bozulmuş olabileceğini size açıkça açıklıyor. Bu, yalnızca kodunuzda diğer tüm hatalar giderilmişse yardımcı olabilir


0

Bir resmi yola kaydetmeye çalışırken de yanımda belirdi

C:\Program Files (x86)\some_directory

ve .exeyönetici olarak çalıştırılmadı, umarım bu da aynı sorunu olan birine yardımcı olabilir.


0

Benim için aşağıdaki kod A generic error occurred in GDI+, bir MemoryStream. Kod bir web sunucusunda çalışıyordu ve siteyi çalıştıran Uygulama Havuzunu durdurup başlatarak çözdüm.

GDI + 'da bazı dahili bir hata olmalı

    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

Bir WPF uygulamasında basit bir resim düzenlemeyi denediğimde bu hatayla karşılaştım.

Bir Görüntü öğesinin Kaynağını bit eşlem olarak ayarlamak dosya kaydetmeyi engeller. Source = null ayarının bile dosyayı yayınlamadığı görülüyor.

Şimdi, görüntüyü hiçbir zaman Görüntü Kaynağı öğesi olarak kullanmıyorum, bu yüzden düzenledikten sonra üzerine yazabilirim!

DÜZENLE

CacheOption özelliğini duyduktan sonra (@Nyerguds sayesinde) çözümü buldum: Yani Bitmap yapıcısını kullanmak yerine ayarlamadan sonra Uri'yi ayarlamalıyım CacheOption BitmapCacheOption.OnLoad( Image1aşağıda Wpf Imageöğesi var)

Onun yerine

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

kullanın:

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

Şuna bakın: WPF Görüntü Önbelleğe Alma


1
WPF görüntülerinin BitmapCacheOption.OnLoad, yükleme kaynağından bağlantısını kesmek için belirli bir parametresi vardır .
Nyerguds

Teşekkür ederim @Nyerguds, yorumunuza kadar doğru soruları sormadım
mkb

0

Bu kodu deneyin:

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

Görüntüleri yeniden boyutlandırmak için görüntü işlemci kullandım ve bir gün "GDI + 'da genel bir hata oluştu" istisnası aldım.

Bir süre baktıktan sonra uygulama havuzunu geri dönüştürmeye çalıştım ve bingo çalışıyor. Bu yüzden buraya not alıyorum, umarım yardımcı olur;)

Şerefe

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.