2016-03-21 26 views
2

我一直在使用32位C#WPF應用程序,該應用程序在ListBox中顯示大量大圖像(在許多情況下爲1080p)。問題是,保留在我的C#對象的BitmapSource對象(我已經綁定到)增加了內存顯着,因爲我已經創建的複製/複製的BitmapSource的字節渲染之前。如果我保留BitmapSource對象以便重用它或在其他地方重新顯示它,則由於複製之前渲染,我最終會得到原始圖像字節的多個副本。更具體地說,在渲染之前調用CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)。帶有堆棧跟蹤的內存/堆分析證實了在渲染前字節被複制的想法。防止C#WPF BitmapSource在渲染前被複制

唯一的「解決方法」我已經創建,其產生的BitmapSource 每次需要時間如下:

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); 

的最後一行有我自己的函數來實際產生的BitmapSource對象,出於這樣的問題的範圍。

解決方法是對性能極差,因爲我做的不只是一個,但每前的數據的兩個副本(一個進入的BitmapSource,一個渲染)呈現到列表框。保留BitmapSource可以刪除所有重複的複製操作,但對內存使用情況非常重要。

這裏是我的列表框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> 

問:有沒有什麼辦法來防止WPF從複製字節之前渲染的時候,我已經很存儲在RAM中的所有字節,並在圖像上稱爲.Freeze()我想我的圖像字節的一個副本在RAM中:不多也不少。

可能相關:.NET Memory issues loading ~40 images, memory not reclaimed - 似乎無關,因爲我從原始字節構建BitmapSource對象,而不是(文字)流對象。

編輯:有趣的澄清 - 我在兩個不同的屏幕上顯示這些BitmapSource項目在2個不同的列表框項目。如果我把周圍的對象時,內存使用僅增加了第一呈現的BitmapSource的,而不是在隨後的呈現,無論哪個屏幕或ListBox的的BitmapSource出現。

回答

0

我無法阻止渲染前的副本。在嘗試從BitmapImageCacheOption = BitmapCacheOption.None之間的所有內容到從文件加載而不是內存中的圖像之後,修復在RAM中保留1個字節的操作相對簡單。

爲了解決這個問題,創建從BitmapSource繼承自己的自定義類。按照接受的答案here中的代碼,根據您自己的圖像格式的需要進行調整。例如,我需要使用自己的步幅值而不是提供的值,因爲我將24bpp數組轉換爲Pbgra32格式。我使用不安全的複製代碼來獲得更快的副本(再次,爲我的用例進行了修改)。我已將我的代碼複製到此帖子的底部,但與鏈接的SO帖子非常相似。

但是,您的自定義BitmapSource仍有2個字節副本。 (CopyPixels功能名稱可以解決這個問題。)要刪除現在無關的副本,只需設置_data = null並讓GC在可能的情況下進行清理。田田!RAM中的一個字節副本,性能很快,ListBox滾動工作,您可以在其他屏幕和其他地方重複使用您的BitmapSource,並且可以使用內存。

我很擔心如果在render之後調用CreateInstanceCore(),這可能會破壞除我自己以外的其他用例。

class RGB24BitmapSource : BitmapSource 
{ 
    private byte[] _data; 
    private int _stride; 
    private int _pixelWidth; 
    private int _pixelHeight; 

    public RGB24BitmapSource(int pixelWidth, int pixelHeight, IntPtr data, int dataLength, int stride) 
    { 
     if (dataLength != 0 && data != null && data.ToInt64() != 0) 
     { 
      _data = new byte[dataLength]; 
      Marshal.Copy(data, _data, 0, dataLength); 
     } 
     _stride = stride; 
     _pixelWidth = pixelWidth; 
     _pixelHeight = pixelHeight; 
    } 

    private RGB24BitmapSource(int pixelWidth, int pixelHeight, byte[] data, int stride) 
    { 
     _data = data; 
     _stride = stride; 
     _pixelWidth = pixelWidth; 
     _pixelHeight = pixelHeight; 
    } 

    unsafe public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset) 
    { 
     if (_data != null) 
     { 
      fixed (byte* source = _data, destination = (byte[])pixels) 
      { 
       byte* dstPtr = destination + offset; 
       for (int y = sourceRect.Y; y < sourceRect.Y + sourceRect.Height; y++) 
       { 
        for (int x = sourceRect.X; x < sourceRect.X + sourceRect.Width; x++) 
        { 
         byte* srcPtr = source + _stride * y + 3 * x; 
         byte a = 255; 
         *(dstPtr++) = (byte)((*(srcPtr + 2)) * a/256); 
         *(dstPtr++) = (byte)((*(srcPtr + 1)) * a/256); 
         *(dstPtr++) = (byte)((*(srcPtr + 0)) * a/256); 
         *(dstPtr++) = a; 
        } 
       } 
      } 
     } 
     _data = null; // it was copied for render, so next GC cycle could theoretically reclaim this memory. This is the magic fix. 
    } 

    protected override Freezable CreateInstanceCore() 
    { 
     return new RGB24BitmapSource(_pixelWidth, _pixelHeight, _data, _stride); 
    } 

    // DO. NOT. COMMENT. THESE. OUT. IF YOU DO, CRASHES HAPPEN! 
#pragma warning disable 0067 // disable unused warnings 
    public override event EventHandler<DownloadProgressEventArgs> DownloadProgress; 
    public override event EventHandler DownloadCompleted; 
    public override event EventHandler<ExceptionEventArgs> DownloadFailed; 
    public override event EventHandler<ExceptionEventArgs> DecodeFailed; 
#pragma warning restore 0067 

    public override double DpiX 
    { 
     get { return 96; } 
    } 

    public override double DpiY 
    { 
     get { return 96; } 
    } 

    public override System.Windows.Media.PixelFormat Format 
    { 
     get { return PixelFormats.Pbgra32; } 
    } 

    public override BitmapPalette Palette 
    { 
     get { return BitmapPalettes.WebPalette; } 
    } 

    public override int PixelWidth 
    { 
     get { return _pixelWidth; } 
    } 

    public override int PixelHeight 
    { 
     get { return _pixelHeight; } 
    } 

    public override double Width 
    { 
     get { return _pixelWidth; } 
    } 

    public override double Height 
    { 
     get { return _pixelHeight; } 
    } 
}