Releasing the cache of a WPF Image Control - c#

I have a timer and on every tick I want to take an image file from memory and change the image that is being displayed in the Image with this piece of code
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Render,
new Action(() =>
{
ms.Seek(0, SeekOrigin.Begin);
e.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.None;
bitmapImage.EndInit();
CameraImageBox.BeginInit();
CameraImageBox.Source = bitmapImage;
CameraImageBox.EndInit();
bitmapImage = null;
ms.Flush();
}));
The Image control turns pitch black after a couple of dozen of images and the whole ui turns quite unresponsive. The memory use jumps to a whopping 1gig, I'm assuming the image controls render cache doesn't get released as e.Image is a static resource that gets redrawn every time.
Is there a better way to do this, like rendering the image in a Rectangle or manually releasing the cache?

it is my understanding that you are adding the image many multiple times to the MemoryStream at every single iteration.
this because your ms object exists from outside and is never disposed. If I am understanding what happens correctly:
// go to begin of stream
ms.Seek(0, SeekOrigin.Begin);
// write your bitmap content into the stream
e.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
...
...
...
// flush to write content to the stream
ms.Flush();
now in theory I understand you are overwriting the same bytes all the time, but why don't you dispose the MemoryStream with a using block inside your method and test the results instead of having it open as a global variable?

Related

C# BitmapImage source memory issues when updating source [duplicate]

I am loading and unloading images in Canvas. I used the below code to load the Image.
Before loading my Image the memory consumption is 14.8MB.
Canvas c = new Canvas();
Image im = new Image();
ImageSource src = new BitmapImage(new Uri(#"E:Capture.png"));
im.Source = src;
im.Height = 800;
im.Width = 800;
c.Children.Add(im);
homegrid.Children.Add(c); //homegrid is my grid's name
The Image displayed correctly and the memory consumption now is 20.8MB. Then I unloaded the Image by the below code:
foreach (UIElement element in homegrid.Children)
{
if (element is Canvas)
{
Canvas page = element as Canvas;
if (page.Children.Count > 0)
{
for (int i = page.Children.Count - 1; i >= 0; i--)
{
if (page.Children[i] is Image)
(page.Children[i] as Image).Source = null;
page.Children.RemoveAt(i);
}
}
page.Children.Clear();
page = null;
}
}
homegrid.Children.RemoveAt(2);
InvalidateVisual();
The Image gets removed after this, but the memory is still 20.8 MB.
Can anyone help me out this?
First of all you should test by explicitly invoking GC.Collect() to collect memory and see that memory releases or not because GC collection is indeterministic. You can't be sure that after your method execution GC runs and reclaim the memory.
So , at end put this code to explicitly force GC to run to check if actually memory is released or not:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
However, there is some known memory leak issues in BitmapImage creation which you can refer here, here and here.
Actually under the covers WPF keeps a strong reference between the static BitmapImage and the Image and hook some events on Bitmap image. So, you should freeze the bitmapImage before assigning to image. WPF doesn't hook events on freezed bitmapImage. Also set CacheOption to avoid any caching memory leak of bitmapImage.
Image im = new Image();
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(#"E:Capture.png");
bi.EndInit();
bi.Freeze();
ImageSource src = bi;
im.Source = src;
im.Height = 800;
im.Width = 800;
In .Net there is something called the garbage collector (GC) that is in charge of managing the memory you're using.
When you create an instance of an object, it requires some more memory.
When you remove your ImageSource from the Children collection, you don't actually free any memory, you just say "I don't want to use this instance anymore".
At this point the GC will help you. It'll automatically detect instances that are not used anymore, and will free the associated memory for you.
Do note it's an automatic process and you shouldn't (and you don't want to) take care of the memory management.
You can call GC.Collect(); to force the garbage collector to do its job right now, you'll see the memory will be released. NOTE: GC.Collect(); should be used in debug to detect memory leaks, but 99% of times you shouldn't call it explicitly in production code. GC.Collect(); is an operation that can use a lot of CPU time.

Prevent Memory Bloat When Loading Multiple Images In WPF

I have a very simple WPF app which is used to preview images in any given folder one image at a time. You can think of it as a Windows Image Viewer clone. The app has a PreviewKeyUp event used to load the previous or next image in the folder if the left arrow or right arrow key is pressed.
<Grid>
<Image x:Name="CurrentImage" />
</Grid>
private void Window_PreviewKeyUp(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Left:
DecreaseIndex();
break;
case Key.Right:
IncreaseIndex();
break;
}
var currentFile = GetCurrentFile();
CurrentImage.Source = new BitmapImage(new Uri(currentFile));
}
The problem I'm trying to solve is that there is a large amount of memory bloat when loading multiple images until garbage collection occurs. You can see this in the screenshot I took of the app's memory usage. It's not uncommon for it to exceed 300 MB before garbage collection occurs.
I tried wrapping the image in a using statement, but that doesn't work because BitmapImage doesn't implement IDisposable.
using (var image = new BitmapImage(new Uri(currentFile)))
{
CurrentImage.Source = image;
}
What can I do to prevent memory bloat when loading multiple images into my app?
When you say preview, you probably don't need the full image size. So besides of calling Freeze, you may also set the BitmapImage's DecodePixelWidth or DecodePixelHeight property.
I would also recommend to load the images directly from a FileStream instead of an Uri. Note that the online doc of the UriCachePolicy says that it is
... a value that represents the caching policy for images that come from an HTTP source.
so it might not work with local file Uris.
To be on the safe side, you could do this:
var image = new BitmapImage();
using (var stream = new FileStream(currentFile, FileMode.Open, FileAccess.Read))
{
image.BeginInit();
image.DecodePixelWidth = 100;
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
image.Freeze();
CurrentImage.Source = image;
Call .Freeze() on the bitmap object, this sets it in to a read-only state and releases some of the handles on it that prevents it from getting GC'ed.
Another thing you can do is you can tell the BitmapImage to bypass caching, the memory you see building up could be from the cache.
CurrentImage.Source = new BitmapImage(new Uri(currentFile),
new RequestCachePolicy(RequestCacheLevel.BypassCache));
Lastly, if there is not a lot of programs running on the computer putting memory pressure on the system .net is allowed to wait as long as it wants for a GC. Doing a GC is slow and lowers performance during the GC, if a GC is not necessary because no one is requesting the ram then it does not do a GC.

MemoryStream expandable with initial buffer

I am trying to load some filestream into a memorystream. The source stream could be any stram, but the focus here is the destination stream.
The source has more than 3Gb.
I tryed 4 different approaches to this.
1)fDest = new MemoryStream(); and then read 4K blocks into this. This takes forever com complete as the buffer must be resized on every step.
2)fDest = new MemoryStream(int.MaxValue); Got OutOfMemoryException on creation
3)fDest = new MemoryStream(1073741823); and then read 4K blocks into this. This loads fast but i get an OutOfMemory exception when the buffer needs to be resized.
4)fDest = new MemoryStream(1073741824);and then read 4K blocks into this. This works. The buffer resize is ok.
Can anyone point me some direction on why i got this behaviour with just one byte in diference on buffer size? I

Proper way of setting an icon as the source of image control in WPF?

I have an image control in my WPF application:
<Image x:Name="image" Source="{Binding}"/>
...and I'm trying to figure out which one would be the most efficient way of setting its source from an icon. I am using SystemIcons.WinLogo as my test subject.
First way involves CreateBitmpapSourceFromHIcon:
image.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(
SystemIcons.WinLogo.Handle, Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
The second approach uses BitmapImage and sets its source from the memory stream:
var ms = new MemoryStream();
SystemIcons.WinLogo.ToBitmap().Save(ms, System.Drawing.Imaging.ImageFormat.Png);
ms.Position = 0;
var bmpi = new BitmapImage();
bmpi.BeginInit();
bmpi.StreamSource = ms;
bmpi.EndInit();
image.Source = bmpi;
Which one should I use? They both work and I haven't noticed much difference in performance on my system.
Both will serve the same purpose. If you ask me I would go with first approach because that's straight forward and no need to get icon first save in memory stream.
However, if you want to go with second approach make sure you call Freeze() on bitmapImage instance to avoid any memory leaks. Also freezing it will make it thread safe i.e. you can create a bitmapImage in background thread and can still set as Image source on UI thread.
var bmpi = new BitmapImage();
bmpi.BeginInit();
bmpi.StreamSource = ms;
bmpi.EndInit();
bmpi.Freeze(); <-- HERE
image.Source = bmpi;

How to copy DispatcherObject (BitmapSource) into different thread?

I am trying to figure out how can I copy DispatcherObject (in my case BitmapSource) into another thread.
Use case:
I have a WPF app that needs to show window in a new thread (the app is actually Outlook addin and we need to do this because Outlook has some hooks in the main UI thread and is stealing certain hotkeys that we need to use - 'lost in translation' in interop of Outlook, WPF (which we use for UI), and Winforms (we need to use certain microsoft-provided winforms controls)).
With that, I have my implementation of WPFMessageBox, that is configured by setting some static properties - and and one of them is BitmapSource for icon. This is used so that in startup I can set WPFMessageBox.Icon once, and since then, every WPFMessageBox will have the same icon.
The problem is that BitmapSource, which is assigned into icon, is a DispatcherObject, and when read, it will throw InvalidOperationException: "The calling thread cannot access this object because a different thread owns it.".
How can I clone that BitmapSource into the actual thread? It has Clone() and CloneCurrentValue() methods, which don't work (they throw the same exception as well). It also occured to me to use originalIcon.Dispatcher.Invoke( do the cloning here ) - but the BitmapSource's Dispatcher is null, and still - I'd create a copy on a wrong thread and still couldnt use it on mine. BitmapSource.IsFrozen == true.
Any idea on how to copy the BitmapSource into different thread (without completely reconstructing it from an image file in a new thread)?
EDIT:
So, freezing does not help: In the end I have a BitmapFrame (Window.Icon doesn't take any other kind of ImageSource anyway), and when I assign it as a Window.Icon on a different thread, even if frozen, I get InvalidOperationException: "The calling thread cannot access this object because a different thread owns it." with a following stack trace:
WindowsBase.dll!System.Windows.Threading.Dispatcher.VerifyAccess() + 0x4a bytes
WindowsBase.dll!System.Windows.Threading.DispatcherObject.VerifyAccess() + 0xc bytes
PresentationCore.dll!System.Windows.Media.Imaging.BitmapDecoder.Frames.get() + 0xe bytes
PresentationFramework.dll!MS.Internal.AppModel.IconHelper.GetIconHandlesFromBitmapFrame(object callingObj = {WPFControls.WPFMBox.WpfMessageBoxWindow: header}, System.Windows.Media.Imaging.BitmapFrame bf = {System.Windows.Media.Imaging.BitmapFrameDecode}, ref MS.Win32.NativeMethods.IconHandle largeIconHandle = {MS.Win32.NativeMethods.IconHandle}, ref MS.Win32.NativeMethods.IconHandle smallIconHandle = {MS.Win32.NativeMethods.IconHandle}) + 0x3b bytes
> PresentationFramework.dll!System.Windows.Window.UpdateIcon() + 0x118 bytes
PresentationFramework.dll!System.Windows.Window.SetupInitialState(double requestedTop = NaN, double requestedLeft = NaN, double requestedWidth = 560.0, double requestedHeight = NaN) + 0x8a bytes
PresentationFramework.dll!System.Windows.Window.CreateSourceWindowImpl() + 0x19b bytes
PresentationFramework.dll!System.Windows.Window.SafeCreateWindow() + 0x29 bytes
PresentationFramework.dll!System.Windows.Window.ShowHelper(object booleanBox) + 0x81 bytes
PresentationFramework.dll!System.Windows.Window.Show() + 0x48 bytes
PresentationFramework.dll!System.Windows.Window.ShowDialog() + 0x29f bytes
WPFControls.dll!WPFControls.WPFMBox.WpfMessageBox.ShowDialog(System.Windows.Window owner = {WPFControlsTest.MainWindow}) Line 185 + 0x10 bytes C#
Once you call Freeze, it should work on multiple threads.
bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);
This comes at a price, but it's pretty cheap comparing to serializing.
Long answer.
The key is to create the bitmap on the thread you want to use. So you can't cache your icon in some static field/property, bud load it (from file, resource, stream or whatever) every time you're opening a new window on new thread.
BitmapFrame can be used on the thread it was created only.
Even cloning doesn't work here, as you correctly stated (which just sucks).
I had exactly the same problem and solved it just by loading icon every time, in my particular case simply by calling
// get your stream somewhere -
window.Icon = BitmapFrame.Create(stream)
And this is how you can get your icon from resource in WPF:
var streamResourceInfo = Application.GetResourceStream(new Uri(#"pack://application:,,,/YourAssembly;relative path to the icon", UriKind.RelativeOrAbsolute));
// use streamResourceInfo.Stream
One workaround that does work, although not very performant, is creating a memory stream from the image data, then reconstructing the image on the thread you want to use it on.
Example for BitmapSource:
Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate()
{
//serialize image on UI thread
imageStream = GetImageBytes(cameraImage);
}
...
//reconstruct image on a different thread:
Bitmap bitmap = new Bitmap(imageStream);
private MemoryStream GetImageBytes(BitmapSource image)
{
MemoryStream ms = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(ms);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}

Categories

Resources