facelessuser / coloraide

A library to aid in using colors
https://facelessuser.github.io/coloraide
MIT License
206 stars 12 forks source link

How to confine manipulated colors to sRGB space #415

Closed cfra closed 7 months ago

cfra commented 7 months ago

Hello, when using OKLCH lightness to adjust an rgb color, the resulting color for some cases contains negative coordinates:

In [48]: Color('#FECE00').set('oklch.lightness', 0.75)
Out[48]: color(srgb 0.84223 0.65716 -0.23291 / 1)

I guess this is to be expected because, as far as I understand it, sRGB cannot represent all coordinates representable by the OKLCH color space.

However, I am wondering if there is a way to ensure the resulting color is limited to the nearest neighbor in sRGB color space.

cfra commented 7 months ago

I /believe/ that the proper solution is to call color.convert('srgb', fit=True).

Leaving this open though to a) have this confirmed or corrected and b) maybe discuss whether it could be useful to extend the documentation about this behavior, currently I just found this note, which did not directly point to a solution:

Indirect channel modification is very useful, but keep in mind that it may give you access to color spaces that are incompatible due to gamut size.
facelessuser commented 7 months ago

Yep, we have a whole section covering how to "fit" or gamut map a color here.

In short, you can gamut map a color at any time by calling .fit() or .clip().

The only place gamut mapping happens automatically is when you serialize the color to a string via .to_string() and the color is bound to a specific gamut, but that can be disabled via .to_string(fit=False).

In addition, we do provide the convenience .convert() return a color within gamut mapped by enabling it via .convert(space, fit=True), or by passing in the gamut mapping approach via a string or a dictionary object that sets the gamut mapping options. This only works if the color space you are converting to is bound to a gamut.