gka / chroma.js

JavaScript library for all kinds of color manipulations
https://gka.github.io/chroma.js/
Other
10.18k stars 547 forks source link

HSV/HSL values are not preserved #208

Open lazd opened 5 years ago

lazd commented 5 years ago

Run the following in the console on the docs page at https://gka.github.io/chroma.js/

Hue changes when S and V change

> chroma({ h: 3, s: 0.20, l: 0.20}).get('hsl.h')
2.9999999999999893
> chroma({ h: 3, s: 0.16, l: 0.16}).get('hsl.h')
4.615384615384631
> chroma({ h: 3, s: 0.25, l: 0.25}).get('hsl.h')
1.8750000000000067
> chroma({ h: 3, s: 0.5, l: 0.5}).get('hsl.h')
2.834645669291342
> chroma({ h: 3, s: 0.1, v: 0.1}).get('hsv.h')
3.0000000000000293
> chroma({ h: 3, s: 0.2, v: 0.2}).get('hsv.h')
2.99999999999999
> chroma({ h: 3, s: 0.16, v: 0.16}).get('hsv.h')
2.999999999999997
> chroma({ h: 3, s: 0.25, v: 0.25}).get('hsv.h')
3

Expected: In all of the above cases, h should be 3.

S/V change when bounds are reached

> chroma({ h: 20, s: 1, v: 0 }).get('hsv.s')
0

Expected: s should be 1.

Environment

Chroma 2.0.2 Chrome Version 71.0.3578.98 (Official Build) (64-bit) on macOS 10.12.6

gka commented 5 years ago

this is due to the fact that internally, chroma.js is rounding the RGB values after it converted them from hsl. this is fine if you're planning on using the color as RGB but we're losing precision if the plan is to go back to hsl. I need to check back with older versions of chroma, but I think it's always been like that. https://github.com/gka/chroma.js/blob/master/src/io/hsl/hsl2rgb.js#L31

chroma({ h: 20, s: 1, v: 0 }) is pure black #000000, and while you could argue for both, chromajs decided that black is, like white and gray, a hue-less color, hence the saturation is 0.

lazd commented 5 years ago

@gka thanks for the reply. I understand the reason for the s: 1, v:0 transformation.

I tested 1.4.x of Chroma and found the same issues, but I did not test older versions.

What are you thinking should be done to address this, if anything?

As for my use case: Right now I am using Chroma to read in colors provided by the user, but I'm also using it after the fact to modify the colors based on picked S/V values. You can see my prototype here: https://codepen.io/lazd/pen/jQdGVq?editors=0010

  1. Try manipulating the S/V "ColorArea" picker inside of the ColorWheel -- you can see the wheel's hue change (H not preserved)
  2. If you move the handle to the lower right of the ColorArea, you'll see it snap to the lower left (S/V not preserved)
  3. If you move the handle in the lower left quadrant of the ColorArea, you can see it jitter about (changing s can affect l, for example chroma({ h: 0, s: 0.106, l: 0.16 }).set('hsl.s', 0.9).get('hsl.l') => 0.1588235294117647).

Now part of this is because of how I implemented things (manipulating HSV, but displaying as HSL), but part of it is because of Chroma's not preserving the H/S/V that I am manipulating. I'd appreciate any suggestions you have as to how to handle this, as I'm rather green (pun intended) when it comes to manipulating color across spaces.

gka commented 5 years ago

I see. the only way for this to be fixed in chroma.js would be to store RGB channels as floats instead of rounding to bytes, which essentially means storing more precision than true color (24bit).

I think what I would do is to store the active HSL coordinate somewhere outside of chroma.js.