c# - 将 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
}
}
}
}
}
解决方案
您可以使用 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");
}
}
推荐阅读
- c# - 清除 CollectionViewSource 的源时,WPF 接口变得无响应
- qgis - 从 QGIS 发布编辑到 Open Street Map 数据库
- swift - Swift init() 可以返回以前创建的对象吗?
- python - 将 3D numpy 转换为数组到 4D 数组而不更改
- javascript - 无法从地理服务器中的 SQL Server 加载图层
- powershell - 可以从 cmd 调用 headless 但不能从 powershell
- azure-storage-emulator - Unable to start Azure Storage Emulator
- php - Laravel paginate() 方法不适用于 distinct() 方法
- ruby-on-rails - Rails: Add form from different model
- vba - VBA循环遍历InputBox中给出的行数