Faraaz bhilwade
Faraaz bhilwade

Reputation: 49

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.

Upvotes: 1

Views: 587

Answers (2)

Nico Zhu
Nico Zhu

Reputation: 32775

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

You have no need use RenderTargetBitmap to save InkCanvas to image. UWP InkCanvas has SaveAsync method that could save the StrokeContainer stream to image file directly. For example.

async void OnSaveAsync(object sender, RoutedEventArgs e)
{
    // We don't want to save an empty file
    if (inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count > 0)
    {
        var savePicker = new FileSavePicker();
        savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
        savePicker.FileTypeChoices.Add("png with embedded ISF", new[] { ".png" });

        StorageFile file = await savePicker.PickSaveFileAsync();
        if (null != file)
        {
            try
            {
                using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    // Truncate any existing stream in case the new file
                    // is smaller than the old file.
                    stream.Size = 0;
                    await inkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
                }
                
            }
            catch (Exception ex)
            {
             
            }
        }
    }
    else
    {
     
    }
}

For more detail please refer UWP simple ink code sample scenario 3

Update

I am getting a blank image after trying to save the file.

Above code can only save InkCanvas stroke, I checked your code, I found you have not place any element in the selectionCanvas. so the RenderTargetBitmap of selectionCanvas will black empty. Please try to use img_grid to replace.

Hey sorry but now the InkToolbar is also getting copied on the image along with the ink changes i make :(

It's by-design, RenderTargetBitmap will render all element that was viewed, for your scenario, we suggest you make rectangle to covered InkToolbar or set img_inktoolbar Visibility as Collapsed, before you capture the screen and reset it after finish.

Upvotes: 1

Faraaz bhilwade
Faraaz bhilwade

Reputation: 49

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

Upvotes: 0

Related Questions