Reputation: 441
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?
Upvotes: 1
Views: 121
Reputation: 39007
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;
}
}
}
}
Upvotes: 2