【问题标题】:Emulate Photoshop's "Color Overlay" using CSS filters?使用 CSS 过滤器模拟 Photoshop 的“颜色叠加”?
【发布时间】:2015-06-10 02:33:24
【问题描述】:

我有一个图标,我想使用 CSS 更改它的颜色。它位于 CSS 中内联的数据 uri 优化的 SVG 中。

通常,这是不可能的。这就是发明图标字体的原因。与 SVG 相比,它们的主要优势是能够从 CSS 接收 colortext-shadow 规则。好吧,CSS filters 现在能够对任意图像执行这两种操作,they now work in all Blink, Webkit and Gecko browserscan be expected for future IE/Spartan

text-shadow 替换很容易;只需使用drop-shadow filter

然而,尽管所有必要的过滤器都在这里,但事实证明,将图像着色为特定颜色非常棘手。到目前为止,我的理论如下:

  • 使用contrast(0) 将整个图像变成纯灰色,同时保留 Alpha 通道(Mozilla wiki 说它会变成黑色,但在所有浏览器中它都会变成灰色,一定是错字)。
  • 使用sepia(1),因为我们无法对灰度图像的色相/饱和度进行操作。这确保了整个图像由我们可以进行数学运算的参考颜色组成;具体来说,#AC9977

此时,我们应该能够使用hue-rotatesaturatebrightness 将整个图像从现在的纯色#AC9977 转换为我们想要的任何颜色。

首先,浏览器使用什么颜色坐标?我无法确定 the spec 是否有意义,以确定它是否使用 HSL (Lightness) or HSV (Value),但由于 HSB(Brightness)是 HSV 的另一个名称,我想它正在使用 HSV。此外,使用 brightness(999) 之类的东西会使颜色饱和(而不是使它们变成白色),这会在 HSV 中发生,但不会在 HSL 中发生。

基于这个假设,我们将进行如下操作:

  • 计算#AC9977与我们想要的颜色之间的色相差异,并使用hue-rotate
  • 计算两者之间的饱和度差异,并使用saturate
  • 计算两者之间的亮度差,并使用brightness

由于这不是手工完成的工作,我们将使用LESS preprocessor

.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);
}

据我了解,这应该可行。 But it isn't为什么,以及如何让它发挥作用?

我想要实现的示例

将图标视为图像或元素(背景图像、基于 CSS 的形状等),具有任何颜色,并具有由透明度定义的形状( em> 一个可以简单叠加的矩形图像)。我想让它完全由带有 CSS 的特定颜色组成(大概是使用filters)。

                

我计划将其实现为带有颜色参数的LESS mixin,但仅指导 HSB 函数背后的逻辑就足够了。

【问题讨论】:

  • 我不完全确定您要达到的目标。 用纯色填充到底是什么意思?你能在 Photoshop 或类似工具中制作一个模型,看看结果应该是什么样子吗?
  • PS 忽略亮度似乎提供了纯色,我也不知道为什么,但它似乎更朝着你想要的方向发展。
  • @BramVanroy 考虑example 中的图像。我想要的是它们仅由特定颜色组成(在示例中为#729FCF),同时保持 alpha 通道。例如,考虑一个以 SVG 表示的图标,它最初是白色的,您希望它在不同情况下变成某种红色或绿色。在某种程度上,我或多或少可以“观察”颜色,但我正在尝试制作可以重复使用且不需要猜测的东西,并且在我对颜色的基本理解中,我制作了一种算法似乎不起作用。
  • @BramVanroy 示例已添加。

标签: css image-manipulation hsv color-space css-filters


【解决方案1】:

我有时试图实现你想要的,但没有成功。

无论如何,您还有其他选择,使用混合模式:

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">

我错过了透明度要求。让我们再试一次 :-)。缺点:需要设置2次图片。

#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,则以下示例可以是您的解决方案 你需要一个灰色和黑色的滤镜,像这样

.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>

【讨论】:

  • 您展示的示例为图像着色(并且它本身很酷;不知道mix-blend-mode),但它似乎只是解决方案的一部分(制作一个整体只是一种特定颜色的图标),据我所知,它不适用于具有透明度的图像(它也会着色它们背后的任何东西)。
  • 我看到了您的编辑。有点遗憾,它只适用于 webkit,因为它很整洁。但是,更大的问题是,如果您考虑我给出的示例,red 的结果将是something like this - 想想 Photoshop 中的“颜色叠加”。
  • 看起来很有希望!然而,结果与“颜色叠加”效果的样子大不相同,see what I mean。但总而言之,这可能是一个指向正确方向的指针。
  • 更改示例,现在使用黑色/灰色滤镜和其他混合模式
  • 恭喜。这确实是一种解决方案,可以产生所需的视觉输出,并且当然值得赞成,尽管它会导致限制(现在需要在服务器端编辑每个图像文件,并且如果没有多个图像,它就无法优雅地降级,在这种情况下额外的预着色图像也可以工作)。由于这些限制,我不能接受当前形式的答案,尽管我对你在那里所做的工作感到惊讶。我尝试将它与 css 过滤器结合使用(以产生“灰色上的黑色”图像),但似乎它们和他们奇怪的数学在混合后开始发挥作用。
【解决方案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.
}

如果你想玩它,here's a codepen(它会自动编译 LESS)。

请注意,这还不够好,如果您发布更好的答案(包括使用不同的方法来解决问题),我可能会接受您的答案,并且不会接受我自己的答案,除非它可以代表任何给定的颜色(它目前不能;我现在可能已经放弃了)。

【讨论】:

    【解决方案3】:

    您必须创建一个通过 CSS 过滤器引用的 SVG 过滤器才能接近这一点。它不是真正的叠加混合 - 需要混合模式。但我认为它实际上可以让你得到你想要的。请注意,滤镜中的色相旋转基本上被破坏了——它只是 RGB 空间中的一个近似值,它使饱和颜色非常错误。 (它实际上在幕后使用了原始的 SVG 过滤器数学)。

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

    【讨论】:

    • 如果不将图像转换为在页面标记中使用硬编码颜色的 SVG,这是否可行?
    • 有两种方法可以做到这一点,而无需将源图像转换为 SVG 本身 1) 使用内联 svg 并让您的图像元素引用外部 png - 就像我在上面所做的那样或 2) 引用这个 svg通过 css 过滤器(-webkit-filter 等)语法进行过滤。前者适用于所有近期浏览器和IE10及以上版本,后者仅适用于支持css过滤器的浏览器。
    • 嗯,关键是要以一种比将额外标记硬编码到 HTML 中更方便和/或更优雅的方式来执行此操作(每次我想要另一种颜色时,这些 SVG 元素也需要更新)。我很欣赏这种努力,但我最初的动机是想办法让这些风格问题回到样式表中并留在那里。我什至不介意将 SVG 作为 data: 放入 CSS 中以用于过滤器,但它似乎不会被解释为使用 URL。
    • 您可以拥有一个单独的 SVG 文件,其中仅包含过滤器定义。我认为你过度限制了你的问题。我不知道确切的用例,但与在 Photoshop 中执行此操作相比,能够动态跨浏览器执行此操作是一个巨大的胜利。
    • Dynamically 很好,但我的意思是,如果我必须提出另一个请求或更改标记以添加样式帮助器,那么它不仅仅是添加另一个数据的目的:uri'd (svg) 图像到 CSS(并跳过额外的请求/标记修改)。它不能只是一个技术演示解决方案。
    猜你喜欢
    • 2017-09-03
    • 1970-01-01
    • 2021-11-16
    • 2015-08-12
    • 1970-01-01
    • 1970-01-01
    • 2014-06-09
    • 2017-03-19
    • 1970-01-01
    相关资源
    最近更新 更多