Working with WPF would never become so easy if there was no inbuilt capability to take snaps or print a visual object. Microsoft made a very constructive class hierarchy to add functionality in each of the classes and hence built the solid foundation of WPF presentation model. Every element that we place in WPF is inherited from a Visual. BCL added inbuilt functionality for a visual to render itself as Bitmapsource
or to print the same Visual directly to the printer. In this post, I will discuss how easily you could render a Visual as BitmapSource
and later use it as source of an Image control.
Using Sample Application
RenderTargetBitmap
is a class that is basically used to render a Visual to a bitmap object. Let's demonstrate this using an InkCanvas
. You might already know, there is a special canvas element called InkCanvas
which lets the user draw an image on the screen. So before we proceed with the code, let's discuss the sample application a bit.
In the above snap, you can see that I have placed one InkCanvas
which allows you to write dynamically on the screen. The buttons will Render the bitmap image from the Canvas and add the item on the ListBox
on the Right. You can see after I click on Render as Bitmap button, it actually places the same visual as BitmapSource
on the Right hand side ListBox
.
Similar to what we worked on, I changed the content a bit and took another shot, and it exactly does the same.
Finally, when I click on Render
the Grid
as Bitmap
, it actually renders the whole Grid
including the listbox
and all as Grid
. There are few buttons more, like Save Selection as JPEG / Print as Visual, each of them has its own functionality.
You can download the sample here.
Using the Code
So, as you see the basic functionality, let me discuss the code.
<InkCanvas EditingMode="Ink" Grid.Row="1" x:Name="inkCanvas" Background="AliceBlue" />
<ListBox x:Name="lstImages" Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding ImageCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="UniformToFill"
MaxWidth="150" MaxHeight="150" Margin="10" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" Width="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ScrollContentPresenter}},
Path=ActualWidth}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<StackPanel Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal">
<Button x:Name="btnRenderBitmap" Content="Render as BitMap"
Click="btnRenderBitmap_Click" />
<Button x:Name="btnRenderwhole" Content="Render the Grid as Bitmap"
Click="btnRenderwhole_Click" />
<Button x:Name="btnRenderJPEG" Content="Save Selecteditem as Jpeg"
Click="btnRenderJPEG_Click" />
<Button x:Name="btnPrintVisual" Content="Print the Visual"
Click="btnPrintVisual_Click" />
</StackPanel>
So here I have placed one InkCanvas
element and a ListBox
with few buttons. The XAML is quite straightforward, and when buttons are clicked, the listbox
will update itself.
private ObservableCollection<BitmapSource> imagecollection;
public ObservableCollection<BitmapSource> ImageCollection
{
get
{
this.imagecollection = this.imagecollection ??
new ObservableCollection<BitmapSource>();
return this.imagecollection;
}
}
public RenderTargetBitmap RenderVisaulToBitmap(Visual vsual, int width, int height)
{
RenderTargetBitmap rtb = new RenderTargetBitmap
(width, height, 96, 96, PixelFormats.Default);
rtb.Render(vsual);
BitmapSource bsource = rtb;
this.ImageCollection.Add(bsource);
return rtb;
}
I have used a model to use RenderTargetBitmap
. Basically, the model will actually separate the logic from the presentation. I have created an ObservableCollection
of BitmapSource
objects. Now when Button1
is clicked, I use RenderTargetBitmap
to render the Visual.
RenderTargetBitmap
takes width
, height
, dpiX
and dpiY
as arguments and renders the BitmapSource
object from the Visual. rtb.Render(visual)
gets the BitmapSource
object, which I have added to the ImageCollection
. As it is directly bound to the ListBox
, the ListBox
updates the Image instantly.
To Encode the BitmapSource to Actual Image
Rendering an image from a BitmapSource
object is actually not a big deal. Let us look at the code below:
public MemoryStream GenerateImage
(Visual vsual, int widhth, int height, ImageFormat format)
{
BitmapEncoder encoder = null;
switch (format)
{
case ImageFormat.JPG :
encoder = new JpegBitmapEncoder();
break;
case ImageFormat.PNG:
encoder = new PngBitmapEncoder();
break;
case ImageFormat.BMP:
encoder = new BmpBitmapEncoder();
break;
case ImageFormat.GIF:
encoder = new GifBitmapEncoder();
break;
case ImageFormat.TIF:
encoder = new TiffBitmapEncoder();
break;
}
if (encoder == null) return null;
RenderTargetBitmap rtb = this.RenderVisaulToBitmap(vsual, widhth, height);
MemoryStream file = new MemoryStream();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(file);
return file;
}
Here, I have used an enumerable which decides the format of the image. In the method, when I pass JPEG, it will actually create an object of JpegBitmapEncoder
which lets me to encode the BitmapSource
into compressed JPEG format. encoder.Frames
allows you to add a BitmapFrame
which would later be rendered as Image
. You can use JpegBitmapDecoder
to decode JPEG image. Once I call encoder.Save
, it saves the image into Stream
.
private void btnRenderJPEG_Click(object sender, RoutedEventArgs e)
{
MemoryStream memstream = this.DataModel.GenerateImage
(inkCanvas, (int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight, ImageFormat.JPG);
if (memstream != null)
{
SaveFileDialog fdlg = new SaveFileDialog
{
DefaultExt="jpg",
Title="Choose filename and location",
Filter="*Jpeg files|.jpg|Bmp Files|*.bmp|PNG Files|
*.png|Tiff Files|*.tif|Gif Files|*.gif"
};
bool? result = fdlg.ShowDialog();
if (result.HasValue && result.Value)
{
using (FileStream fstream = File.OpenWrite(fdlg.FileName))
{
memstream.WriteTo(fstream);
fstream.Flush();
fstream.Close();
}
}
}
}
From the front end, I call the method, and store the stream into a file location.
Printing a Visual
Printing a Visual WPF is somewhat the easiest of the whole lot if you want to print the inkCanvas
directly to the printer. In that case, you don't need to create a PrintDocument
. I will discuss how to print FlowDocument
later. To print a visual, you need to use PrintDialog
.
PrintDialog dlg = new PrintDialog();
dlg.PrintVisual(this.inkCanvas, "The picture is drawn dynamically");
Thus PrintDialog
has a method that allows you to pass a description and a visual, and eventually the command will print the visual from printer.
Try the sample application here.
I hope this has made it clear on how to work with Visual. If you want to comment, feel free to do so.
Thanks for reading.