Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Add perceptual colour spaces #354

Open
pnorman opened this issue Jun 4, 2014 · 15 comments
Open

Add perceptual colour spaces #354

pnorman opened this issue Jun 4, 2014 · 15 comments

Comments

@pnorman
Copy link
Contributor

pnorman commented Jun 4, 2014

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@1b97c3f

Cross-reference gravitystorm/openstreetmap-carto#599
gravitystorm/openstreetmap-carto#564 (comment)

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

@pnorman
Copy link
Contributor Author

pnorman commented Sep 24, 2015

If coded, would the maintainers be interested?

@tmcw
Copy link
Contributor

tmcw commented Sep 24, 2015

Yep.

@pnorman
Copy link
Contributor Author

pnorman commented Dec 8, 2015

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

@nebulon42
Copy link
Collaborator

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
Copy link
Collaborator

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:

  • The colour object which currently holds colours in RGB should know if its colour is defined in a perceptual colour space or not.
  • The colour transformations should use the colour space in which the colour is defined instead of converting it to HSL for transformation. This would also ensure backwards compatibility.
  • Additionally there could be lightenp, darkenp or similar named colour functions that convert a colour into perceptual colour space before transforming it. This would support the latter use case mentioned above.

@nebulon42
Copy link
Collaborator

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 colour object knows if it is a perceptual colour or not
  • internally colours are no longer represented in rgb but in hsl (or husl) values
  • colour functions like lighten, darken operate on the respective colour space (internally they only manipulate hsl or husl values)
  • there are lightenp, darkenp, ... functions to force a colour into perceptual colour space
  • hue, saturation, ... always return the value of the (converted) non-perceptual colour
  • huep, saturationp, ... always return the value of the (converted) perceptual colour
  • the mix function operates on perceptual colours if there is at least one perceptual colour in its parameters

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
Copy link
Contributor Author

pnorman commented Dec 15, 2015

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
Copy link
Collaborator

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 - gka/chroma.js#86

@nebulon42
Copy link
Collaborator

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
Copy link
Contributor Author

pnorman commented Dec 17, 2015

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
Copy link
Collaborator

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
Copy link
Contributor Author

pnorman commented Dec 17, 2015

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
Copy link
Collaborator

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
Copy link
Contributor Author

pnorman commented Dec 19, 2015

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

@nebulon42
Copy link
Collaborator

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

@nebulon42 nebulon42 reopened this Dec 6, 2016
@nebulon42 nebulon42 removed this from the v0.16 milestone Dec 11, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants