title: WPF Leak when adding multiple items to a canvas and rendering using RenderBitmapTarget
body: <p>I've been having a whale of a time diagnosing a leak in a service which is rendering multiple images to a canvas and then pushing out a bitmap or PNG at the end (and doing this multiple times). On start up the service will rocket up to 600MB+ and then keep on increasing until it's taken almost all that it can get hold of. It will never decrease once started. </p>
  
  <p>I've instrumented and run the service using the VS2012 perf mon and seen that there are large amounts of Byte arrays laying about after processing has been completed. I've tried a GC clear to see if it would wipe them out to no avail. Looking into it using WinDbg I can see that the byte array is being held onto by a long chain of items (mostly WPF objects) which is keeping the image in ram. </p>
  
  <p>I've had a look over MSDN for all the objects that are being used and can't find anything that points out a problem with the way the code is running.</p>
  
  <p>I've put together some sample code that closely follows the service (some bits reduced for bevity). This is called off a thread (set to STA). The only other differece is the service code uses MemoryStreams to load images sourced from a DB rather than the URIs I am using. The streams are disposed of:</p>
  
  <pre><code>    public static void TestThread()
      {
          const int rounds = 50;
          const int innerRounds = 50;
          var randomiser = new Random(DateTime.Now.Millisecond);
  
          // Simulating some live values
          const float scaling = 2.67F;
          const int pageWidth = 363;
          const int pageHeight = 516;
          const int dpi = 96;
  
          // To simulate the live system using multiple images
          // This is an list of images of different sizes etc
          var imageList = new List&lt;ImageData&gt;
          {
              new ImageData{Uri = new Uri(@"..."), Height = 2592},
              new ImageData{Uri = new Uri(@"..."), Height = 1339},
              new ImageData{Uri = new Uri(@"..."), Height = 386},
              new ImageData{Uri = new Uri(@"..."), Height = 968},
              new ImageData{Uri = new Uri(@"..."), Height = 1952},
              new ImageData{Uri = new Uri(@"..."), Height = 1024},
          };
  
          var proc = Process.GetCurrentProcess();
  
          for (var i = 0; i &lt; rounds; ++i)
          {
              var canvas = new Canvas();
              canvas.BeginInit();
              canvas.SnapsToDevicePixels = false;
              canvas.UseLayoutRounding = false;
              canvas.Width = pageWidth;
              canvas.Height = pageHeight;
              canvas.Background = Brushes.White;
  
              for (var j = 0; j &lt; innerRounds; ++j)
              {
                  var img = new Image {Stretch = Stretch.Fill};
                  var bitmapImage = new BitmapImage();
                  bitmapImage.BeginInit();
                  bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
  
                  var imageNo = randomiser.Next(0, imageList.Count - 1);
  
                  bitmapImage.UriSource = imageList[imageNo].Uri;
                  int imageHeight = imageList[imageNo].Height;
                  bitmapImage.DecodePixelHeight = (int) (imageHeight * scaling * 1.8);
                  bitmapImage.EndInit();
  
                  if (bitmapImage.CanFreeze)
                  {
                      bitmapImage.Freeze();
                  }
  
                  var opacityMask = new ImageBrush();
                  var opactityBitmap = new BitmapImage();
                  opactityBitmap.BeginInit();
                  opactityBitmap.CacheOption = BitmapCacheOption.OnLoad;
  
                  imageNo = randomiser.Next(0, imageList.Count - 1);
                  opactityBitmap.UriSource = imageList[imageNo].Uri;
                  int opacityImageHeight = imageList[imageNo].Height; ;
                  opactityBitmap.DecodePixelHeight = (int)(opacityImageHeight * scaling * 1.8);
                  opactityBitmap.EndInit();
  
                  if (opactityBitmap.CanFreeze)
                  {
                      opactityBitmap.Freeze();
                  }
  
                  opacityMask.ImageSource = opactityBitmap;
                  img.OpacityMask = opacityMask;
  
                  img.Source = bitmapImage;
                  img.Width = pageWidth * scaling;
                  img.Height = pageHeight * scaling;
                  Canvas.SetLeft(img, 0);
                  Canvas.SetTop(img, 0);
                  canvas.Children.Add(img);
                  img.Opacity = 50F;
              }
  
              canvas.LayoutTransform = null;
              var size = new Size(Math.Max(canvas.Width, 5), Math.Max(canvas.Height, 5));
              canvas.Measure(size);
              canvas.Arrange(new Rect(size));
              canvas.EndInit();
  
              var renderTargetBitmap = new RenderTargetBitmap(pageWidth, pageHeight, dpi, dpi, PixelFormats.Default); //xxx
  
              renderTargetBitmap.Render(canvas);
  
              if (renderTargetBitmap.CanFreeze)
              {
                  renderTargetBitmap.Freeze();
              }
  
              System.Drawing.Image imageData;
              using (var ms = new MemoryStream())
              {
                  var image = new PngBitmapEncoder();
                  image.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
                  image.Save(ms);
                  imageData = System.Drawing.Image.FromStream(ms);
              }
  
  
              var encoder = Encoder.Quality;
              var encoderParams = new EncoderParameters(1);
              encoderParams.Param[0] = new EncoderParameter(encoder, 100L);
  
              ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
              var codecInfo = codecs.FirstOrDefault(x =&gt; x.FormatID == ImageFormat.Png.Guid);
  
              Byte[] bitmapArray;
              using (var ms = new MemoryStream())
              {
                  imageData.Save(ms, codecInfo, encoderParams);
                  bitmapArray = ms.GetBuffer();
              }
  
              var filepath = string.Format(@"C:\temp\{0}.png", i);
              imageData.Save(filepath, ImageFormat.Png);
  
              var gcMemory = GC.GetTotalMemory(false) / 1024;
              Console.WriteLine("Proc mem = {0}KB, GC = {1}KB", (proc.PrivateMemorySize64 / 1024), gcMemory);
          }
  
          Console.WriteLine("Exiting");
      }
  </code></pre>
  
  <p>I'm hoping it's something obvious I'm missing here. Thanks for all your help!</p>