Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

How to Render Bitmap or to Print a Visual in WPF

5.00/5 (9 votes)
19 Aug 2010CPOL3 min read 67.2K   783  
The article shows how you could use RenderTargetBitmap to render a visual into BitmapSource and convert the same into actual image.

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.

XML
<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.

C#
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:

C#
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.

C#
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.

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)