Search code examples
c#canvasuwp-xamlxamarin.uwpinkcanvas

how to save work done on canvas using InkCanvas to an image file in UWP C#?


I want to save my work done on a canvas in my UWP app. I am using InkCanvas to draw lines on an selected image inside the canvas and I want to save the canvas work to a new image file.

I am getting a blank image after trying to save the file. I've tried two approaches to save file.

work done:

xaml code

<Button Click="ShowPopup" Content="click me"/>
<Popup x:Name="IMG_G"  Width="600" Height="300" HorizontalAlignment="Left" ManipulationMode="All">
<Grid x:Name="img_grid" Height="300" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" ManipulationMode="Scale">
      <Image VerticalAlignment="Top" HorizontalAlignment="Left"
             x:Name="img" Stretch="Fill" Height="300" ManipulationMode="All">
      </Image>
      <Canvas x:Name="selectionCanvas" Width="600" Background="Transparent" Height="300"/>
      <InkCanvas x:Name="inker" />
      <InkToolbar x:Name="img_inktoolbar" TargetInkCanvas="{x:Bind inker}" 
                  VerticalAlignment="Top">
      </InkToolbar>
</Grid>
</Popup>
<Button Content="Save"
        Width="100"
        Height="25"
        HorizontalAlignment="Center"
        VerticalAlignment="Center" Click="BtnSave_Click"/>

Code Behind

public DrawLines()
    {
        InitializeComponent();

        inker.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
        inker.InkPresenter.UnprocessedInput.PointerPressed += StartLine;
        inker.InkPresenter.UnprocessedInput.PointerMoved += ContinueLine;
        inker.InkPresenter.UnprocessedInput.PointerReleased += CompleteLine;
        inker.InkPresenter.InputProcessingConfiguration.RightDragAction = InkInputRightDragAction.LeaveUnprocessed;
    }

private async void ShowPopup(object sender, RoutedEventArgs e)
    {
        var _filePicker = new FileOpenPicker();
        _filePicker.SuggestedStartLocation = PickerLocationId.Desktop;
        _filePicker.ViewMode = PickerViewMode.Thumbnail;
        _filePicker.FileTypeFilter.Add(".bmp");
        _filePicker.FileTypeFilter.Add(".jpg");
        StorageFile _file = await _filePicker.PickSingleFileAsync();
        IRandomAccessStream imageStream = await _file.OpenAsync(FileAccessMode.Read);
        BitmapImage bmpimage = new BitmapImage();
        await bmpimage.SetSourceAsync(imageStream);
        img.Source = bmpimage;
        IMG_G.IsOpen = true;
    }

private void StartLine(InkUnprocessedInput sender, PointerEventArgs args)
    {
        line = new Line();
        line.X1 = args.CurrentPoint.RawPosition.X;
        line.Y1 = args.CurrentPoint.RawPosition.Y;
        line.X2 = args.CurrentPoint.RawPosition.X;
        line.Y2 = args.CurrentPoint.RawPosition.Y;

        line.Stroke = new SolidColorBrush(Colors.Purple);
        line.StrokeThickness = 4;
        selectionCanvas.Children.Add(line);
    }

private void ContinueLine(InkUnprocessedInput sender, PointerEventArgs args)
    {
        line.X2 = args.CurrentPoint.RawPosition.X;
        line.Y2 = args.CurrentPoint.RawPosition.Y;
    }

private void CompleteLine(InkUnprocessedInput sender, PointerEventArgs args)
    {
        List<InkPoint> points = new List<InkPoint>();
        InkStrokeBuilder builder = new InkStrokeBuilder();


        InkPoint pointOne = new InkPoint(new Point(line.X1, line.Y1), 0.5f);
        points.Add(pointOne);
        InkPoint pointTwo = new InkPoint(new Point(line.X2, line.Y2), 0.5f);
        points.Add(pointTwo);

        InkStroke stroke = builder.CreateStrokeFromInkPoints(points, System.Numerics.Matrix3x2.Identity);
        InkDrawingAttributes ida = inker.InkPresenter.CopyDefaultDrawingAttributes();
        stroke.DrawingAttributes = ida;
        inker.InkPresenter.StrokeContainer.AddStroke(stroke);
        selectionCanvas.Children.Remove(line);
    }

Approach 1 for saving the file

private async void BtnSave_Click(object sender, RoutedEventArgs e)
    {
        StorageFolder pictureFolder = KnownFolders.SavedPictures;
        var file = await pictureFolder.CreateFileAsync("test2.bmp", CreationCollisionOption.ReplaceExisting);
        CanvasDevice device = CanvasDevice.GetSharedDevice();

        CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, (int)img.ActualWidth, (int)img.ActualHeight, 96);

        //get image's path
        StorageFolder folder = ApplicationData.Current.LocalFolder;
        //Get the same image file copy which i selected to draw on in ShowPopup() but I actually wanted to get the edited canvas
        StorageFile Ifile = await folder.GetFileAsync("Datalog_2020_09_22_10_44_59_2_3_5_RSF.bmp");
        var inputFile = Ifile.Path;

        using (var ds = renderTarget.CreateDrawingSession())
        {
            ds.Clear(Colors.White);
            CanvasBitmap image = await CanvasBitmap.LoadAsync(device, inputFile);
            //var image = img2.Source;
            // I want to use this too, but I have no idea about this

            ds.DrawImage(image);
            ds.DrawInk(inker.InkPresenter.StrokeContainer.GetStrokes());
        }
        // save results           

        using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Jpeg, 1f);
        }
    }

Results

desired result

Note: Arrow is drawn by me on the image Desired result

Result I am getting with this approach

test2.bmp (image is getting zoomed in for some reason)

test2.bmp

Approach 2 for saving the file

private async void BtnSave_Click(object sender, RoutedEventArgs e)
    {
        RenderTargetBitmap bitmap = new RenderTargetBitmap();
        await bitmap.RenderAsync(selectionCanvas);
        Debug.WriteLine($"Capacity = {(uint)bitmap.PixelWidth}, Length={(uint)bitmap.PixelHeight}");
        var pixelBuffer = await bitmap.GetPixelsAsync();
        Debug.WriteLine($"Capacity = {pixelBuffer.Capacity}, Length={pixelBuffer.Length}");
        byte[] pixels = pixelBuffer.ToArray();
        var displayInformation = DisplayInformation.GetForCurrentView();
        StorageFolder pictureFolder = KnownFolders.SavedPictures;
        var file = await pictureFolder.CreateFileAsync("test2.bmp", CreationCollisionOption.ReplaceExisting);
        using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
            encoder.SetPixelData(BitmapPixelFormat.Bgra8,
                                 BitmapAlphaMode.Ignore,
                                 (uint)bitmap.PixelWidth,
                                 (uint)bitmap.PixelHeight,
                                 displayInformation.RawDpiX,
                                 displayInformation.RawDpiY,
                                 pixels);
            await encoder.FlushAsync();
        }
    }

Result with this approach

For some reason I am getting all black image

test2.bmp

All black image

Any help will be apppreciated. Any help with approach 2 will be better.


Solution

  • I made some changes in approach 2 and got the desired result

    Approach 2 for saving the file

    private async void BtnSave_Click(object sender, RoutedEventArgs e)
    {
        // In order to hide the InkToolbar before the saving the image
        img_inktoolbar.Visibility = Visibility.Collapsed;
    
        RenderTargetBitmap bitmap = new RenderTargetBitmap();
        await bitmap.RenderAsync(img_grid);
        Debug.WriteLine($"Capacity = {(uint)bitmap.PixelWidth}, Length={(uint)bitmap.PixelHeight}");
        var pixelBuffer = await bitmap.GetPixelsAsync();
        Debug.WriteLine($"Capacity = {pixelBuffer.Capacity}, Length={pixelBuffer.Length}");
        byte[] pixels = pixelBuffer.ToArray();
        var displayInformation = DisplayInformation.GetForCurrentView();
        StorageFolder pictureFolder = KnownFolders.SavedPictures;
        var file = await pictureFolder.CreateFileAsync("test2.bmp", CreationCollisionOption.ReplaceExisting);
        using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
            encoder.SetPixelData(BitmapPixelFormat.Bgra8,
                                 BitmapAlphaMode.Ignore,
                                 (uint)bitmap.PixelWidth,
                                 (uint)bitmap.PixelHeight,
                                 displayInformation.RawDpiX,
                                 displayInformation.RawDpiY,
                                 pixels);
            await encoder.FlushAsync();
        }
    }