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?
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;
}
}
}
}
Related
Here is the problem.
I have a view, that should display an image and some controls.
User add new images, changes some options and click "finish".
Images are large and very large (400-1500 MB Tiff)
User should see the preview of image, but it is ok if it loading for 10-15 sec or even more, he have a job for this time.
Image is binding through MVVM pattern like simple string (file will be always in local folder)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
Problem is that all is hangs when user try to add a file for loading time.
I understand that this case should be solved through multithreading - but have no idea how to implement this.
I tryed to update image from view in different thread like this:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
But anyway at point
ImagePreview.Source = t_source;
everything hangs up until image fully loaded.
Is there a way to load a preview in the background and show it without those terrible hangs?
The probably most simple way of asynchronously loading an image is via an asynchronous Binding. You would not have to deal with Threads or Tasks at all.
<Image Source="{Binding Image, IsAsync=True}"/>
A possible view model could look like shown below, where you must make sure that the Image property getter can be called from a background thread.
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
As you already mentioned, you are blocking the UI thread with the image load. You can use a WriteableBitmap class instance as the source for your Image. This will let you load the image on a background thread or async task. Here is a quick guide (not mine) on the issue.
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
Another option would be using priortybinding with the highest priorty to the full image and a lower priority to the faster-loading preview image. MS has documented priority binding here:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding
I have a LongListSelector that I am using to display a bunch of images as like thumbnails. At a certain point when scrolling (an infinite scrolling scenario) the images are downloading and the app slows down (i.e scrolling becomes slow on LongListSelector) after the images show up the app is back snappy again.
I am using xaml that looks like this:
<Image Stretch="UniformToFill"
Margin="-3,0,0,0"
Source="{Binding video_thumbnail}"
Opacity="1"
Height="200" Width="480" />
video_thumbnail is a string.
Should I be creating the image this way or is there a better way to optimize my code?
I had similar problem. I have solved it by implementing my own queue for downloading images. Just download one image at the time, so you will have just one thread for it.
You may think that images will be downloaded slower, but it's not true. Downloading 5 images one by one takes the same amount of time when downloading all 5 at the same time. I tested it in my own app.
Here's my example. First, I have created a class to store basic info about videos: url to a thumbnail and the thumbnail itself as ImageSource, so you can easily bind it to Source property of Image in XAML.
public class VideoItem
{
public string Url { get; private set; }
public ImageSource Thumbnail { get; set; }
public VideoItem(string url)
{
this.Url = url;
}
}
The next class will store all the videos in one observable list, so you can use it in binding. The list will grow as thumbnails will be downloaded, one by one.
public class VideoLibrary
{
private WebClient thumbnailDownloader = new WebClient();
private Queue<VideoItem> downloadQueue = new Queue<VideoItem>();
private bool isBusy = false;
public ObservableCollection<VideoItem> Videos = new ObservableCollection<VideoItem>();
public VideoLibrary()
{
thumbnailDownloader.OpenReadCompleted += OnThumbnailDownloaded;
}
// It will not start downloading process but only add a new video item (without thumbnail) to download queue.
public void Download(string url)
{
downloadQueue.Enqueue(new VideoItem(url)); // Just add to queue.
CheckQueue();
}
// Check whether there are new thumbnails to download. If so, start downloading just one.
private void CheckQueue()
{
if (isBusy) // Stop! Downloading in progress...
return;
if (downloadQueue.Count > 0)
{
isBusy = true;
VideoItem item = downloadQueue.Peek();
thumbnailDownloader.OpenReadAsync(new Uri(item.Url));
}
}
// One thumbnail has been downloaded. We can add it to the list of videos and check the queue again.
private void OnThumbnailDownloaded(object sender, OpenReadCompletedEventArgs e)
{
isBusy = false;
if (e.Cancelled)
{
CheckQueue(); // Just try again.
}
else if (e.Error != null)
{
downloadQueue.Dequeue(); // Skip this video (thumbnail), check the next one.
CheckQueue();
}
else if (downloadQueue.Count > 0)
{
VideoItem item = downloadQueue.Dequeue(); // Remove the video from queue.
WriteableBitmap bitmap = new WriteableBitmap(null);
bitmap.SetSource(e.Result);
item.Thumbnail = bitmap; // Thumbnail is ready to use.
Videos.Add(item); // Add to list.
CheckQueue();
}
}
}
According to this article:
Never bind server-hosted images directly to the control, because Silverlight runtime will use the UI thread (using WebClient) to fetch that image from the server, that can make the UI unresponsive for some time.
Use a background thread and HttpWebRequest class based implementation to download the image data in an efficient way which finally creates BitmapImage and sets that as the source. A clean MVVM wrapper around this would make your entire Image management pretty easy.
Also, go through Best Practices for Windows Phone development`
set BitMapCredtiOptions value to BackGroundCreation.This will allow it to use a cached image if one already exists for the UriSource.
If the image is a large size, such as a high resolution image captured by many phone cameras, then a considerable amount of CPU processing can be used up in decoding all the pixels of the image. If you know the size of the image frame you intend to render, you can use that information to set the pixel size to decode.
<Image ..>
<Image.Source>
<BitmapImage
UriSource="{Binding video_thumbnail}"
CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
im trying to update an image in an image control that is bound to a class which implements INotifyPropertyChanged. i've tried most of the methods which relate to refreshing bitmap cache so that the image can refresh but none seems to work for my case. the image contorl is defined in the xaml file as: <Image Source="{Binding Chart}" Margin="0 0 0 0"/>
and in the code behind the class is:
private ImageSource imagechart = null;
public ImageSource Chart
{
get
{
return imagechart;
}
set
{
if (value != imagechart)
{
imagechart = value;
NotifyPropertyChanged("Chart");
}
}
}
after an event i now set the image using the following code:
c.Chart = image;
when i now run my application this will display the image but during the running of the application i update the image but calling this c.Chart = image; displays the initial image. i came to understand that WPF caches the image but all methods claiming to solve this dint work for me. one of the solutions that did not work for me is Problems overwriting (re-saving) image when it was set as image source
Try to change the return Type of your Image property to Uri. The TypeConverter on the Source Property should do the rest. If this doesnt work, verify that the resource has actually changed.
You can read the resource from your assembly using Assembly.GetManifestResourceStreams and resolve the bytes. Than manually save them with File.WriteAllBytes to your output directory an see if it has the expected image.
As far as i know Application Ressources (which are embedded into the assembly) can not be changed during runtime (?). You are referencing the assembly ressource and not an output ressource with your pack uri.
thank you all for your input coz through them i finally figure a way around this. so my xaml still remains bound as <Image Source="{Binding Chart}" Margin="0 0 0 0"/> but in the code behind i changed the class property chart to return a bitmap as shown below:
private BitmapImage image = null;
public BitmapImage Chart
{
get
{
return image;
}
set
{
if (value != image)
{
image = value;
NotifyPropertyChanged("Chart");
}
}
}
this class mind you implements INotifyPropertyChanged . at the point where i set the image i am now using this code:
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
//in the following code path is a string where i have defined the path to file
img.UriSource = new Uri(string.Format("file://{0}",path));
img.EndInit();
c.Chart = img;
this works well for me and refreshes the image upon update.
I am making a simple image viewer in WPF using FreeImage wrapper for .NET (http://freeimage.sourceforge.net/)
Here is the code
public static void OpenImage(string path)
{
_rawImage = new FreeImageBitmap(path);
BitmapSource bs = Utils.BitmapToBitmapSource(_rawImage.ToBitmap());
mainWindow.imageComponent.Source = bs;
mainWindow.imageComponent.Width = _rawImage.Width;
mainWindow.imageComponent.Height = _rawImage.Height;
}
[System.Runtime.InteropServices.DllImport("gdi32")]
static extern int DeleteObject(IntPtr o);
public static BitmapSource BitmapToBitmapSource(System.Drawing.Bitmap source)
{
IntPtr ip = source.GetHbitmap();
BitmapSource bs = null;
try
{
bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(ip,
IntPtr.Zero, Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(ip);
}
return bs;
}
The problem is ram spike and overall ram usage when displaying the image. The image I use for testing is a 5000x5000 jpeg. FreeImage reports it takes up 70mb of ram when in memory, which is correct. My app takes about 100mb(~30 for WPF and 70 for image) if I run only this part:
_rawImage = new FreeImageBitmap(path);
but when the full code is ran memory spikes to about 280mb which is way too much. In production code I can obviously dispose of all unused items but the initial spike is too much. I use IrfanView for image browsing and with same image it takes up a mere 77mb of memory.
I would like some solution(if there is one) to get rid of the spike it takes to load and convert the image into format that wpf Image can display. Maybe further reduce ram usage if possible. I work with big images and it's terrible if it will take 3x memory to load one image. I am rather new to WPF and this stuff overall so there might be something I'm missing.
If there is no possible solution in WPF, maybe something else? I am open for suggestions.
I tried searching but failed to find anything to solve my current problem.
Thanks a lot.
Here is my take:
<Window x:Class="LargeJpeg.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Image x:Name="Image" Stretch="None"/>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.None;
bitmap.UriSource = new Uri(#"C:\5x5.jpg", UriKind.Absolute);
bitmap.DecodePixelWidth = (int)Image.ActualWidth;
bitmap.EndInit();
bitmap.Freeze();
Image.Source = bitmap;
}
}
Average memory usage: 130 mb on a 5000 x 5000 jpeg.
I want to display all images stored in the Windows Phone 8 photo folder in my custom gallery which uses a ListBox for displaying the images.
The ListBox code is as follows:
<phone:PhoneApplicationPage.Resources>
<MyApp:PreviewPictureConverter x:Key="PreviewPictureConverter" />
</phone:PhoneApplicationPage.Resources>
<ListBox Name="previewImageListbox" VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel CleanUpVirtualizedItemEvent="VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1">
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding Converter={StaticResource PreviewPictureConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With the following converter:
public class PreviewPictureConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
PreviewImageItem c = value as PreviewImageItem;
if (c == null)
return null;
return c.ImageData;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Images are stored in a custom class:
class PreviewImageItem
{
public Picture _picture = null;
public BitmapImage _bitmap = null;
public PreviewImageItem(Picture pic)
{
_picture = pic;
}
public BitmapImage ImageData
{
get
{
System.Diagnostics.Debug.WriteLine("Get picture " + _picture.ToString());
_bitmap = new BitmapImage();
Stream data = _picture.GetImage();
try
{
_bitmap.SetSource(data); // Out-of memory exception (see text)
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception : " + ex.ToString());
}
finally
{
data.Close();
data.Dispose();
data = null;
}
return _bitmap;
}
}
}
The following code is used to set the ListBox data source:
private List<PreviewImageItem> _galleryImages = new List<PreviewImageItem>();
using (MediaLibrary library = new MediaLibrary())
{
PictureCollection galleryPics = library.Pictures;
foreach (Picture pic in galleryPics)
{
_galleryImages.Add(new PreviewImageItem(pic));
}
previewImageListbox.ItemsSource = _galleryImages;
};
Finally here is the "cleanup" code:
private void VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1(object sender, CleanUpVirtualizedItemEventArgs e)
{
PreviewImageItem item = e.Value as PreviewImageItem;
if (item != null)
{
System.Diagnostics.Debug.WriteLine("Cleanup");
item._bitmap = null;
}
}
All this works fine but the code crashes with an OutOfMemoryException after a few images (especially when scrolling fast). The method VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 is called regulary (e.g. every 2 or 3 listbox entries) when the ListBox is scrolled.
What's wrong with this sample code?
Why is memory not freed (fast enough)?
Oh, I recently killed whole day to make this working!
So the solution is:
Make your Image control free resources. So set the
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
as it was mentioned before.
Make sure you virtualize _bitmap on every item of the list. You should load it on demand (LongListSelector.Realized method) and you have to destroy it! It won't going to collect automatically and GC.Collect doesn't work either.
Null reference is not working too :(
But here is the method:
Make 1x1 pixel file. Copy it into assembly and make resource stream from it to dispose your images with 1x1 pixel blank. Bind custom dispose method to LongListSelector.UnRealized event (e.Container handles your list item).
public static void DisposeImage(BitmapImage image)
{
Uri uri= new Uri("oneXone.png", UriKind.Relative);
StreamResourceInfo sr=Application.GetResourceStream(uri);
try
{
using (Stream stream=sr.Stream)
{
image.DecodePixelWidth=1; //This is essential!
image.SetSource(stream);
}
}
catch { }
}
Working for me in LongListSelector with 1000 images 400 width each.
If you miss the 2 step with the data collection you can see the the good results but the memory overflows after 100-200 items scrolled.
You just had Windows Phone with show all the picture's in a user's media library "pictures" folder on screen. That's unbelievably memory intensive and considering the 150MB limit on WP8 apps it's no wonder you're getting OOM exceptions.
A few things you should consider adding:
1) Set Source and SourceUri properties to null when scrolling the listboxitem out of view. See "Caching Images" in Stefan's article here # http://blogs.msdn.com/b/swick/archive/2011/04/07/image-tips-for-windows-phone-7.aspx
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
2) If you're on WP8 make sure to set DecodePixelWidth and/or DecodePixelHeight. That way an image will be loaded into memory, resized permanently and only the resized copy is stored in memory. The images loaded into memory can be much bigger then the screen size of the phone itself. So cropping those down to the right size and only storing the resized images is vital. Set BitmapImage.DecodePixelWidth=480 (at maximum) to help with that.
var bmp = new BitmapImage();
// no matter the actual size,
// this bitmap is decoded to 480 pixels width (aspect ratio preserved)
// and only takes up the memory needed for this size
bmp.DecodePixelWidth = 480;
bmp.UriSource = new Uri(#"Assets\Demo.png", UriKind.Relative);
ImageControl.Source = bmp;
(code sample from here)
3) Why are you using Picture.GetImage() instead of Picture.GetThumbnail()? Do you really need the image to take up the whole screen?
4) Consider moving from ListBox to LongListSelector if this is a WP8 exclusive app. LLS has much, much better virtualization then ListBox. Looking at your code sample it might be enough for you to just change your XAML ListBox element to LongListSelector element.
Try this approach: Image downloader with auto memory cleaning. Sample project here: https://simca.codeplex.com/