WPF RenderTargetBitmap Missing Elements - wpf

I have a TreeView with small icons displayed in the data template. I'm trying to save the Treeview as a PNG using RenderTargetBitmap.
The image saves correctly on small data sets. However, if the data set becomes too large, some of the icons are excluded from the final image. The magic number seems to be 200 items. It doesn't seem to matter if the tree is deep or wide, after 200 items, the icons are not rendered.
Added Code
So here is my code that I'm using to create an image.
RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
(int)_treeView.ActualWidth,
(int)_treeView.ActualHeight,
96, 96, PixelFormats.Default);
targetBitmap.Render(_treeView);
Added Screen Shot
Notice the missing icons way over on the right side of the tree.
Now if I collapse a few branches, thus hiding some of the other icons, then these icons are included. It's almost like RenderTargetBitmap.Render doesn't have the power to render all of the icons. Or it may have something to do with virtual panels.
Here is a closer look.

What I immediately noticed that you have HUGE image. Width 12000. I am surprised that you even got that close.
As MSDN states, the texture width/height are limited by DirectX texture limits.
The maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a Microsoft DirectX texture; for more info see Resource Limits (Direct3D). This limit can vary depending on the hardware whre the app runs. Very large content that exceeds this limit might be scaled to fit. If scaling limits are applied in this way, the rendered size after scaling can be queried using the PixelWidth and PixelHeight properties. For example, a 10000 by 10000 pixel XAML visual tree might be scaled to 4096 by 4096 pixels, an example of a particular limit as forced by the hardware where the app runs.
http://msdn.microsoft.com/library/windows/apps/dn298548
I suspect these things:
Virtualization cutting off some things - I've had the exact problem in past with DataGrid, and the problem was virtualization. Your case doesn't seem like one though.
Too big texture can cause undefined behaviour.
You can try disabling hardware acceleration. The thing causes quite few hardcore bugs. http://msdn.microsoft.com/en-us/library/system.windows.media.renderoptions.processrendermode.aspx
Other than that - it will be tricky, but I am pretty sure that it will work beautifully:
1) start with the root object, and traverse the root object childrens recursively, until you find an object that is less than 1000 x 1000. Take picture of it using RenderTargetBitmap(BMP) and merge it to IN-MEMORY-BMP. Do it for each children.
You should be able to calculate all this stuff.

For the records: there's a workaround.
Instead of rendering your Visual directly with RenderTargetBitmap, use an interim DrawingVisual. Paint your Visual into the DrawingVisual using a VisualBrush and then use RenderTargetBitmap with the DrawingVisual.
Like this:
public BitmapSource RenderVisualToBitmap(Visual visual)
{
var contentBounds = VisualTreeHelper.GetContentBounds(visual);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(visual);
drawingContext.DrawRectangle(visualBrush, null, contentBounds);
}
var renderTargetBitmap = new RenderTargetBitmap((int)contentBounds.Width, (int)contentBounds.Height, 96, 96, PixelFormats.Default);
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
Note however that as your VisualBrush gets bigger the resulting image gets more and more fuzzy (when rendering with high DPI). To work around this problem use a series of smaller VisualBrush "tiles" as described here:
https://srndolha.wordpress.com/2012/10/16/exported-drawingvisual-quality-when-using-visualbrush/

Related

Data binding performance issues

I am writing a map control that can display a bench of geometries. For better performance, I draw all my geometries using DrawingVisuals which I then write into a RenderTargetBitmap as shown in the code below:
public class Map{
public ImageSource MapDrawingImage{get;set;}
private void RenderMap(){
MapDrawingImage= new RenderTargetBitmap(w, h, 96, 96, PixelFormats.Pbgra32);
foreach (Layer layer in map.Layers) {
System.Windows.Media.DrawingVisual layerDrawing = Render(layer, map);
MapDrawingImage.Render(layerDrawing);
}
}
}
In order to display the map, the main window has an Image control which Source is set to Map.MapDrawingImage image source. To automatically update the image, I use the following data binding:
RenderOptions.SetBitmapScalingMode(mapImage, BitmapScalingMode.LowQuality);
// Map image binding
Binding mapBinding = new Binding();
mapBinding.Source = map;
mapBinding.Path = new PropertyPath("MapDrawingImage");
mapImage.SetBinding(System.Windows.Controls.Image.SourceProperty, mapBinding);
This works very well when the map is static. However, in a dynamic mode where the map is updated at a rate of 5 to 10 times a second, the data binding seems to fall a bit short behind and the application slows down. I have searched for long days and I found out that:
RenderTargetBitmap does not use hardware acceleration thus causing some delays when rendering the map.
Data binding might also cause some delays refreshing the map image
Is there any better way to improve the map performance (RenderTargetBitmap replacement, data binding improvement) ?
Thanks in advance,
Databinding is updated in a seperate thread. So this will always be with a delay. Also it works with a queue, so when the databinding can't keep up the queue will grow bigger and bigger. The solution would be that you use an image which is static and change the image itself instead of replacing it with another image.

WriteableBitmap adding black padding to right

I'm trying to make an Image out of one of my Canvas (myPrintingCanvas). But the Width of the image is getting atleast twice wider than the Canvas, and the extra space that is created is back. If I try on another Canavas (LayoutRoot), it works as intended.
My observation is that on myPrintingCanvas the ActualWidth is always 0. LayoutRoot has a correct ActualWidth. Not sure if it has anything to do with the extra padding, and I have failed on getting the ActualWidth for myPrintingCanvas (using UpdateLayout and Measure).
Code:
//Code to render the content of myPrintingCanvas
...
//Make the WriteableBitmap
WriteableBitmap myWriteableBitmap = new WriteableBitmap(myPrintingCanvas, null);
Have you tried Creating the WriteableBitmap with a fixed size and then calling its Render method to render the bitmap? Canvas has some weird behaviors because it is an anti-pattern in Silverlight and WPF and breaks many rules about layout. The code for creating a fixed size WriteableBitmap and rendering to it would look something like this:
int width = 640;
int height = 480;
WriteableBitmap bmp = new WriteableBitmap(width, height);
bmp.Render(myPrintingCanvas, null);
bmp.Invalidate();
One advantage of this technique is that if you need to grab multiple images you can reuse the same WriteableBitmap instead of recreating it every time.

Create and resize an image in WPF from System.Drawing.Bitmap areas

I'm trying to implement a function that takes a System.Drawing.Bitmap object and renders it on a WPF Canvas. The bitmap has to be cropped and joined a few times before rendering.
Environment: WPF application running on .NET 3.5 SP1
Input: System.Drawing.Bitmap object, of size 800x600 and pixel format RGB24
Goal: to display an image which is composed of two stripes of the input bitmap (on one line). The stripes are two bitmap halves - (0,0,800,300) and (0,300,800,600). Later on I want to be able to scale the image up or down.
I've already implemented a solution with GDI and Graphics.DrawImage (that renders into a Bitmap object), but I want to improve performance (this function could be called 30 times per second).
Is there a faster way to implement this with WPF, assuming I want to render the image on a WPF window?
The best solution I found so far is using WriteableBitmap, something like this:
void Init()
{
m_writeableBitmap = new WriteableBitmap(DesiredWidth, DesiredHeight, DesiredDpi, DesiredDpi, PixelFormats.Pbgra32, null);
{
void CopyPixels(System.Drawing.Bitmap frame, Rectangle source, Point destBegin)
{
var bmpData = frame.LockBits(source, ImageLockMode.ReadOnly, frame.PixelFormat);
m_writeableBitmap.Lock();
var dest = new Int32Rect(destBegin.X, destBegin.Y, bmpData.Width, bmpData.Height);
m_writeableBitmap.WritePixels(dest, bmpData.Scan0, bmpData.Stride * bmpData.Height, bmpData.Stride);
m_writeableBitmap.Unlock();
frame.UnlockBits(bmpData);
}
CopyPixels would be called twice for the use case I described in my question (two stripes).

How to use PixelFormats.IndexedX with RenderTargetBitmap?

I am looking to render a DrawingVisual (visual in the example) to a bitmap using RenderTargetBitmap with the view to set this bitmap as the background to a Canvas as below:
var bmp = new RenderTargetBitmap(2000, 50, 120, 96, PixelFormats.Indexed2);
bmp.Render(visual);
var brush = new ImageBrush(bmp) { Stretch = Stretch.Fill };
Canvas.Background = brush;
When using PixelFormats.Default as the last argument to RenderTargetBitmap, the image renders as expected. However, when I choose PixelFormats.Indexed2 (or any of the PixelFormats.IndexedX), my code seems to exit the method without an exception, the bmp.Render line is never called and hence the image is not displayed on the Canvas.
How to use the IndexedX pixel formats with RenderTargetBitmap? Or are there other ways to reduce the memory footprint of the image? It only uses three colors, so using a palette rather than 32bit RGB seemed the way to go.
You can't. RenderTargetBitmap only supports the Pbgra32 pixel format. That's because WPF's rendering system works entirely in 32 bits per pixel. That's the format in which it generates images, and it's also the format in which it prefers images to be in if you want to render them. (If you provide it with a bitmap in any other format, it'll need to convert it into a 32 bit per pixel representation first.)
What are you planning to do with this bitmap? If you want to render it in a WPF application, it'll need to be converted to a 32bpp format first in any case, so you risk using more memory if you attempt to hold it internally in any other format. (You'll have your supposedly memory-efficient representation and the version WPF's actually able to work with.) Not to mention the extra CPU time spent converting between your chosen format and a format WPF can work with.

How to render a bitmap from a WPF element that has a bitmap effect?

I'm able to render a Visual to a bitmap fine with this code:
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush brush = new VisualBrush(target);bounds.Value.Size));
context.DrawRectangle(brush, null, new Rect(new Point(), bounds.Value.Size));
}
renderBitmap.Render(visual);
return renderBitmap;
The problem is that if the Visual has a bitmap effect like a drop shadow on it, then the resulting image is squished. It seems that its trying to fit the visual with the drop shadow into an image the size of the visual without the drop shadow.
In most cases (like drop shadow) the actual rendering of the effect falls outside of the bounds of the element itself. Relying on the ActualHeight and ActualWidth to size you image then causes the squeezing effect you're seeing. The best solution would be to use a parent container instead but that might require changes to your layout. You may also be able to calculate additional padding values to add to the element's size that will compensate for the effect rendering. It might be possible to derive those values by inspecting the properties of the Effect itself and will probably involve some trial and error too.
The effect has a set of padding properties and thes are used to set the size of rendering area used by the effect - see if these have been modified and if so adjust the size of the rendered visual. Have a look at RenderTargetBitmap - Visual vector to bitmap and the articles at WPF Workings

Resources