为什么我的 SVG feTurbulence 输出中有细黑线?

Why are there thin, dark lines in my SVG feTurbulence output?

在试验 feTurbulence 滤镜原语时,我在意想不到的地方出现了细细的暗线。它们在 numOctaves="1" 时最为明显。他们为什么在那里?

假设我从 https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement 中的参考代码开始(修复它以便编译)。我称之为

turbulence(
            0,        /* color channel */,
            point,    /* {x,y} */
            1.0, 1.0, /* fBaseFreqX and Y */
            1,        /* numOctaves */
            0,        /* bFractalSum */
            0,        /* bDoStitching */
            0.0, 0.0, /* fTileX and Y */
            0.0, 0.0, /* fTileWidth and Height */
            )

(我的完整资源可在 https://gitlab.com/AlanDeSmet/svg-1.1-feturbulence 获得)

从 0.0 到 10.0 迭代 x 和 y,获取 300 个样本,并将每个样本乘以 256 创建一个 300x300 灰度图像:

这是我希望看到的。它看起来类似于

等程序生成的 Perlin 湍流

Adobe Flash (source):

3ds Max (source):

但是,如果我使用 feTurbulence 创建 SVG 并在 Firefox、Chromium 或 Inkscape(我相信这是 3 个独立的实现)中查看它,我会得到:

来源:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg height="10" width="10" version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter color-interpolation-filters="sRGB"
     id="test-turbulence" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" numOctaves="1" baseFrequency="1" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 0 1 " />
  </filter>
  <rect width="10" height="10" style="filter:url(#test-turbulence)" x="0" y="0" />
</svg>

(我正在使用 color-interpolation-filters="sRGB" 来更接近地匹配我的简单程序的输出。它不会改变结构,它只是使图像“变暗”。

图像中有细的暗线,这是我没有预料到的。这是并排比较;我在左侧(或上方)使用标准参考实现,在右侧(或下方)使用 Chromium 的输出(看起来与 Firefox 和 Inkscape 相同)。

这似乎符合标准,因为三个不同的渲染器都同意,但我认为这不是标准参考实现所做的,也不是其他一些程序所做的。

为什么我尝试使用标准的参考实现与 Firefox、Chrome 和 Inkscape 所做的有所不同?该标准是否应该与为 Perlin 湍流实施的其他程序不同?如果有,有什么区别?

这不是所使用的数学。这是Chromium源码的相关注释:

/** About the noise types : the difference between the first 2 is just minor tweaks to the algorithm, they're not 2 entirely different noises. The output looks different, but once the noise is generated in the [1, -1] range, the output is brought back in the [0, 1] range by doing :

 *  kFractalNoise_Type : noise * 0.5 + 0.5

 *  kTurbulence_Type   : abs(noise)

Very little differences between the 2 types, although you can tell the difference visually.

https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/shaders/SkPerlinNoiseShader.cpp?q=SkPerlinNoiseShader&ss=chromium

好吧,我不知道它是否有帮助,但是当您将所有位置的不透明度设置为 1 时,这些线程的不透明度区域为零或接近零,这些区域将转换为完全不透明。您可以通过添加绿色背景并将 alpha 乘以 128 来看到它。

svg{
  background: green;
}
<svg height="600px" width="800px" viewBox="0 0 800 600">
  <filter color-interpolation-filters="sRGB"
     id="test-turbulence" x="0%" y="0%" width="100%" height="100%">
    <feTurbulence type="turbulence" numOctaves="1" baseFrequency=".02" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 128 0 " />
  </filter>
  <rect width="800" height="600" filter="url(#test-turbulence)" x="0" y="0" />
</svg>

总结

这是预期的行为。 您可以使用 alpha 来避免它 湍流通道,而不是任何颜色通道。

您可以使用 feColorMatrix 从 alpha 通道创建灰度湍流:

<filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
  <feTurbulence type="turbulence" baseFrequency="0.02" />
  <feColorMatrix
     values="0 0 0 1 0
             0 0 0 1 0
             0 0 0 1 0
             0 0 0 0 1 " />
</filter>

但是为什么?

该行为令人惊讶,但它符合 SVG 规范并且可能是 正确用于某些用途。

意外的线条来自 alpha 通道,尽管已将其丢弃! 例如,这里是所有四个频道。观察线程跨越所有 颜色通道并与接近零的部分匹配 alpha 通道。

(用于生成它的 SVG 在下面的“Alpha Comparison SVG”下。)

SVG specification (backup link) 说:

Unless otherwise stated, all image filters operate on premultiplied RGBA samples. Filters which work more naturally on non-premultiplied data (feColorMatrix and feComponentTransfer) will temporarily undo and redo premultiplication as specified.

“预乘”在这种情况下是指预乘 alpha,其中 颜色通道由 alpha 调整。预乘是好的,因为它 允许合成和过滤正常工作。它发生在 场景,你可以忽略它...除非你修改 alpha 通道。

问题是预乘丢失数据。而当 alpha 值 approach 0(完全透明),数据丢失特别严重。什么时候 feColorMatrix 或 feComponentTransfer "暂时撤消和重做 premultiplication", 撤销操作只是一个近似值。那个数据 损失表现为整个图像中的意外线条。

例如,给定一个颜色通道为

的输入图像

其alpha通道为

颜色通道的预乘版本将是

尝试撤消预乘会产生以下结果:

整个图像都有损坏(略低于 50% 的像素不匹配), 但当 alpha 接近于零时,与原始版本的差异最为显着。

(这些图像是由下面“比较图像生成器”下的 Python 代码创建的。premul_alphaunpremul_alpha 基于
Inkscape's implementation)

type="fractalNoise"呢?

以上都适用于<feTurbulence type="fractalNoise">,那为什么没有问题呢?

因为 <feTurbulence type="fractalNoise" numOctaves="1"> 是原始 Perlin 2d 噪声,并且 Perlin noise is in the range −0.707 through 0.707 (backup link)。它被视为 −1 到 1 的范围。将该范围重新映射到 0 到 255,所有值最终都在 37 到 217 之间。损坏存在,但是因为 alpha 永远不够接近 0,所以您看不到它。

它在 type="turbulence" 时变得可见,因为 Perlin 湍流使用原始噪声的绝对值。因此范围变为 0.000 到 0.707,最终在 0 到 217 范围内。这也是为什么 fractalNoise 没有任何纯黑色而 turbulence 有(以及为什么两者都没有任何纯白色)的原因。

(这方面的来源在下面的“湍流与噪声”中。)

脚注

Alpha 比较 SVG

这是比较 feTurbulence 发出的四个通道的 SVG。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="230"
   width="800"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-red" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-green" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 1 0 0 0
               0 1 0 0 0
               0 1 0 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-blue" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 1 0 0
               0 0 1 0 0
               0 0 1 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <text x="100" y="220" text-anchor="middle">Red Channel</text>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-red)" />

  <text x="300" y="220" text-anchor="middle">Green Channel</text>
  <rect x="200" y="0" width="200" height="200"
     style="filter:url(#turbulence-green)" />

  <text x="500" y="220" text-anchor="middle">Blue Channel</text>
  <rect x="400" y="0" width="200" height="200"
     style="filter:url(#turbulence-blue)" />

  <text x="700" y="220" text-anchor="middle">Alpha Channel</text>
  <rect x="600" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>

比较图像生成器

此代码生成了上面的四个正方形示例图像。

#! /usr/bin/python3

from PIL import Image

def premul_alpha(color,alpha):
    temp = alpha * color + 128
    res = (temp + (temp >> 8)) >> 8
    return res

def unpremul_alpha(color, alpha):
    if alpha == 0: return color # Nonsensical operation
    res = int((255 * color + alpha/2) / alpha)
    return res

originalimg = Image.new("L",(256,256))
original_px = originalimg.load()
alphaimg = Image.new("L",(256,256))
alpha_px = alphaimg.load()
premulimg = Image.new("L",(256,256))
premul_px = premulimg.load()
restoredimg = Image.new("L",(256,256))
restored_px = restoredimg.load()
damagedimg = Image.new("L",(256,256),0)
damaged_px = damagedimg.load()


total = 0
dmg_count =0
for color in range(256):
    for alpha in range(0,256):
        original_px[color,alpha] = color;
        alpha_px[color,alpha] = alpha;
        during  = premul_alpha(color,alpha)
        premul_px[color,alpha] = during
        restored = unpremul_alpha(during,alpha)
        restored_px[color,alpha] = restored
        total += 1
        if restored != color:
            dmg_count += 1
            damaged_px[color,alpha] = 255
print(f"{dmg_count}/{total} -> {dmg_count/total}")

originalimg.save("original.png")
alphaimg.save("alpha.png")
premulimg.save("premul.png")
restoredimg.save("restored.png")
damagedimg.save("damaged.png")

湍流与噪声

噪音:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="200"
   width="200"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="fractalNoise" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>

湍流:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="200"
   width="200"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>