利用SVG中的对称性;误解或错误的渲染器实现



我想在我的SVG设计中利用对称性,但大多数查看器(包括Firefox和Chromium浏览器)都会在数字清楚地告诉我不应该有间隙/线条的地方渲染间隙/线条。使用缩放将这些设计渲染得更大可以减少那些错误渲染的线条的数量和厚度。

我是不是误解了svg格式?实现svg渲染在数学上是不是太耗费资源了?还是没有人关心这样的边缘案例?

一个更好地说明我所说内容的例子:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="98" height="98" fill="red" fill-opacity="0.7">
<g id="2">
<g id="4">
<path id="p" d="m49,35c-3,0 -4.575713,0.605785 -6.9934,1.888339L31.330807,18.397364 37,15C38,7.76 38,6 39,0.7 39.7,0 44,0 49,0Z"/>
<use xlink:href="#p" transform="rotate(-60, 49, 49)"/>
<use xlink:href="#p" transform="translate(98) scale(-1, 1) rotate(60, 49, 49)"/>
</g>
<use xlink:href="#4" transform="translate(98) scale(-1, 1)"/>
</g>
<use xlink:href="#2" transform="rotate(180, 49, 49)"/>
</svg>

我在这里找到:如果两个部分不透明的形状重叠,我可以只显示它们重叠的一个形状吗?使用过滤器可以使重叠的形状在不透明度方面表现得像不一样,但这仍然让我感到困扰,因为需要这样的变通方法。它增加了复杂性、文件大小,并降低了与未实现这些过滤器的查看器的兼容性。

将该变通方法应用于上述示例:

<svg width="98" height="98" fill-opacity=".7" fill="red" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="f">
<feComponentTransfer>
<feFuncA type="table" tableValues="0 .7 .7" />
</feComponentTransfer>
</filter>
<g filter="url(#f)">
<g id="2">
<g id="4">
<path id="p" d="m51 35c-3-0-6 0-10 3l-15-15 11-7c2-8 1-8 2-14 0.7-0.7 7-0.7 12-0.7"/>
<use transform="rotate(-60 49 49)" xlink:href="#p"/>
<use transform="translate(98) scale(-1 1) rotate(60 49 49)" xlink:href="#p"/>
</g>
<use transform="translate(98) scale(-1 1)" xlink:href="#4"/>
</g>
<use transform="rotate(180 49 49)" xlink:href="#2"/>
</g>
</svg>

想想任何类型的渲染引擎都必须如何渲染SVG。它逐步遍历SVG,一次绘制一个元素。

除非该形状是正好符合像素边界的直线或矩形,否则它需要平滑该形状的边缘。它使用一种称为蚂蚁混叠的技术来实现这一点。它绘制半透明像素,以近似于仅部分覆盖像素的边缘。

例如,如果一个形状正好覆盖半个像素,则渲染器将使用50%的alpha将颜色绘制到该像素中。

然后它继续移动并绘制下一个形状。即使这个形状有一个数学上一致的边界,你也可能不会得到一个完美的连接。

原因如下。

想象三个相邻的像素,其中形状的边缘正好穿过中心像素的一半。

+--------+--------+--------+
First shape drawn:   |  100%  |   50%  |    0%  |
+--------+--------+--------+

这里的百分比表示绘制到每个像素中的形状的颜色量。左侧像素为100%。中间像素中的50%颜色(alpha)。并且没有将颜色绘制到右侧像素中。

现在想象一个与第一个形状共享边界的第二个形状。你可能会想象,会发生以下情况。

+--------+--------+--------+
First shape drawn:   |  100%  |   50%  |    0%  |
+--------+--------+--------+
Second shape drawn:  |    0%  |   50%  |  100%  |
+--------+--------+--------+
Resulting image:     |  100%  |  100%  |  100%  |
+--------+--------+--------+

但事实并非如此。第一个形状已经渲染为像素。渲染器对以前绘制的东西的形状没有记忆。它只有以前渲染的像素的颜色。

当它在第一步或第二步绘制中间像素时,它会将50%的新颜色与像素值混合。公式大致如下:

result = 0.5 * old_pixel_colour + 0.5 * new_pixel_colour

例如,让我们以上面的像素百分比为例,想象我们在白色背景上绘制两个红色形状。

绘制第一个形状后,像素应该看起来像这样。

rgb(255, 0, 0)         rgb(255, 128, 128)        rgb(255, 255, 255)
[0 * bg + 1.0 * red]  [0.5 * bg + 0.5 * 50%_red]  [1.0 * bg + zero_red]

其中bg表示像素开始时的白色背景颜色。50%_red表示抗锯齿用于表示半覆盖像素的50%透明红色。

第二次通过后,像素将看起来像这样:

rgb(255, 0, 0)            rgb(255, 192, 192)          rgb(255, 0, 0)
[1.0 * first + no_red]  [0.5 * first + 0.5 * 50%_red]  [0 * first + 1.0 * red]

其中first表示在绘制第一形状之后的像素的颜色。我希望这是有道理的。

或者以颜色百分比(红色)表示。

+--------+--------+--------+
First shape drawn:   |  100%  |   50%  |    0%  |
+--------+--------+--------+
Second shape drawn:  |    0%  |   50%  |  100%  |
+--------+--------+--------+
Resulting image:     |  100%  |   75%  |  100%  |
+--------+--------+--------+

所以我希望你能明白为什么这些边界像素最终会显示出一条微弱的白线。这是因为您正在混合两层抗锯齿像素。它实际上是一条浅红线。

因此,渲染器可以分析形状堆栈的像素覆盖率。但这在数学上非常复杂。这将大大减慢渲染过程。

Paul LeBeau给出了抗锯齿工作原理的一般解释。值得注意的是,您可以在shape-rendering表示属性设置为crispEdges的情况下关闭此算法。渲染器将尝试绘制完全不透明或完全透明的每个像素:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="98" height="98" fill="red" fill-opacity="0.7">
<g id="2" shape-rendering="crispEdges">
<g id="4">
<path id="p" d="m49,35c-3,0 -4.575713,0.605785 -6.9934,1.888339L31.330807,18.397364 37,15C38,7.76 38,6 39,0.7 39.7,0 44,0 49,0Z"/>
<use xlink:href="#p" transform="rotate(-60, 49, 49)"/>
<use xlink:href="#p" transform="translate(98) scale(-1, 1) rotate(60, 49, 49)"/>
</g>
<use xlink:href="#4" transform="translate(98) scale(-1, 1)"/>
</g>
<use xlink:href="#2" transform="rotate(180, 49, 49)"/>
</svg>

这可能会有所帮助,具体取决于使用情况。边缘将出现";粗糙的";,但这可能是可以接受的。

但这并不是问题的结束。在理想的环境中,渲染器将始终决定位于"像素"顶部的像素;接缝";要么属于一边,要么属于另一边。但在离散数字的真实世界中,它可能会决定像素不属于任何一个,并使其透明。

特别是如果对形状应用了一个或多个变换,应用程序可能会决定必须对数字进行多次舍入才能计算最终渲染,舍入误差可能会累积起来。(根据我的经验,Chrome比其他浏览器更容易产生这种效果。)

最后,该规范给渲染器留下了很大的回旋余地;可能";(或不)使用该属性值:

为了实现清晰的边缘,用户代理可能会关闭所有直线和曲线的抗锯齿,或者可能只关闭接近垂直或水平的直线的抗锯齿。此外,用户代理可以调整线条位置和线条宽度,以使边缘与设备像素对齐。

不幸的是,结论是每个渲染器都使用自己的优化策略,您无法保证最终会到达哪里。

最新更新