Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HSL color space blending seems incorrect (possibly HWB as well) #3176

Closed
facelessuser opened this issue Jan 16, 2020 · 6 comments
Closed

HSL color space blending seems incorrect (possibly HWB as well) #3176

facelessuser opened this issue Jan 16, 2020 · 6 comments

Comments

@facelessuser
Copy link

Description

I believe that blend and blenda do not properly blend colors in the HSL namespace correctly. This is based on my understanding of the spec. It is certainly debatable if I am correct. This applies to color schemes. I assume the problem may also exist with HWB.

My observations are based on my emulation of Sublime colors as done in the ExportHtml plugin. I have successfully been blending colors in the RGB name space using a linear algorithm where c1 is base value of a given channel and c2 is the blend value: color(<base_color> blend(<blend_color> 30%)). f would be the percentage.

def rgb_blend_channel(c1, c2, f):
    """Blend the red, green, blue style channel."""

    return clamp(
        round_int(abs(c1 * f + c2 * (1 - f))),
        0, 255
    )

This has proven good for the RGB channels and will stand as an example of the linear blending that I will comment on. I realize that Sublime may do things slightly different, but based on tests, I was assume it is something very similar.

HSL, based on the now abandoned spec, seems to imply that each channel in the HSL space is also blended in a linear fashion. With the hue channel though, there is another requirement where the angle of shortest distance should be where the blending takes place. It seems Sublime handles the shortest distance.

To replicate this, in ExportHTML, we use the following algorithm. c1 once again represents the base while c2 represents the color to blend. We account for the angle of shortest distance by getting the delta, and if the angle passes the 180 threshold, we take the smallest number and add 360. This allows us to blend the colors using the smallest angle. Afterwards, we simply scale it back down to be between the correct range. What you'll notice though, we have to invert the blend factor with f = 1.0 - f to emulate Sublime. This seems wrong, but necessary to match Sublime's blending. I imagine this could be caused by inverting the factor (as shown below) or swapping c1 and c2 incorrectly.

def hue_blend_channel(c1, c2, f):
    """Blend the hue style channel."""

    c1 *= 360.0
    c2 *= 360.0

    if abs(c1 - c2) > 180.0:
        if c1 < c2:
            c1 += 360.0
        else:
            c2 += 360.0
    # This shouldn't be necessary and is probably a bug in Sublime.
    f = 1.0 - f

    value = abs(c1 * f + c2 * (1 - f))
    while value > 360.0:
        value -= 360.0

    return value / 360.0

Additionally, it is noticed that when we emulate Sublime's blending of the Saturation and Luminance channel that it seems sometimes the blending is done correctly and sometimes the c1 and c2 get swapped (or the factor inverted). This can often be noticed by putting a darker color as the base than the blend color and then doing vice versa. I do not recall which one exhibits the expected vs unexpected, but in these two cases one evaluates with the correct factor, and the other either swaps c1 and c2 or inverts the factor.

For instance if given the two colors:

color(#000 blend(hsl(199, 56%, 73%) 70% hsl))
color(hsl(199, 56%, 73%) blend(#000 70% hsl))

One of these colors will blend saturation and lums with the correct factor of 0.7 while the other will blend with 0.3. Again, I don't recall which off hand.

Steps to reproduce

  1. Use color blending as described above in the HSL namespace. Potentially in HWB as well as I assume the algorithm is similar, but I have not verified.

Expected behavior

If I am correct, I would expect the hue blending to blend in the opposite direction than it does currently, and I would expect that Saturation and Lums to blend consistently in the same direction instead of sometimes inverting the blend factor.

Actual behavior

Hue appears to blend in the wrong direction, and saturation and lums seems to blend in the wrong direction only sometimes.

Environment

  • Build: 3210
  • Operating system and version: Any
  • [Linux] Desktop Environment and/or Window Manager:Any
@wbond
Copy link
Member

wbond commented Mar 3, 2020

There were two bugs in the hue LERP function I wrote. Unfortunately the tests I grabbed from the CSS spec used examples with 50% blending, so I didn't notice.

These should both be fixed in the next build.

@wbond wbond removed their assignment Mar 3, 2020
@facelessuser
Copy link
Author

Cool. That was also my issue during implementation, which it is why I was more "I think" instead of "I know". The CSS spec varies in vaugeness.

So you mind explaining what two bugs you found? I'm curious if my initial feelings were correct, and how I might adjust my approach to match Sublime's future behavior.

@wbond
Copy link
Member

wbond commented Mar 3, 2020

Sure:

  • The base and mixin colors were passed in using an opposite order of our other lerp() functions, this was missed since the tests all used a 50% factor
  • The algorithm I used adjusts the factor to 100% - factor if the base hue channel was greater than the mixin (along with swapping the hue channels between the two colors and negating the delta). This factor adjustment was being applied to the non-hue components also. Thus depending on the hue and distance between the colors, sometimes the non-hue components would be interpolated incorrectly.

@facelessuser
Copy link
Author

Cool! That explains the saturation and luminance swap that happened only on occasion. That also explains why hue always seemed to be in the opposite order.

@wbond wbond added the R: fixed label Mar 27, 2020
@wbond
Copy link
Member

wbond commented Mar 27, 2020

This should be fixed in build 4069

@wbond wbond closed this as completed Mar 27, 2020
@facelessuser
Copy link
Author

Just tested in 4069. Works great. Thanks a lot @wbond !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants