Color Choosing Paradox, also Warmer vs Cooler

Could you point me at the code where you undertake these LAB corrections? I’d be very interested in seeing how you do it.

I have post a file “color.txt” , 17 days ago
I think there is all need to understand: LUT creation, usage, etc.
The main(s) function(s) are “Labgamutmunsell” and “AllMunsellLch” and “MunsellLch”

Jacques

So I’ve been fooling around a bit with python-colorspacious, and jammed it into MyPaint to do some experiments. Adjusting lightness and colorfulness in realtime with CIECAM02 and various illuminants can be pretty interesting and (hopefully) somewhat useful.

Here’s Blue adjusted via the axes of +lightness and -colorfulness with D65 on the left and A on the right. I suppose it shouldn’t be that surprising that “A” looks quite a bit like a sunset, since illuminant A is pretty close to sunset as far as Kelvin goes (2856K vs 3200K):

Screenshot%20from%202018-01-27%2021-26-07

and here’s Red adjusted with D75, D55, and A from top to bottom, only adjusting the colorfulness:
Screenshot%20from%202018-01-27%2019-31-48

I made a couple videos in case it helps
https://www.youtube.com/watch?v=FOwTDlGzyMY
https://www.youtube.com/watch?v=6Sh4Zu0LBpQ

1 Like

Observe the Hue and RGB columns.

Source: Standard illuminant - Wikipedia

I’d like to add some others from this table, or even a custom option. Even after seeing this table, it still surprises me to get a rich color from “A” when fully desaturating a color. It’s just odd, even though it totally makes sense (the white point is the “grey/white/black” point).

I’m trying to implement channel-locked color pickers but having a really annoying problem gamut mapping the color back to within-gamut. Say I take a blue color and convert that to CIECAM JCh with python-colorspacious:

from colorspacious import cspace_convert
cspace_convert((0,0,1), “sRGB1”, “JCh”)
array([ 21.074306 , 90.51466637, 257.9160105 ])

then I change the hue and convert that back to sRGB:

cspace_convert((21.074306 , 90.51466637, 0), “JCh”, “sRGB1”)
array([ 0.57352534, -0.3367575 , 0.26130328])

Now I have a negative value and want to perform gamut mapping to get back to sRGB. LittleCMS should do this but I can’t seem to find any way to use it from within python. PIL Image/ PIL ImageCMS doesn’t seem to support floating points for more than one channel. So, not sure how I can do CM with an Image object without 3 channels… dohh

im=PIL.Image.new(“F”, (1,1), color=(0.5, 0.5, 0.5))
Traceback (most recent call last):
File “”, line 1, in
File “/usr/lib/python2.7/dist-packages/PIL/Image.py”, line 2063, in new
return Image()._new(core.fill(mode, size, color))
TypeError: a float is required

I haven’t gotten around to ciecam02 even though I have been meaning to do so. This stuff takes me a lot of time to digest because I am no artist, color, photo, math or dev wiz. ATM, I still have the wrong python installed lol. Anyway, at one point, I bookmarked this (see examples), which might be relevant to this thread.

1 Like

That might be exactly what I need, actually (thanks!!). I wonder if that gamut.limit function is equivalent to a perceptual rendering intent or a relative chromatic, etc.

Now I just wish it was already ported to python.

Edit: Now I’m not quite sure if the gamut.limit function is exactly what I want to do. In my example, I want to take a Blue sRGB(0,0,1) and manipulate it to the nearest color with CIECAM hue angle 0. I don’t want to interpolate or modify h-- only J and C. That is, I want the resulting hue to be 0 and J and C adjusted if necessary to stay within sRGB. I suppose my assumption is that all hue angles are going to be possible as long as the illuminant is somewhere within the sRGB locus.

edit. It dawned on me that really Lightness and Hue are the two dimensions that are always going to be possible, and it is only Chromacity that is going to have to be adjusted to stay within the sRGB gamut. So, I should be able to build a loop where CIECAM—>sRGB and reduce chroma some small amount between loops until all sRGB values are 0-1. Once within a certain small threshold I could then clip it to account for rounding errors. edit: I guess there is a name for this kind of thing: Newton’s method.

I wonder what happens if you make the illuminant sRGB(0,0,1) (XYZ[0.18049265, 0.0722005 , 0.95049713])? Does that make a lot of hue angles out-of-gamut? Or, does that simply mean that a lot of hue angles are identical to sRGB(0,0,1) but perfectly valid?

Getting back to the red apple example… if I paint a red apple under a warm light, it seems that as the apple turns to face the light the color is going to be desaturated by the illuminant. So, choosing a warm illuminant (like A) might be very helpful to get this gradient. The shadow side of the apple must also have an illuminant if we are to see it, obviously. This is where it gets more confusing. In my red gradient example above, the saturated reds are sRGB red colors, so I suppose that implies a D65 illuminant. If I painted a red apple using this gradient, would that imply that the ambient/shadow side is a D65 light source and the lit side is an “A” light source? image

So I’m still doing naive clipping of sRGB values, but already the gradient generator is looking pretty nice. Here’s a couple examples, top to bottom order is RGB, HSV, HCY, then CIECAM (JCh w/ D65):

image
image

That first step from blue towards yellow seems pretty harsh on the CIECAM, but I’m hoping that is because of the clipping RGB.

edit: I implemented a naive gamut squishing logic. I think it looks a bit better:
image

I think I’ve wrapped my brain around this a bit more, and it has everything to do with the illuminant. I may be regurgitating a few things @Elle has already said, so bear with me.

In order to make any color “warmer”, we need an illuminant that is a warm color, and then we can just “desaturate” our color and it will progressively become “warmer”. Likewise, in order to make any color “cooler”, we need a cool illuminant and then, again, desaturate our color.

I think the specific term is “Excitation Purity”, Colorfulness - Wikipedia. Basically the distance from your arbitrarily set white point, which is your illuminant.

I’m going to add another “picker” thingy to MyPaint so that you can pluck the illuminant from the canvas. My thinking is that if you’re painting a picture you can define your light sources either in the painting or off to one side. Then you can swap illuminants whenever you want and adjust your brushcolor accordingly. We’ll see how that goes.

Ok I’ve got an “illuminant picker” thingy working and it is kind of interesting. Here’s a little demo:

image

So I started off with that violet color on the first flower in the middle. After I painted the center, and “picked” that color as the illuminant and then increase the chroma just a bit. Then I rotated the hue and drew the petals. So, for a cool illuminant, all the hues are cool and the hues were limited to one side of the color wheel, just like a limited palette. This was keeping CIECAM J+M static and only adjust the hue.

As I progressed from flower to flower, I picked a new illuminant for each center and adjusted the chroma a bit (so that the hue stayed on one side of the color wheel), then rotated the hue around and tried to guess which hue was most “north/south/east/west” of the whitepoint, which was difficult because the whitepoint was not the center of the HSV wheel but some point elsewhere. The only way to identify the center was to hold down the rotate hue button and visualize the circular shape it was making. Sometimes the shape is very oblong or erratic depending on the illuminant.

1 Like

Here’s an example of a single “hue” (that blue color on top) modified via lightness and colorfulness (CIECAM), with a very warm orange illuminant. It really is pretty neat, although it turns everything upside-down… lightening or reducing the colorfulness of the blue brushcolor moves it towards the orange illuminant… whilst darkening or increasing colorfulness moves it towards the original blue. Does this make sense? I’m not sure, but it is pretty lovely to play with.

1 Like

Hi @briend - your new color code makes awesomely lovely colors with artistically valid uses. As far as conceptually what the code is actually doing, I’m still confused about how the paint swaths turn from blue to orange back in post 55. I understand that “A” is much warmer than “D65”, but I don’t have any clue how mathematically you are producing these results. The fact that you can manipulate the CIECAM02 equations is very impressive - to me those equations are not easy to figure out. And python is not a language that I can read.

1 Like

Thanks @Elle! I can’t say I understand the CIECAM equations at all, I’m “cheating” by using the colorspacious library: http://colorspacious.readthedocs.io/en/latest/reference.html#specifying-colorspaces (it seems this library is limited to sRGB? sigh). Maybe one day I’ll try the really spiffy-looking colour-science library: http://colour-science.org/

I guess for the blue–>orange–>white behavior it seems to make sense if you consider that reducing the colorfulness or making a surface color lighter both imply that you’re going to see more of the illuminant color. I’m still a bit foggy as to why it goes to white eventually, but I read somewhere that with enough intensity all colors turn white because our visual system is peaked out on all channels (or something to that effect).

Now I’m trying to figure out if it makes sense to mess with the gui and the picking of colors from the palette. Right now it is a bit odd in that it lets you choose impossible colors for a given illuminant. Normally with D65 the range of lightness and colorfulness is between ~0-100 and ~0-40 for any given hue. But things get wonky with extremely colorful illuminants. Say I make my illuminant a pure sRGB blue (0,0,1). It should be pretty hard to get a pure yellow surface color, right? Nope, if I pick yellow from the palette, it will happily paint with that color. Internally it computes a ridiculous set of ciecam attributes to accommodate the color-- a lightness of 12,000 and colorfulness of 3,000. I guess that might be totally rational-- in order for an object to appear bright yellow under a pure blue light it would have to be super-natural?

The other thing I just noticed is that the blue illuminant is the most extreme example, any other colors for an illuminant are not nearly as bad, only increases the lightness range to 0-400, and colorfulness 0-300 or so. I suppose that means that a blue light is possibly the worst light for color reproduction?

CIECAM02 is not easy… You may also have a look at the ciecam02 functions in RawTherapee. They are quite mature now and solve some special cases which are not solved in the original equations. I (and also @jdc) can point you to the relevant part of the source in case you’re interested.

Edit: for example this case is not handled in the original ciecam02 implementation:

Ingo

Thanks @heckflosse. @jdc sent me a code link as well in regards to gamut mapping. I’m afraid my programming skills are not quite there to be able to understand much of this; if it’s not a python library I can import it’s probably beyond my grasp for now. Fortunately I haven’t really noticed anything horribly broken with python-colorspacious library, other than it leaving me with out-of-gamut sRGB results which I think I’ve solved more or less.

Ciecam02 began to be implanted in RT in 2012 … with many problems related to the gamut of Ciecam

Since working on one hand on these limitations and on the other hand on an optimization, we have Ingo @heckflosse and me, made ciecam02 usable in a majority of cases

Last fall, @Elle Elle Stone , proposed to add the “symetric” mode which allows in particular to fully use the chromatic adaptation functions. I have create a branch “cat02wb” where this adaptation is used complementary to WB (whithout using GUI of Ciecam, and limited to cat02).

This adaptation has 2 interests:

1 - catching the errors of WB, because it is obvious that except for special case the WB is a kind of black box, where the real WB comes out of the riddle rather than science
2 - to use ciecam as it is intended, that is to say, to adapt the colors as do our eyes and our brain and not only in a Cartesian way, for example taking a picture in tungsten light (2856K) for example in a shop.
If you look the photo, and what your eyes seen, without Ciecam you will see a big differences, with Ciecam virtually no.

Of course, Ciecam is not limited to cat02…

One of the last limitations of ciecam02 is the nature of some illuminants.When the CRI is bad (less than 90 I think, for example Illuminants Fluorescent) the results will be bad, and I think there is no solution except not using these illuminants.

jacques

Thanks @jdc for the background info. Do you know if the problems you’ve had with ciecam02 are supposed to be resolved with ciecam16?

Does any software support CIECAM16? Honest question.

I saw this thread: CAM16[-UCS] · Issue #10 · njsmith/colorspacious · GitHub
But I can’t access the journal for free. Python Colour-Science (Colour) just added CAM16 a few months ago it seems.

I did a test where I took one palette and adjusted it for various standard and custom illuminants. Do you think this looks about right?

1 Like