如何将使用 InkCanvas 在画布上完成的工作保存到 UWP C# 中的图像文件?

Posted

技术标签:

【中文标题】如何将使用 InkCanvas 在画布上完成的工作保存到 UWP C# 中的图像文件?【英文标题】:how to save work done on canvas using InkCanvas to an image file in UWP C#? 【发布时间】:2021-05-26 16:56:54 【问题描述】:

我想将我在画布上完成的工作保存在我的 UWP 应用中。我正在使用 InkCanvas 在画布内的选定图像上绘制线条,并且我想将画布工作保存到新的图像文件中。

我在尝试保存文件后得到一张空白图像。我尝试了两种保存文件的方法。

工作完成:

xaml 代码

<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"/>

代码背后

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

保存文件的方法一

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

结果

想要的结果

注意:箭头是我在图片上画的

我用这种方法得到的结果

test2.bmp(图像由于某种原因被放大)

保存文件的方法二

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

这种方法的结果

由于某种原因,我得到了全黑图像

test2.bmp

我们将不胜感激。对方法 2 的任何帮助都会更好。

【问题讨论】:

【参考方案1】:

如何将使用 InkCanvas 在画布上完成的工作保存到 UWP C# 中的图像文件中?

您无需使用RenderTargetBitmap 将 InkCanvas 保存到图像。 UWP InkCanvas 具有 SaveAsync 方法,可以将StrokeContainer 流直接保存到图像文件。例如。

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
    
     
    

更多细节请参考UWPsimple ink代码示例场景3

更新

我在尝试保存文件后得到一个空白图像。

上面的代码只能保存InkCanvas的笔画,我检查了你的代码,发现你没有在selectionCanvas中放置任何元素。所以selectionCanvasRenderTargetBitmap 将变黑为空。请尝试使用img_grid替换。

抱歉,现在 InkToolbar 也与我所做的墨水更改一起被复制到图像上:(

这是设计使然,RenderTargetBitmap 将渲染所有查看过的元素,对于您的场景,我们建议您制作矩形以覆盖InkToolbar 或将 img_inktoolbar Visibility 设置为 Collapsed,然后再捕获屏幕并在完成后重置它.

【讨论】:

您好,感谢您的宝贵时间。我尝试使用inkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream); 但我得到的只是墨水笔触,图像变得透明,即我得到的只是墨水笔触,没有图像背景【参考方案2】:

我对方法 2 做了一些改变,得到了想要的结果

保存文件的方法二

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

【讨论】:

以上是关于如何将使用 InkCanvas 在画布上完成的工作保存到 UWP C# 中的图像文件?的主要内容,如果未能解决你的问题,请参考以下文章

自定义InkCanvas(MSDN代码示例无法正常工作)

如何在画布内拖动用户控件

如何将 html 画布转换为图像,然后将该画布绘制到画布上

WPF 使用 Microsoft.Toolkit.Wpf.UI.Controls 的 InkCanvas 时加上背景色和按钮方法

如何在 Flutter 的画布上的路径内绘制图案?

WPF 使用 Microsoft.Toolkit.Wpf.UI.Controls 的 InkCanvas 时加上背景色和按钮方法