CSS 滤镜:使用色调旋转仅更改某些颜色(如 Photoshop Hue/Saturation)

CSS filter: use hue rotation to change certain colors only (like Photoshop Hue/Saturation)

我想知道 pos 是否可以使用 css/svg 滤镜执行 hue/saturation 颜色转换,就像 Photoshop Hue/Saturation 有效。

本质上,根据我的阅读,Photoshop 在内部将所有像素从 RGB 表示转换为 HSL 表示,并且基本上增加了色调 (H)、饱和度 (S) 或亮度 ( L) 根据用户使用滑块定义的值,如下图所示:

这里,我chose默认范围(Master),这意味着所有个色调值都会被考虑。

使用CSS hue-rotate过滤器,我们可以得到一个近似的结果:

由于另一个问题的回答中指出的问题,有些颜色并不完全相同:。 (这对我来说没问题,我不需要它像 Photoshop 一样准确。)

因此,从本质上讲,这两种方法的内部程序似乎大致相同。

现在,Photoshop 还允许我定义要考虑调整的颜色范围,如下图所示:

从本质上讲,这个值范围意味着任何 hue 值超出范围限制的颜色都将被忽略。因此,在我的示例中,颜色 #1、#2 和 #5 保持不变。

我正在尝试使用 CSS os SVG 滤镜来做同样的事情,但我找不到方法。我正在阅读滤镜效果文档 (https://drafts.fxtf.org/filter-effects/),看看是否有任何东西可以用来定义范围,但我找不到任何东西。

有谁知道是否有办法实现我的意图? CSS 过滤器的任何有效替代方案,也许?

编辑: 这个片段显示了我用 filter: hue-rotate(45deg) 得到的结果,以及我想要获得的结果。

.block-wrapper {
  width: 100%;
  height: 50px;
  display: flex;
  margin-bottom: 10px;
}


.block {
  width: 20%;
  height: 100%;
}


.b1 {
  background-color: rgb(29 85 34);
}

.b1-goal {
  background-color: rgb(29 85 34);
}

.b2 {
  background-color: rgb(32 53 79);
}

.b2-goal {
  background-color: rgb(32 53 79);
}

.b3 {
  background-color: rgb(175 43 52);
}

.b3-goal {
  background-color: rgb(173 75 51);
}

.b4 {
  background-color: rgb(172 94 50);
}

.b4-goal {
  background-color: rgb(166 160 44);
}

.b5 {
  background-color: rgb(96 230 33);
}

.b5-goal {
  background-color: rgb(96 230 33);
}


.hue-45 {
  filter: hue-rotate(45deg);
}
<h3>Original</h3>
<div class="block-wrapper">
  <div class="block b1"></div>
  <div class="block b2"></div>
  <div class="block b3"></div>
  <div class="block b4"></div>
  <div class="block b5"></div>
</div>

<h3>With <code>hue-rotate: 45deg;</code></h3>
<div class="block-wrapper hue-45">
  <div class="block b1"></div>
  <div class="block b2"></div>
  <div class="block b3"></div>
  <div class="block b4"></div>
  <div class="block b5"></div>
</div>

<h3>What I want: update hue only for red colors</h3>
<div class="block-wrapper">
  <div class="block b1-goal"></div>
  <div class="block b2-goal"></div>
  <div class="block b3-goal"></div>
  <div class="block b4-goal"></div>
  <div class="block b5-goal"></div>
</div>

这实际上很难立即做到,因为 feColorMatrix 处理 RGB 中的颜色,而在 HSL 中还没有办法做到这一点。 (如果我错了请纠正我。)

所以我找到了一个接近您可能想要的解决方案。这个想法是首先屏蔽掉你不想进行色调旋转的颜色。然后对剩余部分进行色相旋转并将其粘贴到原件的顶部。

带有过滤器的 SVG 代码如下所示:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
  <filter id="partial-hue-rotation">
    <!--
      1) Mask away the colors that shouldn't be hue-rotated.
        This is done based on the R-channel value only.
        The R-channel value comes in at [0-1],
        so multiply it by 255 to get the original value (as in rgb()).
        Then subtract (lowest R-channel value of color range - 1)
        to leave all color with a R-channel value higher than that.
    -->
    <feColorMatrix
      color-interpolation-filters="sRGB"
      type="matrix"
      in="SourceGraphic"
      result="only-red-visible"
      values="1 0 0 0 0
              0 1 0 0 0
              0 0 1 0 0
              255 0 0 0 -171"
    /><!-- Colors with R-channel > 171 will be left (and thus effected). -->

    <!--
      2) Apply hue rotation to remaining colors.
    -->
    <feColorMatrix
      type="hueRotate"
      values="45"
      in="only-red-visible"
      result="rotated-part"   
    />
    
    <!--
      3) Now paste the rotated part on top of the original.
    -->
    <feMerge>
      <feMergeNode in="SourceGraphic" />
      <feMergeNode in="rotated-part" />
    </feMerge>
  </filter>
  
  <!--
    This filter is to check if the right range is hue-rotated.
    All white areas will be rotated.
    The bottom row of values can be copied over the bottom row
    of the filter above.
  -->
  <filter id="test-partial-hue-rotation">
    <feColorMatrix
      color-interpolation-filters="sRGB"
      type="matrix"
      in="SourceGraphic"
      result="marked-range"
      values="0 0 0 0 1
              0 0 0 0 1
              0 0 0 0 1
            255 0 0 0 -171"
    /><!-- Colors with R-channel > 171 will be white. -->
    <feMerge>
      <feMergeNode in="marked-range" />
    </feMerge>
  </filter>
</svg>

要应用过滤器,只需将 filter: url(#partial-hue-rotation) 添加到元素的 CSS。

要测试您是否在影响正确的 colors/parts,您可以将 filter: url(#test-partial-hue-rotation); 添加到元素的 CSS。所有白色部分都将进行色调旋转。 (您可能需要将父级的背景颜色设置为黑色才能看到它。)

注意事项和限制:

  1. 此方法仅适用于您要进行色相旋转的颜色可以由单个 RGB 通道分隔的情况。例如:所有 R 值 > X 的颜色。然后将 255 放在第一列,将 -X 放在矩阵中 alpha 行的最后一列。 (255 在第二列用于 B 值选择等)
  2. 这不是最终解决方案,因为所有较浅的颜色(具有可能高于阈值的 R、G 和 B 值)也将进行色调旋转。
  3. 显然,由于 alpha 通道用于遮罩,这仅适用于不透明 colors/content。
  4. 此外,此滤镜针对特定的颜色范围和旋转值进行了硬编码。因此,可扩展性不强,但可能对个别实例有用。
  5. 显然 CSS hue-rotatefeColorMatixhueRotate 的计算方式也存在差异 (source)。这可以通过将 color-interpolation-filters="sRGB" 添加到 hueRotate feColorMatrix 标记(不确定)来消除。

无论如何,这是第一次尝试,也许这种方法可以帮助你。 :)

Working JSFiddle here

更多信息:

feColorMatrix documentation

有关如何计算色调旋转的颜色矩阵的详细信息,请参阅 C++ implementation of the Chrome browser

另见 matrix equivalents of shorthand filters

this post.

更新:版本 2

所以在阅读和思考之后,我想到了使用混合模式 difference 来为滤镜提供有关哪些颜色是 'in range' 并且应该受到影响的信息。 其工作方式如下:

  1. 用您的范围的中间颜色(例如红色)填充整个图像(区域)。
  2. 使用<feBlend>与原版和flood的区别模式。 (最暗的部分与中间色的重叠最多,例如在色轮上最接近它。)
  3. 反转差异并将它们转换为平均灰度。 (由于是我们需要的纯数值平均,所有通道都取了0.3333次。)
  4. 我们现在使用 feColorMatrix 将此灰度转换为 alpha 值,同时将它们映射为具有最低的 2/3 透明(将被删除)。
  5. 使用feComposite遮罩原始图像并仅对这部分应用效果(色调旋转)。
  6. 然后将受影响的部分粘贴到原来的上面。
  7. 完成!

可以选择要影响的颜色范围的中点和宽度:

  • 中间色设置为feFloodflood-color。 (使用完全饱和和 50% 亮度的颜色以获得最佳效果,因此 #ff0000#00ff00 等)
  • 宽度由 Alpha 通道在 feColorMatrixresult="alpha-mask" 中的偏移量选择。示例:保留色轮的 1/3 给出的偏移值为 (2/3) * -255.

Updated working JSFiddle here. (The bottom one, filter #partial-hue-rotation.)

注: 色调旋转效果很糟糕,所以不确定那里出了什么问题,但结果颜色与 CSS 的 hue-rotate() 滤镜相同..所以,是的..

更新:版本 3

遗憾的是,上面的 滤镜无法对所有颜色正常工作 。对于将 SourceGraphic 正确转换为灰度色调值(其中 0deg = black360deg = white)的 SVG 滤镜,请查看我在 [=49= 中制作的 #hue-values 滤镜].

如果只想对所有 reds/greens/blues/cyans/magentas/yellows 应用滤镜效果,可以使用同一个 JSFiddle 中的 #tonegroup-select 滤镜。

这个过滤器的代码是:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <filter id="tonegroup-select"
            x="0%" y="0%"
            width="100%" height="100%"
            primitiveUnits="objectBoundingBox"
            color-interpolation-filters="sRGB"
        >
            <!-- Compare RGB channel values -->
            <feColorMatrix type="matrix" in="SourceGraphic" result="test-r-gte-g"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  255 -255 0 0 1"
            />
            <feColorMatrix type="matrix" in="SourceGraphic" result="test-r-gte-b"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  255 0 -255 0 1"
            />

            <feColorMatrix type="matrix" in="SourceGraphic" result="test-g-gte-r"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  -255 255 0 0 1"
            />
            <feColorMatrix type="matrix" in="SourceGraphic" result="test-g-gte-b"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 255 -255 0 1"
            />

            <feColorMatrix type="matrix" in="SourceGraphic" result="test-b-gte-r"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  -255 0 255 0 1"
            />
            <feColorMatrix type="matrix" in="SourceGraphic" result="test-b-gte-g"
                values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 -255 255 0 1"
            />

            <!-- Logic masks for tone groups -->
            <!-- For example: all red colors have red channel values greater than or equal to the green and blue values -->
            <feComposite operator="in" in="test-r-gte-g" in2="test-r-gte-b" result="red-mask" />
            <feComposite operator="in" in="test-g-gte-r" in2="test-g-gte-b" result="green-mask" />
            <feComposite operator="in" in="test-b-gte-r" in2="test-b-gte-g" result="blue-mask" />
            <feComposite operator="in" in="test-g-gte-r" in2="test-b-gte-r" result="cyan-mask" />
            <feComposite operator="in" in="test-b-gte-g" in2="test-r-gte-g" result="magenta-mask" />
            <feComposite operator="in" in="test-r-gte-b" in2="test-g-gte-b" result="yellow-mask" />

            <!-- Select all colors in tone group -->
            <!-- Note: uncomment the right tone group selection here -->
            <!-- Note: greyscale colors will always be selected -->
            <feComposite operator="in" in="SourceGraphic" in2="red-mask" result="selection" />
            <!-- <feComposite operator="in" in="SourceGraphic" in2="green-mask" result="selection" /> -->
            <!-- <feComposite operator="in" in="SourceGraphic" in2="blue-mask" result="selection" /> -->
            <!-- <feComposite operator="in" in="SourceGraphic" in2="cyan-mask" result="selection" /> -->
            <!-- <feComposite operator="in" in="SourceGraphic" in2="magenta-mask" result="selection" /> -->
            <!-- <feComposite operator="in" in="SourceGraphic" in2="yellow-mask" result="selection" /> -->

            <!-- Cut selection from original image -->
            <!-- Note: use same mask for `in2` attribute as with selection -->
            <feComposite operator="out" in="SourceGraphic" in2="red-mask" result="not-selected-source" />

            <!-- Apply effects to `selection` only -->
            <feColorMatrix
                type="saturate"
                values="0"
                in="selection"
                result="edited-selection"   
            />
            <!-- After all effects, adjustments, etc -->
            <!-- the last `result` output name should be "edited-selection" -->

            <!-- Bring it all together -->
            <feMerge>
                <!-- <feMergeNode in="selection" /> --><!-- Uncomment to check selection -->
                <feMergeNode in="not-selected-source" />
                <feMergeNode in="edited-selection" />
            </feMerge>
        </filter>
    </defs>
</svg>

在代码内的注释中,您可以找到有关工作原理的更多信息以及如何使用它的说明。

有关更多信息和参考,请查看: