none
How to render a WPF visual to a bitmapsource without blocking the UI RRS feed

  • Question

  • I tried to use a bitmapcachebrush to copy a visual in GPU and freeze it before rendering it in a background task. 
    Unfortunately I got stuck. 
    Here is my current code:

    public static async Task<BitmapSource> RenderAsync(this Visual visual)
            {
                var bounds = VisualTreeHelper.GetDescendantBounds(visual);
    
                var bitmapCacheBrush = new BitmapCacheBrush(visual);
                bitmapCacheBrush.BitmapCache = new BitmapCache();
    
                // We need to disconnect the visual here to make the freezable freezable :). Of course this will make our rendering blank
                // Is there any way to disconnect the visual without deleting the bitmapcache of the visual?
                bitmapCacheBrush.Target = null;
                bitmapCacheBrush.Freeze();
    
                var bitmapSource = await Task.Run(() =>
                {
                    var renderBitmap = new RenderTargetBitmap((int)bounds.Width,
                                                                 (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);
    
                    var dVisual = new DrawingVisual();
                    using (DrawingContext context = dVisual.RenderOpen())
                    {
    
                        context.DrawRectangle(bitmapCacheBrush,
                                              null,
                                              new Rect(new Point(), new Size(bounds.Width, bounds.Height)));
                    }
    
                    renderBitmap.Render(dVisual);
                    renderBitmap.Freeze();
                    return renderBitmap;
                });
    
                return bitmapSource;
            }
    
    Are there any hacks/tricks to disconnect the bitmapcachebrush's target without deleting its visual presentation?
    Or do you have any other ideas how to render a WPF visual asynchronously or with a good performance?
    Thank you guys in advance! 

    Friday, April 15, 2016 11:33 AM

Answers

  • >>Are there any hacks/tricks to disconnect the bitmapcachebrush's target without deleting its visual presentation?

    I don't think so. You cannot freeze a Visual. A Visual can only be used on the same STA thread on which it was originally created. So you won't be able to freeze the BitmapCacheBrush until you disconnect the unfreezable visual from it.

    >>Or do you have any other ideas how to render a WPF visual asynchronously

    You cannot create a visual one thread and then use it on another one because of the thread affinity. Depending on what you are trying to do, you could possibly clone the element and then render it on a background thread. Something like this:

          
      public static void RenderAsync(UIElement visual)
            {
                string xaml = XamlWriter.Save(visual);
    
                Thread thread = new Thread(() => 
                {
                    UIElement clonedVisual;
                    using (StringReader stringReader = new StringReader(xaml))
                    using (XmlReader xmlReader = XmlReader.Create(stringReader))
                        clonedVisual = (UIElement)XamlReader.Load(xmlReader);
    
                    RenderTargetBitmap rtb = new RenderTargetBitmap(50, 50, 96, 96, PixelFormats.Pbgra32);
                    Size size = new Size(50, 50);
                    clonedVisual.Measure(size);
                    clonedVisual.Arrange(new Rect(size));
                    rtb.Render(clonedVisual);
    
                    JpegBitmapEncoder jpg = new JpegBitmapEncoder();
                    jpg.Frames.Add(BitmapFrame.Create(rtb));
                    using (Stream stm = File.Create("test.jpg"))
                    {
                        jpg.Save(stm);
                    }
    
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
    
                //return bitmapSource;
            }
    

    But you still won't be able to create a new element on a background three and then use it on your main thread. This is not possible.

    Please remember to close your threads by marking helpful posts as answer and then start a new thread if you have a new question. Please don't ask several questions in the same thread.

    Friday, April 15, 2016 3:31 PM

All replies

  • Those guys on stackoverflow are correct, Andreas.

    You can't access a visual on a different thread it was created on without freezing it and if you freeze it then bad things happen.

    You could just do a screendump.

    But otherwise I think you have to accept that render is synchronous and it's unlikely you will gain much running it on another thread.

    You can clone simple controls using xamlreader and writer but that has some limitations and it's not free either.

    Actually creating the control again from scratch on a separate thread is possible.

    As mentioned here:

    http://stackoverflow.com/questions/5189139/how-to-render-a-wpf-usercontrol-to-a-bitmap-without-creating-a-window

    You would need to measure arrange explicitly.

    UserControl control = new UserControl1();
    
    control.Measure(new Size(300, 300));
    control.Arrange(new Rect(new Size(300,300)));
    
    RenderTargetBitmap bmp = new RenderTargetBitmap(300, 300, 96, 96, PixelFormats.Pbgra32);
    
    bmp.Render(control);
    
    var encoder = new PngBitmapEncoder();
    
    encoder.Frames.Add(BitmapFrame.Create(bmp));
    
    using (Stream stm = File.Create(@"c:\test.png"))
       encoder.Save(stm);


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    Friday, April 15, 2016 3:04 PM
    Moderator
  • >>Are there any hacks/tricks to disconnect the bitmapcachebrush's target without deleting its visual presentation?

    I don't think so. You cannot freeze a Visual. A Visual can only be used on the same STA thread on which it was originally created. So you won't be able to freeze the BitmapCacheBrush until you disconnect the unfreezable visual from it.

    >>Or do you have any other ideas how to render a WPF visual asynchronously

    You cannot create a visual one thread and then use it on another one because of the thread affinity. Depending on what you are trying to do, you could possibly clone the element and then render it on a background thread. Something like this:

          
      public static void RenderAsync(UIElement visual)
            {
                string xaml = XamlWriter.Save(visual);
    
                Thread thread = new Thread(() => 
                {
                    UIElement clonedVisual;
                    using (StringReader stringReader = new StringReader(xaml))
                    using (XmlReader xmlReader = XmlReader.Create(stringReader))
                        clonedVisual = (UIElement)XamlReader.Load(xmlReader);
    
                    RenderTargetBitmap rtb = new RenderTargetBitmap(50, 50, 96, 96, PixelFormats.Pbgra32);
                    Size size = new Size(50, 50);
                    clonedVisual.Measure(size);
                    clonedVisual.Arrange(new Rect(size));
                    rtb.Render(clonedVisual);
    
                    JpegBitmapEncoder jpg = new JpegBitmapEncoder();
                    jpg.Frames.Add(BitmapFrame.Create(rtb));
                    using (Stream stm = File.Create("test.jpg"))
                    {
                        jpg.Save(stm);
                    }
    
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
    
                //return bitmapSource;
            }
    

    But you still won't be able to create a new element on a background three and then use it on your main thread. This is not possible.

    Please remember to close your threads by marking helpful posts as answer and then start a new thread if you have a new question. Please don't ask several questions in the same thread.

    Friday, April 15, 2016 3:31 PM
  • Thank you both for detailed answer! 


    Monday, April 18, 2016 9:29 AM