I have a simple XAML like this:
<UserControl x:Class="blabla.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Name="myUserControl">
<Grid Name="myGrid" Background="White">
<TextBlock Name="myTextBlock"/>
</Grid>
</UserControl>
Now I'd like to create a new view in the background by using this xaml. After this, I'd like to change the controls (or maybe add some controls by myself), save it to a Bitmap and assign it to the Clipboard (just to see the output). I would have tried it like this:
MyView view = new MyView();
view.myTextBlock.Text = "TEST";
Rect bounds = VisualTreeHelper.GetDescendantBounds(view);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(view);
ctx.DrawRectangle(vb, null, new Rect(new System.Windows.Point(), bounds.Size));
}
RenderTargetBitmap bmp = new RenderTargetBitmap((int)view.ActualWidth, (int)view.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(bmp));
System.Windows.Clipboard.SetImage(pngImage.Frames[0]);
Unfortunately, the Size of the view is not being changed. Trying to set a hard coded width/height by produces a black image/bitmap.
How can I create a simple Bitmap out of a XAML?
Making the comment to official answer. However, I see that this solution is not the most elegant one. General thing is, that the window, resp. view, will result in black area when rendered while it is not visible/redered itself by showing it anyhow.
The workarround would be to show this view (as you wrote, but in Window).
Small idea doing this, move the window out of visible screen to avoid flickering.
example:
var v = new MainWindow();
v.Top = -10000; // far away
v.Left = -10000; // far away
v.Show();
// .. do rendering
v.Close();
Related
I want to create a WPF application where I can draw every single pixel within a WPF window. One challenge here is that Window has no property providing the size the window's content should be. This is normally not needed, because the WPF layouting can fit for example a Grid perfectly into the available space automatically. But when creating a bitmap, one needs to know exactly how many pixels are available before the layouting of that control happens.
Manipulating pixels in WPF is a bit convoluted. An Image is used whose Source is set to a BitmapSource which is created using an integer array. The pseudo code looks like this:
var integerArray = new int[horzontalPixelCount*verticalPixelCount];
var bitmap = BitmapSource.Create(horzontalPixelCount, verticalPixelCount,
dpiScale.PixelsPerInchX, dpiScale.PixelsPerInchX, PixelFormats.Bgr32, null,
integerArray , horzontalPixelCount*4);
var image = new Image() {Source = bitmap};
Window.Content = image;
Question: how do I know the value of horzontalPixelCount and verticalPixelCount ?
Note:
Window.ActualWidth and Window.ActualHeight provide the complete size of the window, not just the size the content should use.
Window.Content is null when I need to know how many pixels are available within the window. Code like ((FrameworkElement)MainWindow.Content).ActualHeight will not work.
WPF uses for Height and Width device independent units, but the sizing of integerArray must be based on actual pixels.
It was difficult to figure out what space is available within the window where content can be drawn. The solutions I found on StackOverflow were all based on using the size of the Window.Content, by default often a Grid. But this caused me 2 problems:
That size is only available once the first WPF layout cycle is completed.
Window.Content will be null when I need the available size and only later will I assign something to Window.Content.
To get the available size, I came up with the following solution:
var border = (Border)GetVisualChild(0);
var horzontalPixelCount = (int)(border.RenderSize.Width*dpiScale.DpiScaleX);
var verticalPixelCount = (int)(border.RenderSize.Height*dpiScale.DpiScaleY);
This works because the top most visual child of a Window is always a Border, even when Window.Content is null. Interestingly border.RenderSize is already available when border.ActualSize is still 0.
WPF uses device independent units, which must get converted into how many real pixels are needed for the dimension of the integerArray. Nowadays a PC might have 2 monitors with 2 different resolutions. For this to work, one must use a DpiScale, which can be found like this:
var dpiScale = VisualTreeHelper.GetDpi(this);
The complete code:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WindowBitmap {
public partial class MainWindow: Window {
public MainWindow() {
InitializeComponent();
SizeChanged += MainWindow_SizeChanged;
}
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) {
var dpiScale = VisualTreeHelper.GetDpi(this);
var border = (Border)GetVisualChild(0);
var horzontalPixelCount = (int)(border.RenderSize.Width*dpiScale.DpiScaleX);
var verticalPixelCount = (int)(border.RenderSize.Height*dpiScale.DpiScaleY);
var integerArray = new int[horzontalPixelCount*verticalPixelCount];
var random = new Random();
for (int i = 0; i < integerArray.Length; i++) {
integerArray[i] = random.Next();
}
var bitmap = BitmapSource.Create(horzontalPixelCount, verticalPixelCount,
dpiScale.PixelsPerInchX, dpiScale.PixelsPerInchX, PixelFormats.Bgr32, null,
integerArray, horzontalPixelCount*4);
var image = new Image() {Source = bitmap};
Content = image;
}
}
}
<Window x:Class="WindowBitmap.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WindowBitmap"
mc:Ignorable="d"
Title="MainWindow" WindowState="Maximized">
</Window>
I kept rendering a black/blank image of a UserControl not shown in window. The UserControl contains LiveChart's CartesianChart and few UIElements. First I borrowed solutions from SO: C# chart to image with LiveCharts
and Github: Live-Charts/ChartToImageSample.xaml.cs and apply SaveToPng and EncodeVisual on a simple UIControl generated in runtime (not added or shown in window), which works.
Canvas control = new Canvas() { Width=100, Height=100, Background = new SolidColorBrush(Colors.Pink)};
control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
control.Arrange(new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height));
control.UpdateLayout();
SaveToPng(control, #"C:\Foo.png");
private void SaveToPng(FrameworkElement visual, string fileName)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
EncodeVisual(visual, fileName, encoder);
}
private static void EncodeVisual(FrameworkElement visual, string fileName, BitmapEncoder encoder)
{
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (FileStream stream = File.Create(fileName)) encoder.Save(stream);
}
But when I applied the same logic to a UserControl
UserControl control = new UserControl(params)
// canvas.Children.Add(control); <-- if comment out, output blank/black image
control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
control.Arrange(new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height));
foreach (CartesianChart cc in FindVisualChildren<CartesianChart>(control))
{
cc.Update(true, true); //force chart redraw
}
control.UpdateLayout();
SaveToPng(control, #"C:\Foo.png");
<!-- UserControl XAML -->
<UserControl>
<Grid>
<Border>
<Grid>
<Label></Label>
<Label></Label>
<Label></Label>
<CartesianChart DisableAnimations="True"></CartesianChart>
</Grid>
</Border>
</Grid>
</UserControl>
That output a blank/black image. And only when I add UserControl into a UIControl existed and visible on the window/XAML will the UserControl gets rendered properly to an image (e.g. canvas.Children.Add(lc)). I then also tried the solution described in SO: How to render a WPF UserControl to a bitmap without creating a window, but I get the identical result - that is I need to first add this UserControl to a visible UIControl, whether or not I perform UpdateLayout on the UserControl.
Why the first example (Canvas) gets rendered, but not the second example (UserControl)? What are the difference and how can I fix this?
For me this works fine for only the chart as well as for the whole user control:
(Note: be sure your usercontrol has a visible background color, if the background is transparent it will be rendered transparent which most image viewer show as black)
EDIT: ok, I guess my solution won't work if the chart isn't visible. But if you can't get it to work otherwise, why not show the usercontrol in a new window, save it and close that window again?
public static BitmapSource RenderChartAsImage(FrameworkElement chart)
{
RenderTargetBitmap image = new RenderTargetBitmap((int)chart.ActualWidth, (int)chart.ActualHeight,
96, 96, PixelFormats.Pbgra32);
image.Render(chart);
return image;
}
public static void SaveChartImage(FrameworkElement chart)
{
System.Windows.Media.Imaging.BitmapSource bitmap = RenderChartAsImage(chart);
Microsoft.Win32.SaveFileDialog saveDialog = new Microsoft.Win32.SaveFileDialog();
saveDialog.FileName += "mychart";
saveDialog.DefaultExt = ".png";
saveDialog.Filter = "Image (*.png)|*.png";
if (saveDialog.ShowDialog() == true)
{
using (FileStream stream = File.Create(saveDialog.FileName))
{
System.Windows.Media.Imaging.PngBitmapEncoder encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bitmap));
encoder.Save(stream);
}
}
}
I am working on a program that can print out cards for a card game. In the program there is a class and a user control for each card type. I have a big list containing all of my cards. To print the cards, I dynamically create the controls in the code behind, add those to a Print Document, and have it print. This works! Here is an example of the generation of the control that I use:
AttackCardControl cpTop = new AttackCardControl();
cpTop.DataContext = StateManager.CardsToPrint.ElementAt(i);
Viewbox vb = new Viewbox() { Width = 240, Height = 336 };
vb.Child = cpTop;
sp.Children.Add(vb);
sp is a Stack Panel I use to arrange the cards on a page, and i is the iterator for the for loop that this is contained in. I run this through a series of for loops along with some other small irrelevant things and it works just fine.
Now I am creating a new feature that allows a user to export the cards to a PNG. I decided to do it in a very similar way to how I print the cards. This is what I use:
for (int i = 0; i < StateManager.CardsToPrint.Count; ++i)
{
Canvas cv = new Canvas();
cv.Width = 825;
cv.Height = 1125;
if (StateManager.CardsToPrint.ElementAt(i).GetType() == typeof(AttackCard))
{
AttackCardControl cardControl = new AttackCardControl();
cardControl.DataContext = StateManager.CardsToPrint.ElementAt(i);
cv.Children.Add(cardControl);
}
FileHandling.ExportToPng(new Uri(path + "/" + StateManager.CardsToPrint.ElementAt(i).Name + ".png"), cv);
}
This should export all of my Attack cards in the list to a png, and it does. However the elements tied to the Data Context are not getting updated (The Name, Effect Text, Flavor Text, etc.). So at the end, I just get a blank Attack Card that has a proper file name. When I follow it through when debugging, the DataContext remains populated with all the correct data, but when it exports to a png, none of the elements of the user control show their values. Here is the code to my ExportToPng method:
public static void ExportToPng(Uri path, Canvas surface)
{
if (path == null) return;
// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
// Get the size of canvas
System.Windows.Size size = new System.Windows.Size(surface.Width, surface.Height);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
// Create a file stream for saving image
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
{
// Use png encoder for our data
PngBitmapEncoder encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
// Restore previously saved layout
surface.LayoutTransform = transform;
}
I just don't understand why it works for printing, but it doesn't work for this exporting to a PNG. Any help would be greatly appreciated.
UPDATE
I just created a sample project that has just this issue, and can not seem to find a way to resolve it. The sample project has 3 things: MainWindow, UserControl1, and Model.cs.
The MainWindow is what I use for controls and to display the issue. The code behind contains all the logic for where the error occurs. I did not bother with MVVM for this simple example.
Here is the XAML:
<Window x:Class="ExportTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ExportTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel Name="sp" Margin="10">
<Button Content="Export Control to PNG" Click="Button_Click" Width="150" Height="30"/>
</StackPanel>
</Window>
And here is the code behind:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ExportTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Model m = new Model("Testing");
UserControl1 uc1 = new UserControl1();
uc1.DataContext = m;
sp.Children.Add(uc1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var dialog = new System.Windows.Forms.FolderBrowserDialog();
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string path = dialog.SelectedPath;
Canvas cv = new Canvas();
cv.Width = 825;
cv.Height = 1125;
Model m = new Model("Testing");
UserControl1 uc1 = new UserControl1();
uc1.DataContext = m;
cv.Children.Add(uc1);
// The card control is losing it's data context
ExportToPng(new Uri(path + "/" + m.Name + ".png"), cv);
}
}
public void ExportToPng(Uri path, Canvas surface)
{
if (path == null) return;
//// Save current canvas transform
//Transform transform = surface.LayoutTransform;
//// reset current transform (in case it is scaled or rotated)
//surface.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(surface.Width, surface.Height);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
// Create a file stream for saving image
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
{
// Use png encoder for our data
PngBitmapEncoder encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
//// Restore previously saved layout
//surface.LayoutTransform = transform;
}
}
}
Here is the XAML for the UserControl1 (the user control that we are trying to export to a PNG):
<UserControl x:Class="ExportTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ExportTest"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" Background="Red">
<TextBlock Text="Name: "/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</UserControl>
There is no code behind needed for this user control.
Lastly, we have the Model calss. This will be the model for the Data Context for UserControl1. Here is the class:
namespace ExportTest
{
public class Model
{
private string _Name;
public string Name { get { return _Name; } set { _Name = value; } }
public Model()
{
Name = "";
}
public Model(string name)
{
Name = name;
}
}
}
This will recreate my exact issue. In the Main Window, we get something that looks like this:
As you can see, the UserControl1 is catching the DataContext and is displaying the name Testing. If I click the Export Control to PNG button though, this is what I get for my exported PNG file:
Somehow, my data is missing. Any clues? Thanks for the help!
// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
and then later
// Restore previously saved layout
surface.LayoutTransform = transform;
Does not work the way you probably think it does. Transform is a class, not a struct. When you set surface.LayoutTransform to null, you are setting transform to null too.
Try commenting out all those lines and see what happens. If it works but the cards are rotated, then you have to either deep clone the layouttransform, or just save the properties that do change.
So I just found the answer here:
Saving FrameworkElement with its DataContext to image file does no succeed
There needs to be a call to UpdateLayout() to fix the bindings right before we render the control as an image.
The Source of the Image is bound to a URL which points to an image.
If the image at the URL is smaller then the MaxHeight and MaxWidth, the following code works great. The image size is exactly the same as the url and the window is sized properly.
If the image at the URL is larger then the MaxHeight and MaxWidth, only a portion of the image is displayed. The image does not get shrunk to fit into the window.
If I remove Stretch="None", the large picture then shrinks to fit into the MaxHeight and MaxWidth, looks great, but the small image gets expanded to consume all available space and looks like crap.
Here are two images I have been testing with:
http://imgur.com/iaBp2Fv,fiRrTJS#0
<Window x:Class="MyNamespace.Windows.PictureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Profile Picture" ResizeMode="NoResize" UseLayoutRounding="True" SizeToContent="WidthAndHeight" MaxHeight="750" MaxWidth="750">
<Image Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding}" />
</Window>
What you could do is to to remove the Stretch="None", as you say, to make the large image shrink down. But, to avoid the small image to be scaled up, you just add this property:
StretchDirection="DownOnly"
This prevents small images from being scaled upwards, and allows large images to be scaled down. The Window resizes appropriately as well.
This is the code that I have tested in LinqPad. Just change showLarge to true and false to switch between the images.
bool showLarge = false;
var w = new Window();
w.ResizeMode = ResizeMode.NoResize;
w.UseLayoutRounding = true;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.MaxHeight = 750;
w.MaxWidth = 750;
Image img = new Image();
img.HorizontalAlignment = HorizontalAlignment.Center;
img.VerticalAlignment = VerticalAlignment.Center;
img.StretchDirection = StretchDirection.DownOnly;
if(showLarge)
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/iaBp2Fv.jpg"));
else
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/fiRrTJS.jpg"));
w.Content = img;
w.ShowDialog();
In your code behind, create a property as
public Point ImageSize {get;set}
Get the image from the URL in the constructor/Initialize() and set ImageSize accordingly
Bind the height and width of your window to ImageSize.X and ImageSize.Y
Height="{Binding ImageSize.Y}" Width="{Binding ImageSize.Y}"
I have a UserControl in a windows phone 8 app, where the user draws on this Usercontrol. I would like to convert this to an image, an example could be bitmap.
I have found this "converting a canvas into bitmap image in android", but I need it for Windows Phone 8.
The usercontrol is positioned in a canvas. The optimal would be if I only converted the usercontrol with information to an image. But If this cant be done then the canvas. If it has to be the Canvas, is it possible to set the background around the usercontrol to be invisible, since this information is not wanted.
EDIT
Maybe this link can help somebody.
How to render a WPF UserControl to a bitmap without creating a window
I will post my solution when it is done, I will also look into converting a bitmap back to usercontrol, if someone has looked into this please inform me about this.
EDIT 2
Has someone used this library
http://writeablebitmapex.codeplex.com/
Should be pretty light weight and can see there is a function crop image. So maybe this is exactly what I need.
EDIT 3
So I have been looking more into this and finally found something that was exactly what I wanted, see http://www.kunal-chowdhury.com/2012/12/how-to-crop-image-based-on-shape-or-path.html
But I cannot seem to get this working. Has anyone an idea for this?
solution
I use writeablebitmap to capture the ui element and i save it to isolatedstorage using mediastream. I can then reload it and use the image as imagesource and thereby crop the element to the wished shape.
This allows you to do it with pure WPF code using a RendeTargetBitmap, DrawingVisual and a VisualBrush.
Get System.Drawing.Bitmap of a WPF Area using VisualBrush
Posted as an answer because I had to do some hunting for a solution using visual brush and not a window.
Here is the the important code, horribly stolen:
public BitmapSource ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap((int)(element.RenderSize.Width), (int)(element.RenderSize.Height), 96, 96, PixelFormats.Pbgra32);
var brush = new VisualBrush(element);
var visual = new DrawingVisual();
var drawingContext = visual.RenderOpen();
drawingContext.DrawRectangle(brush, null, new Rect(new Point(0, 0),
new Point(element.RenderSize.Width, element.RenderSize.Height)));
drawingContext.Close();
target.Render(visual);
return target;
}
If you want to include some extra masking here, push an opacity mask while drawing.
drawingContext.PushOpacityMask(brush);
However, you should not have to as the same effect can be accomplished through XAML using the opacity mask property on your target element.
Here is my sample XAML
<Window x:Class="UIToBitmap.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Ellipse x:Name="circle" Height="100" Width="100" Fill="Blue"/>
<Grid Background="Black">
<Image x:Name="Image" Height="100"></Image>
</Grid>
</StackPanel>
</Window>
And the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UIToBitmap
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var source = ConvertToBitmapSource(circle);
Image.Source = source;
}
public BitmapSource ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap((int)(element.RenderSize.Width), (int)(element.RenderSize.Height), 96, 96, PixelFormats.Pbgra32);
var brush = new VisualBrush(element);
var visual = new DrawingVisual();
var drawingContext = visual.RenderOpen();
drawingContext.DrawRectangle(brush, null, new Rect(new Point(0, 0),
new Point(element.RenderSize.Width, element.RenderSize.Height)));
drawingContext.PushOpacityMask(brush);
drawingContext.Close();
target.Render(visual);
return target;
}
}
}
Note that the circle that is drawn on the black grid retains its opacity - only the circle is drawn.
public static class SBA
{
public static WriteableBitmap SaveAsWriteableBitmap(Canvas surface)
{
if (surface == null) return null;
// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(surface.ActualWidth, surface.ActualHeight);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Get the size of canvas
size = new Size(surface.ActualWidth, surface.ActualHeight);
// Measure and arrange the surface
// VERY IMPORTANT
surface.Measure(size);
surface.Arrange(new Rect(size));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(surface);
//Restore previously saved layout
surface.LayoutTransform = transform;
//create and return a new WriteableBitmap using the RenderTargetBitmap
return new WriteableBitmap(renderBitmap);
}
public static BitmapImage WriteableBitmapToBitmapImage(WriteableBitmap wbm)
{
BitmapImage bmImage = new BitmapImage();
using (MemoryStream stream = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(wbm));
encoder.Save(stream);
bmImage.BeginInit();
bmImage.CacheOption = BitmapCacheOption.OnLoad;
bmImage.StreamSource = stream;
bmImage.EndInit();
bmImage.Freeze();
}
return bmImage;
}
public static BitmapImage CanvasToBitmap(Canvas c)
{
return WriteableBitmapToBitmapImage(SaveAsWriteableBitmap(c));
}
}
for your need there is third party library called WriteableBitmapEx that has lots of other feature that you may be in need in future.so just install this library from NugetPackageManager and then you can make a writeableBitmap of any control on the UI and then convert it to any image. i have a sample for it in which i have convert an button to an writeablebitmap and then save it to medialibrary(photos) in phone.here is the simple code but make sure you have install the WriteableBitmapEx.
here btn a Button defined in xaml..
private void btn_Click_1(object sender, RoutedEventArgs e)
{
BitmapImage img = new BitmapImage();
imagebitmap = new WriteableBitmap(btn, null);
imagebitmap.SaveToMediaLibrary("hello", false);
}
you have to use this code directly. hope it helps you
solution I use writeablebitmap to capture the ui element and i save it to isolatedstorage using mediastream. I can then reload it and use the image as imagesource and thereby crop the element to the wished shape.