I've been working with a 32-bit C# WPF application that displays a big amount of large images (1080p in many cases) in a ListBox. The problem is that retaining a BitmapSource object in my C# object (that I've bound to) increases memory dramatically because the bytes of the BitmapSource that I have created are duplicated/copied before render. If I keep the BitmapSource object in order to reuse it or redisplay it somewhere else, I end up with multiple copies of the raw image bytes floating around due to the copy-before-render. More specifically, CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
is called before render. A memory/heap analysis with a stack trace confirms the idea that bytes are copied before render.
The only "workaround" I have created, which generates the BitmapSource every time it is needed is as follows:
ImageData data = _backendImage.getData();
SWIGTYPE_p_unsigned_char rawData = _backendImage.getRawData();
IntPtr dataPointer = SWIGTYPE_p_unsigned_char.getCPtr(rawData).Handle;
GC.Collect(); // forces garbage collection on not-displayed images
return Utilities.GenerateBitmapSource((int)_backendImage.getWidth(), (int)_backendImage.getHeight(), _backendImage.getByteOrder(), dataPointer, data.Count);
The final line there is my own function to actually generate a BitmapSource object and is out of the scope of this problem.
The workaround is extremely poor on performance, since I'm doing not just one, but two copies of the data (one into the BitmapSource, one to render) before every render into the ListBox. Keeping the BitmapSource around removes all of the duplicate copy operations, but is extremely heavy on memory usage.
Here is my ListBox XAML:
<ListBox Name="SlideShowListBox" ItemsSource="{Binding SlideData.PreviewData}"
SelectedIndex="{Binding SelectedIndex}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Extended"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<ListBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal" Margin="0, 2.5, 0, 2.5">
<Label Content="{Binding data.ImageNumber}" VerticalAlignment="Top" Width="30" HorizontalContentAlignment="Right" Margin="0,-6.5,0,0"/>
<Grid>
<Image Source="{Binding data.ImageThumbnail}" RenderOptions.BitmapScalingMode="HighQuality" VerticalAlignment="Top" HorizontalAlignment="Left"
Name="ListImage"
MaxWidth="{Binding ElementName=ListBoxItemSizer,
Path=ActualWidth, Converter={ikriv:MathConverter}, ConverterParameter=(x - 20)}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding data.IsHidden}" Value="True">
<Setter Property="Opacity" Value="0.5"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</VirtualizingStackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Question: Is there any way to prevent WPF from copying the bytes before render when I already am storing all the bytes in RAM and have called .Freeze()
on the image? I would like one copy of my image bytes to be in RAM: no more, no less.
Possibly related: .NET Memory issues loading ~40 images, memory not reclaimed -- Seems unrelated because I am building BitmapSource objects from raw bytes, not a (literal) stream object.
Edit: Interesting clarification -- I am showing these BitmapSource items in 2 different ListBox items on two different screens. If I keep the objects around, the RAM usage only increases on first render of the BitmapSource and not on subsequent renders, regardless of which screen or ListBox the BitmapSource appears on.