4

I am developing one windows phone application which useful for upload images to web server. I am selecting all images from my device into one List object. I am converting all bitmap image to byte[] one by one.

My code

public byte[] ConvertToBytes(BitmapImage bitmapImage)
    {
        byte[] data = null;
        WriteableBitmap wBitmap = null;

        using (MemoryStream stream = new MemoryStream())
        {
            wBitmap = new WriteableBitmap(bitmapImage);
            wBitmap.SaveJpeg(stream, wBitmap.PixelWidth, wBitmap.PixelHeight, 0, 100);
            stream.Seek(0, SeekOrigin.Begin);
            //data = stream.GetBuffer();
            data = stream.ToArray();
            DisposeImage(bitmapImage);
            return data;
        }

    }
    public void DisposeImage(BitmapImage image)
    {
        if (image != null)
        {
            try
            {
                using (MemoryStream ms = new MemoryStream(new byte[] { 0x0 }))
                {
                    image.SetSource(ms);
                }
            }
            catch (Exception ex)
            {
            }
        }
    }

Conversion from bitmap to byte

 using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (!store.DirectoryExists("ImagesZipFolder"))
            {
                //MediaImage mediaImage = new MediaImage();
                //mediaImage.ImageFile = decodeImage(new byte[imgStream[0].Length]);
                //lstImages.Items.Add(mediaImage);

                store.CreateDirectory("ImagesZipFolder");
                for (int i = 0; i < imgname.Count(); i++)
                {
                    using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(@"ImagesZipFolder\" + imgname[i], FileMode.CreateNew,store))
                    //using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(@"ImagesZipFolder\text.txt" , System.IO.FileMode.OpenOrCreate, store))                        
                    {
                       // byte[] bytes = new byte[imgStream[i].Length];
                        byte[] bytes = ConvertToBytes(ImgCollection[i]);
                        stream.Write(bytes, 0, bytes.Length);
                    }
                }
            }
            else {
                directory = true;
            }
          }

I have 91 images in my emulator. When I am converting all these bitmap images into byte[], get's following error on line wBitmap = new WriteableBitmap(bitmapImage);

An exception of type 'System.OutOfMemoryException' occurred in System.Windows.ni.dll but was not handled in user code

Error

What can I do to solve this error?

Web Service

If we are sending huge file to web service, is it gives error like

An exception of type 'System.OutOfMemoryException' occurred in System.ServiceModel.ni.dll but was not handled in user code

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ajay
  • 6,418
  • 18
  • 79
  • 130
  • 1
    Do you need to have all byte arrays in memory at the same time? Maybe it is possible to convert image to bytes and upload one by one. – empi Jul 30 '13 at 12:52
  • Yes I want it at a time. I am creating a zip file of that all images – Ajay Jul 30 '13 at 12:54
  • So you are loading 91 images into memory? No surprise you are running out of it. – Nikita B Jul 30 '13 at 12:57
  • What is more be careful with GetBuffer - msdn: Note that the buffer contains allocated bytes which might be unused. For example, if the string "test" is written into the MemoryStream object, the length of the buffer returned from GetBuffer is 256, not 4, with 252 bytes unused. To obtain only the data in the buffer, use the ToArray method; however, ToArray creates a copy of the data in memory. – empi Jul 30 '13 at 12:58
  • Just fyi, there are bitmaps which can not be loaded into memeory on mid-end PC-s. I mean single bitmap. Thats how much memory they can consume. And you load an array of 91 on mobile device -_- – Nikita B Jul 30 '13 at 13:00
  • @empi I am passing bitmap image one by one. When function return the byte array, I am going to store it in isolated storage. – Ajay Jul 30 '13 at 13:11
  • @NikitaBrizhak Thanks for reply. But I am passing it one by one. when one bitmap image convert into byte[] array, I am disposing, flushing the memory – Ajay Jul 30 '13 at 13:40
  • @Ajay But you're keeping each byte array in memory, right? How long are the 91 byte arrays combined? – Kevin Gosse Jul 30 '13 at 14:20
  • @KooKiz How can I clear the memory after every conversion of bitmap image to byte[] array – Ajay Jul 31 '13 at 04:51
  • **If** the issue comes from a leak with the BitmapImage, you can use this workaround: http://stackoverflow.com/a/13482619/869621 However I strongly suspect you're keeping somewhere a reference either to the bitmaps you've manipulated or to the byte arrays, preventing the memory to be freed. Can you show the portion of code in charge of calling `ConvertToBytes`? – Kevin Gosse Jul 31 '13 at 05:56
  • @KooKiz check my code, I have edited it – Ajay Jul 31 '13 at 05:59
  • You don't keep the byte arrays, that's good. You could save a bit of memory by passing directly the stream to your `ConvertToBytes` method and have it writing in it directly instead of returning a byte array, but I don't think it would be enough to fix your memory problem. The real problem is ImgCollection: you shouldn't carry around a list of BitmapImage. Load them one at a time, right before converting them to bytes, and don't keep them. – Kevin Gosse Jul 31 '13 at 06:19
  • @KooKiz I have added all image in ImgCollection by using MediaLibrary class. Every time ImgCollection list clean and refill. – Ajay Jul 31 '13 at 06:24
  • @Ajay It's not about cleaning it, it's about having less things in memory at a given time. Problem: You don't have enough memory to keep those 91 pictures around at the same time. Solution: don't keep the 91 pictures at the same time. – Kevin Gosse Jul 31 '13 at 07:15

3 Answers3

2

What can I do to solve this error?

Change the way you do things, to use less memory.

For instance, instead of converting every picture then uploading them, convert a picture, upload it, convert the next, and so on.

If you really want to process all the pictures at once, you can store the byte arrays in the isolated storage rather than keeping them in memory, and read them back when needed.

Basically, rethink your process and use the storage to use less memory at a given time.

That or ask Microsoft to lift the memory limit on Windows Phone, but it may be a tad trickier.

Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
  • Thanks for reply. I am doing same as you say. I am passing image one by one and writing it in isolated storage. – Ajay Jul 30 '13 at 13:01
  • Hey KooKiz, I have checked this link http://stackoverflow.com/questions/13355496/cannot-find-the-memory-leak/13482619#13482619 and added "DisposeImage" method in my code. I am calling this method before return in "ConvertToBytes" method. But I am getting this error "The component cannot be found. (Exception from HRESULT: 0x88982F50)" – Ajay Jul 31 '13 at 12:38
  • Hello, I am getting the error on a line "using (MemoryStream ms = new MemoryStream(new byte[] { 0x0 }))" – Ajay Aug 01 '13 at 04:45
  • @Ajay Then it's by design, the try/catch is there to swallow this exception. The BitmapImage will keep consuming memory until a new picture is loaded. The very purpose of this hack is to load an invalid picture to force the BitmapImage to free the memory previously used, and yet don't use memory for the new picture (since it's invalid and cannot be loaded). The exception slows down the execution when the debugger is attached (when you start the application directly from Visual Studio), but no impact is noticeable when running without Visual Studio. – Kevin Gosse Aug 01 '13 at 05:57
  • That said, just nulling the image source (`image.Source = null;`) is sufficient in *most* cases. Except in some obscure conditions, like in the question linked, where even nulling the source isn't enough to force the runtime to free the memory. – Kevin Gosse Aug 01 '13 at 05:59
  • But we are writing the bitmap image in memory stream in method "ConvertToBytes". It's very important to me to solve these errors. I have tried all possibilities. Do you have any solution on this? please help me. – Ajay Aug 01 '13 at 06:29
  • @Ajay I don't see what the problem is. Write the bitmap in the stream, like you're already doing, **then** call the `DisposeImage` method to clear the memory used by the BitmapImage – Kevin Gosse Aug 01 '13 at 07:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34596/discussion-between-ajay-and-kookiz) – Ajay Aug 01 '13 at 07:26
  • I have found another way to convert. check I have posted my answer – Ajay Aug 05 '13 at 10:53
1

The problem lies within how the GC and bitmap images work together as described in this bug report: https://connect.microsoft.com/VisualStudio/feedback/details/679802/catastrophic-failure-exception-thrown-after-loading-too-many-bitmapimage-objects-from-a-stream#details

From the report:

When Silverlight loads an image, the framework keeps a reference and caches the decoded image until flow control is returned to the UI thread dispatcher. When you load images in a tight loop like that, even though your application doesn't retain a reference, the GC can't free the image until we release our reference when flow control is returned.

After processing 20 or so images, you could stop and queue the next set using Dispatcher.BeginInvoke just to break up the work that is processed in one batch. This will allow us to free images that aren't retained by your application.

I understand with the current decode behavior it's not obvious that Silverlight is retaining these references, but changing the decoder design could impact other areas, so for now I recommend processing images like this in batches.

Now, if you're actually trying to load 500 images and retain them, you are still likely to run out of memory depending on image size. If you're dealing with a multi-page document, you may want to instead load pages on demand in the background and release them when out of view with a few pages of buffer so that at no point do you exceed reasonable texture memory limits.

Fix:

private List<BitmapImage> Images = .....;
private List<BitmapImage>.Enumerator iterator;
private List<byte[]> bytesData = new List<byte[]>();

public void ProcessImages()
{
    if(iterator == null)
        iterator = Images.GetEnumerator();

    if(iterator.MoveNext())
    {
        bytesData.Add(ConvertToBytes(iterator.Current));

        //load next images
        Dispatcher.BeginInvoke(() => ProcessImages());
    }else{
        //all images done
    }
}

public static byte[] ConvertToBytes(BitmapImage bitmapImage)
{
    using (MemoryStream stream = new MemoryStream())
    {
        var wBitmap = new WriteableBitmap(bitmapImage);
        wBitmap.SaveJpeg(stream, wBitmap.PixelWidth, wBitmap.PixelHeight, 0, 100);
        stream.Seek(0, SeekOrigin.Begin);
        return stream.ToArray();
    }
}
SynerCoder
  • 12,493
  • 4
  • 47
  • 78
  • I have tried this solution but I am getting same error on same line. :( Please check edited question – Ajay Jul 31 '13 at 13:13
0

I have found new way to convert Bitmap Image to byte[] array. There is no need to write image in memory. Hare is code

 public byte[] GetBytes(BitmapImage bi)
    {
        WriteableBitmap wbm = new WriteableBitmap(bi);
        return ToByteArray(wbm);
    }
    public byte[] ToByteArray(WriteableBitmap bmp)
    {
        // Init buffer
        int w = bmp.PixelWidth;
        int h = bmp.PixelHeight;
        int[] p = bmp.Pixels;
        int len = p.Length;
        byte[] result = new byte[4 * w * h];

        // Copy pixels to buffer
        for (int i = 0, j = 0; i < len; i++, j += 4)
        {
            int color = p[i];
            result[j + 0] = (byte)(color >> 24); // A
            result[j + 1] = (byte)(color >> 16); // R
            result[j + 2] = (byte)(color >> 8);  // G
            result[j + 3] = (byte)(color);       // B
        }

        return result;
    }
Ajay
  • 6,418
  • 18
  • 79
  • 130