颜色过滤器可以让你的灰色天空变蓝

Avatar of Amelia Bellamy-Royds
Amelia Bellamy-Royds

DigitalOcean 为你的旅程的每个阶段提供云产品。 立即开始使用 价值 200 美元的免费积分!

以下是由 Amelia Bellamy-Royds 撰写的客座文章。我一直很喜欢照片中的“双色调”效果。在 Photoshop 中,你可以创建它们,方法是将图像转换为灰度模式,然后转换为双色调。因此,亮部被“映射”到一种颜色,暗部被映射到另一种颜色。它不仅看起来很酷,而且颜色较少的图像文件大小更小,因此有利于性能。当我看到 Amelia 在 CodePen 上通过 SVG 对此进行编程操作时,我问她是否愿意通过客座文章来教我们。幸运的是,它在这里!

曾经,如果你想要在你的网页设计中使用艺术图像,你需要在 Photoshop 中创建它们。或者,如果你喜欢边缘化和开源,可以使用 GIMP。但无论哪种方式,最终结果都是一个单独的、静态的图像文件,它被上传到你的服务器,下载到用户的 Web 浏览器,并完全按照你创建的方式显示。如果你想根据用户交互打开或关闭图形效果,你需要导出两个不同的图像文件,然后使用 JavaScript 或 CSS 伪类交换它们。

图形效果——最初是在 SVG 中,现在是在 CSS 中——正在改变这种现状。你可以在浏览器中直接应用类似 Photoshop 的过滤器或混合图层。这意味着你可以使用单个图像文件,并在用户与它交互时以多种方式呈现它。这也意味着你可以对无聊的黑白照片进行很多有趣的处理。

过滤器效果基础

最容易使用的图形效果是简写过滤器函数。你可以使用 CSS filter 属性应用它们。浏览器在将相应元素绘制到页面之前,会操作它的外观。过滤器函数在 Firefox 35 版(2015 年 1 月稳定版)及更高版本中被无前缀地支持。它们在 Safari、Chrome 和基于 Blink 的 Opera 中使用 -webkit- 前缀已支持多年。

(我们将在文章结尾讨论适用于 Internet Explorer 的替代方案;现在,你需要使用其中一个浏览器来查看演示!)

有关可用 filter 函数的完整描述(含示例!),请查看关于 filter 属性的年鉴页面

一些需要注意的事项

  • 你可以通过空格分隔列表应用一系列过滤器。
  • 过滤器创建堆叠上下文。
  • 过滤器被应用于整个元素(包括文本、背景图像及其所有子内容),然后将它们叠加在一起。
  • 浏览器中仍然存在一些错误的边缘情况,例如当存在固定定位的子元素或隐藏的溢出时会发生什么。

一个简单实用的技巧是将彩色图像转换为黑白图像,使用 grayscale(100%)saturate(0%)。以下演示使用此方法为一对聪明的饼干创建单色个人资料图片。悬停/聚焦时会移除过滤器,以显示完整的彩色图像。为了弥补颜色对比度和鲜艳度损失,使用(方便命名的)brightness()contrast() 过滤器函数增加了图像的整体亮度和对比度。它甚至还添加了一个简短的过渡,以淡入淡出颜色。

查看 Pen CSS Filters 灰度 -> 彩色,作者:Amelia Bellamy-Royds (@AmeliaBR) 在 CodePen 上。

这一切都很不错。获取彩色图像,告诉浏览器将其转换为灰色。选择性地反转,以获得令人愉悦的用户交互。

**但是,如果可以做相反的事情呢?**如果你可以从灰度图像开始,然后告诉浏览器给它上色?

灰度 JPEG 图像通常比等效的彩色照片文件大小小 5-25%(取决于照片和压缩比——JPEG 对颜色通道的压缩比亮度细节高得多)。对于无损图像格式(例如 PNG),文件大小的变化可能更大。当性能至关重要时,这些额外的 KB 可能很重要。想象一下,如果你的页面上有一整页的个人资料图片。用户只会看到其中一些图片是彩色的,那么为什么用他们的数据流量发送彩色照片呢?过滤器还需要 CPU 来处理,还需要内存来存储结果。如果只过滤悬停的照片,而将其他照片保持为正常状态,不是更好吗?

最后,你必须考虑对不支持 CSS 过滤器的浏览器的影响。你想要全部彩色还是全部灰度作为回退?

只有一个简写过滤器函数可以为灰度图像添加颜色:sepia(),它会给白色和灰色添加黄色色调。对于彩色图像,它会首先隐式地将其转换为基于亮度的灰度。它还会增加整体亮度和对比度,尽管不如前面的示例使用单独的过滤器那样多。下面的演示在与它交互之前看起来几乎与前面的演示相同,但这一次,图像实际上被保存为灰度 JPEG。悬停时会应用棕褐色效果,以添加一点(虚假的)颜色。

查看 Pen CSS Filters 灰度 -> 棕褐色,作者:Amelia Bellamy-Royds (@AmeliaBR) 在 CodePen 上。

这是一个很酷的效果,但我不能说我对棕褐色暗示的特定黄色和米色色调感到满意。我不能选择自己的颜色吗?

我可以。只是不能使用简写过滤器函数。这些简写函数都可以用长格式编写,作为 SVG 过滤器元素。如果你想要更多自定义控制,你只需要创建等效的 SVG 过滤器并自行自定义它。

SVG 过滤器是在 <svg> 中定义的 <filter> 元素。有关整体语法的更多信息,请查看 Joni Trythall 于 2014 年 9 月发布的关于 SVG 照明过滤器的优秀文章。要使用过滤器,你需要为它指定一个 id,然后在另一个元素的 filter 属性中使用 URL 引用。

.filter-this {
  filter: url(#myFilter);
}
.more-filters-please {
  filter: url("/assets/filters.svg#filter-1");
}

在 SVG 中,SVG 过滤器在大多数现代浏览器中受支持,包括 Internet Explorer 10 和 11。请注意“在 SVG 中”。SVG 过滤器在 IE 或 Edge(尚未)上对 HTML 内容不支持。IE9 和 Opera Mini 用户将看不到此效果。(其他浏览器中也存在一些错误和限制,但它们不影响本文讨论的颜色过滤器。)对于将 SVG 过滤器应用于非 SVG 内容,Firefox 和最新版本的 Chrome 和 Opera 在无前缀的 filter 属性中支持它。Safari 和旧版 Blink 在带前缀的 -webkit-filter 属性中支持它。

sepia() 简写过滤器函数在 SVG 过滤器中使用 <feColorMatrix> 操作符定义。因此,要创建你自己的彩色效果,你可以创建一个颜色矩阵过滤器并调整参数。
以下是 100% 棕褐色的 SVG 过滤器的样子

<filter id="sepia">
 <feColorMatrix type="matrix"
     values="0.393 0.769 0.189 0 0
         0.349 0.686 0.168 0 0
         0.272 0.534 0.131 0 0
         0   0   0   1 0"/>
</filter>

很简单,对吧?只需稍微调整一下这里的一些数字,那里的一些数字,然后……意识到你完全不知道自己在做什么。

是时候深入了解矩阵代数的奇妙世界了。

数学部分

你需要了解的关于 <feColorMatrix> 的第一件事是,values 中的数字列表构成一个矩阵。具体来说,它是一个五列四行矩阵,如上所示通过保留空格来表示。

(旁注:这仅适用于 type="matrix"。其他 type 选项是 saturatehue-rotate,它们接受一个单个数值并且具有简写 CSS 等价物,以及 luminanceToAlpha,它不接受任何值。)

如果你对高中代数课的记忆模糊不清,这里是对矩阵和矩阵乘法的复习

  • 矩阵本质上是描述一组代数方程的简写方式。你使用矩阵中行和列中的位置,而不是使用像xy 这样的字母来描述变量数量。
  • 一个多部分值——例如x,y 点或 RGBa 颜色——可以用一个单行或单列矩阵表示,也称为向量。
  • 当你将一个矩阵和一个向量相乘时,你是在将向量中的值代入矩阵中的变量,然后将每个方程的结果加起来。

颜色矩阵过滤器是通过将 RGBa 输入颜色表示为一个向量来创建的,其中每个通道上的值都已缩放到 0-1 范围。该向量乘以矩阵,然后将结果转换回 RGBa 输出颜色。每个向量和矩阵都添加了一行/列,以便在需要时可以按固定量移动颜色。

矩阵方程,使用NraCa 形式的变量来表示矩阵参数,看起来像这样

|R2|   | Nrr Ngr Nbr Nar Cr|   |R1|
|G2|   | Nrg Ngg Nbg Nag Cg|   |G1|
|B2| = | Nrb Ngb Nbb Nab Cb| * |B1|
|A2|   | Nra Nga Nba Naa Ca|   |A1|
| 1|   | 0   0   0   0   1 |   | 1|

也可以写成一系列五个方程

R2 = Nrr*R1 + Ngr*G1 + Nbr*B1 + Nar*A1 + Cr*1
G2 = Nrg*R1 + Ngg*G1 + Nbg*B1 + Nag*A1 + Cg*1
B2 = Nrb*R1 + Ngb*G1 + Nbb*B1 + Nab*A1 + Cb*1
A2 = Nra*R1 + Nga*G1 + Nba*B1 + Naa*A1 + Ca*1
1 =  0*R1 +  0*G1 +  0*B1 +  0*A1 + 1*1

最后一行计算结果为 1=1,因此你可以放心地忽略它。它在 <feColorMatrix> 属性中也被忽略,该属性只指定矩阵的前四行(总共 20 个数字)。

对于其他行,你正在创建每个 rgba 输出值,它是 rgba 输入值乘以对应矩阵值,再加上一个常数的总和。如果你还没有弄清楚我的命名约定,Nra 是原始红色通道进入输出 Alpha 通道的比例,而Ca 是添加到 Alpha 通道的常数。

颜色矩阵的一个简单例子是用于luminanceToAlpha颜色矩阵类型的矩阵。它等同于以下矩阵

| 0      0       0      0 0 |
| 0      0       0      0 0 |
| 0      0       0      0 0 |
| 0.2126 0.7152  0.0722 0 0 |
| 0      0       0      0 1 |

由于前三行的值都为零,因此红、绿和蓝通道的输出也为零(即黑色)。Alpha 通道的输出是作为三个输入颜色通道的函数创建的:主要来自绿色,少量来自红色,少量来自蓝色。如果输入颜色为纯白色(即 RGB 值在缩放后都为 1),则 alpha 值为

A2 = 0.2126*1 + 0.7152*1 + 0.0722*1 = 1

换句话说,完全不透明。如果输入颜色为纯黑色(即 rgb 值都为 0),则 alpha 值为

A2 = 0.2126*0 + 0.7152*0 + 0.0722*0 = 0

换句话说,完全透明。任何其他颜色最终都会得到介于两者之间的 alpha 值。颜色越亮,不透明度越高;颜色越暗,透明度越高。为 RGB 通道分配的特定数字旨在反映强红色、强绿色和强蓝色之间感知亮度的差异。(这些数字在各种标准中是固定的,但科学并不完全准确。你看到的亮度会受到你使用的显示器(或打印机)类型以及你自己的眼睛对每种颜色的敏感度的影响。)

因此,回到 sepia 矩阵

0.393 0.769 0.189 0 0
0.349 0.686 0.168 0 0
0.272 0.534 0.131 0 0
0     0     0     1 0

如前所述,第五行被排除在外,因为它永远不会改变。该矩阵执行两项操作:将彩色图像扁平化为灰度(并进行亮度调整),然后将其染成黄色。Alpha 通道不会改变:输出 A2 值恰好是输入 A1 通道的 1 倍。

当输入颜色为黑色时,输出仍然为黑色,因为所有数字都将乘以 0。但是,当输入颜色为白色时,输出如下

R2 = 0.393*1 + 0.769*1 + 0.189*1 = 1.351
G2 = 0.349*1 + 0.686*1 + 0.168*1 = 1.203
B2 = 0.272*1 + 0.534*1 + 0.131*1 = 0.937

因此颜色为 rgb(135.1%, 120.3%, 93.7%)。现在,等等,你说“嘿,你不能有大于 100% 的 rgb 值”。你不能,但你可以。它们会被钳位到 100%,因此结果是输入图像的白色部分会变成淡黄色。如果你稍微降低输入值,你实际上不会看到红色和绿色通道的减少,直到输出下降到 100% 以下,所以你仍然会得到黄色,但不会那么淡(因为蓝色通道明显下降)。

对于中等输入,每个通道的缩放值为 0.5,输出颜色将按如下方式计算

R2 = 0.393*0.5 + 0.769*0.5 + 0.189*0.5 = 0.6755
G2 = 0.349*0.5 + 0.686*0.5 + 0.168*0.5 = 0.6015
B2 = 0.272*0.5 + 0.534*0.5 + 0.131*0.5 = 0.4685

换句话说,它将是橙黄色灰色,大约为 rgb(68%, 60%, 47%)。

很好。

如果你精通代数和/或算术,你可能会注意到,中等灰色输入的输出值恰好是白色输入的一半。当输入图片是灰度时,你总是会为红色、绿色和蓝色输入通道中的每一个得到相同的值。因此,你可以通过这样写来简化很多数学运算

R2 = 1.351 * gray
G2 = 1.203 * gray
B2 = 0.937 * gray

要创建具有相同结果的颜色矩阵,你可以将每行中 RGB 列中的任何一个设置为这些值,并将其他值保留为零

| 1.351 0  0  0 0 |
| 1.203 0  0  0 0 |
| 0.937 0  0  0 0 |
| 0     0  0  0 0 |

或者

| 1.351  0  0     0 0 |
| 0   1.203 0     0 0 |
| 0   0     0.937 0 0 |
| 0   0     0     0 0 |

只要输入图像为灰度,这些结果与原始 sepia 滤镜矩阵的结果完全相同。这很好,因为这篇文章都是关于给灰度图像着色。(在经历了这么多数学运算后,你可能已经忘记了。)

在我们回到演示之前,我应该提醒一下。你可能认为,为了创建一个 RGB 输入向量,每个通道的值都为 0.5,你应该使用 rgb(50%, 50%, 50%) 或 #888 或 gray。如果你使用的是<feColorMatrix> 过滤器,你将是正确的,但如果你使用的是sepia() 简写函数,你将是错误的。这是因为所有简写过滤器都使用sRGB 颜色模型 将输入颜色缩放为计算中使用的值。

相反,SVG 过滤器默认情况下使用输入 RGB 值和矩阵数学中使用的值之间的直接数学转换。这可以通过color-interpolation-filters 属性进行控制,该属性可以作为过滤器上的属性或(可继承的)CSS 属性进行设置。该属性的两个值为linearRGBsRGB

其余的示例将使用sRGB。sRGB 模式最有效地保留了输入图像各部分之间的感知亮度差异。但它对结果有重大影响,因此,如果你使用相同的数字但得到非常不同的效果,那可能是原因。这也意味着颜色不能轻易用手计算。特别难以定义精确创建中等灰度的特定输出颜色的过滤器。如果你在浏览器中玩弄过滤器,通常这不是问题。但是,如果你试图在你的网页设计中匹配特定的颜色,你可能需要测试color-interpolation-filters: linearRGB;

单色着色

sepia 滤镜会创建单色效果。输入中的黑白灰度值映射到黑白淡黄色。由于红色和绿色通道的钳位效应,它不是一个完美的单色效果,但它类似于单色效果。

要创建你自己的单色着色过滤器,你需要更改 sepia 矩阵的简化版本中的值。要创建黑色到桃红色的过滤器(大约为 rgb(100%, 80%, 65%)),可以使用以下方法

<filter id="monochrome" 
    color-interpolation-filters="sRGB"
    x="0" y="0" height="100%" width="100%">
  <feColorMatrix type="matrix"
   values="1.00 0 0 0 0 
           0.80 0 0 0 0 
           0.65 0 0 0 0 
           0    0 0 1 0" />
</filter>

它看起来像这样。我已经注释掉了添加悬停效果的部分,这样你就可以只看到最终的结果。

查看此笔:SVG Filters Gray -> Monochrome Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

如果你熟悉单色打印图像,效果可能与你认为的单色图像相反。在印刷中,你通常控制图像部分的颜色。因此,打印机单色图像将具有暗颜色到白色的过渡。为了在 RGB 彩色显示器上创建这种效果,我们需要回到我们基本的颜色矩阵方程

R2 = Nrr*R1 + Ngr*G1 + Nbr*B1 + Nar*A1 + Cr*1
G2 = Nrg*R1 + Ngg*G1 + Nbg*B1 + Nag*A1 + Cg*1
B2 = Nrb*R1 + Ngb*G1 + Nbb*B1 + Nab*A1 + Cb*1
A2 = Nra*R1 + Nga*G1 + Nba*B1 + Naa*A1 + Ca*1
1 =  0*R1 +  0*G1 +  0*B1 +  0*A1 + 1*1

当输入颜色为黑色时,所有Nij 值都会消失(乘以 RGB 输入通道中的 0 值)。你唯一能控制的是最后一列中的常量。因此,如果你希望图像的暗部分以深蓝色绘制(例如 rgb(5%, 15%, 50%)),你需要一个类似于以下矩阵的矩阵

? ? ? 0 0.05
? ? ? 0 0.15
? ? ? 0 0.50
0 0 0 1 0

问号位置的数字在输入为黑色时没有任何作用。它们控制着图像的其余部分会发生什么。如果它们都为零,那么每个输入像素都会具有相同的恒定输出,整个图像最终会变成深蓝色。在 CSS 中创建纯深蓝色区域有更简单的方法,所以这可能不是你想要的。

如前所述,我们可以通过记住我们的输入图像是灰度,并且 RGB 通道总是相等来简化事情。我们只需要更改一列

? 0 0 0 0.05
? 0 0 0 0.15
? 0 0 0 0.50
0 0 0 1 0

我们希望当输入为白色时,输出颜色为白色(全为 1),所以你可能会认为你可以用 1 替换每个问号。但是,你仍然添加了这些常量到每个像素。一旦你将它们添加进来,你将得到一个曝光过度,并且大部分较浅细节都变得模糊的图像。

相反,你需要计算需要添加到每个通道的量,以创建对白色输入值产生白色最终值的量。换句话说,问号被 1减去用于黑色的常量颜色替换

0.95 0 0 0 0.05
0.85 0 0 0 0.15
0.50 0 0 0 0.50
0    0 0 1 0

使用该矩阵,结果如下

查看此笔:SVG Filters Gray -> Invert Monochrome Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

双色调着色

到目前为止,我们已经使用颜色矩阵过滤器来创建黑色到颜色的单色和颜色到白色的单色。为什么不颜色到颜色呢?当然可以!从技术上讲,这不再是单色图像了:在印刷中,这种效果被称为双色调。

方法与颜色到白色矩阵相同。你将常量(最后一列)设置为输入图像黑色部分所需的值,然后将其他列之一设置为该值与你希望白色部分显示的颜色的差值。

因此,要创建深蓝色到桃红色的双色调,可以使用以下矩阵

0.95 0 0 0 0.05
0.65 0 0 0 0.15
0.15 0 0 0 0.50
0    0 0 1 0

白色点将最终变为 rgb(95%+5%, 65%+15%, 15%+50%)。这与之前的桃红色相同。但是,所有中间灰度都将位于该颜色和深蓝色之间。

演示就是这样做的

查看此笔:SVG Filters Gray -> Duotone Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

当然,你为白色和黑色点选择的颜色不必是可实现的颜色。你可以像 sepia 滤镜那样“曝光过度”你的颜色。你也可以通过将黑色点的某些颜色通道设置为负值来“曝光不足”。对于颜色的重大变化,黑色点和白色点之间的差异也可能为负。(你甚至可以使用这种方法反转颜色以创建照片负片效果,尽管为此目的有一个 CSS 简写函数。)

下一个演示使用更饱和的颜色来展示负颜色偏移和落在 0-1 范围之外的颜色值的用法。

查看此笔:Psychedelic SVG Filters Gray -> Duotone Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

在观看演示时,你可能会惊讶地发现图像上的box-shadow也变成了紫色。过滤器应用于所有其他 CSS 效果之后,但剪切和遮罩除外。如果你想创建一个不会随过滤器改变颜色的阴影,你需要使用过滤器来创建它。你可以在 SVG 过滤器中执行此操作,或者使用简写drop-shadow()过滤器函数,它接受与text-shadow()属性相同的参数。它与框阴影不完全相同(你不能使用“扩展”参数,并且它会受到内容中任何透明区域的影响),但在这种情况下,它非常接近。

查看笔:v2: Psychedelic SVG Filters Gray -> Duotone Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

如果你已经看到了这里,你可能已经有点厌倦了我的咧嘴笑脸,所以让我们尝试一个不同的例子。我从 Chris Coyier 为在图像上叠加文本的帖子创建的示例中分叉了下一个笔。为了使文本可读,你需要降低图像的对比度和强度。Chris 使用的技术(非常有用)是在彩色图像上叠加一个半透明的单色渐变。

对于许多图像而言,最终结果与我的彩色灰度图像看起来并没有太大区别。在演示中,每个幻灯片的第一个版本都有一个彩色的背景图像,而第二个版本是双色调灰度。

查看笔:feColorMatrix – Tinted grayscale hero images by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

双色调图像比阴影颜色图像的对比度略低,略微扁平。考虑到保持文本可读性的愿望,这并非一定是坏事。但这可能不是你想要的。

到目前为止,我一直在使用的双色调方法的主要局限性在于,为了创建蓝色中间色调,我需要为我的黑点使用深蓝色值,为我的中点使用浅蓝色值。

为了摆脱这种限制,我们必须摆脱线性代数。我们需要在我们的着色能力方面实现指数级改进。

伽马校正着色

指数级改进,我的意思是数学指数。(什么?你以为我们已经完成了数学部分?还没有!)具体来说,我的意思是伽马校正因子。

伽马校正是一种常见的数字图像处理方法,用于创建亮度或颜色强度的非线性变化。在所有颜色通道上等效应用它,可以增加或减少中间色调的亮度,而不会过度或不足地曝光高光和暗调。单独应用于每种颜色,它可以改变图像的整体色彩平衡,而不会改变黑白色。

<feColorMatrix>双色调效果可以被认为是在 sRGB 色彩立方体中选择两种颜色,并在它们之间绘制一条直线。所有灰度都映射到这条线上一个值。

如果你将颜色特定的伽马因子应用于该线,你可以在三维色彩空间中创建一条曲线。它可以从黑色开始,到白色结束,但仍然可以通过红色、蓝色或绿色而不是纯灰色进行变化。

你不能用矩阵乘法应用伽马因子。但是,你可以用<feComponentTransfer>过滤器来实现。它支持对颜色通道执行各种不同的功能,但一次只能执行一个通道。每个通道都有自己的元素来描述将要应用的转换函数:<feFuncR><feFuncG><feFuncB><feFuncA>。每个函数元素都有一个type属性来描述将使用的数学运算,然后各种其他属性为每个类型提供参数。

(旁注:实际上我们可以使用<feComponentTransfer>来实现上述许多过滤器,尽管这会略微增加标记。你可以为每个颜色函数使用linear类型,它将输入值乘以slope参数并添加一个固定intercept。截距将是黑点值,而斜率将是移到白点所需的改变量。你不能使一种颜色的输出取决于另一种颜色的输入,但对于灰度输入,这并不重要。)

一个基本的伽马调整颜色过滤器看起来像这样

<filter id="gamma-red" 
    color-interpolation-filters="sRGB"
    x="0" y="0" height="100%" width="100%">
  <feComponentTransfer>
    <feFuncR type="gamma" exponent="0.5" />
    <feFuncG type="gamma" exponent="1.1" />
    <feFuncB type="gamma" exponent="1.4" />
  </feComponentTransfer>
</filter>

最终方程如下

R2 = R1^0.5
G2 = G1^1.1
B2 = B1^1.4

当输入颜色通道为 0 时,输出也为 0(0 的任何次方仍然是 0)。当输入通道为 1 时,输出也为 1(1 的任何次方仍然是 1)。对于所有介于两者之间的值,小于 1 的指数会增加输出中的颜色,而大于 1 的指数会减小输出中的颜色。这与许多软件程序中定义伽马因子的方式相反,这些程序使用 1/gamma 的指数。

如以下演示所示,上面给出的gamma-red过滤器创建了一个图像,其中黑色仍然是黑色,白色仍然是白色,但所有灰色都被映射到勃艮第红色。

查看笔:SVG Filters Gray -> Non-linear Color by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

然而,gamma 型分量转换函数比这更强大。除了指定指数之外,你还可以指定另外两个属性:amplitudeoffsetamplitude是将输入值提高到指数因子后乘以的结果的一个因子。offset是随后添加的常数值。换句话说,你的最终方程看起来更像这样

R2 = Nr * R1^Er + Cr
G2 = Ng * G1^Eg + Cg
B2 = Nb * B1^Eb + Cb

常数偏移量仍然为输入黑色像素设置输出颜色。这些常数的总和加上振幅因子为输入白色像素设置输出颜色。指数定义了你的颜色线将在这两个点之间如何弯曲。你可以尝试从方程中找出确切的值,但最好在实时编辑器中打开它,并调整数字,直到它看起来符合你的要求。

这就是我如何获得摩天大楼演示的这个版本的

查看笔:feComponentTransfer – Tinted grayscale hero images by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

你能分辨出哪个是真正的彩色图像吗?如果你没有将它们并排放置,你能分辨出来吗?
当然,并非所有图像都那么容易复制。你永远无法从灰度图像中获得完整的彩色图像。摩天大楼之所以有效,是因为原始图像的颜色变化非常有限。但是,这种有限的颜色变化是该图像非常适合作为背景的主要原因。如果你要追求这种效果,你绝对可以从灰度照片中创建它。

可怜的 Internet Explorer 用户

正如我在文章开头提到的,Firefox 上对 HTML 内容的 SVG 过滤器支持可以追溯到几年前(带有-webkit-前缀)的 Chrome、Opera 和 Safari。预计它也将在 Microsoft Edge 中得到支持。但与此同时,仍然有大量网络用户无法在他们的网页体验中享受到美丽的过滤器效果。

然而,SVG 内容中对 SVG 过滤器的支持要好得多(IE10+)。SVG 内容可以包含嵌入的 JPEG 图像。因此,你可以通过将要过滤的图像移到内联 SVG 标记中来快速提高浏览器支持。它需要一些额外的标记,但最终结果看起来完全一样。

查看笔:IE10+ Tinted grayscale hero images by Amelia Bellamy-Royds (@AmeliaBR) on CodePen.

但是,这仍然不是普遍支持。如果你的老板或客户坚持要求网站必须在所有浏览器上看起来相同,包括 Internet Explorer 8,那么你需要执行一些手动或服务器端图像处理来创建所有效果的彩色版本,以及一些脚本(客户端或服务器端)来替换它们。

对于更务实的回退,你主要关心的是界面是否可用以及文本是否可读。对于灰度到彩色的悬停效果,这可以简单地确保有其他界面提示表明某个东西是交互式的。对于叠加在图像上的文本,这有点棘手。如果没有过滤器,摩天大楼演示中使用的灰度图像太亮,对比度太高,无法读取文本。一个解决方案是在将图像转换为灰色时将其变暗,这样过滤器只处理着色。其他方法将需要浏览器支持测试,然后更改其他样式属性。

不幸的是,客户端对filter支持的测试有点棘手。你不能仅仅测试该属性是否受支持,因为这并不能告诉你它是否会对 HTML 元素有任何影响。CSS @supports 规则也由于同样的原因而失败。你可以测试简写函数是否受支持(通过将虚拟元素的过滤器样式设置为函数字符串,然后查看当你读回计算后的样式时该函数是否仍然存在),或者你可以测试 SVG 中是否支持过滤器(通过确认你可以创建一个 SVG 元素,并且它具有element.style.filter属性),但你无法直接测试filter: url(#blur)在声明为<div>时是否会产生任何影响。

最后的想法

过滤器只是改变了网络上图像可能性的新图形效果之一。混合模式和蒙版将具有同等的影响。单色和双色调颜色效果也可以通过混合和蒙版来实现。与 CSS 中的大多数内容(以及整个网络)一样,有多种方法可以实现给定的效果。

在决定使用哪种方法时,请考虑您的支持和回退选项,以及对代码可维护性的影响。混合背景很方便,因为它们可以在没有任何额外标记的情况下创建。但 SVG 滤镜图像在内联 <svg> 中具有最佳支持。

使用 SVG 滤镜而不是 CSS 简写滤镜函数的一个限制是,url() 引用无法被动画化或过渡。您可以使用 SMIL 动画元素或 JavaScript 动画化滤镜本身的属性。但是,这会影响所有使用该滤镜的地方,而不仅仅是单个悬停元素。或者,您可以采用老派的方法,使用图像的两个副本——一个经过滤镜,另一个没有——并更改顶层的不透明度以慢慢显示底层。与老派技术不同,您的用户只需下载一个文件。