color.js
color.js copied to clipboard
`lighten()` issues
I noticed that the library has a lighten
and darken
method. It doesn't seem to be documented in the API, but it does seem to be used in the docs.
let color = new Color("lch(50% 50 10)");
color = color.set({
h: h => h + 180,
c: 60
}).lighten();
Maybe it is experimental, or maybe it was forgotten to add it in the API page, but regardless, I figured I'd give some feedback.
The first issue is that you can darken white
, but you cannot lighten black
. Maybe this is done on purpose to match CSS filters such as brightness which, for whatever reason, also do not lighten black
. If so, then I guess the rest of this can be ignored, but if it was truly desired to have a method that lightens and darkens any color, then a modification should probably be made.
Currently, the functions look like this:
export function lighten (color, amount = .25) {
let space = ColorSpace.get("oklch", "lch");
let lightness = [space, "l"];
return set(color, lightness, l => l * (1 + amount));
}
export function darken (color, amount = .25) {
let space = ColorSpace.get("oklch", "lch");
let lightness = [space, "l"];
return set(color, lightness, l => l * (1 - amount));
}
The logic fails to lighten black
simply due to the fact that anything multiplied by 0 is going to be zero, but this could be modified to something like the following though and work to lighten and darken any color within the SDR range of lightness. The results should be pretty much the same with the addition that black will no longer break.
export function lighten (color, amount = .25) {
let space = ColorSpace.get("oklch", "lch");
let lightness = [space, "l"];
return set(color, lightness, l => l + amount * (1 + l));
}
export function darken (color, amount = .25) {
let space = ColorSpace.get("oklch", "lch");
let lightness = [space, "l"];
return set(color, lightness, l => l - l * amount);
}
The one caveat is that you cannot lighten a color when l == 1
and darken a color when l == 0
. I'm also not sure that lightening and darkening beyond this range even make sense when using an SDR lightness range as used with OkLCh.
You can still lighten a color with a lightness well below 0 and even darken a color with a lightness beyond 1. Even with HDR colors, I don't think there would be a need to darken below zero, but if you absolutely wanted to allow lightening beyond the range of OkLCh's max of 1, then you could maybe allow an HDR mode which would use a different color space such as JzCzhz which has a much greater range (or some other space), or just allow raising the max lightness beyond 1. Or maybe just have a separate HDR lighten/darken 🤷🏻.
If desired, you could also have the function always clamp lightness beyond SDR range to zero or one if the intention is to work only within the SDR range.
Anyways, just thought I'd give some feedback and at least maybe spark a discussion about the approach. But like I said, if black being a color you cannot modify is exactly what is desired, then feel free to just close this issue.
I do want to rephrase things slightly, it is not exactly the same. While some values do evaluate the same, what is being done is very different. I guess currently, lightening/darkening is done by a percentage of the current value, while what I am suggesting is increasing/decreasing by a percentage of the distance to the maximum lightness or minimum darkness. So scaling is a bit different.
These two functions are experimental and preliminary, and undocumented because that was a first cut (and seen as unsatisfactory).
It all depends on what the user expects. There are three main approaches:
- Add or subtract a constant offset, which slides the whole range up (or down) while keeping the relative proportions. The part at the top (or bottom) clips so the end of the range all maps to the same lightness.
- Multiply or divide by a factor, which pins black at black and stretches or shrinks the range. There is clipping for this lighten algorithm, too.
- Use some sigmoid or gamma function so that black and white are pinned but the midrange colors get lighter (or darker). The sigmoid function has an issue where colors near the top actually get a bit darker when lightened, which is counter-intuitive. The gamma function is commonly used in SDR TV to give an overall lighter or darker picture (and in HDR TV to cope with both increased ambient luminance and increased peak luminance).
There are other, less common, options which I have not seen used in practice. For example to lighten, divide by some factor (thus pinning black and darkening white) then shift up by that same factor (restoring white to white, and raising black).
These two functions are experimental and preliminary, and undocumented because that was a first cut (and seen as unsatisfactory).
Yep, this is what I was guessing.
Doing some more research, what I suggested turns out to be what SASS does in their scale-color
approach, just focused on lightness.
It all depends on what the user expects.
Yeah, I guess that is indeed the real question. Personally, I think the layperson would probably get confused if lighten could not lighten black, but I guess if it is documented as such, they'd get used to it, but that does seem like a confusing default.
I do wonder if something as simple as tinting and shading a color is really all a user would need even if it technically isn't just adjusting the lightness, but instead interpolating towards white or black.
I played around with these two options. I tried what seems equivalent to the SASS scaling approach and compared it to simply tinting or shading a color. The below shows tinting then scaling. When scaling, since I was using Oklab to adjust the lightness, it required me to gamut map in OkLCh as well or you could get some color shifts in things like blue. While not the same, they seem to yield similar results (at least in the small bit if testing I did).

Then with darkening

Anyways, it sounds like you are still deeply considering the approach to take, and that you are aware of the "pinning to black" issue / feature, which in the end, may or may not be by design in the final approach.