简体   繁体   English

导出 UWP canvas 到 SVG

[英]Export UWP canvas to SVG

I'm trying to create an SVG file from some shape objects(path geometries, ellipses etc.) drawn on XAML canvas controls (the canvases are rendered on top of each other inside grid controls).我正在尝试从 XAML canvas 控件上绘制的某些形状对象(路径几何、椭圆等)创建 SVG 文件(画布在每个其他内部控件的顶部呈现) It looks like Win2D can provide the classes to generate the SVG file, but I'm struggling to figure out how to populate the CanvasSvgDocument class with the shapes.看起来 Win2D 可以提供来生成 SVG 文件,但我正在努力弄清楚如何用形状填充 CanvasSvgDocument class。

This is the only partial example I've found, but the answer seems to include a conversion to XML strings to load into the CanvasSvgDocument which seems like doing the same task twice (as SVG files are XML). 是我找到的唯一部分示例,但答案似乎包括转换为 XML 字符串以加载到 CanvasSvgDocument 中,这似乎两次执行相同的任务(因为 SVG 文件是 XML)。 Is anybody able to provide an example of how I might do this?有没有人能够提供一个我可以如何做到这一点的例子?

My current best guess of what the resulting code might look like is:我目前对结果代码的最佳猜测是:

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
                }
            }
        }
    }
}

You could use CanvasGeometry.CreateInk convert the ink strokes into geometry and use the relevant methods under the CanvasGeometry namespace to get the path, then write a custom class to read the parse path.您可以使用 CanvasGeometry.CreateInk 将墨迹笔划转换为几何图形,并使用 CanvasGeometry 命名空间下的相关方法获取路径,然后编写自定义 class 来读取解析路径。 Finally, the generated CanvasSvgDocument object is used to save the stream containing the svg content.最后,生成的CanvasSvgDocument object用于保存包含svg内容的stream。

Please refer to the following sample to do these steps.请参考以下示例来执行这些步骤。 ( Note: download Win2D.uwp package) 注:下载Win2D.uwp包)

XAML code: 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>

Code behind:后面的代码:

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

Custom class:定制 class:

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

In the end I was able to use the XmlWriter class to write my own canvas-to-svg converter.最后,我能够使用 XmlWriter class 编写自己的画布到 svg 转换器。 Using the example in the question:使用问题中的示例:

using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Svg;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Xml;
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 XmlWriter Writer { get; }
        public SVGWriter(System.IO.Stream stream)
        {
            Writer = XmlWriter.Create(stream, new XmlWriterSettings()
            {
                Indent = true,
            });
            Writer.WriteStartElement("svg", "http://www.w3.org/2000/svg");
            Write("version", "1.1");
        }

        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)
                {
                    else if (path.Data is EllipseGeometry ellipseGeometry)
                    {
                        Writer.WriteStartElement("ellipse");
                        Write("stroke", ellipseGeometry.Stroke);
                        Write("stroke-width", ellipseGeometry.StrokeThickness);
                        Write("cx", ellipseGeometry.Center.X);
                        Write("cy", ellipseGeometry.Center.Y);
                        Write("rx", ellipseGeometry.RadiusX);
                        Write("ry", ellipseGeometry.RadiusY);
                        Writer.WriteEndElement();
                    }
                }
                else if (element is TextBlock textBlock)
                {
                    Writer.WriteStartElement("text");
                    Write("x", Canvas.GetLeft(textBlock));
                    Write("y", Canvas.GetTop(textBlock) + textBlock.ActualHeight);
                    Write("font-family", textBlock.FontFamily.Source);
                    Write("font-size", $"{textBlock.FontSize}px");
                    Writer.WriteString(textBlock.Text);
                    Writer.WriteEndElement();
                }
            }
        }

        private void Write(string name, string value)
        {
            Writer.WriteAttributeString(name, value);
        }

        private void Write(string name, double value)
        {
            Write(name, ((float)value).ToString());
        }

        public void Dispose()
        {
            Writer.WriteEndElement();
            Writer.Close();
            Writer.Dispose();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM