将 XAML 矢量图形转换为 PNG






4.87/5 (11投票s)
介绍如何使用矢量图形作为位图图形的源。
- C# Visual Studio 2010 源代码: 下载 XAMLtoPNG.zip
- 示例数据
介绍
我发现可扩展应用程序标记语言(XAML)中的矢量图形非常有用,可以基于矢量图形制作图标,因为这些矢量图形包含漂亮的渐变和其他可缩放的元素,在各种分辨率下看起来都很美观。
显然,矢量图形不能取代图形设计师,制作精美的 16x16 像素图标可能仍需要手动操作。但可缩放图形可以在此尺寸之上(例如 32x32 或 256x256 像素)产生令人印象深刻的效果。
矢量图形格式有很多种。但如今最流行的两种可用于图标的格式是:
- 可扩展应用程序标记语言(XAML)
- 可缩放矢量图形(SVG)
请查看“参考资料”部分,了解更多支持这些技术的开源工具的详细信息。
我希望能找到一种方法,用基于 XAML 的图标来构建一个图标集。因此,我下载了一个示例项目,并为其封装了一个命令行工具 [2]。结果并不是什么特别花哨或新颖的东西,只是一个可以用来将 XAML 转换为任意 PNG 尺寸位图文件的命令行工具。这个工具,除其他功能外,可以作为构建过程的一部分,并根据语言/文化偏好生成本地化的图像。
为了验证这个概念,我添加了两个示例数据项目(其中一个源自 SVG),并在此过程中发现了一个很酷的 SVG 编辑器,叫做 InkScape [3]。
你可能会问:“既然 WPF 原生支持 XAML,为什么还要进行这种转换呢?”。答案很简单,一旦你尝试将大量基于 XAML 的图像(比如 40 个)放入它们各自的容器中(比如我们在 40 个工具栏按钮中使用 40 个基于 XAML 的图像),你就会发现渲染时间会大大增加,你的应用程序响应时间也会显著变慢。这是因为矢量图形在渲染时比基于像素的图形消耗更多的资源。我们当然可以在 WPF 应用程序中使用矢量图形,但应限制这种直接使用。因此,虽然我们可以安全地将矢量图形作为我们图像(图标)的基础,但在我们的实际运行时代码中使用矢量图形还有很长的路要走。
如何使用示例数据将 XAML 转换为 PNG
下载 XAMLtoPNG.zip 文件并将其保存在一个专用文件夹中。在该专用文件夹中创建一个名为 Data 的新子文件夹,并下载本文附带的示例数据 zip 文件:
- OpenIconLibrary.zip
- TangoProject.zip
到那个文件夹里。现在,将每个 zip 文件解压缩到各自的子目录中,以便内容存储在独立的子文件夹中。你的文件夹结构应该看起来像这样:
- XAMLtoPNG
+- XAMLtoPNG
+- Properties
- Data
+- TangoProject
+- PNG
+- XAML
+- OpenIconLibrary
+- PNG
+- XAML
浏览到 XAMLtoPNG Visual Studio 2010 项目文件,并使用“全部重新生成”(Rebuild All)编译项目。确保至少有一个 Debug 版本的可执行文件,因为如果你使用“Windows 资源管理器”(Windows Explorer)浏览到 OpenIconLibrary 或 TangoProject 目录,就可以使用这个版本。查看并执行每个目录中的 Convert.bat 文件。
这应该是测试从 XAML 到 PNG 转换所需的全部步骤。检查 PNG 子文件夹以查看结果。
使用代码
XMLtoPNG 工具的代码相当少,Laurent Bugnion [2] 已经解释了为什么他在源代码中使用了 STA(单线程单元)。我当然可以去掉这个,并且可以很容易地通过在 Program
类中使用适当的 [STAThread]
属性使整个命令行工具成为 STA 线程。但我认为保持原样并添加一些功能会更有趣。因此,我在 Params
类中添加了一个命令行解析器,使其能够在输出文件中缩放和调整 PNG 图像的大小。
您可以使用 -XDPI
和 -YDPI
开关来放大和缩小图像。这些值的默认大小是 96 DPI,对于我试过的许多图像来说都运行良好。
你也可以使用 -XSize
和 -YSize
参数来定义转换过程生成的 PNG 文件的像素大小。
-ScaleMode
可以是 Target
或 Original
。它决定了图形应该基于预期的输出像素大小进行缩放,还是基于输入仅使用 DPI 作为缩放因子进行缩放。根据我的经验,我只使用默认的 -ScaleMode Target
,效果相当不错。但我将其开放给每个人选择他们喜欢的方式。
最后,还有 -Input
和 -Output
参数,它们决定了 XAML 输入和 PNG 输出的位置。-Input
参数可以指向单个 XAML 文件或包含 XAML 文件的目录。-Output
参数也是如此(更多细节请参考示例 Convert.bat 文件)。
这样,我们既可以将一个完整目录的 XAML 文件转换为 PNG 文件,也可以转换单个文件。如果未提供输出参数,输出的 PNG 文件会与 XAML 文件保存在同一位置。
最少需要的参数是指向 XAML 输入文件的 -Input
参数。
重要提示
请仅使用 XAMLtoPNG 命令行工具在存储临时文件的位置生成文件,因为该工具会不经任何询问直接覆盖现有的 PNG 文件。该程序使用起来是安全的,但我不想让任何人丢失他们宝贵的 PNG 文件。所以,只需将您用该工具生成的图标集保存在一个单独的文件夹中,一切都会没问题。
代码工作原理
那么,转换在底层是如何工作的呢?首先,在 Program 类的主入口点,要么会根据命令行参数构建一个 Params
对象,要么会指示出现错误。
string strError;
Params progArgs;
int iRet = Params.ParseCmdLine(args, out progArgs, out strError);
if (iRet != 0)
{
Console.WriteLine(strError);
return iRet;
}
if (progArgs.InputFiles.Count == 0)
{
Console.WriteLine("No XAML files found for conversion.");
return 0;
}
progArgs
对象包含一个文件输入属性 progArgs.InputFiles
和一个文件输出属性 progArgs.OutputFiles
,它们代表了文件的(字符串)路径引用的集合。程序会遍历这个集合,在每个循环中转换每个输入文件。
for (int i = 0; i < progArgs.InputFiles.Count; i++)
{
string inputFile = progArgs.InputFiles[i];
string outputFile = progArgs.OutputFiles[i];
try
{
using (Stream stream = File.OpenRead(inputFile))
{
using (Stream streamOut = File.OpenWrite(outputFile))
{
XamlToPngConverter converter = new XamlToPngConverter();
converter.Convert(stream, progArgs.XSize, progArgs.YSize,
progArgs.XDPI, progArgs.YDPI, progArgs.ThisScale, streamOut);
streamOut.Close();
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error converting:");
Console.WriteLine(inputFile);
Console.WriteLine("into:");
Console.WriteLine(outputFile + "\n");
Console.WriteLine(ex.ToString());
}
}
在上述主循环中的 progArgs.XSize
、progArgs.YSize
、progArgs.XDPI
、progArgs.YDPI
、progArgs.ThisScale
参数,要么是 Params
类的构造函数中设置的默认值,要么是如上所述通过命令行提供的值。
现在我们来看 XamlToPngConverter
类的 Convert
方法,可以看到创建了一个名为 pngCreationThread
的新 Thread
对象。
Thread pngCreationThread = new Thread((ThreadStart) delegate()
这个 Thread
以单线程单元(STA)模式执行,因为 WPF 在执行渲染时需要这样做。Start
语句在后台启动线程,而 Join
语句会暂停调用线程,直到后台线程完成。
pngCreationThread.IsBackground = true; pngCreationThread.SetApartmentState(ApartmentState.STA); pngCreationThread.Start(); pngCreationThread.Join();
现在,让我们看看后台线程做了什么。读取 XAML 只需一行代码:
element = XamlReader.Load(xamlInput) as FrameworkElement;
这里的 FrameworkElement
类是 System.Windows.FrameworkElement
,它是 WPF 中许多控件(Page、Image、Panel)的基类:http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.aspx#inheritanceContinued
这就是为什么我们要求输入的 XAML 必须包含这些控件类之一(或其继承类)作为 XAML 的根标签。在上面的示例数据项目中,我使用了 Canvas
类作为根标签。这样做是可行的,因为 Canvas
类继承自 Panel
类。
转换本身由以下语句实现:
Size renderingSize = new Size(width, height);
element.Measure(renderingSize);
Rect renderingRectangle = new Rect(renderingSize);
element.Arrange(renderingRectangle);
BitmapSource xamlBitmap = RenderToBitmap(element, width, height, dpiX, dpiY, thisScale);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(xamlBitmap));
enc.Save(pngOutput);
输入元素的大小(从而也是输出的大小)是在调用 FrameworkElement
的 Arrange
方法时设置的。RenderToBitmap
方法返回一个 BitmapSource
对象,该对象被添加为一帧到 PngBitmapEncoder
对象中。然后,PngBitmapEncoder
对象被保存为 PNG 输出文件。
RenderToBitmap
方法本质上是业务的核心,它负责渲染输出位图。其大小可以基于输入的原始尺寸(可能比输入大或小),也可以基于输出(这类似于以 96 DPI 进行垂直和水平拉伸)。
switch (thisScale)
{
case ScaleTO.OriginalSize:
bounds = VisualTreeHelper.GetDescendantBounds(target);
break;
case ScaleTO.TargetSize:
bounds = new Rect(0, 0, width,height);
break;
default:
throw new ArgumentException(string.Format("The scaling mode: {0} is not supported.", thisScale.ToString()));
}
XAML 是如何工作的
前面的部分已经阐明了 C# 代码(大部分由 Laurent Bugnion 编写)[2] 的工作原理。在这一部分,我想就如何使用 XAML 设计出外观得体且不费吹灰之力的图标提供一些提示。
请下载并解压 XAMLToPNG Sample.zip
文件以跟随这个小教程。这个 zip 文件中包含的 XAML 文件是从 XAMLALOT [4] 下载的(源地址见 XAML 中的 URL 注释)。我移除了 XAMLALOT 中每个外部 canvas 标签的 width 和 height 属性,因为我想随心所欲地缩放图像。我使用 KAXAML [1] 来编辑我下载的 XAML 代码,但你也可以使用任何 XML 编辑器。
让我们来看看 01_mail forward
子目录开始。我们打开 mail-forward.Canvas.XAML
文件。

这个文件中的符号由一个邮件信封和一个箭头组成。现在,这个 XAML 文件中的每个对象都存储在 canvas 或 path 标签中。因此,通过简单地移除标签,我们可以使用 KAXAML 从原始文件中移除元素。
例如,我们可以移除 [148 - 193] 行之间的 canvas 标签,最终得到一个没有箭头的漂亮邮件符号(我忘了在这里移除箭头的阴影——当你操作时别忘了)。

这就是我在 ForwardArrow.Canvas.xaml
文件中提取箭头的方法。

...而且,我还在 ForwardArrow.Canvas.xaml
文件中使用了一个渲染转换语句,让那个箭头指向上方,这可以用于表示上传或启动过程的符号...

00_letter.Canvas.xaml
文件中的信封符号也是用类似的方式创建的。我从上面讨论的 mail-forward.Canvas.XAML
文件中提取了信封符号。
01_protect.Canvas.xaml
文件中的保护符号是从 network-wireless-encrypted.Canvas.xaml
文件中提取的。
![]() |
最后,将 00_letter.Canvas.xaml 和 01_protect.Canvas.xaml 两个文件的内容合并到一个最终文件 03_letter-protect.Canvas.xaml 中。在这个文件中,我还将保护符号染成了绿色(表示类似受保护的电子邮件——不管那可能是什么)。最终图像的绿色渐变是从 00_Go up 子目录中的 XAML 文件“偷”来的。从同一图标集中的其他文件复制颜色是我的一个好习惯,因为这样更有可能使结果与图标集的其余部分保持令人愉悦的一致性。 |
更多资源
互联网上丰富的 SVG 和 XAML 资源令人惊叹。有许多来自各种 Linux 发行版 [4] 或其他项目的精美图标集,使得即使是业余时间开发的自制软件也能普遍使用漂亮的图标。
任何这些来源都可以作为起点。可以使用 [1] 或 [3] 中列出的矢量编辑器来编辑 XAML 或 SVG 图像。
将 SVG 转换为 XAML
SVG 图像可以使用 'SharpVectors - SVG# Reloaded' [3] 转换为 XAML(详情请见 OpenIconLibrary.zip 中的 Readme.txt 文件)。要获得基于 canvas 的 XAML,需要进行一些搜索和替换,例如使用 Notepad++。
但另一个无需额外麻烦就能工作的工具是 XAMLTune [3]。我下载了它,并成功地将原始的 tango 项目 SVG 文件转换成了可以直接加载到 KAXAML 中的 XAML。这里有一个使用 XAMLTune 将 SVG 转换为 XAML 的简短指南:
- 下载并解压 zip 文件(我用的是 XamlTune v0.3.zip)。
请注意该 zip 文件中的 'svg2xaml' 和 'XamlTune.App' 子文件夹。前者中的可执行文件用于将 SVG 格式的文件转换为 XAML,而后者可用于美化 XAML。 - 现在下载一个 SVG 示例项目,例如,从这里下载 Tango 项目:
http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz - 从 tango 项目中提取文件,并使用类似这样的命令行进行转换(你可能需要调整路径):
svg2xaml\svg2xaml.exe /r .\tango-icon-theme-0.8.90\scalable
(scalable 是 tango 项目中包含 SVG 源文件的子文件夹) 我成功地将 Tango 项目中除了一个之外的所有 SVG 文件都转换成了 PNG 和 XAML 文件。这使我能够在 KAXAML 编辑器中打开 XAML,无需任何进一步的编辑。在 KAXAML 中有一个 XAMLScrubber 功能,可以用来将 XAML 重新格式化为易于阅读的格式。
我没能让 XAMLTune.exe 运行起来——它在我这里崩溃了好几次——但转换工具 SVG2XAML.exe 似乎运行稳定,并且输出的 XAML 非常有用。
你甚至可以使用 Visual Studio 2010 Express 设计器来编辑 XAML 图像,而且我想说,如果你需要花钱,市面上有一系列可观的商业产品(如 Expression Blend、Photoshop 等),这也不是什么新鲜事。
深入研究一下,让我知道你的使用体验如何。我发现编辑矢量图并基于它们设计图标非常愉快,无论其来源是 SVG 还是 XAML,对我来说都无所谓。
关注点
一个 XAML 到 PNG 的转换工具,比如本文中发布的这个,可能会很有趣,因为它可能会让其他人更容易地使用矢量图形作为他们图标艺术的基础,而不是仅仅使用位图图像。更重要的是,如果需要的话,这个转换过程可以成为构建过程的一部分。而这种能力反过来可能会在未来开启全新的图形设计方式。
参考文献
- [1]
KAXAML http://kaxaml.codeplex.com
XDraw http://xdraw.codeplex.com/
- [2]
使用服务器端 WPF 将 XAML 转换和自定义为 PNG
作者:Laurent Bugnion http://blog.galasoft.ch/archive/2008/10/10/converting-and-customizing-xaml-to-png-with-server-side-wpf.aspx
- [3]
SVG 资源 InkScape - 自由绘画
http://inkscape.org/
XAML 和 WPF 中的图形
https://codeproject.org.cn/Articles/22716/Graphic-in-XAML-and-WPF
SVG 图像控件
https://codeproject.org.cn/Articles/92434/SVGImage-Control
XAMLTune - 一个 svg 到 xaml 的转换库和 XAML 格式化程序,可清理和优化 xaml
http://xamltune.codeplex.com/
SharpVectors - SVG# Reloaded
http://sharpvectors.codeplex.com/
C# SVG 渲染引擎
http://svg.codeplex.com/ - [4] Open Icon Library 标准包
http://openiconlibrary.sourceforge.net
Tango 图标
http://commons.wikimedia.org/wiki/Tango_icons
Markus Egger 的 XAMLALOT
http://www.xamalot.com/ - [5] 一个在 Visual Studio 中将 XAML 转换为图像的工具。
http://xamlimageconverter.codeplex.com/
历史
- 2012年7月16日 - 发布文章初版。
- 2012年7月17日 - 清理了代码中的小问题并扩展了文章内容
- 2012年7月30日 - 增加了关于将 SVG 转换为 XAML 的小节