使用CSS滤镜模拟Photoshop的“颜色叠加”效果?

10

我有一个图标,想要使用CSS更改其颜色。它在CSS中的data-uri优化SVG中。

通常情况下,这是不可能的。这就是为什么发明了图标字体的原因;它们比SVG的主要优点是能够从CSS接收color和text-shadow规则。好吧,CSS滤镜现在能够对任意图像执行这两个操作,而且它们现在适用于所有Blink、Webkit和Gecko浏览器可以预期将来会在IE/Spartan中使用

替换text-shadow很容易;只需使用drop-shadow滤镜即可。

然而,将图像着色成特定的颜色却非常棘手,尽管所有必要的滤镜都在这里。直到现在,我的理论如下:

  • 使用contrast(0)将整个图像变为纯灰色,同时保留alpha通道(Mozilla wiki说它会变成黑色,但在所有浏览器中都会变成灰色,可能是一个错误)。
  • 使用sepia(1),因为我们不能对灰度图像进行色相/饱和度操作。这确保整个图像由一个我们可以进行数学计算的参考颜色组成;具体来说,是#AC9977
在这一点上,我们应该能够使用hue-rotatesaturatebrightness将整个图像从现在的固体#AC9977变成任何我们想要的颜色。
首先,浏览器使用什么颜色坐标?我无法理解规范以确定它是否使用HSL(亮度)或HSV(值),但由于HSB(亮度)是HSV的另一个名称,我想它使用HSV。此外,使用类似brightness(999)的东西会使颜色饱和(而不是使它们变白),这在HSV中会发生,但在HSL中不会发生。
基于这个假设,我们将按以下方式进行:
  • 计算#AC9977和我们想要的颜色之间的色调差异,并使用hue-rotate
  • 计算两者之间的饱和度差异,并使用saturate
  • 计算两者之间的亮度差异,并使用brightness
由于这不是手工完成的工作,我们将使用LESS 预处理器
.colorize(@color) {
    @sepiaGrey: #AC9977;
    @hOffset: (hsvhue(@color) - hsvhue(@sepiaGrey)) * 1deg;
    @sRatio: unit(hsvsaturation(@color) / hsvsaturation(@sepiaGrey));
    @vRatio: unit(hsvvalue(@color) / hsvvalue(@sepiaGrey));
    -webkit-filter: contrast(0) sepia(1) hue-rotate(@hOffset) saturate(@sRatio) brightness(@vRatio);
    filter: contrast(0) sepia(1) hue-rotate(@hOffset) saturate(@sRatio) brightness(@vRatio);
}

这是我理解的做法。但它并不起作用为什么呢?如何使其起作用?

我尝试实现的示例

考虑一个图标,可以是图像或元素(背景图像、基于CSS的形状等),具有任何颜色,并且由透明度定义形状(不是可以简单叠加的矩形图像)。我想使用CSS将其完全组成特定颜色(可能需要使用filters)。

                 example

我打算将其实现为一个LESS mixin,需要一个颜色参数,但是对HSB函数背后的逻辑的指导已经足够了。

我不完全确定你想要实现什么。你所说的“填充纯色”具体指什么?你能否在Photoshop或类似软件中制作一个结果预览图? - Bram Vanroy
留下亮度似乎会导致结果呈现出纯色,我也不确定为什么,但它似乎更符合你想要的方向。 - Bram Vanroy
@BramVanroy 请考虑示例中的图像。我想要的是,它们只由特定颜色(在示例中为#729FCF)组成,同时保留alpha通道。例如,考虑一个用SVG表示的图标,它最初是白色的,您希望它变成不同情况下的某种红色或绿色。我可以更多或少地“眼测”颜色,但我正在尝试制作一些可重复使用且不需要猜测的东西,在我对颜色的基本理解中,我已经制作了一个似乎不起作用的算法。 - Camilo Martin
@BramVanroy 的示例已添加。 - Camilo Martin
3个回答

3
我有时尝试达到你想要的目标,但没有成功。
无论如何,您可以使用混合模式作为替代方案。

div {
    background-color: green;
    mix-blend-mode: color;
    position: absolute;
    width: 200px;
    height: 400px;
}
<div></div>
    <img src="http://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Hsl-hsv_models.svg/400px-Hsl-hsv_models.svg.png" height="400">

我想要加入透明度的要求。让我们再试一次 :-). 缺点是需要设置图片两次。

#test1 {
  background: linear-gradient(red, red), url("http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
  width: 100%;
  height: 500px;
  background-blend-mode: hue;
  -webkit-mask-image: url("http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
}

body {
  background-color: lightblue;
}
<div id="test1">
</div>

好的,假设所需结果是:您有一张图片,可以作为遮罩。你想使用这个遮罩来设置一个覆盖在现有图片上的彩色覆盖层,但你想让颜色在CSS样式中指定,以便轻松进行编辑。

如果你可以改变图片,让使用的通道是亮度而不是alpha通道,那么以下示例可以解决你的问题,你需要使用灰色和黑色颜色的滤镜,像这样:

mask file

.test {
        width: 200px;
    height: 200px;
    display: inline-block;
    background-image: url(http://i.stack.imgur.com/kxKXy.png); 
    background-size: cover;
    background-blend-mode: exclusion;
    mix-blend-mode: hard-light;
}

.testred {
    background-color: red;
}

.testblue {
    background-color: blue;
}

body {
    background: repeating-linear-gradient(45deg, lightblue 0px, lightyellow 50px);
}
<div class="test testred"></div>
<div class="test testblue"></div>


1
你展示的例子对图像进行了着色(而且本身非常酷;我不知道 mix-blend-mode),但它似乎只是解决方案的一部分(将整个图标变成一个特定颜色),就我所看到的,它不适用于带有透明度的图像(它会着色它们后面的任何东西)。 - Camilo Martin
我看到了你的编辑。有点遗憾它只能在Webkit上运行,因为它很棒。然而,更大的问题是,如果你考虑我给出的例子,red的结果会像这样http://i.imgur.com/qXF6sQB.png - 想象一下Photoshop中的“颜色叠加”。 - Camilo Martin
1
更改示例,现在使用黑色/灰色滤镜和其他混合模式。 - vals
1
恭喜。这确实是一个可以产生所需视觉输出的解决方案,尽管它会带来一些限制(现在需要在服务器端编辑每个图像文件,并且如果没有多个图像,则无法优雅地降级,在这种情况下,额外的预先着色图像也可以起作用)。由于这些限制,我不能接受当前形式的答案,尽管我对你所做的工作感到惊讶。我已经尝试将其与CSS过滤器结合使用(以生成“黑色底灰色”图像),但似乎它们和它们奇怪的数学在混合之后才开始生效。 - Camilo Martin
谢谢你的尝试,真的。我有点固执,所以我会继续努力,希望能弄清楚为什么hue-rotate是由魔鬼本人编码的 - 只有HSB滤镜应该足够了,最终我可能会解决它并发布一个答案。无论如何,你的解决方案对我来说非常有帮助,现在我们可以使用混合模式!看看我可以用屏幕和几个渐变做什么 - Camilo Martin
显示剩余3条评论

2

我在数学方面取得了一些进展,但它们并不美观。理想情况下,我认为任何颜色最多可以用以下CSS滤镜表示:

(-webkit-)filter: contrast(0) sepia(1) hue-rotate(X) saturate(Y) brightness(Z);

换句话说,理想情况下,我们应该能够将任何颜色表示为相对于杏仁灰色 (#AC9977) 的色调、饱和度和亮度坐标。
虽然我还没有找到实现这一点的方法(也不确定是否可能),但我成功地制作了一个可以接受任何纯色(R、G、B、C、M、Y)或任何中性颜色(白色、黑色和灰色)的实现。其中有一些是优化过的(例如,黑色只是 brightness(0))。此外,如果您指定的颜色具有透明度,那么透明度将被添加为 opacity 滤镜。
目前这是代码(使用 LESS 编写):
// Filter prefixer.
.filter(@filters) { -webkit-filter+_: @filters; filter+_: @filters; }

// Helper that conditionally adds opacity filter when color calls for it.
._conditional-opacity(@color) when (alpha(@color) < 1) {
  .filter(round(opacity(alpha(@color)), 3));
}

// Helper that adds a brightness filter when necessary.
._conditional-brightness(@channel) when (@channel < 255) {
  .filter(brightness(round(@channel / 255, 3)));
}

// Special case for pure black.
.colorize(@color) when (fade(@color, 100%) = #000) {
  .filter(brightness(0));
  ._conditional-opacity(@color);
}

// Special case for pure grey and off-by-one-grey.
.colorize(@color) when (fade(@color, 100%) = #7F7F7F),
                       (fade(@color, 100%) = #808080) {
  .filter(contrast(0));
  ._conditional-opacity(@color);
}

// Special case for shades of pure red.
.colorize(@color) when (red(@color) > 0)
                   and (green(@color) = 0)
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) saturate(999));
  ._conditional-brightness(red(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure green.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) > 0)
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) hue-rotate(99deg) saturate(999));
  ._conditional-brightness(green(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure blue.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) = 0)
                   and (blue(@color) > 0) {
  .filter(contrast(0) sepia(1) hue-rotate(199deg) saturate(999));
  ._conditional-brightness(blue(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure cyan.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) > 0)
                   and (blue(@color) = green(@color)) {
  .filter(contrast(0) sepia(1) invert(1) saturate(999));
  ._conditional-brightness(blue(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure magenta.
.colorize(@color) when (red(@color) = blue(@color))
                   and (green(@color) = 0)
                   and (blue(@color) > 0) {
  .filter(contrast(0) sepia(1) hue-rotate(-99deg) saturate(999));
  ._conditional-brightness(red(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure yellow.
.colorize(@color) when (red(@color) > 0)
                   and (green(@color) = red(@color))
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) hue-rotate(199deg) saturate(999) invert(1));
  ._conditional-brightness(green(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure grey and white.
.colorize(@color) when (red(@color) = green(@color))
                   and (green(@color) = blue(@color))
                   and not (blue(@color) = 0) // We've optimized these before.
                   and not (blue(@color) = 127)
                   and not (blue(@color) = 128) {
  .filter(contrast(0) brightness(round(blue(@color) / 255 * 2 + .00765, 3)));
  ._conditional-opacity(@color);
}

.colorize(@color) when (default()) {
  // General case not figured out yet.
}

如果你想尝试一下,这里有一个CodePen(它可以自动编译LESS)。
请注意,这仅仅是不够完美的。如果你提供了更好的答案(包括使用其他方法解决问题),我可能会接受你的答案,而不是选择我的答案,除非我的答案能代表任何给定的颜色(目前无法实现;我可能已经放弃了)。

1
你需要创建一个通过CSS滤镜引用的SVG滤镜才能接近这个效果。这不是真正的叠加混合-那需要使用blend-mode。但我认为它实际上可以达到你想要的效果。请注意,滤镜中的色相旋转基本上是有问题的-它只是在RGB空间中的近似值,这使得饱和颜色非常错误。(实际上是在内部使用原始的SVG Filter数学方法)。

<svg width="800px" height="600px">
<defs>
  <filter id="fakeOverlay">
    <feColorMatrix type="luminanceToAlpha" result="L2A"/>
    <feFlood flood-color="cyan" result="colorfield"/>
    <feBlend mode="multiply" in="L2A" in2="colorfield"/>
    <feComposite operator="in" in2="SourceGraphic"/>
  </filter>
  </defs>
<image filter="url(#fakeOverlay)" width="800" height="400" xlink:href="http://i.stack.imgur.com/Mboab.png"/>
  
</svg>


好的,整个重点是以一种比在HTML中硬编码额外标记更方便和/或更优雅的方式来完成这个过程(这些SVG元素每次我想要另一种颜色时都需要更新)。我很感激你的努力,但我的初衷是找到一种方法,让这些样式问题回到样式表中并留在那里。我甚至不介意将SVG作为data:放入CSS中,只是为了过滤器,但它似乎没有像使用URL时那样被解释。 - Camilo Martin
你可以使用单独的SVG文件来存储筛选器定义。我认为你过于限制了问题。虽然我不知道具体的用例,但是能够在跨浏览器动态地完成这个任务比在Photoshop中完成要更加方便。 - Michael Mullany
天哪,120个请求?这只有在流媒体网站或类似的网站上才能接受。对于普通网站,您可以将所有CSS组合,将所有javascript组合,并将大多数图像(除了头像,缩略图之类的)放入CSS中作为数据URI,永久缓存CSS和JS,客户端甚至不需要304它们。根据我的经验,这使得任何东西都变得非常快速。仅在页面呈现时加载的是额外的请求。 120个请求绝对是疯狂的,在移动设备上加载会很慢。 - Camilo Martin
http://www.keynote.com/performance-indexes/mobile-retail-us (实际上是平均96个请求)我不是说这是正确的 - 我只是说这就是现实。 - Michael Mullany
你好,我知道这是一个旧的答案,但我将你的代码以不同的方式复制到了CSS文件中,它正在覆盖图像,但颜色是黑色而不是青色。在这个解决方案中,有可能指定其他的flood-color值吗?这是我尝试的要点,只有黑色叠加 https://gist.github.com/Modicrumb/c7eff02ff79bc19ad42c6890c73426f8。 - Royalty
显示剩余6条评论

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