svg 中的黑白(非灰度)滤镜,理想情况下 snap.svg

Black and white (not grayscale) filter in svg, ideally snap.svg

编辑 1: 重写问题以便更清楚地理解。

这个真的让我大开眼界。

我想对图像应用滤镜,使超过某个阈值的值显示为全白,而所有等于或小于阈值的值都显示为全黑。

这是一张表示 3x3 像素图像的 RGB 值的地图:

|(255,255,255)|(220,220,220)|(100,100,100)|  
|(254,254,254)|(12 ,12 ,12 )|(38 ,38 ,38 )|  
|(201,201,201)|(105,105,105)|(60 ,60 ,60 )|

应用我的过滤器后,我希望收到一张图像,其中所有大于 200 的值都转换为 (255,255,255),所有等于或小于 200 的值都转换为 (0,0,0),例如:

|(255,255,255)|(255,255,255)|(0  ,0  ,0  )|
|(255,255,255)|(0  ,0  ,0  )|(0  ,0  ,0  )|
|(255,255,255)|(0  ,0  ,0  )|(0  ,0  ,0  )|

关于我应该从哪里开始的任何想法?这在 svg 中甚至可能吗? 我知道我可以在 svg 的过滤器中创建矩阵乘法,但我不知道如何解决这个问题。

谢谢!

编辑 1: 我在数学交流中发布的相关问题: https://math.stackexchange.com/questions/2087482/creating-binary-matrix-for-threshold-as-a-result-of-matrix-multiplcation

编辑 2::从 Snap.svg 代码中提取:此矩阵会将彩色图像转换为灰度图像:

<feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/>

我不想获得灰度,而是想修改此处的值以获得黑白。我想选择阈值,高于该阈值的值返回为白色,低于该阈值的值返回为黑色

附加信息:根据 MSDN,乘法是这样发生的:

Resulting vector   my coveted matrix     original vector
    | R' |     | a00 a01 a02 a03 a04 |   | R |
    | G' |     | a10 a11 a12 a13 a14 |   | G |
    | B' |  =  | a20 a21 a22 a23 a24 | * | B |
    | A' |     | a30 a31 a32 a33 a34 |   | A |
    | 1  |     |  0   0   0   0   1  |   | 1 |

这依次应用于输入图像的每个像素。

您所说的RGB转灰度是一个线性过程。它可以通过矩阵乘法来完成。 每个输出值都是 R、G 和 B 值的加权和。如果您为每个输出通道选择相同的权重,则生成的 RGB 图像将显示为灰色。

二值化是一种非线性运算。因此无法修改该转换矩阵,因此它将进行黑白而不是灰度。

我不知道是否有标志或其他东西可以添加到 svg 文件中,使它看起来是黑白的,但你可以编写一个小程序来适当地替换颜色值。

这就是 SVG 的 feComponentTransfer 原语的用途,类型="discrete"。请查阅 webplatform documentation 以了解如何使用该元素。

您可以使用 <feComponentTransfer type="discrete"> 过滤器原语实现阈值化。

这是一个例子。

<svg width="300" height="550" style="background-color: linen">
  <defs>
    <linearGradient id="gradient">
      <stop offset="0" stop-color="white"/>
      <stop offset="1" stop-color="black"/>
    </linearGradient>
    
    <filter id="threshold" color-interpolation-filters="sRGB">
      <feComponentTransfer>
        <feFuncR type="discrete" tableValues="0 1"/>
        <feFuncG type="discrete" tableValues="0 1"/>
        <feFuncB type="discrete" tableValues="0 1"/>
      </feComponentTransfer>
    </filter>
  </defs>

  <rect x="50" y="50" width="200" height="200" fill="url(#gradient)"/>
  <rect x="50" y="300" width="200" height="200" fill="url(#gradient)" filter="url(#threshold)"/>
</svg>

这个基元的工作原理是为每个颜色分量创建一个 table 波段。但是,波段数与输入有关,与输出无关。把它想象成输入被分成许多大小相等的带。然后为每个波段分配一个输出值。因此,如果您希望分裂发生在 50%(R、G 或 B = 0.5 (128)),那么您将创建两个带,“0 1”。第一个波段 (0 -> 0.5) 中的值被分配值 0,第二个波段 (0.5 -> 1) 中的值被分配值 1.

例如,如果您想将阈值设置为 20%(0.2 或 51),则需要创建五个波段。并且输出 table 值将是“0 1 1 1 1”。

<svg width="300" height="550" style="background-color: linen">
  <defs>
    <linearGradient id="gradient">
      <stop offset="0" stop-color="white"/>
      <stop offset="1" stop-color="black"/>
    </linearGradient>
    
    <filter id="threshold" color-interpolation-filters="sRGB">
      <feComponentTransfer>
        <feFuncR type="discrete" tableValues="0 1 1 1 1"/>
        <feFuncG type="discrete" tableValues="0 1 1 1 1"/>
        <feFuncB type="discrete" tableValues="0 1 1 1 1"/>
      </feComponentTransfer>
    </filter>
  </defs>

  <rect x="50" y="50" width="200" height="200" fill="url(#gradient)"/>
  <rect x="50" y="300" width="200" height="200" fill="url(#gradient)" filter="url(#threshold)"/>
</svg>

这种工作方式的缺点是它确实意味着如果您想以任意组件值作为阈值,您可能需要在 table.

中有多达 256 个值

为了演示这一点,在最后一个示例中,我使用一些 javascript 来根据范围滑块设置的值更新过滤器 table。

var selector = document.getElementById("selector");
var funcR = document.getElementById("funcR");
var funcG = document.getElementById("funcG");
var funcB = document.getElementById("funcB");

function updateFilter() {
  // Input value
  var threshold = selector.value;
  // Create the table
  var table = "0 ".repeat(threshold) + "1 ".repeat(256-threshold);
  // Update the filter components
  funcR.setAttribute("tableValues", table);
  funcG.setAttribute("tableValues", table);
  funcB.setAttribute("tableValues", table);
}

selector.addEventListener('input', updateFilter);

// Call the filter updater once at the start to initialise the filter table
updateFilter();
<svg width="300" height="300" style="background-color: linen">
  <defs>
    <filter id="threshold" color-interpolation-filters="sRGB">
      <feComponentTransfer>
        <feFuncR id="funcR" type="discrete" tableValues="0 1 1 1"/>
        <feFuncG id="funcG" type="discrete" tableValues="0 1 1 1"/>
        <feFuncB id="funcB" type="discrete" tableValues="0 1 1 1"/>
      </feComponentTransfer>
    </filter>
  </defs>

  <image xlink:href="https://placekitten.com/g/300/300"
         x="50" y="50"width="200" height="200"
         filter="url(#threshold)"/>
</svg>

<form>
  <input id="selector" type="range" min="0" max="256"/>
</form>