none
RenderTargetBitmap.Render() throws OutOfMemoryException when rendering large visuals.

    Question

  • I am trying to render a very large Canvas (7000x7000 or larger) to a Bitmap file. I attempt to do this by creating a RenderTargetBitmap, calling its Render() method and afterwards, save that bitmap to file.

    My problem is that RenderTargetBitmap.Render() throws an OutOfMemoryException on my machine with the following stacktrace:

       at System.Windows.Media.Composition.DUCE.Channel.Present()
       at System.Windows.Media.Renderer.Render(IntPtr pRenderTarget, Channel channel, Visual visual, Int32 width, Int32 height, Double dpiX, Double dpiY, Matrix worldTransform, Rect windowClip, Boolean fRenderForBitmapEffect)
       at System.Windows.Media.Imaging.BitmapVisualManager.Render(Visual visual, Matrix worldTransform, Rect windowClip, Boolean fRenderForBitmapEffect)
       at System.Windows.Media.Imaging.BitmapVisualManager.Render(Visual visual)
       at System.Windows.Media.Imaging.RenderTargetBitmap.Render(Visual visual)
       at WPFTest.Window1.CaptureToBitmap(Visual SourceVisual, PixelFormat PixelFormat, RenderTargetBitmap& RenderedBitmap) in C:\testprojects\WPFTest\WPFTest\Window1.xaml.cs:Zeile 146.

    The problem may be reproduced on other machines as well. Source code is at the end of this message. This happens with .NET SP1 on an Intel Core2 Duo 6400 (2.13 GHz) with 3.49 GB RAM (according to system information of Control Panel), with a video adapter with Intel Q965/Q963 Express Chipset Family with 256MB of video memory.

    Is there any way to work around this issue? Scaling down the Canvas is not an option, i need the resulting bitmap to be in the original size. If you can't reproduce the exception, can you make some guess why it's happening at my machine and what different approach i may use to render a large Canvas to a bitmap image?

    Thanks for any help!


    XAML:

    <Window x:Class="WPFTest.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Window1" Height="600" Width="800">
      <DockPanel>
        <Button HorizontalAlignment="Center" Padding="10,0,10,0" MaxWidth="120" Content="RENDER!" Click="Button_Click" DockPanel.Dock="Bottom"/>
        <ScrollViewer VerticalScrollBarVisibility="Visible">
          <Canvas x:Name="myCanvas" />
        </ScrollViewer>
      </DockPanel>
    </Window>

    Code behind:

    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;
    using Microsoft.Win32;
    
    namespace WPFTest
    {
      /// <summary>
      /// Demo window showing out-of-memory problems with RenderTargetBitmap. Rendering large Visuals will result in OutOfMemoryException (see below).
      /// </summary>
      public partial class Window1 : Window
      {
        /// <summary>
        /// Canvas size (both width and height)
        /// </summary>
        private static double CANVASSIZE = 7000d;
    
        /// <summary>
        /// Creates window, resizes canvas, fills canvas with random rectangles
        /// </summary>
        public Window1()
        {
          InitializeComponent();
          myCanvas.Width = CANVASSIZE;
          myCanvas.Height = CANVASSIZE;
          
          // just to make sure resulting bitmap will not be larger than what we specified for canvas...
          myCanvas.ClipToBounds = true;
          
          CreateRandomRectangles();
        }
    
        /// <summary>
        /// Creates random rectangles on the canvas
        /// </summary>
        public void CreateRandomRectangles()
        {
          Random rnd = new Random();
          int upperBound = 10 * (int)CANVASSIZE;
    
          for (int i = 0; i < upperBound; i++)
          {
            Rectangle rec = new Rectangle();
    
            // Assign random color;
            rec.Fill = new SolidColorBrush(Color.FromArgb(255, (byte)(rnd.NextDouble() * 255), (byte)(rnd.NextDouble() * 255), (byte)(rnd.NextDouble() * 255)));
    
            // Random pos + size
            double posX = rnd.NextDouble() * CANVASSIZE,
                posY = rnd.NextDouble() * CANVASSIZE,
                w = rnd.NextDouble() * CANVASSIZE,
                h = rnd.NextDouble() * CANVASSIZE;
            rec.Width = w;
            rec.Height = h;
    
            // Random opacity.
            rec.Opacity = rnd.NextDouble();
    
            // Randomly assign border or not
            if (rnd.NextDouble() > 0.5d)
            {
              rec.Stroke = Brushes.Black;
              rec.StrokeThickness = 1d;
            }
    
            // Add to canvas
            Canvas.SetLeft(rec, posX);
            Canvas.SetTop(rec, posY);
            myCanvas.Children.Add(rec);
          }
    
        }
    
        /// <summary>
        /// To return the application DPI (should be 96 anyway, but just in case)
        /// </summary>
        public static double[] GetApplicationDPI(Window w)
        {
          Matrix m = PresentationSource.FromVisual(w).CompositionTarget.TransformToDevice;
          return new double[]
          {
            m.M11 * 96,
            m.M22 * 96
          };
        }
        // Save Canvas as bitmap.
        public static bool CaptureToBitmap(Visual SourceVisual, PixelFormat PixelFormat, out RenderTargetBitmap RenderedBitmap)
        {
          Window rootWindow = Window.GetWindow(SourceVisual);
          bool hasWindow = rootWindow != null;
          if (hasWindow)
          {
            double[] dpi = GetApplicationDPI(rootWindow);
    
            Rect bounds = VisualTreeHelper.GetDescendantBounds(SourceVisual);
            RenderTargetBitmap rtb = new RenderTargetBitmap((int)(bounds.Width), (int)(bounds.Height), dpi[0], dpi[1], PixelFormat);
    
            DrawingVisual dv = new DrawingVisual();
            using (DrawingContext ctx = dv.RenderOpen())
            {
              VisualBrush vb = new VisualBrush(SourceVisual);
              ctx.PushOpacityMask(Brushes.White);
              ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
            }
    
            try
            {
              // CODE SHOULD THROW AN OUTOFMEMORYEXCEPTION RIGHT HERE!!!
              rtb.Render(dv);
              // CODE SHOULD THROW AN OUTOFMEMORYEXCEPTION RIGHT HERE!!!
    
            }
            catch (Exception ex)
            {
              foreach (System.Collections.DictionaryEntry de in ex.Data)
              {
                object a = de.Key;
                object b = de.Value;
              }
      
            }
            
            
            RenderedBitmap = rtb;
          }
          else
            RenderedBitmap = null;
    
          return hasWindow;
        }
    
        /// <summary>
        /// Click event handler of button. Canvas is rendered to bitmap and saved.
        /// </summary>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
          //Let the user choose location and image file type
          SaveFileDialog dlg = new SaveFileDialog()
          {
            FileName = "testbitmap",
            DefaultExt = ".bmp",
            Filter = "Windows Bitmap (.bmp)|*.bmp|Portable Network Graphics (.png)|*.png|JPEG (.jpg, .jpeg)|*.jpg;*.jpeg",
            AddExtension = true,
            OverwritePrompt = true,
            ValidateNames = true
          };
    
          if (dlg.ShowDialog(this) == true)
          {
            string ext = System.IO.Path.GetExtension(dlg.FileName);
            BitmapEncoder enc = null;
            switch (ext)
            {
              case ".bmp":
                enc = new BmpBitmapEncoder();
                break;
              case ".jpg":
              case ".jpeg":
                enc = new JpegBitmapEncoder();
                break;
              case ".png":
                enc = new PngBitmapEncoder();
                break;
              default:
                return;
            }
    
            if (enc != null)
            {
              RenderTargetBitmap rtb;
    
              // Render bitmap and save it.
              if (CaptureToBitmap(myCanvas, PixelFormats.Pbgra32, out rtb))
              {
                BitmapFrame bmf = BitmapFrame.Create(rtb);
                enc.Frames.Add(bmf);
                using (Stream stm = File.Create(dlg.FileName))
                {
                  enc.Save(stm);
                  stm.Close();
                }
              }
            }
          }
        }
      }
    }
    

    Friday, January 14, 2011 8:33 AM

Answers

All replies

  • Hi mwarpheus,

    Welcome to our forum.

    As for your issue:

    --> My problem is that RenderTargetBitmap.Render() throws an OutOfMemoryException on my machine with the following stacktrace:

    I have reproduced your issue based on your source code.

    I think the cause of your issue is your memory not enough to render this large image.

    If you compile your project as X64, then the exception will be fixed.

    On the other hand, you could try to use MultiThread to complete your project, because your image is " a very large Canvas (7000x7000 or larger)", you could render it on the other Thread.

    For more details about this topic, you could refer to below links, I think it will be very helpful:

    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/01e944d2-3d7b-42c8-9f3d-bf74fd22182f

    http://msdn.microsoft.com/en-us/magazine/cc163328.aspx

    http://www.codeproject.com/                                               (you could search samples on this site)

     

    Best regards,


    Sheldon _Xiao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, January 18, 2011 3:57 AM
    Moderator
  • Hello Sheldon _Xiao,

    thank you for your Answer.

    Unfortunately, compiling the project as x64 is not an option as the software needs to run on x86 machines. Also, I tried to perform the rendering-to-bitmap on a separate thread, but the problem persists, i.e. the same OutOfMemoryException is thrown, only wrapped in a TargetInvocationException.

     

    By not "enough memory", i suppose you mean the memory of the video card? Is there any way to bypass this issue for systems with small-memory video cards?

    Edit: Rendering the canvas to smaller portions, saving them to temp files and stitching them together at the end does not work either. Rendering even a 100x100 portion of this huge canvas produces an OutOfMemoryException as well.

     

     

    Tuesday, January 18, 2011 9:27 AM
  • Hi mwarpheus,

    As for those large canvas, I think you could try to use BimapCacheBrush to complete it, for more details you could refer to:

    http://msdn.microsoft.com/en-au/library/system.windows.media.bitmapcachebrush.aspx

    and this blog:

    http://blogs.msdn.com/b/llobo/archive/2009/11/10/new-wpf-features-cached-composition.aspx?wa=wsignin1.0

    I am sorry that I have not tried it, however, I think you could have a try.

     

    Best regards,


    Sheldon _Xiao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, January 21, 2011 8:43 AM
    Moderator
  • Hi mwarpheus,

    I am makring your issue as "Answered", if you have new findings about this issue, please unmark.

     

    Best regards,


    Sheldon _Xiao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, January 24, 2011 10:57 AM
    Moderator
  • Hi mwarpheus,

    I am undergoing the same issue as quoted by you above. Had found the solution for it?

    Please do let me now as it is not getting solved here with me..

    Thanks & best regards

    Friday, October 19, 2012 7:29 AM
  • Hello,

    I have faced the same troubles. Have you got a working answer?

    Best regards,

    Roman

    Thursday, July 24, 2014 7:24 PM
  • I'm also having the same issues.

    We are compiling much smaller images (typically no bigger than 200x200 pixels).

    Sometimes, our code will run through 1,000 images and work flawlessly.

    Other times, it'll completely crash "vshost.exe" on the first time, or (if we're lucky) show that an exception occured on this RenderTargetBitmap call, sometimes telling us that it's attempted to write to protected memory.

    If you Google this issue, you'll find that it's a memory leak that's been around for 6-7 years, but Microsoft has decided not to fix it.

    There are suggestions to work around it  (garbage collection, WaitForPendingFinalizers, etc), but nothing has worked for me.

    Short answer: if you can avoid using this library completely, you'll save yourself some therapy.  It's not stable.

    Friday, September 12, 2014 8:46 AM