I have an application(WPF) which creates BitmapImages in huge numbers(like 25000). Seems like framework uses some internal logic so after creation there are approx 300 mb of memory consumed(150 virtual and 150 physical). These BitmapImages are added into Image object and they are added into Canvas. The problem is that when I release all those images memory isn't freed. How can I free memory back?
The application is simple:
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas>
<Button Content="Add" Grid.Row="1" Click="Button_Click"/>
<Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/>
</Grid>
Code-behind
const int size = 25000;
BitmapImage[] bimages = new BitmapImage[size];
private void Button_Click(object sender, RoutedEventArgs e)
{
var paths = Directory.GetFiles(#"C:\Images", "*.jpg");
for (int i = 0; i < size; i++)
{
bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
var image = new Image();
image.Source = bimages[i];
canvas.Children.Add(image);
Canvas.SetLeft(image, i*10);
Canvas.SetTop(image, i * 10);
}
}
private void Remove_click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < size; i++)
{
bimages[i] = null;
}
canvas.Children.Clear();
bimages = null;
GC.Collect();
GC.Collect();
GC.Collect();
}
This is a screenshot of ResourceManager after adding images
There was a bug in Wpf that we were bitten by where BitmapImage objects are not released unless you freeze them. https://www.jawahar.tech/home/finding-memory-leaks-in-wpf-based-applications was the original page where we discovered the issue. It should have been fixed in Wpf 3.5 sp1 but we were still seeing it in some situations. Try changing your code like this to see if that is the problem:
bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
bimages[i].Freeze();
We routinely freeze our BitmapImage objects now as we were seeing other instances in the profiler where Wpf was listening for events on the BitmapImage and thereby keeping the image alive.
If the Feeze() call isn't an obvious fix for your code, I would highly recommend using a profiler such as the RedGate Memory Profiler - that will trace a dependency tree that will show you what it is that is keeping your Image objects in memory.
What worked for me was to:
Set the Image control's ImageSource to null
Run UpdateLayout() before removing the control that contains the Image from the UI.
Make sure that you Freeze() the BitmapImage when you create it and that there were no non-weak references made to the BitmapImage objects used as ImageSources.
My cleanup method for each Image ended up being as simple as this:
img.Source = null;
UpdateLayout();
I was able to arrive at this through experimentation by keeping a list with a WeakReference() object pointing at every BitmapImage that I created and then checking the IsAlive field on the WeakReferences after they were supposed to be cleaned up in order to confirm that they'd actually been cleaned up.
So, my BitmapImage creation method looks like this:
var bi = new BitmapImage();
using (var fs = new FileStream(pic, FileMode.Open))
{
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = fs;
bi.EndInit();
}
bi.Freeze();
weakreflist.Add(new WeakReference(bi));
return bi;
I followed the answer given by AAAA. Orignal code causing memory filled up is:
if (overlay != null) overlay.Dispose();
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);
Inserted AAAA's code block, C# add "using System.Threading;" and VB add "Imports System.Threading":
if (overlay != null) overlay.Dispose();
//--------------------------------------------- code given by AAAA
Thread t = new Thread(new ThreadStart(delegate
{
Thread.Sleep(500);
GC.Collect();
}));
t.Start();
//-------------------------------------------- \code given by AAAA
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);
Repeat looping this block now makes a steady and low memory footprint. This code worked using Visual Studio 2015 Community.
This is still an array
BitmapImage[] bimages = new BitmapImage[size];
Arrays are continuous fixed-length data structures, once memory allocated for the whole array you cannot reclaim parts of it. Try using another data structures (like LinkedList<T>) or other more appropriate in your case
I am just telling my experience about reclaiming BitmapImage memory. I work with .Net Framework 4.5. I create simple WPF Application and Load a large image file. I tried to clear Image from memory using following code:
private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
{
image1.Source = null;
GC.Collect();
}
But It didn't work. I tried other solutions too , but I didn't get my answer. After a few days struggling, I found out if I press the button twice, GC will free the memory. then I simply write this code to call GC collector a few second after clicking the button .
private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
{
image1.Source = null;
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate
{
System.Threading.Thread.Sleep(500);
GC.Collect();
}));
thread.Start();
}
this code just tested in DotNetFr 4.5. maybe you have to freeze BitmapImage object for lower .Net Framework .
Edit
This Code doesn't work unless layout get updated. I mean if parent control get removed, GC fails to reclaim it.
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'm trying to display an animated GIF on a form from an embedded resource, yet nothing is displayed, which makes me think loading from a stream doesn't work this way.
If I ditch this idea and load from file, the GIF is displayed correctly.
Is this something that just won't work, or have I made a mistake along the way? Also, this is being done from within a DLL.
My Code:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Set the image
this.pictureBox.Image = GetImageFromManifest("TempNamespace.Wait.gif");
// Remove the close button
var hwnd = new WindowInteropHelper(this).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
}
public System.Drawing.Image GetImageFromManifest(string sPath)
{
// Ready the return
System.Drawing.Image oImage = null;
try
{
// Get the assembly
Assembly oAssembly = Assembly.GetAssembly(this.GetType());
string[] names = oAssembly.GetManifestResourceNames();
// Get the stream
Stream oStream = oAssembly.GetManifestResourceStream(sPath);
// Read from the stream
oImage = System.Drawing.Image.FromStream(oStream);
}
catch (Exception ex)
{
// Missing image?
}
//Return the image
return oImage;
}
My XAML:
<wfi:WindowsFormsHost HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="50">
<winForms:PictureBox x:Name="pictureBox" Height="50" Width="50" SizeMode="StretchImage" BackgroundImageLayout="None"/>
</wfi:WindowsFormsHost>
In summary, the problem was caused by setting the form's 'SizeToContent' value in either the XAML or the constructor (in my case, it was set to 'SizeToContent.WidthAndHeight').
Removing this property, and setting it in the 'Loaded' event rectified the issue. I assume that the Windows Form Host doesn't render correctly with animated GIF files when the form itself is not painted.
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/
I have a databound object within a WPF control that is 'previewing' a mutlipage tiff.
The object has a public PreviewImage, and CurrentPreviewPage.
It has a private PreviewPages which is a collection of MemoryStreams (each representing a page of the Tiff).
Upon the get of the PreviewImage (the first time) this code runs:
if (PreviewPages.Count == 0)
{
Image myImg = System.Drawing.Image.FromFile(_LocalFile);
for (int i = 0; i < (NumberOfPages); i++)
{
myImg.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
System.IO.MemoryStream ms = new System.IO.MemoryStream();
myImg.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
PreviewPages.Add(ms);
}
}
The previous code takes about 10 seconds to run for a 1100KB 17 page TIFF. There must be a better way of handling this.
Afterwards, this is called:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 1000;
//bi.UriSource = new Uri(fiTemp.FullName);
bi.StreamSource = new System.IO.MemoryStream(PreviewPages[CurrentPreviewPage - 1].ToArray());
bi.EndInit();
_PreviewImage = bi;
Now, after the initialization this code works fantastically (it can change pages as fast as you can drag a bound slider). Any help would be much appreciated.
Load your first page on the primary thread and then additional pages in the background - see BackGroundWorker. Only primary thread can access the UI. On the BackGroundWorker you need to decide if you are going to get the pages 2-x one at a time or all at once. The user cannot get to page 2 until you bring page 2 to the UI thread. I would implement cancel. If the user gives up you don't want to tie up CPU.