Saving Bitmap Moves The Canvas - c#

I'm trying to save a canvas drawing as a bitmap. The code works fine, however once the drawing has been saved the canvas moves to the top left of the parent application. My code is as follows:
public void SaveBitmap()
{
Size size = new Size(canvas.ActualWidth, canvas.ActualHeight);
canvas.Measure(size);
canvas.Arrange(new Rect(size));
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(canvas);
using (FileStream outStream = new FileStream("C:\\Users\\Darren\\Desktop\\test.bmp", FileMode.Create))
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(outStream);
}
}
The line that caused the problem is canvas.Arrange. Anybody shed any light?
Thanks.

The reason is that you’re not specifying any position in your Rect constructor, which therefore defaults to a position of (0,0).
My suggestion for using RenderTargetBitmap is to place your Canvas inside a Grid, and then perform any explicit positioning required by your UI on this outer Grid, letting your inner Canvas naturally assume a position of (0,0) within this parent Grid.
For example, if you have:
<Window>
<Canvas Left="10" Top="30" />
</Window>
Change it to:
<Window>
<Grid Left="10" Top="30">
<Canvas />
</Grid>
</Window>
Then, you could eliminate your calls to Measure and Arrange altogether. However, make sure that you still pass the child Canvas to your RenderTargetBitmap.Render method, not the parent Grid.

Related

Rendering WPF to PNG produces blank image

I am trying to render a figure to a PNG using WPF (rather than the old GDI API) and the result is always blank.
I am writing a small program that will produce images routes around a warehouse to various items. I am using WPF API for drawing rather than the old GDI API as the images need to be both saved to disk and displayed within a WPF application. The problem is that all of the png files that it produces are currently entirely black.
To reduce the scope of the problem and prove that it was not a problem with code for drawing the map I made a minimal test method that should simply render a red circle on a black background. However the result is still an entirely black.
[TestMethod]
public void DrawingTest()
{
//define the dimensions of the test image
int width = 100;
int height = 100;
int dpi = 100;
//define where the image should be saved
var path = #"C:\temp\DrawingTest.png";
//create a bitmap to render the image into
RenderTargetBitmap bitmap = new RenderTargetBitmap(
width, height, dpi, dpi, PixelFormats.Default);
//create a drawing context to draw the image onto
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext canvas = drawingVisual.RenderOpen())
{
//draw a circle
canvas.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1), new System.Windows.Point(50, 50), 50, 50);
//render the image into a bitmap
bitmap.Render(drawingVisual);
}
//create the file encoder
var encoder = new PngBitmapEncoder();
//encode the bitmap in PNG format
encoder.Frames.Clear();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
//make sure that the file can be saved in this location
var directory = System.IO.Path.GetDirectoryName(path);
if (!System.IO.Directory.Exists(directory))
System.IO.Directory.CreateDirectory(directory);
if (System.IO.File.Exists(path))
System.IO.File.Delete(path);
//save the map to file
using (var stream = System.IO.File.Create(path))
encoder.Save(stream);
}
The test method runs without any errors and produces a png file at the location that I expected. However, this file is simply a black square rather than the red circle in a black square that I was expecting. Can anybody tell me why it is just rendering a black image, and what I can do to make it show the red circle.
You have to call bitmap.Render(drawingVisual) after the DrawingContext is closed.
From the Remarks on DrawingContext.Close:
A DrawingContext must be closed before its content can be rendered ...
So just move the Render call outside the using block:
using (DrawingContext dc = drawingVisual.RenderOpen())
{
dc.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1),
new System.Windows.Point(50, 50), 50, 50);
}
bitmap.Render(drawingVisual);
Besides that, the red Pen seems redundant, since the circle is already filled red. So this should be ok:
dc.DrawEllipse(Brushes.Red, null, new System.Windows.Point(50, 50), 50, 50);
Also make sure to create the bitmap with a DPI value of 96, which corresponds to WPF device-independent units. Otherwise you would have to scale the drawing appropriately.
One and a half year later I faced the same problem - how to properly render a canvas without displaying it in the screen.
The solution was simpler than I thought and produces exactly the result one expects.
Long story short - to render the canvas without displaying it call Arrange(Rect finalRect) method on your canvas:
canvas.Arrange(new Rect(new Size(width, height)));
According to the documentation it:
Positions child elements and determines a size for a UIElement. Parent elements call this method from their ArrangeCore(Rect) implementation (or a WPF framework-level equivalent) to form a recursive layout update. This method constitutes the second pass of a layout update.

RenderTargetBitmap renders whole screen instead of Visual only [duplicate]

This question already has answers here:
Re positioning canvas control
(1 answer)
Snapshot of an WPF Canvas Area using RenderTargetBitmap
(2 answers)
Closed 4 years ago.
i want to save a circle as png.
This is the code is use:
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)Width, (int)Height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(kreis);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
if (File.Exists(dateipfad + textBox.Text + ".png"))
File.Delete(dateipfad + textBox.Text + ".png");
using (Stream fileStream = File.Create(dateipfad + textBox.Text + ".png"))
{
pngImage.Save(fileStream);
}
however it saves always a 525x350 png file (with the circle).
What i want, is that the size of the png file is the same size than the circle.
But when i change Width and Height to Kreis.Width and Kreis.Height
by the way: "Kreis" is german for "circle"
then the image has the correct size, but doesn't contain the circle in it, because the circle is on the right side of the application. And it just makes a screen from the left side.
Is it possible to not only set Width and Height for the RenderTargetBitmap but somehow also set the startX and startY?
Edit:
this is the XAML of the circle:
<Ellipse x:Name="kreis" Grid.Column="1" HorizontalAlignment="Center" Height="{Binding ElementName=slider, Path=Value}" Margin="0" Stroke="#FF8F8FF9" VerticalAlignment="Center" Width="{Binding ElementName=slider, Path=Value}" Fill="#FF5555D1" StrokeThickness="2"/>
This is the image i recieve:
(if you click on the image, you can see that it contains not only the circle. What i would need is that the image contains the circle only, so the width/height of the image is the width/height of the circle.
Try the answer of ILIA BROUDNO in this question: Snapshot of an WPF Canvas Area using RenderTargetBitmap
Your XAML shows that the circle is included in a grid, because the attached property Grid.Column is set to 1. This makes me assue that the size of the circle is painted relative to the grid or even to the window.
So creating a VisualBrush from the Kreis-Element and then explicitly painting this VisualBrush on position 0,0 might solve your problem.

Copying rotated image to existing image in C# code-behind

I am trying to copy a rotated image to an existing image in the code-behind.
Here are my 2 images:
<Image x:Name="tempImage1" Source="Images/firstImage.jpg" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Center" />
<Image x:Name="myImage2" Source="Images/secondImage.jpg" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Center" />
And the rotation in the code-behind:
RotateTransform rt = new RotateTransform(-theta, 200, 300);
tempImage1.RenderTransform = rt;
myImage2.Source = tempImage1.Source;
I know I'm not doing it right (still very new to this). Could someone please help point me in the right direction? The result I'm looking for is to have myImage2 have show tempImage1 in its rotated form, not the original source.
Using a RenderTargetBitmap solved the issue for me.
With images tempImage1 and myImage2 in your XAML:
RenderTargetBitmap rtb = new RenderTargetBitmap(_width, _height, 96, 96, PixelFormats.Default);
rtb.Render(tempImage1);
myImage2.Source = rtb;
The above is done with the assumption that tempImage1 has already been rotated and transformed.
Well if you do that you must know that the first image would be visible as it is in the background well I simply rotated as per your question in an angle of 30 degree the XAML remains same the new C# code is
RotateTransform rt = new RotateTransform();
rt.Angle = 30;
tempImage1.RenderTransform = rt;
myImage2.Source = tempImage1.Source;

How can I use vector graphics (defined in XAML) as the image of a Ribbon menu item?

I need to convert my WPF application, which has a Ribbon, to use vector graphics for the images on buttons, etc., instead of bitmaps. I've got this working so far, except for the RibbonApplicationMenu items, which use their RibbonApplicationMenu.ImageSource property, with type ImageSource to set the graphic that shows up at the left side of the ribbon menu item.
My vector graphics are defined like this in XAML:
<ControlTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ViewBox Width="148.854" Height="150.500">
<Canvas Width="148.854" Height="150.500">
<Path>
<!-- . . . yadda yadda yadda . . . -->
</Path>
</Canvas>
<Viewbox>
</ControlTemplate>
These render properly when I set them to be the .Template of a Button, etc.
I can't figure out, however, how to use them for the images that show up in the RibbonApplicationMenu controls. Just setting the .Template property of the RibbonApplicationMenu does not work (it fills the entire control seems to break its functionality).
I attempted to use a VisualBrush and then render it to a RenderTargetBitmap to use as the .ImageSource (I also have to work mostly in code behind for reasons I won't get into):
ContentControl cc = new ContentControl(); // just picked ContentControl as a test
cc.Template = myTemplate; // assume myTemplate has the proper ControlTemplate
VisualBrush visBrush = new VisualBrush();
visBrush.Visual = cc;
// not really sure about the parameters to this next line
RenderTargetBitmap rend =
new RenderTargetBitmap(148.854, 150.5, 120, 96, PixelFormats.Pbgra32);
rend.Render(cc);
RibbonApplicationMenuItem menu = new RibbonApplicationMenuItem();
menu.ImageSource = render;
This compiles, but just shows a blank where the image would go. If I instead use a BitmapImage loaded from an image file as the .ImageSource, it displays correctly.
Any ideas how I can get this to work?
Two things that might need to be done:
Firstly there is no explicit size set for the Canvas in the ControlTemplate, you'll need to assign it a width and height (see the upvoted answer here for details).
The other thing is that you need to Arrange and Measure the Viewbox so that it assumes the required dimensions.
So altering the xaml for the vector graphics to something like:
<ControlTemplate x:Key="Template">
<Viewbox>
<Canvas Height="100" Width="130">
<Path>
<!-- data -->
</Path>
</Canvas>
</Viewbox>
</ControlTemplate>
And the code to:
ControlTemplate controlTemplate =
FindResource("Template") as ControlTemplate;
ContentControl content = new ContentControl();
content.Template = controlTemplate;
// ensure control has dimensions
content.Measure(new Size(200, 200));
content.Arrange(new Rect(0, 0, 200, 200));
RenderTargetBitmap render =
new RenderTargetBitmap((int)content.ActualWidth, (int)content.ActualHeight, 120, 96, PixelFormats.Pbgra32);
render.Render(content);
RibbonApplicationMenuItem menu = new RibbonApplicationMenuItem();
menu.ImageSource = render;
Will hopefully get it working.

Taking bitmap of Canvas is incorrectly based on top left corner of Window

I have a canvas in my Window and occasionally I want to take snapshots of the content.
I have the following method to do so:
private PngBitmapEncoder captureVisual(Visual v)
{
RenderTargetBitmap bmp = new RenderTargetBitmap(546, 410, 120, 96, PixelFormats.Pbgra32);
bmp.Render(v);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Interlace = PngInterlaceOption.On;
encoder.Frames.Add(BitmapFrame.Create(bmp));
return encoder;
}
I call this method with the following method which is called by a press button action(the idea is to add the picture to another canvas which will display the "photo":
private void take_photo(Object sender, RoutedEventArgs e)
{
System.Windows.Controls.Image photoImg = new System.Windows.Controls.Image();
BitmapEncoder enc = captureVisual(videoCanvas);
photoImg.Source = enc.Frames[0];
photoCanvas.Children.Add(photoImg);
}
The problem:
When I take the photo the origin of the photo/bitmap is not the origin of the canvas but the origin of the window. So the Canvas appears in the bitmap translated towards bottom/right in the same position as it is in relation to the top-left corner of the window. I have no idea why this is happening.
As an example see the pic below: There is white space to the top left and the actual content is translated to the bottom right. The origin should be where the actual image is located. I will try to put around a border to make it easier to visualize the problem(sorry I don't know how to put a border around the image if anyone knows please tell me or edit it yourself. Thanks).
Thanks to the comment of dowhilefor I got the answer from this link:
The problem is WPF implicitly adds the margins of the Canvas in relation to the parent container. In my case the parent container was a Grid and I didn't define any margins, the canvas was filling a specific cell. Nevertheless this somehow gets incorporated in the bitmap.
The solution, wrap the Canvas around another Canvas. That's stupid but it works. IMHO this is a bug in WPF:
<Canvas
Name="outerCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1" Grid.Column="1">
<Canvas
Name="videoCanvas"
Canvas.Left="0"
Canvas.Top="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
/>
</Canvas>

Categories

Resources