WPF CroppedBitmap + RenderTargetBitmap = EventHandler leak problem and Freeze problem too

406 Views Asked by At

I am wondering if there is a .net EventHandler leak surrounding CroppedBitmap and RenderTargetBitmap. It's causing me a huge nightmare!

My WPF app runs an always running render call at 60fps. I noticed that after a time, the content being rendered got slower and slower and slower.

After much investigation, including profiling in visual studio and using ANTS memory profiler, I narrowed it down to using a CroppedBitmap.

I've got some simple demonstration of problem code, which includes...

RenderTargetBitmap srcBitmap = new RenderTargetBitmap((int)scaledWidth, (int)scaledHeight, 96, 96,     PixelFormats.Default);
RenderTargetBitmap destBitmap = new RenderTargetBitmap((int)scaledWidth, (int)scaledHeight, 96, 96,     PixelFormats.Default);

<code loop>
{...
    DrawingVisual DV = new DrawingVisual();
    DrawingContext DC = DV.RenderOpen();

    var srcRec = new Int32Rect(x,y,w,h);
    var srcCrop = new CroppedBitmap(srcBitmap, srcRec);

    var destRec = new Rect(.....);
    DC.DrawImage(srcCrop, destRec);

    DC.Close();
    destBitmap.Render(DV);

    DC = null;
    DV = null;
...}

After CroppedBitmap is called using srcBitmap, srcBitmap has an event handler added to it. To be specific, if you break this down to a finer level, setting CroppedBitmap.Source = srcBitmap will add this event handler.

CroppedBitmap has DownloadCompleted, DownloadFailed, DownloadProgress events. I would make a guess it's the DownloadCompleted.

Checking the number of EventHandlers (using the _downloadEvent property on the srcBitmap/RenderTargetBitmap), this is indeed increasing by one each time CroppedBitmap is called - and never goes down. I can only come to the conclusion that CroppedBitmap is adding an event handler to the RenderTargetBitmap which is never getting removed. After running for a while, this ends up with x000's of event handlers on the RenderTargetBitmap, which I presume .net is going through and causing my speed degradation.

I can't for the life of me work out what to do about this! The image is being set on a simple Image control, nothing fancy.

I've tried tracing down from when this happens into the depths of the .net framework, line by line stepping through, but get lost when the debugger starts to hit optimized .net libraries, can't dig any further.

The presentation of the bitmap is simple...

Content.Source = srcBitmap;
....
<Image x:Name="Content" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderOptions.BitmapScalingMode="LowQuality" Stretch="None" IsHitTestVisible="False"></Image>

Just to be clear, as an example the code can run with no rendering, no draw image etc, - just the new CroppedBitmap... code is needed to show this behaviour. The other lines were included above for completeness. Makes no difference.

The above example was run against .NET Framework 4.7.2 to make sure the latest version had this problem.

I also tried Freezing the CroppedBitmap, just in case that made a difference. Apparently not. In fact, calling srcCrop.Freeze resulted in srcBitmap.isFrozen being true! erm..... so it seems CroppedBitmap makes alterations to the bitmap it is being cropped against?

Couldn't find any information, help, articles etc. on the above :(

Note it's not RenderTargetBitmap leaking memory which you see mentioned around in support forums, this is something else. Run this code without the CroppedBitmap and it's fine. I use RenderTargetBitmaps in several places, all fine (and I also already account for ramping memory usage as described in that article, and I'm already calling GC.Collect etc.). I also reuse my RenderTargetBitmaps, so they are only created once. (Probably why the event handlers pile up when multiple CroppedBitmap's are applied to them).

I've tried using reflector to snag the event handlers being created so I can manually remove them, but (despite various examples on the internet) can't get hold of wherever they are hiding to do this. Must be there somewhere!

Anyone any ideas what I might be able to do to fix this?

Thanks for your help in advance...

1

There are 1 best solutions below

2
On BEST ANSWER

Instead of using a CroppedBitmap, you may use an ImageBrush with an appropriate Viewbox:

var viewbox = new Rect(
    x * 96 / srcBitmap.DpiX,
    y * 96 / srcBitmap.DpiY,
    w * 96 / srcBitmap.DpiX,
    h * 96 / srcBitmap.DpiY);

var srcBrush = new ImageBrush
{
    ImageSource = srcBitmap,
    Viewbox = viewbox,
    ViewboxUnits = BrushMappingMode.Absolute,
};

// instead of DC.DrawImage(srcCrop, destRec);
DC.DrawRectangle(srcBrush, null, destRec);