SVG中的黑白(非灰度)滤镜,最好使用Snap.svg。

3

编辑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 |

这反过来应用于输入图像的每个像素。

你可以查看 SVG 滤镜。很难确定你想要什么。有一个初始的图像或 SVG 将被擦除吗? - Ian
我需要创建一个滤镜,将一张图片转换成单色图像(不是灰度图像,而是严格的黑白图像)。所有大于x的值都应该显示为白色,所有小于或等于x的值都应该显示为黑色。 - Michael Seltenreich
如果您知道内置的SVG过滤器或类似功能,那就太好了。如果没有,我将不得不解决这个数学问题。 - Michael Seltenreich
我理解您想将 RGB 图像转换为黑白图像。正如您后来所说,您可能希望应用于每个像素的应用程序返回 (0,0,0) 或 (255,255,255)(其中 A 代表不透明度)。我的问题是:如果您得到一个像素值为 (203,97,150),输出结果会是什么? - Exodd
我没有一个像素是 (203,97,150)。在我的输入中,所有三个字段都是相等的,格式始终为 (x,x,x)。 - Michael Seltenreich
3个回答

10
您可以使用 <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>

这个原始方法的工作方式是,为每个颜色分量创建一个带有不同数量的区间的表格。然而,区间的数量与输入相关,而不是输出。可以将输入分成相等大小的若干部分,然后为每个部分分配一个输出值。例如,如果您想在50%(R、G或B = 0.5(128))处进行分割,则需要创建两个区间,“0 1”。第一个区间(0 -> 0.5)中的值将被赋值为0,而第二个区间(0.5 -> 1)中的值将被赋值为1。

因此,例如,如果您想在20%(0.2或51)处进行阈值处理,则需要创建五个区间。输出表格值将为“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>

这种方法的缺点在于,如果您想在任意分量值处进行阈值处理,您可能需要在表格中拥有高达256个值。
为了演示这一点,在最后一个示例中,我使用一些javascript根据由范围滑块设置的值来更新筛选器表格。

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>


IE/Edge仅支持在feComponentTransfer数组中最多64个值 - 提前告知 - Michael Mullany
2
这似乎不是事实。我稍微修改了脚本,使其在IE中运行,并且在IE11中似乎运行良好。没有尝试Edge。https://jsfiddle.net/ga0xh0oj/ - Paul LeBeau
曾经是这样的情况。如果能修复就太好了。 - Michael Mullany

2

SVG中的feComponentTransfer原语,type="discrete"用于什么目的?请参考Webplatform文档以了解如何使用该元素。


链接似乎已经损坏。 - ldirer

0

你提到的RGB转灰度的过程是一个线性过程。它可以通过矩阵乘法完成。 每个输出值都是R、G和B值的加权和。如果你选择相同的权重用于每个输出通道,那么得到的RGB图像将呈现为灰色。

二值化是一种非线性操作。因此,不可能修改该转换矩阵以使其产生黑白效果而不是灰度。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接