Render a "not visible" WPF controls to an bitmap image - wpf

Render a WPF control to a bitmap image is not a trivial task as I found out today. As I know now dealing with the parent control margin is a problem, as Rick Strahl wrote in his blog
http://www.west-wind.com/weblog/posts/2007/Sep/10/Rendering-a-WPF-Container-to-Bitmap
So far I am able to create bitmaps of any control that is visible inside a window but what I really need to do is create bitmaps of invisible controls. I just create them in code - simples shapes like rectangle and ellipse - and want to save them as bitmaps to disk.
For me this turn out to be a personal nightmare. Since my ActualHeight and ActualWidth is always 0 I use Height and Width instead. But all I get is a empty image in the size of my control.
How can I create a bitmap of any control without adding it to a visible window?

New elements haven't had their layout performed. You need to call Measure and Arrange on the control before you render it.
Canvas c = new Canvas();
Rectangle r = new Rectangle
{
Fill = Brushes.Orange,
Width = 200,
Height = 100
};
Ellipse e = new Ellipse
{
Fill = Brushes.DodgerBlue,
Width = 100,
Height = 100
};
Canvas.SetLeft(e, 150);
c.Children.Add(r);
c.Children.Add(e);
var size = new Size(250,100);
c.Measure(size);
c.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(250, 100, 96, 96, PixelFormats.Pbgra32);
bmp.Render(c);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using(var s = File.OpenWrite("image.png"))
enc.Save(s);

Related

RenderTargetBitmap renders a canvas wrong

I am trying to save the content of a WPF Canvas in an image with the code:
Rect rect = new Rect(canvas.RenderSize);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom,
96d,
96d,
PixelFormats.Default);
rtb.Render(canvas);
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes("D://logo.png", ms.ToArray());
The canvas has a default horizontal alignment of 'Center' and it seems like this is an issue as the rendered image is actually the size of the canvas but includes the blank left margin, only showing part of the actual canvas content. But the blank space isn't even part of the canvas itself.
I tried to wrap the canvas in a grid and put it in a center column but it still renders the blank space from the left. If I align the image left horizontally it indeed renders the whole canvas, but I kind of need it to be centered. Is there any way I can modify the code to only include the canvas content and not the margin?

How to fix VisualBrush lost line?

In WPF ,we can use VisualBrush do some thing like ppt's left side.
But I see the VisualBrush may lost the line in Rectangle when I zoom the VisualBrush to a small size.Like the image:
You can see VisualBrush lost the Bottom line.
But what I want is like the below image:
When I try use the BitmapImage that use RenderTargetBitmap to get a image and use linear interpolation algorithm to zoom will get a clearness image.
Can I change VisualBrush's algorithm I think it may use neighborhood-pixels algorithm.
Are there any printscreen algorithm that have a good performance like VisualBrush.
When I change my search key to ViewBox ,I can find the same question as this one :how to avoid a single pixel line disappear in wpf?
There is a class named TransformedBitmap which can scale your RenderTargetBitmap with default scaling algorithm.
Use the code below:
public static BitmapSource ToBitmapSource(this Visual visual, Size size)
{
var bounds = VisualTreeHelper.GetDescendantBounds(visual);
var width = (int) Math.Round(bounds.Width);
var height = (int) Math.Round(bounds.Height);
var bitmap = new RenderTargetBitmap(width, height, 96.0, 96.0, PixelFormats.Pbgra32);
bitmap.Render(visual);
return new TransformedBitmap(bitmap, new ScaleTransform(size.Width / width, size.Height / height));
}
I've tried this method in my demo and got the result below. You may noticed that the small rectangle in the left-top corner lost nothing.

Drawing to a bitmap from a WPF canvas

I have a canvas that contains an Image in which I dislay an existing BMP. I draw rectangles on the canvas and add these to the Children colllection. When I click save, I want to update the underlying BMP file.
The following code works, but the rectangle that gets drawn to the BMP is way smaller than what I drew. I guess there's some difference in the co-ordinates? Maybe I shouldn't be using System.Drawing?
using (Graphics g = Graphics.FromImage(image))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
foreach (var child in canvas.Children)
{
if (child is System.Windows.Shapes.Rectangle)
{
var oldRect = child as System.Windows.Shapes.Rectangle;
// need to do something here to make the new rect bigger as the scale is clearly different
var rect = new Rectangle((int)Canvas.GetLeft(oldRect), (int)Canvas.GetTop(oldRect), (int)oldRect.Width, (int)oldRect.Height);
g.FillRectangle(Brushes.Black, rect);
}
}
... code to save bmp
All suggestions welcome!
Thanks
Try using the System.Windows.Media.Imaging.RenderTargetBitmap Class (an example here).
Wpf uses Device Independent Graphics so you have to compensate for the DPI :
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Canvas1.Width, (int)Canvas1.Height, 96, 96, PixelFormats.Default);
bmp.Render(Canvas1);
From Third Link:
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.

Saving multiple canvas controls as an image in silverlight/wpf

I have a situation where I have a canvas with an image and I lay multiple canvas controls over it and each canvas may have multiple controls drawn on them (Ellipses, Lines, etc). What is the best/most efficient way to save all these canvas controls toone image? I assume WritableBitmap, but how will this work with multiple canvas controls? One given is that each canvas will be the same size.
EDIT:
This is what I tried. I also tried with the dpi of the original image, which gives me the tiny result.
BitmapSource bms = (BitmapSource)this.ctlImage.Source;
Matrix m = PresentationSource.FromVisual(this.ctlImgCanvas).CompositionTarget.TransformToDevice;
RenderTargetBitmap rtb = new RenderTargetBitmap(bms.PixelWidth, bms.PixelHeight, m.M11 * 96.0, m.M22 * 96.0, PixelFormats.Pbgra32);
rtb.Render(this.ctlImgCanvas);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(memStream);
This will take care of everything including children of the canvas. You don't need to know the size of the canvas in advance.
Matrix m =
PresentationSource.FromVisual(canvas).CompositionTarget.TransformToDevice;
BitmapSource panelBitmap = new RenderTargetBitmap(
(int)Math.Floor(canvas.ActualWidth),
(int)Math.Floor(canvas.ActualHeight),
m.M11 * 96.0, // For DPI. This hardcoded value is correct. Check WPF DPI
m.M22 * 96.0,
PixelFormats.Default);
panelBitmap.Render(canvas);
using (FileStream fs = File.Open([filename], FileModel.Create, FileAccess.Write))
{
BitmapEncoder encoder = GetBitmapEncoderForFileExtension([filename]); // See documentation for which encoder to use when.
encoder.Frames.Add(BitmapFrame.Create(panelBitmap ));
encoder.Save(fs);
}
Save the container of the Canvas's to a bitmap, it'll pick up all the children.

Issue while saving a Transparent Canvas in WPF

I have created a Grid in WPF with Red background. The Grid contains a transparent Canvas with some fixed size. Now while trying to export the Canvas as Image, I am getting an image with black background. But when there is some color in Canvas (say White or Red), I am getting a proper image. Can anybody please tell me why the image is generating with black background if a Canvas has a transparent color.
Example:
Grid grid = new Grid();
grid.Background = new SolidColorBrush(Colors.Red);
grid.Width = 500;
grid.Height = 300;
Canvas c = new Canvas();
c.Width = 500;
c.Height = 300;
c.Background = new SolidColorBrush(Colors.Transparent);
c.MouseLeftButtonUp += new MouseButtonEventHandler(c_MouseLeftButtonUp);
grid.Children.Add(c);
LayoutRoot.Children.Add(grid);
Inside MouseEvent handler of Canvas, I am saving it as jpg image.
Inside MouseEvent handler of Canvas, I am saving it as jpg image.
Save it as PNG. These type of images are specialized in saving transparent backgrounds.

Resources