mapbox / carto

fast CSS-like map stylesheets
https://cartocss.readthedocs.io/
Apache License 2.0
652 stars 129 forks source link

Add perceptual colour spaces #354

Open pnorman opened 10 years ago

pnorman commented 10 years ago

https://github.com/mapbox/carto/blob/master/lib/carto/functions.js#L35-L55 defines rgb, rgba, hsl, hsla, etc

It would be useful to be able to input colours directly into carto in a perceptual colour space like Lab, Luv, and Lch. It makes it much easier to keep some parts of how the colour looks constant while adjusting others.

Currently this has to be done manually with an external program, which is what I did for gravitystorm/openstreetmap-carto@1b97c3f2414af60d5a4540c778d39fd0aea4e0d3

Cross-reference gravitystorm/openstreetmap-carto#599 https://github.com/gravitystorm/openstreetmap-carto/pull/564#issuecomment-44768993

I'll probably take this on, but I wanted to get the links together in one place.

pnorman commented 8 years ago

If coded, would the maintainers be interested?

tmcw commented 8 years ago

Yep.

pnorman commented 8 years ago

Note to self: https://github.com/gka/chroma.js

nebulon42 commented 8 years ago

Maybe having a look at http://www.husl-colors.org/ could be beneficial?

Edited: LCh has the problem that it is possible to specify colours that cannot be represented in RGB. Thus, specifying colours can result in a trial and error process. If you would clamp the colour to RGB bounds you could end up with some different colour you wanted to specify. HUSL tries to circumvent this problem (with the drawback of a less well defined chroma component).

nebulon42 commented 8 years ago

Another thing I thought about is regarding colour functions. While anyone who defines perceptual colours is less inclined to use colour transformations outside this perceptual colour space the other way round could be a possible use case when e.g. you have a RGB colour but want to saturate it in the perceptual colour space.

So I think it could work like that:

nebulon42 commented 8 years ago

In this branch you find a first working version that adds husl perceptual colours to carto: https://github.com/nebulon42/carto/tree/perceptual-color All internal tests are passing and I have rendered a map with it, but it has not been extensively tested.

It has the following characteristics:

The similarity of hsl and husl in terms of values keeps the representation simple. The perceptual colour space is kind of sticky. Once you are in it is difficult to get out again, but I thought this would be no problem.

The branch builds upon the changes @pnorman has proposed in #418. There is no PR yet, because I'd like to get some feedback first and I have to extend test coverage. Also I don't know how much work @pnorman has already invested in something similar.

pnorman commented 8 years ago

I'd rather get Lab and Lch in before considering non-standard ones like husl. Or hcl instead of lch, since they're the same colourspace in a different order.

The similarity of hsl and husl in terms of values keeps the representation simple. The perceptual colour space is kind of sticky. Once you are in it is difficult to get out again, but I thought this would be no problem.

Something like chroma.lch(80, 40, 130).rgb(); seems to work fine for me.

The branch builds upon the changes @pnorman has proposed in #418. There is no PR yet, because I'd like to get some feedback first and I have to extend test coverage. Also I don't know how much work @pnorman has already invested in something similar.

I have follow-up stuff, but I want to get #418 merged first. I'm also wondering if it makes sense to change how colours are stored to use chroma.js objects.

nebulon42 commented 8 years ago

How does chroma.js solve the problem that in Lch you can specify colours that cannot be represented in RGB? edit: I think this issue might be related to that problem - https://github.com/gka/chroma.js/issues/86

nebulon42 commented 8 years ago

I have tried it now with chroma.lch(97,29,250).rgb() which is no RGB colour. chroma.js clamps the colour to rgb(190,254,255). The same colour converted with python-colormath yields rgb_r:0.7841 rgb_g:0.9951 rgb_b:1.1717 which corresponds to rgb(200,254,299). I don't know why there is a difference in the red value.

I think clamping might be problematic as you would end up with a different colour than you wanted to specify.

pnorman commented 8 years ago

I think clamping might be problematic as you would end up with a different colour than you wanted to specify.

But this is true for saturate and other functions, which can also result in a colour outside sRGB. e.g. saturate(#0000ff, 25%) is (iirc) outside the sRGB gamut. That colour is probably also not real, but there are other examples which are out of sRGB gamut but real colours.

Another related question is, when do you convert bring a colour in gamut with colour mixing steps. desaturate(saturate(x, 10%), 10%) is a no-op if you bring the colour into gamut at the end, but quite different if you bring each step into gamut.

nebulon42 commented 8 years ago

But this is true for saturate and other functions, which can also result in a colour outside sRGB.

Strictly speaking, yes. I think the difference lies in the design workflow and is related to what you can expect and if you get feedback. When you lighten a colour too much you expect it to become white, if you darken it too much you expect black. Your example of saturating a fully saturated colour even more should leave the colour unchanged.

If we restrict the discussion to colour definitions for a moment then you cannot leave sRGB with HSL, you also cannot leave it with HUSL (because it was designed exactly out of this reason). But you can with Lch. I think this is a problem and I experienced it myself when working with the road colour generation script of osm-carto.

When I was experimenting with shield colours I sometimes got an error that some Lch colour was outside of sRGB. I then had to go back and revise the definitions, so that I got a working colour again. This is what I meant with trial-and-error. Even if this would be acceptable for the workflow there is no good way for Carto to warn the user if a colour specification is outside of RGB. This is what I meant with feedback.

Don't get me wrong. I do not advocate HUSL in particular, I just think that a perceptual colour space (based on Lch) where you cannot leave sRGB when defining colours would be convenient. HUSL has also drawbacks e.g. the chroma component is distorted. You cannot saturate some colours as much as others.

Another related question is, when do you convert bring a colour in gamut with colour mixing steps.

I think that a colour should be kept in a particular colour space as long as possible and being converted to RGB at the end. I would expect your example definition to cancel itself out. The equal range of HSL and HUSL for a non-perceptual and perceptual colour space is quite convenient here IMO.

pnorman commented 8 years ago

Another related question is, when do you convert bring a colour in gamut with colour mixing steps.

I think that a colour should be kept in a particular colour space as long as possible and being converted to RGB at the end.

The example I used could be entirely in RGB. There's a difference between colour spaces and being in gamut. Stepping into colour space theory, any three primaries where none of the primaries is a mixture of the other two can be used to represent any colour we can see, but may result in something that cannot be physically realized with light sources (e.g. has negative components)

If you look at Lab colour, negative components are normal there.

nebulon42 commented 8 years ago

I have added test coverage for all colour functions in my branch and uncovered/fixed some implementation issues. With regard to the ongoing discussion: @pnorman would you mind me opening a PR?

pnorman commented 8 years ago

I suggest PRing against my PR, but I'd rather get #418 merged first

nebulon42 commented 7 years ago

There have been claims that no perceptual colour spaces have been implemented in carto. So I reopen the issue and explicitly welcome pull requests!