1

I am working on a Windows Phone application in Xamarin with MvvmCross. In this application the user selects some images from his phone. They get displayed in a list and the user then does stuff with them.

I am using FileOpenPicker for the file selection and from those files i create BitmapImages to display

foreach (StorageFile file in args.Files) {
                    BitmapImage thumbnail = new BitmapImage();
                    thumbnail.DecodePixelType = DecodePixelType.Physical;
                    try {
                        using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read)) {
                            thumbnail.DecodePixelHeight = 70;
                            thumbnail.DecodePixelWidth = 70;

                            thumbnail.SetSource(fileStream);
                            fileStream.Dispose();
                        }
                    }
                    catch (OutOfMemoryException e) {
                        Mvx.Trace("MEMORY IS FULL");
                    }

After some other code i put these BitmapImages in a ObservableCollection and display them like this

<Image Style="{StaticResource imageListImage}" Source="{Binding Thumbnail}"/>

Nothing special about that. The test images that i am using have a total size of 34 MB. Using the performance and diagnostics tool from VS i was able to determine that the memory usage of the app at start was around 16 Mb. When i loaded the test images in to the app it shot up to 58 MB. as if it still used the full size of the images. and (Just for testing) when i took the decodepixelheight and width away it rocketed to about 350 MB. I have absolutely no idea why it is using so much memory for the images.

Because the application must be able to use lots more and bigger images I need to find a way to cut down on the memory usage. Does anyone know a way how I can do this?

StijnvanGaal
  • 441
  • 3
  • 17

1 Answers1

2

Your pictures use 34 MB of storage when they are compressed. To be displayed, they need to be decompressed. A bitmap picture uses 4 bytes per pixel (one byte for each color channel, RGB, plus one byte for the alpha channel). Therefore, a single 5 megapixels picture will use about 20 MB of RAM. That's why using DecodePixelHeight/DecodePixelHeight as often as possible is paramount.

Still, sometimes, you have to manipulate huge pictures (such as the 38 MP pictures of the Lumia 1020, 150 MB of RAM each!!). For that purpose, Nokia released the Imaging SDK (now maintained by Microsoft), allowing you to work on portions of the pictures, and not having to load the full thing in memory.

In your case, the main issue is that you load all the thumbnails at once, even though only a few of them will be displayed simultaneously. If you want to reduce the amount of memory used, you must lazily load the thumbnails (that is, only when they are needed). One way would be to store the file location instead of the BitmapImage, and load the picture as needed. Unfortunately, you can't bind directly a path to the Image control (except if the file is in the application's local storage). In that question, I suggested to use a custom usercontrol for somebody who had a similar issue.

public sealed partial class LocalImage : UserControl
{
    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof (string),
        typeof (LocalImage), new PropertyMetadata(null, SourceChanged));

    public LocalImage()
    {
        this.InitializeComponent();
    }

    public string Source
    {
        get { return this.GetValue(SourceProperty) as string; }
        set { this.SetValue(SourceProperty, value); }
    }

    private async static void SourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var control = (LocalImage)obj;

        var path = e.NewValue as string;

        if (string.IsNullOrEmpty(path))
        {
            control.Image.Source = null;
        }
        else
        {
            var file = await StorageFile.GetFileFromPathAsync(path);

            using (var fileStream = await file.OpenAsync(FileAccessMode.Read))
            {
                BitmapImage bitmapImage = new BitmapImage();
                await bitmapImage.SetSourceAsync(fileStream);
                control.Image.Source = bitmapImage;
            }
        }
    }
}
Community
  • 1
  • 1
Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
  • Thank you for your answer. I have found a solution by now. Though it was not what you showed me, the link you gave me from the other post had the solution. Using the file.getScaledImageAsThumbnailAsync it uses a lot less memory (No idea why). Though in the future my application will also need to show te entire picture library so i will have a look at the ISupportIncrementalLoading that you showed there. Thanks again for the help – StijnvanGaal Nov 06 '15 at 10:15