跳转到主要内容

SVG

从4.21版本开始,TouchGFX支持使用SVG图像。 SVG图像可用于创建带有矢量图形和传统位图图形的用户界面。

SVG图像通过SVGImage Widget包含在用户界面中。 该控件在TouchGFX设计器中可用,可以作为任意其他控件添加到用户界面。

SVG images are only supported on 16bpp, 24bpp, or 32bpp frame buffers.

什么是SVG

可缩放矢量图形(Scalable Vector Graphics,SVG)是一种基于XML的文件格式,通过使用一小组图形基元来定义2D图像。 部分基元为矩形、圆形和曲线。 基元可用颜色或渐变填充。 也可用特定的宽度绘制轮廓(描边)。

SVG图形的第一大特点是可扩展。 这意味着指定的图形不仅可以在单个分辨率下使用,而且能在不降低质量的情况下放大或缩小。 与位图相比,这是一个很大的优势,位图在放大时会引入伪影,而在缩小时会丢失细节。

SVG图形的第二个重要特征是基于矢量。 这意味着图像是由一组几何图形(如直线和圆)组成的,这些图形组合在一起构成了图画。 这与位图图像形成对比,位图图像基本上定义了图像中所有像素的颜色值。 矢量定义的优点是,与位图相比,大多数情况下的图像都非常小,而且更加灵活。 例如,显示几个黄色椭圆的图像可以很容易地改变为由绿色椭圆组成的图像。

TouchGFX支持SVG Tiny 1.2的子集。 在硬件、运行时环境和TouchGFX性能特性的限制下,不可能支持整个规范。

SVG图像可以使用许多成像工具创建,也可以手动编写。 如果您想学习如何编写自己的SVG,请点击这里查看介绍。

SVG与TouchGFX一起使用

SVG图像的使用方式与PNG图像相同。 准备在项目中使用的SVG文件必须放在assets/images文件夹中。 TouchGFX Image Converter和TouchGFX 设计器读取该文件夹中的图像。

SVG图像被转换为CPP代码并链接到应用程序中。 此CPP代码保存在文件generated/images/src/SVGDatabase.cpp

SVG图像与SVGImage控件一起使用。 无论是在代码中还是在TouchGFX Designer中。 该控件均可实现SVG图像的缩放、平移和旋转。

SVGImage控件使用另一个组件VectorRenderer来执行渲染。

使用SVG图像时的软件组件

示例

作为示例,我们将使用此SVG:

<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<circle cx="50" cy="50" r="40" fill="green" />
<polygon points="50,10 78,78 22,78" fill="gold" />
</svg>

SVG由半径为40的深绿色圆圈和黄色(金色)三角形组成。

正如我们所看到的,SVG图像是简单的文本文件,可以用普通的文本编辑器进行修改。 SVG文件可以使用多种标准工具打开。 Chrome浏览器就是SVG检视器的一个例子。

此SVG的渲染如下所示:

箭头SVG

为了在TouchGFX中使用此SVG,我们将文件(arrow.svg)放在项目的assets/images目录中。 这与TouchGFX 设计器使用的PNG文件和图像是同一个目录:

SVG文件放在assets/images文件夹中

在TouchGFX 设计器中,我们可以插入一个SVGImage控件:

插入SVGImage控件

利用右侧SVGImage的属性,我们可以更改控件的大小和比例:

显示SVG箭头

注意“Fit Image To Size(调整图像大小)”复选框。 如果选择,SVG将被缩放以适合控件的大小(旋转将被重置)。 这是一个助手函数,允许您通过拖动Widget(控件)的角来调整SVG的大小,直到大小符合您的需要。

显示SVG箭头三次

要进行上述旋转,必须设置旋转中心。 默认值为x=0,y=0,即Widget(控件)的左上角。 如果我们使用此旋转中心并旋转SVG,它将按时钟方向旋转到SVGImage Widget的左侧,如下图所示:

显示围绕0,0旋转45度的SVG箭头

SVG中SVGImage之外的部分被剪裁(未显示),如上图所示。 灰色矩形表示SVGImage。 不会在灰色区域以外绘制任何内容。 要在SVGImage中旋转SVG,我们必须将旋转中心设置为控件的中心(50,50)。

关于转换详细信息请参阅后面的章节。

目标配置

在目标上使用SVG图像时,需要启用CubeMX中的Vector Rendering(矢量渲染)。 在项目中插入所需的代码,创建SVGImage所需的Vector Renderer(矢量渲染器)。

Note
TouchGFX 设计器(STM32G0和类似平台除外)中提供的所有TouchGFX Boards Specification(板规格)中都启用了Vector Rendering(矢量渲染)。

如果未启用Vector Rendering(矢量渲染),则在构建项目时会出现链接器错误(未定义对`touchgfx::VectorRenderer::getInstance()的引用):

未启用Vector Rendering(矢量渲染)时的链接器错误

通过更改CubeMX中的配置可以解决此问题。 参见Generator用户指南

SVG图像作为C++代码自动包含在目标项目中,因此不需要进一步的操作。

由于SVG图像的渲染时间可能比位图长得多,因此我们建议使用双缓冲(或带有嵌入式GRAM显示的单缓冲)。 可以使用单帧缓存,但应对渲染时间和显示撕裂进行监控。 点击这里了解使用一帧缓存的详细信息。

画布缓冲区

在大多数平台(U5X9除外)上,SVG使用Canvas Widget Renderer进行渲染。 此软件组件在渲染期间会用到缓冲区。 缓冲区的大小会影响性能。 如可能,建议增加缓冲区的大小(通常20-30Kb足矣)。 缓冲区可在TouchGFX Designer中轻松变更:

变更Canvas Buffer(画布缓冲区)大小

记得变更所有相关屏幕中的数值。

目标性能和闪存使用情况

生成的箭头SVG数据大小低于350字节。 与位图相比,这是使用SVG的一个好处。 当位图尺寸为100x100时,箭头尺寸为30.000字节,24位色。 因此,SVG的大小接近位图大小的1%。

图像类型闪存使用情况
位图30.000 字节
SVG350 字节

SVG图像的使用在应用程序中增加了5-10K的额外代码。

使用SVG的缺点是绘制SVG图像的性能在大多数情况下比位图差。 SVG的渲染取决于SVG中元素的数量,并且无上限。 位图的渲染取决于分辨率和alpha混合,并且有上限。
其次,SVG图像大量使用软件渲染,因此CPU负载很高。 另一方面,位图可以由DMA2D加速器专门渲染,其CPU负载较低。

在STM32F746(外部RAM中的帧缓存)上,SVG箭头的渲染时间为1.32ms,这一点非常有用。 H7系列高性能的MCU将提供更短的渲染时间。 当SVG放大(渲染更多像素)时,渲染时间会增加,而旋转不会增加很多。 图片显示STM32F746 Discovery套件上的箭头:

在F746 Discovery套件上显示SVG箭头

最左边的图片未显示SVG,只显示绿色的方框背景。 中间显示比例为1的箭头。 最右边显示比例为2的箭头。 完整帧和仅SVG的渲染时间如下所示:

屏幕渲染时间仅SVG
2.96 msN/A
4.28 ms1.32 ms
6.37 ms3.41 ms
旋转6.53 ms3.57 ms

最后一行显示了箭头旋转77度时的渲染时间。 我们看到旋转对渲染时间的影响并不明显。

图形质量

下图显示了STM32F746 Discovery套件上运行的项目:

显示F746 Discovery套件上的SVG箭头

此项目中,我们在多个SVGImage控件中使用了相同的SVG。

在照片的右侧,我们插入了来自帧缓存的缩放细节(三个箭头)。 这表明SVG图形中的线均为抗锯齿线,即使在旋转和缩放时也是如此。 这是使用SVG图像的主要原因:在旋转和缩放过程中保持图像质量。

支持的SVG元素

TouchGFX未实现整个SVG Tiny 1.2标准。 如果您使用任何不支持的元素或属性,TouchGFX中读取.svg文件的图像转换器将向您发出警告。 重要的缺失部分径向渐变、文本和动画。

TouchGFX支持以下SVG元素。 示例显示了元素的支持属性:

  • Rectangle <rect x="10" y="20" width="100" height="200" rx="5" ry="40" />

  • Circle <circle cx="100" cy="200" r="20" />

  • Ellipse <ellipse cx="100" cy="200" rx="20" ry="30" />

  • Line <line x1="10" y1="10" x2="300" y2="150 />

  • Path <path d="M100 100L75 200h50z" />

  • Polygon <polygon points="10,10 30,30 10,90" />

  • Polyline <polyline points="10,10 30,30 10,90" />

  • LinearGradient <lineargradient id="grad1" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="100%" />

同样支持此类结构元素

  • Group <g />
  • Defs <defs />

TouchGFX支持所有形状转换:矩阵、平移、缩放、旋转、倾斜X、倾斜Y。

线性梯度必须在 <defs>元素内定义。

对于<path>path<path>元素,TouchGFX支持所有路径命令:M, m, L, l, H, h, V, v, C, c, S, s, Q, q, T, t, A, a.

支持填充和描边。

不支持的SVG元素

TouchGFX不支持以下元素:

  • Animations <animate>, <animateColor>, <animateMotion>, <animateTransform>, <discard>, <handler>, <listener>, <mpath>, <script>, <set>, <switch>
  • Audio <audio />
  • ClipPath <clipPath id=.... />
  • Desc <desc />
  • Filter <filter>
  • Foreign Object <foreignObject>
  • Gradient transform <linearGradient gradientTransform="..." />, <prefetch>
  • Radial gradient <radialGradient ... />
  • Solid color <solidColor>
  • Stroke dasharray property <path stroke-dasharray="10,10" ... />
  • Text and fonts <font>, <glyph>, <text>, <hkern>, <missing-glyph>, <tbreak>, <textArea>, <tspan>
  • Use <use>
  • Image <image>
  • Video <video>

元数据和标题元素被忽略。

ViewBox

TouchGFX不支持经常在SVG图像中使用的viewBox属性。 viewBox属性用于定义SVG绘图中使用的坐标系(用户空间)到绘制SVG视图端口的转换。

下面为一个示例:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
...
</svg>

这意味着该SVG在(0,0)至(200,200)的矩形中绘制的任何图形都应拉伸(或收缩)以贴合视图(SVGImage控件)。

在TouchGFX中,viewBox属性被忽略。 这意味着图形无缩放。 在许多SVG图像中,这不是问题,因为图形以正确的比例绘制。 这种情况下,则只需保留viewBox属性或将其删除即可。
图形也并非根据viewBox剪裁,而是根据SVGImage控件剪裁。

如果需要viewBox缩放特定图形,则可通过插入转换(例如在额外<g>元素上)或者通过TouchGFX缩放图形在SVG文件中应用缩放。

如下所示,使用viewBox(不起作用),将一条直线从(0,0)-(1000,1000)缩小到(0,0)-(100,100):

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 1000 1000">
<path d="M10 10L990,990" stroke="blue" stroke-width="10" stroke-linecap="round"/>
</svg>

(预期)结果是直线从(0,0)-(200,200)。 但由于TouchGFX忽略了ViewBox,我们得到了1000x1000像素的SVG图像。 转换的一种方法是修改SVG文件以包含缩放转换:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<g transform="scale(0.2)">
<path d="M10 10L990,990" stroke="blue" stroke-width="10" stroke-linecap="round"/>
</g>
</svg>

此转换将所有内容缩小5倍。

也可以通过SVGImage控件应用缩放来获得此效果:

在TouchGFX 设计器中缩小SVG

如果您在应用程序中进行缩放,最好不要修改.svg文件。

Linear Gradient必须使用ObjectBoundingBox

SVG中的Linear Gradient可以使用两个不同的坐标系指定。 “userSpaceOnUse”或“objectBoundingBox”。 TouchGFX仅支持“objectBoundingBox”。 我们将在此简要讨论如何使用“objectBoundingBox”,以便您可以将任何使用“userSpaceOnUse”的SVG更改为“objectBouindingBox”。 我们从一个示例开始:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<defs>
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="blue" />
<stop offset="50%" stop-color="green" />
<stop offset="100%" stop-color="red" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="40" fill="url(#gradient1)" />
<rect x="130" y="10" width="40" height="80" fill="url(#gradient1)" />
</svg>

定义一个渐变,从开始的蓝色过渡到中间的绿色,最后过渡到红色。 渐变用于填充圆形和矩形:

左上角为蓝色,右下角为红色的线性渐变

梯度的起点定为(x1,y1)=(0%,0%)。 这是指使用此渐变绘制的任何形状(例如圆形或矩形)的边界框左上角。 类似地,终点是边界框右下角。

由于圆形和矩形这两个形状具有不同的宽度(80像素和40像素),因此当应用于形状时渐变的角度也不同。 如果不希望出现这种行为,可以定义多个渐变。 每个图形一个。

SVG中的另一种可能性是使用“userSpaceOnUse”坐标系:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<defs>
<linearGradient id="gradient1" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="blue" />
<stop offset="50%" stop-color="green" />
<stop offset="100%" stop-color="red" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="40" fill="url(#gradient1)" />
<rect x="130" y="10" width="40" height="80" fill="url(#gradient1)" />
</svg>

这将导致不同的渲染(TouchGFX不支持):

使用userSpaceOnUse的线性渐变

看起来不同是因为对于两个形状而言,渐变的起点是整个图像的左上角(覆盖两个形状)。

如果您有一个使用userSpaceOnUse的SVG,可以通过使用渐变计算相对于形状的渐变起点,将其转换为objectBoundingBox。 以下示例中,使用带有像素坐标的userSpaceOnUse对圆进行渐变着色:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<defs>
<linearGradient id="gradient1" gradientUnits="userSpaceOnUse" x1="10" y1="10" x2="90" y2="90">
<stop offset="0%" stop-color="blue" />
<stop offset="50%" stop-color="green" />
<stop offset="100%" stop-color="red" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="40" fill="url(#gradient1)" />
</svg>

我们可以通过计算圆的边界框来转换渐变以使用objectBoundingBox。 该值为(10,10)到(90,90),与渐变相匹配。 因此,我们可以通过用“0%”替换“10”,用“100%”替换“90”,将坐标转换为相对形状坐标。

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<defs>
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="blue" />
<stop offset="50%" stop-color="green" />
<stop offset="100%" stop-color="red" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="40" fill="url(#gradient1)" />
</svg>

记住“%”。 y1=“10”表示y1=“1000%”。

Image Converter警告

如果使用不支持的元素或属性,TouchGFX Image Converter将打印警告或错误。 例如,我们可以放置箭头文本:

<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<circle cx="50" cy="50" r="40" fill="green" />
<polygon points="50,10 78,78 22,78" fill="gold" />
<text x="38" y="78">ABC</text>
</svg>

TouchGFX 设计器在.svg文件上运行Image Converte,并显示错误:

使用不支持的SVG<text>元素时出错

错误输出显示哪个SVG文件包含错误,在哪一行中发现问题,以及使用了哪些不支持的元素。

如上所述,对目标项目的矢量图形支持是在TouchGFX Generator中配置的。 可参见生成器用户指南获取说明。

SVGImage控件在TouchGFX Designer中可用。 此处详细介绍了如何在TouchGFX设计器中使用SVGImage控件。