0

Situation/Problem

When making a SVG filter, I stumbled upon something strange. When converting the original CSS gradient to R, G or B color only (whichever channel has the highest value), I get dithering in the gradient.

Question

While this usually is a good thing for visual performance, it now messes up the calculations/masking in my SVG <filter>.

Is there a way to disable this dithering? Or to prevent it from occurring?

Example

* { margin: 0; padding: 0; box-sizing: border-box; font-family: sans-serif; }

.wheel {
    display: block;
    width: 100%;
    height: 50px;
    /* background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); */
    /* background: linear-gradient(to right, #f88, #ff8, #8f8, #8ff, #88f, #f8f, #f88); */
    /* background: linear-gradient(to right, #f44, #ff4, #4f4, #4ff, #44f, #f4f, #f44); */
    background: linear-gradient(to right, #ffe0e0, #ffffe0, #e0ffe0, #e0ffff, #e0e0ff, #ffe0ff, #ffe0e0);
}

.very-light {
    background: linear-gradient(to right, #fffafa, #fffffa, #fafffa, #faffff, #fafaff, #fffaff, #fffafa);
}

.filter {
    filter: url(#max-channel);
    /* Does not change gradient dithering behaviour */
    /*transform: translateZ(0);*/
}

.expected {
    background: linear-gradient(
        to right,
        #f00 0%,
        #f00 calc((1/6) * 100%),
        #0f0 calc((1/6) * 100%),
        #0f0 calc((3/6) * 100%),
        #00f calc((3/6) * 100%),
        #00f calc((5/6) * 100%),
        #f00 calc((5/6) * 100%),
        #f00 100%
    );
}
<div class="wheel">Original <code>linear-gradient()</code></div>
<div class="wheel filter"><code>linear-gradient()</code> with SVG filter applied</div>
<div class="wheel filter very-light">Even worse on very light gradient..!</div>
<div class="wheel expected">Expected result</div>

<svg
    version="1.1" xmlns="http://www.w3.org/2000/svg"
    width="0" height="0"
>
    <filter id="max-channel" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
    
        <feFlood flood-color="#ff0000" result="red" />
        <feFlood flood-color="#00ff00" result="green" />
        <feFlood flood-color="#0000ff" result="blue" />

        <!-- Separate channels -->
        <feComponentTransfer in="SourceGraphic" result="r-channel">
            <feFuncR type="table" tableValues="0 1"></feFuncR>
            <feFuncG type="table" tableValues="0 0"></feFuncG>
            <feFuncB type="table" tableValues="0 0"></feFuncB>
        </feComponentTransfer>
        <feComponentTransfer in="SourceGraphic" result="g-channel">
            <feFuncR type="table" tableValues="0 0"></feFuncR>
            <feFuncG type="table" tableValues="0 1"></feFuncG>
            <feFuncB type="table" tableValues="0 0"></feFuncB>
        </feComponentTransfer>
        <feComponentTransfer in="SourceGraphic" result="b-channel">
            <feFuncR type="table" tableValues="0 0"></feFuncR>
            <feFuncG type="table" tableValues="0 0"></feFuncG>
            <feFuncB type="table" tableValues="0 1"></feFuncB>
        </feComponentTransfer>
        
        <!-- Red is max mask -->
        <feBlend mode="lighten" in="SourceGraphic" in2="red" result="r-lighten" />
        <feBlend mode="difference" in="SourceGraphic" in2="r-lighten" result="r-lighten-diff" />
        <feColorMatrix type="matrix" in="r-lighten-diff" result="r-max-mask"
            values="0 0 0 0 1  0 0 0 0 0  0 0 0 0 0  -255 0 0 0 1"
        />
        
        <!-- Green is max mask -->
        <feBlend mode="lighten" in="SourceGraphic" in2="green" result="g-lighten" />
        <feBlend mode="difference" in="SourceGraphic" in2="g-lighten" result="g-lighten-diff" />
        <feColorMatrix type="matrix" in="g-lighten-diff" result="g-max-beforemask"
            values="0 0 0 0 0  0 0 0 0 1  0 0 0 0 0  0 -255 0 0 1"
        />
        <feComposite operator="out" in="g-max-beforemask" in2="r-max-mask" result="g-max-mask" />
        
        <!-- Blue is max mask -->
        <feBlend mode="lighten" in="SourceGraphic" in2="blue" result="b-lighten" />
        <feBlend mode="difference" in="SourceGraphic" in2="b-lighten" result="b-lighten-diff" />
        <feColorMatrix type="matrix" in="b-lighten-diff" result="b-max-beforemask"
            values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 1  0 0 -255 0 1"
        />
        <feComposite operator="out" in="b-max-beforemask" in2="r-max-mask" result="b-max-beforemask2" />
        <feComposite operator="out" in="b-max-beforemask2" in2="g-max-mask" result="b-max-mask" />

        <feMerge>
            <!-- <feMergeNode in="SourceGraphic" /> -->
            <!-- <feMergeNode in="r-channel" /> -->
            <!-- <feMergeNode in="r-lighten-diff" /> -->
            <feMergeNode in="b-max-mask" />
            <feMergeNode in="g-max-mask" />
            <feMergeNode in="r-max-mask" />
        </feMerge>

    </filter>
</svg>

Screenshot

Screenshot

Here you have two color wheel bars. The bottom one has the SVG filter applied to it. The filter should show full red on colors where red is the (shared) highest channel, then green, then blue. Ideally it would show 4 blocks that are, if added, have almost equal width. But as you can see, the dithering messes this up, and thus the blocks are dithered areas. For very light color gradients it's even worse.

Platform: Windows 10 - Browser: Chrome version 93.0.4577.82 (64-bit) (But happens in Edge as well.)

Notes:

Update

As suggested by Michael, I tried using a blur before applying the rest of the filter. By adding <feGaussianBlur stdDeviation="1" in="SourceGraphic" result="blurred-img" /> to the filter and using blurred-img instead of SourceGraphic. But as you can see from the screenshot below, it does not disable the dithering or deals with the 'troubled' pixels.

The trouble there is that they probably have a very small positive value because of calculations, but so small that dithering kicks in instead of assuming 0 opacity.

With blur of 1 px

enter image description here

FYI

So although it does not really have something to do with the question, I will explain the use case a bit.

In short, this filter will be used to effect a specific range of colors of the original image based on their hue value.

In order to do that, this filter above makes a mask that will ‘select’ a certain range of colors. The result can then be put through the actual effect and then pasted over the original image. For example all red tones in an image get hue rotated.

4

1 回答 1

0

撤消抖动的最简单方法是向源图形添加一个小模糊,并使用该结果而不是 SourceGraphic 作为输入。

于 2021-09-24T16:05:22.023 回答