2016-03-21 6 views
2

ListBoxに大量の大きな画像(多くの場合1080p)を表示する32ビットC#WPFアプリケーションで作業していました。問題は、私が作成したBitmapSourceのバイトがレンダリングの前に複製/コピーされるため、(私がバインドした)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オブジェクトを生成することがあり、最終的なラインは、とこの問題の範囲外です。

私はListBoxにレンダリングする前に、1つではなく2つのデータコピー(BitmapSourceに1つ、レンダリングに1つ)を実行するので、回避策はパフォーマンスが非常に悪いです。 BitmapSourceをそのままにしておくと、すべての複製操作が削除されますが、メモリ使用量は非常に重いです。ここで

は私の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> 

質問:前に私はすでにRAMのすべてのバイトを格納していますし、画像上.Freeze()を求めているときに、レンダリングバイトをコピーからWPFを防ぐために、どのような方法があります?画像のバイトのコピーをRAMに保存したいと思います。

おそらく関連:.NET Memory issues loading ~40 images, memory not reclaimed - (リテラル)ストリームオブジェクトではなく、生のバイトからBitmapSourceオブジェクトを構築しているため、無関係です。

編集:興味深い説明 - 2つの異なる画面上の2つの異なるListBoxアイテムにこれらのBitmapSourceアイテムを表示しています。オブジェクトを保持すると、BitmapSourceが表示される画面またはリストボックスに関係なく、RAM使用量がの最初のレンダリングで増加し、その後のレンダリングでは増加しません。

答えて

0

レンダリング前にコピーを防止することができませんでした。 BitmapImageからCacheOption = BitmapCacheOption.Noneまでのすべてのものをメモリ内のイメージではなくファイルからロードしようとした後、RAMに1バイトのコピーを保持するという修正は比較的簡単でした。

これを修正するには、BitmapSourceから継承する独自のカスタムクラスを作成します。受け入れられた回答のコードに従って、必要に応じて独自の画像フォーマットを調整します。たとえば、24bpp配列をPbgra32形式に変換していたため、提供された値よりも独自のストライド値を使用する必要がありました。私はより高速なコピーのために安全でないコピーコードを使用しました(私のユースケースのために修正されました)。この記事の最後にコードをコピーしましたが、リンクされたSOの投稿と非常に似ています。

お客様のカスタムBitmapSourceにはまだ2バイトのコピーがあります。 (CopyPixelsの関数名ではそれが分かります)。今では無関係のコピーを取り除くには、_data = nullと設定し、できるだけGCをクリーンアップさせてください。タダ!RAMの1バイトのコピー、パフォーマンスが速い、ListBoxのスクロールが機能する、他の画面や他の場所でBitmapSourceを再利用することができ、メモリの使用が容認されます。

レンダリング後に呼び出された場合、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; } 
    } 
} 
関連する問題