Convert Canvas to BitMapImage & crop to path - c#

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.

Related

Render image from UserControl that has LiveChart

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);
}
}
}

Data Context not updating elements when exporting control to a png WPF C#

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.

Draw a bitmap in WPF

I am trying to draw some string to bitmap at certain position and copy a barcode bitmap to the new bitmap.I have not done with graphics before so i don't know where to start.
Can anyone guide me on this?my output of the bitmap is a receipt like.
Here is a solution. Please make a grid or canvas and put the barcode image and use a label with desired text and put the label on desired location relative to barcode grid. So, trick is you can immediately take screenshot of this grid using following code.Then, you are done.
public void ConvertToBitmapSource(UIElement element)
{
var target = new RenderTargetBitmap(
(int)element.RenderSize.Width, (int)element.RenderSize.Height,
96, 96, PixelFormats.Pbgra32);
target.Render(element);
var encoder = new PngBitmapEncoder();
var outputFrame = BitmapFrame.Create(target);
encoder.Frames.Add(outputFrame);
using (var file = File.OpenWrite("TestImage.png"))
{
encoder.Save(file);
}
}

How to overlap images and store them in new image objects

I have two images,now i want to overlay the images,such that the other image appears on center or left corner of the other image,and then when finally both the images are overlayed i can store it in another new image object,and i want to all this in code behind only not xaml,how to do this?
if (((Grid)sender).Children.Count > 0)
{
gridBackground = (ImageBrush)(((Grid)sender).Background);
gridBackImage = new System.Windows.Controls.Image();
gridBackImage.Source = gridBackground.ImageSource;
}
System.Windows.Controls.Image imgRejectIcon;
if (((Grid)sender).Children.Count > 0)
{
imgRejectIcon = (System.Windows.Controls.Image)(((Grid)sender).Children[0]);
}
Now i want to merge gridBackImage and imgRejection and store it in new image object
You can arrange your two Images in any way that you want and I'll leave that code for you to do (it'd much simpler to do in XAML). In WPF, all UI controls extend the Visual class. This is very useful for making BitmapImages from UI controls, when used with the RenderTargetBitmap.Render method.
First, arrange your two Image controls into a Grid, or other container control, and then you can pass the container control to the RenderTargetBitmap.Render method and create an Image something like this:
RenderTargetBitmap renderTargetBitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(yourContainerControl);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create(filePath))
{
pngImage.Save(fileStream);
}

how to take snapshot from rendered HTML in WPF

Is there any way to take a snapshot of rendered html, i.e from webBrowser control and make an image object of the output?
is it possible by code besides using webbrowser, bcoz i think it needs to be visible on screen before the source is render as output.
i have html string which contains some picture text and table. of height of almost 700px, could be less or more on other scenario.
please guide me on this.
Use a RenderTargetBitmap instance. This allows you to render a control without displaying it on screen. Be careful to give it a width and height ;)
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap%28v=vs.110%29.aspx
Sample from MSDN
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Globalization;
namespace SDKSample
{
public partial class RenderTargetBitmapExample : Page
{
public RenderTargetBitmapExample()
{
Image myImage = new Image();
FormattedText text = new FormattedText("ABC",
new CultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(this.FontFamily, FontStyles.Normal, FontWeights.Normal, new FontStretch()),
this.FontSize,
this.Foreground);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawText(text, new Point(2, 2));
drawingContext.Close();
RenderTargetBitmap bmp = new RenderTargetBitmap(180, 180, 120, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
myImage.Source = bmp;
// Add Image to the UI
StackPanel myStackPanel = new StackPanel();
myStackPanel.Children.Add(myImage);
this.Content = myStackPanel;
}
}
}

Categories