Header Ads

Save Canvas UI Control to Image file in Windows 8.1

Time for blogging again. Today, I shall discuss about how can we save information or graphics which are being displayed on canvas UI control to image file (I am using .PNG image file extension). This discussion is specific to Windows Store 8.1 platform application development, so, when I mean UI control (s), I mean the UI control (s) offer by Windows Store 8.1 platform.


Following are some prerequisites before you proceed any further in this tutorial.

Prerequisites: 

1) Knowledge about Windows Store platform.
2) Knowledge about Asynchronous Programming.  
3) Knowledge about XAML UI controls.

This topic is not new, there are several threads out their that address this topic, however, working coding solution is hardly available, so, I decided to put all those discussions into action. Before I start the coding tutorial, I would like to clarify the main problem in hand. Our problem here is to convert canvas information or graphics into an image and then save that image to an image supported format e.g. png, jpg etc. For this tutorial, I am using .PNG format. It is really important to understand the problem before jumping into proposing any solution. During the research of the solution to this problem, I have come across several threads that says to use WriteableBitmapEx library to address this problem by using Image UI control instead of using Canvas UI control. The reason is that Windows 8 does not support any class or library that can convert any XAML UI control to bitmap for saving the bitmap ultimately as image file. So, the alternates are to use image UI control instead and then automatically associate the WriteableBitmapEx library with the image UI control for manipulating the bitmap. But, phew.... with the release of windows 8.1 SDK, a class called RenderTargetBitmap is also provided that can convert any XAML UI control to bitmap, which allows the developers to be more creative with their application ideas.

You can download the complete source code for this tutorial or you can follow step by step discussion below. The sample code is developed in Microsoft Visual Studio 2013 Ultimate with Windows 8.1 platform.

Download Link

Let us begin now.

1) Create a windows store blank application and name it "RenderCanvasToImage".
2) Replace the existing "MainPage.xaml" with following xaml snippet.

<Page  
   x:Class="RenderCanvasToImage.MainPage"  
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
   xmlns:local="using:RenderCanvasToImage"  
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
   mc:Ignorable="d">  
   <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">  
     <Canvas Background="Cyan" Name="panelcanvas" Margin="47.5,57,327.5,153"  
         Width="700"  
         Height="580">  
       <Image Canvas.Left="260" Canvas.Top="115" Source="index.jpg"/>  
     </Canvas>  
   </Grid>  
   <Page.BottomAppBar>  
     <AppBar x:Name="bottomappbar" Padding="10,0,10,0">  
       <Grid>  
         <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">  
           <Button x:Name="save_to_local" Grid.Column="2" Height="38" Width="115.5" Content="save to local" Margin="0,23" Click="save_to_local_Click"  />  
         </StackPanel>  
       </Grid>  
     </AppBar>  
   </Page.BottomAppBar>  
   <Page.TopAppBar>  
     <AppBar x:Name="topappbar" Padding="10,0,10,0">  
       <Grid>  
         <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">  
           <Button x:Name="btnColorGreen" Background="Green" Content="Green" Grid.Column="1" Margin="0,13,0,0" VerticalAlignment="Top" Click="btnColorGreen_Click"/>  
         </StackPanel>  
       </Grid>  
     </AppBar>  
   </Page.TopAppBar>  
 </Page>  

The only important thing to take care of in this XAML snippet is to fix the size of your canvas UI control, otherwise our output image content will not be arranged properly for saving to image file. Rest is just simple i.e. we have add a predefine testing image inside the canvas UI control and create bottom and top page app bars. The upper bar will allow change the color of our stroke ink and lower app bar will provide option to save the canvas into image on a desktop.  

3) Go to "MainPage.xaml.cs" file and first add some setting variable for handling on screen strokes by user i.e.

  InkManager _inkManager = new Windows.UI.Input.Inking.InkManager();  
     private uint _penID;  
     private uint _touchID;  
     private Point _previousContactPt;  
     private Point currentContactPt;  
     private double x1;  
     private double y1;  
     private double x2;  
     private double y2;  
     //for color changing  
     private byte red;  
     private byte green;  
     private byte blue;  

4) Add following lines of code in the constructor after "InitializeComponent()" method:

     this.InitializeComponent();  
       panelcanvas.PointerPressed += new PointerEventHandler(InkCanvas_PointerPressed);  
       panelcanvas.PointerMoved += new PointerEventHandler(InkCanvas_PointerMoved);  
       panelcanvas.PointerReleased += new PointerEventHandler(InkCanvas_PointerReleased);  
       panelcanvas.PointerExited += new PointerEventHandler(InkCanvas_PointerReleased);  

5) Now add below methods which are related to pointer stroking, they are simple to understand:

     #region pointernav  
       public void InkCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)  
       {  
         if (e.Pointer.PointerId == _penID)  
         {  
           Windows.UI.Input.PointerPoint pt = e.GetCurrentPoint(panelcanvas);  
           // Pass the pointer information to the InkManager.   
           _inkManager.ProcessPointerUp(pt);  
         }  
         else if (e.Pointer.PointerId == _touchID)  
         {  
           // Process touch input  
         }  
         _touchID = 0;  
         _penID = 0;  
         // Call an application-defined function to render the ink strokes.  
         e.Handled = true;  
       }  
       private void InkCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)  
       {  
         if (e.Pointer.PointerId == _penID)  
         {  
           PointerPoint pt = e.GetCurrentPoint(panelcanvas);  
           // Render a red line on the canvas as the pointer moves.   
           // Distance() is an application-defined function that tests  
           // whether the pointer has moved far enough to justify   
           // drawing a new line.  
           currentContactPt = pt.Position;  
           x1 = _previousContactPt.X;  
           y1 = _previousContactPt.Y;  
           x2 = currentContactPt.X;  
           y2 = currentContactPt.Y;  
           if (Distance(x1, y1, x2, y2) > 2.0)  
           {  
             Line line = new Line()  
             {  
               X1 = x1,  
               Y1 = y1,  
               X2 = x2,  
               Y2 = y2,  
               //StrokeThickness = sldStrokeSlider.Value,  
               Stroke = new SolidColorBrush(Color.FromArgb(255,red, green, blue))  
               // Stroke = new SolidColorBrush(Colors.GreenYellow)  
             };  
             _previousContactPt = currentContactPt;  
             // Draw the line on the canvas by adding the Line object as  
             // a child of the Canvas object.  
             panelcanvas.Children.Add(line);  
             // Pass the pointer information to the InkManager.  
             _inkManager.ProcessPointerUpdate(pt);  
           }  
         }  
         else if (e.Pointer.PointerId == _touchID)  
         {  
           // Process touch input  
         }  
       }  
       private double Distance(double x1, double y1, double x2, double y2)  
       {  
         double d = 0;  
         d = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));  
         return d;  
       }  
       public void InkCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)  
       {  
         // Get information about the pointer location.  
         PointerPoint pt = e.GetCurrentPoint(panelcanvas);  
         _previousContactPt = pt.Position;  
         // Accept input only from a pen or mouse with the left button pressed.   
         PointerDeviceType pointerDevType = e.Pointer.PointerDeviceType;  
         if (pointerDevType == PointerDeviceType.Pen ||  
             pointerDevType == PointerDeviceType.Mouse &&  
             pt.Properties.IsLeftButtonPressed)  
         {  
           // Pass the pointer information to the InkManager.  
           _inkManager.ProcessPointerDown(pt);  
           _penID = pt.PointerId;  
           e.Handled = true;  
         }  
         else if (pointerDevType == PointerDeviceType.Touch)  
         {  
           // Process touch input  
         }  
       }  
       #endregion

6) Now, lets create and empty method "save_to_local_Click" as we have define in our XAML, also create the method "btnColorGreen_Click" with following code snippet:

    private void save_to_local_Click(object sender, RoutedEventArgs e)  
     {  
       try  
       {  

       }  
       catch (Exception)  
       {  
       }  
     }  
     private void btnColorGreen_Click(object sender, RoutedEventArgs e)  
     {  
       red = (byte)0;  
       green = (byte)128;  
       blue = (byte)0;  
     }  

7) Execute the project and you will able to see your canvas and can add strokes on it in "black" and "green" colors.

 


8) Now, add following method into your "MainPage.xaml.cs" file and I will explain them:

    #region Show Message method  
     /// <summary>  
     /// Show Message method  
     /// </summary>  
     /// <param name="content">Content parameter</param>  
     /// <param name="title">Title parameter</param>  
     private async void ShowMessage(string content, string title)  
     {  
       MessageDialog msg = new MessageDialog(content, title);  
       await msg.ShowAsync();  
     }  
     #endregion  
     #region Create File method.  
     /// <summary>  
     /// Create File method.  
     /// </summary>  
     /// <param name="filename">File name parameter</param>  
     /// <returns>Returns storage file type object</returns>  
     private async Task<StorageFile> CreateFile(string filename)  
     {  
       // Initialization  
       StorageFile file = null;  
       try  
       {  
         // Setting file properties.  
         FileSavePicker save = new FileSavePicker();  
         save.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop;  
         save.DefaultFileExtension = ".png";  
         save.SuggestedFileName = filename;  
         save.FileTypeChoices.Add("PNG", new string[] { ".png" });  
         // Creating File  
         file = await save.PickSaveFileAsync();  
       }  
       catch (Exception ex)  
       {  
         this.ShowMessage(ex.ToString(), "Error");  
       }  
       return file;  
     }  
     #endregion  
     #region Save to PNG method.  
     /// <summary>  
     /// Save to PNG method.  
     /// </summary>  
     /// <param name="bitmap">Bit map parameter</param>  
     /// <param name="filename">file name parameter</param>  
     /// <returns>Await able .</returns>  
     private async Task SaveToPNG(RenderTargetBitmap bitmap, string filename)  
     {  
       // Verification.  
       if (bitmap == null)  
       {  
         // Message  
         this.ShowMessage("Something goes wronr, try again later", "Error");  
         // Info  
         return;  
       }  
       try  
       {  
         // Create file.  
         StorageFile file = await this.CreateFile(filename);  
         // Verification.  
         if (bitmap == null)  
         {  
           // Message  
           this.ShowMessage("Something goes wronr, try again later", "Error");  
           // Info  
           return;  
         }  
         // Saving to file.  
         using (var stream = await file.OpenStreamForWriteAsync())  
         {  
           // Initialization.  
           var pixelBuffer = await bitmap.GetPixelsAsync();  
           var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;  
           // convert stream to IRandomAccessStream  
           var randomAccessStream = stream.AsRandomAccessStream();  
           // encoding to PNG  
           var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, randomAccessStream);  
           // Finish saving  
           encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)bitmap.PixelWidth,  
                      (uint)bitmap.PixelHeight, logicalDpi, logicalDpi, pixelBuffer.ToArray());  
           // Flush encoder.  
           await encoder.FlushAsync();  
         }  
       }  
       catch (Exception ex)  
       {  
         this.ShowMessage(ex.ToString(), "Error");  
       }  
     }  
     #endregion  
     #region Canvas to BMP method  
     /// <summary>  
     /// Canvas to BMP method.  
     /// </summary>  
     /// <returns>Await able returns render target bit map object </returns>  
     private async Task<RenderTargetBitmap> CanvasToBMP()  
     {  
       // Initialization  
       RenderTargetBitmap bitmap = null;  
       try  
       {  
         // Initialization.  
         Size canvasSize = this.panelcanvas.RenderSize;  
         Point defaultPoint = this.panelcanvas.RenderTransformOrigin;  
         // Sezing to output image dimension.  
         this.panelcanvas.Measure(canvasSize);  
         this.panelcanvas.UpdateLayout();  
         this.panelcanvas.Arrange(new Rect(defaultPoint, canvasSize));  
         // Convert canvas to bmp.  
         var bmp = new RenderTargetBitmap();  
         await bmp.RenderAsync(this.panelcanvas);  
         // Setting.  
         bitmap = bmp as RenderTargetBitmap;  
       }  
       catch (Exception ex)  
       {  
         this.ShowMessage(ex.ToString(), "Error");  
       }  
       return bitmap;  
     }  
     #endregion  
     #region Save Canvas method  
     /// <summary>  
     /// Save Canvas method.  
     /// </summary>  
     private async void SaveCanvas()  
     {  
       // Initialization.  
       string filename = "Canvas File";  
       try  
       {  
         // Save canvas.  
         RenderTargetBitmap bitmap = await this.CanvasToBMP();  
         await this.SaveToPNG(bitmap, filename);  
       }  
       catch (Exception ex)  
       {  
         this.ShowMessage(ex.ToString(), "Error");  
       }  
     }  
     #endregion 

We have added following methods i.e.

1) "SaveCanvas()" method.
2) "CanvasToBMP()" method.
3) "SaveToPNG(...)" method.
4) "CreateFile(...)" method.
5) "ShowMessage(...)" method.

ShowMessage() method is for displaying any message on screen e.g. exceptions etc, CreateFile(..) method creates the target PNG format image file on desktop. SaveCanvas() is high level method that calls two very importan methods i.e. CanvasToBMP() & SaveToPNG(...) method. We have first converted our canvas UI control to bitmap by using RenderTargetBitmap class, for that, we first need to so some settings for our canvas i.e.  

               this.panelcanvas.Measure(canvasSize);
                 this.panelcanvas.UpdateLayout();
                 this.panelcanvas.Arrange(new Rect(defaultPoint, canvasSize));

The above lines of code in CanvasToBMP() first measure the size of the canvas that we want to render into PNG format, I have set the size to our canvas size, then we update our UI layout according to the measure size, after that, we arrange the render bounding of our canvas according to canvas origin point and canvas size, so, that all the children of the canvas are arrange properly before being converted to bitmap. Finally, we convert the canvas UI control to bitmap and by using the settings of SaveToPNG(...) method we have save our canvas UI control information or graphics into PNG image format.  

9) Call "SaveCanvas()" method from within "save_to_local_Click" method. 10) Now, lets execute the project and add some strokes to the canvas and the click the "Save to Local" button to save canvas UI control information or graphics to PNG image file:

 

 

 

11) Now, open the saved PNG file from the desktop and you will see that everything on your canvas UI control has been saved to the PNG image file.


That's about it.

Enjoy!! Coding.

3 comments:

  1. Hello,
    very interressant code. Exactly what I'm trying to do.
    But with WP 8.1, you cannot use "PickSaveFileAsync" in your createfile function anymore.
    You need to use "PickSaveFileAndContinue" instead. The problem is tha this method doesn't produce a value...
    Do you know how to work with this ?
    thank you for your help ...
    Julien (julien.louyot@merckel.fr)

    ReplyDelete
  2. Dear friend
    This code not working, I use Windows Phone 8.1
    InkManager _inkManager = new Windows.UI.Input.Inking.InkManager();

    ReplyDelete
    Replies
    1. Dear Friend,

      This post is for Windows Store 8.1 not Windows Phone 8.1. Windows Phone have other methods for such scenario.

      Delete