首页 > 解决方案 > 将 UWP 画布导出为 SVG

问题描述

我正在尝试从在 XAML 画布控件上绘制的一些形状对象(路径几何、椭圆等)创建一个 SVG 文件(画布在网格控件内彼此重叠呈现)。看起来 Win2D 可以提供生成 SVG 文件的,但我正在努力弄清楚如何用形状填充 CanvasSvgDocument 类。

是我找到的唯一部分示例,但答案似乎包括转换为 XML 字符串以加载到 CanvasSvgDocument 中,这似乎两次执行相同的任务(因为 SVG 文件是 XML)。有没有人能够提供一个我可以如何做到这一点的例子?

我目前对结果代码的最佳猜测是:

using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Svg;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;

namespace MyApp
{
    public class ExportSVG
    {
        private CanvasSvgDocument SVG { get; } = new(new CanvasDevice());

        public async Task SaveASync(IRandomAccessStream stream) => await SVG.SaveAsync(stream);

        public void AddCanvases(UIElement element)
        {
            if (element is Grid grid)
            {
                foreach (UIElement child in grid.Children)
                {
                    AddCanvases(child);
                }
            }
            else if (element is Canvas canvas)
            {
                AddCanvas(canvas);
            }
        }

        public void AddCanvas(Canvas canvas)
        {
            foreach (UIElement element in canvas.Children)
            {
                if (element is Path path)
                {
                    if (path.Data is PathGeometry pathGeometry)
                    {
                        foreach (PathFigure pathFigure in pathGeometry.Figures)
                        {
                            // Add path to SVG
                        }
                    }
                    else if (path.Data is EllipseGeometry ellipseGeometry)
                    {
                        // Add ellipse to SVG
                    }
                }
                else if (element is TextBlock textBlock)
                {
                    // add text to SVG
                }
            }
        }
    }
}

标签: c#svguwpwin2d

解决方案


您可以使用 CanvasGeometry.CreateInk 将墨迹笔划转换为几何图形,并使用 CanvasGeometry 命名空间下的相关方法获取路径,然后编写自定义类来读取解析路径。最后,生成的 CanvasSvgDocument 对象用于保存包含 svg 内容的流。

请参考以下示例来执行这些步骤。(注: 下载Win2D.uwp包)

XAML 代码:

<Page
    x:Class="CanvasToSVG.MainPage"
    …
   mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <StackPanel>
            <InkCanvas x:Name="MyInkConntrol" Height="500">
               
            </InkCanvas>
            <InkToolbar Grid.Row="1" TargetInkCanvas="{x:Bind MyInkConntrol}" HorizontalAlignment="Left">
                <InkToolbarCustomToolButton Click="save">
                    <SymbolIcon Symbol="Save" />
                </InkToolbarCustomToolButton>
            </InkToolbar>
            <Line Stroke="Black"/>
            <Image Name="ImageControl"></Image>
        </StackPanel>
    </Grid>
</Page>

后面的代码:

 public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            MyInkConntrol.InkPresenter.InputDeviceTypes= CoreInputDeviceTypes.Mouse |CoreInputDeviceTypes.Pen |
                                                       CoreInputDeviceTypes.Touch;

            MyInkConntrol.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
            MyInkConntrol.InkPresenter.StrokesErased += InkPresenter_StrokesErased;
        }

        private async void InkPresenter_StrokesErased(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesErasedEventArgs args)
        {
            await RenderSvg();
        }

        private async void InkPresenter_StrokesCollected(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args)
        {
            await RenderSvg();
        }
        public async Task RenderSvg()
        {
            using (var stream=new InMemoryRandomAccessStream())
            {
                await RenderSvg(stream);
                var image= new SvgImageSource();
                await image.SetSourceAsync(stream);
                ImageControl.Source = image;
            }
        }
       
        public async Task RenderSvg(IRandomAccessStream randomAccessStream)
        {
            var sharedDevice = CanvasDevice.GetSharedDevice();
            using (var offscreen = new CanvasRenderTarget(sharedDevice, (float)MyInkConntrol.RenderSize.Width, (float)MyInkConntrol.RenderSize.Height, 96))
            {
                using (var session = offscreen.CreateDrawingSession())
                {
                    var svgDocument = new CanvasSvgDocument(sharedDevice);

                    svgDocument.Root.SetStringAttribute("viewBox", $"0 0 {MyInkConntrol.RenderSize.Width} {MyInkConntrol.RenderSize.Height}");

                    foreach (var stroke in MyInkConntrol.InkPresenter.StrokeContainer.GetStrokes())
                    {
                        var canvasGeometry = CanvasGeometry.CreateInk(session, new[] { stroke }).Outline();

                        var pathReceiver = new CanvasGeometryToSvgPathReader();
                        canvasGeometry.SendPathTo(pathReceiver);
                        var element = svgDocument.Root.CreateAndAppendNamedChildElement("path");
                        element.SetStringAttribute("d", pathReceiver.Path);
                        var color = stroke.DrawingAttributes.Color;
                        element.SetColorAttribute("fill", color);

                    }

                   await svgDocument.SaveAsync(randomAccessStream);
                }

            }
        }

        private async void save(object sender, RoutedEventArgs e)
        {
            FileSavePicker savePicker = new FileSavePicker();
            savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
            savePicker.FileTypeChoices.Add("svg file", new List<string>() { ".svg" });
            savePicker.SuggestedFileName = "NewSvgfile1";
            var file = await savePicker.PickSaveFileAsync();
            if (file != null)
            {
                using (var writeStream = (await file.OpenStreamForWriteAsync()).AsRandomAccessStream())
                {
                    await RenderSvg(writeStream);
                    await writeStream.FlushAsync();
                }
            }
        }
    }

自定义类:

public class CanvasGeometryToSvgPathReader: ICanvasPathReceiver
{
    private readonly Vector2 _ratio;
    private List<string> Parts { get; }
    public string Path => string.Join(" ", Parts);
    public CanvasGeometryToSvgPathReader() : this(Vector2.One)
    { }

    public CanvasGeometryToSvgPathReader(Vector2 ratio)
    {
        _ratio = ratio;
        Parts = new List<string>();
    }

    public void BeginFigure(Vector2 startPoint, CanvasFigureFill figureFill)
    {
        Parts.Add($"M{startPoint.X / _ratio.X} {startPoint.Y / _ratio.Y}");
    }

    public void AddArc(Vector2 endPoint, float radiusX, float radiusY, float rotationAngle, CanvasSweepDirection sweepDirection, CanvasArcSize arcSize)
    {
      
    }

    public void AddCubicBezier(Vector2 controlPoint1, Vector2 controlPoint2, Vector2 endPoint)
    {
        Parts.Add($"C{controlPoint1.X / _ratio.X},{controlPoint1.Y / _ratio.Y} {controlPoint2.X / _ratio.X},{controlPoint2.Y / _ratio.Y} {endPoint.X / _ratio.X},{endPoint.Y / _ratio.Y}");
    }

    public void AddLine(Vector2 endPoint)
    {
        Parts.Add($"L {endPoint.X / _ratio.X} {endPoint.Y / _ratio.Y}");
    }

    public void AddQuadraticBezier(Vector2 controlPoint, Vector2 endPoint)
    {
        //
    }

    public void SetFilledRegionDetermination(CanvasFilledRegionDetermination filledRegionDetermination)
    {
       //
    }

    public void SetSegmentOptions(CanvasFigureSegmentOptions figureSegmentOptions)
    {
        //
    }

    public void EndFigure(CanvasFigureLoop figureLoop)
    {
        Parts.Add("Z");
    }
}

推荐阅读