ColorAide is a pure Python, object oriented approach to colors.
```python
from coloraide import Color c = Color("red") c.to_string() 'rgb(255 0 0)' c.convert('hsl').to_string() 'hsl(0 100% 50%)' c.set("lch.chroma", 30).to_string() 'rgb(173.81 114.29 97.218)' Color("blue").mix("yellow", space="lch").to_string() 'rgb(255 65.751 107.47)' ```
ColorAide particularly has a focus on the following:
Accurate colors.
Proper round tripping (where reasonable).
Be generally easy to pick up for the average user.
Support modern CSS color spaces and syntax.
Make accessible many new and old non-CSS color spaces.
Provide a number of useful utilities such as interpolation, color distancing, blending, gamut mapping, etc.
Provide a plugin API to extend supported color spaces and more.
Allow users to configure defaults to their liking.
With ColorAide, you can specify a color, convert it to other color spaces, mix it with other colors, output it in different CSS formats, and much more!
https://facelessuser.github.io/coloraide
MIT
We should consider the possibility of adding the CSS approach to powerless channels when interpolating. In general, the concept is pretty straightforward. When interpolating, if lightness creates a white or black color, chroma/saturation becomes powerless during the interpolation. It's kind of like how hue is generally powerless and gets undefined when chroma/saturation is 0 (or near zero). This would apply to Lab colors as well with lightness causing a
and b
to become powerless. The big issue is with colors like CAM16 JMh and HCT. Neither of which have easy to predict achromatic responses and could cause complications in how things get sanely calculated for them.
Currently, linear
mode for interpolation is very much like CSS. Two colors are evaluated at a time, and undefined values are evaluated only between those two colors. This is okay, and helps us provide a CSS like experience, but consider the following examples.
We want to take a list of colors and set all the lightness to undefined except the start and end colors which will have 0 and 1 respectively for lightness. The second case does this for alpha. The results are not great
But in our spline interpolations, we had to create a new undefined handler as 4 colors must be used at one time for the splines, so just evaluating interpolation with the context of just two colors doesn't work (unless you only have two colors).
So taking the same undefined handling that we use in splines and applying it to linear piecewise interpolation, we are able to have these same two examples give us a nice intuitive response. In both the lightness case and the alpha case, the colors in between that do not have those values defined have that component interpolated continuously throughout all the colors between.
It's a far more practical approach to undefined values. We already use it in splines, so we'd just create a linear version using this approach. Those who want to use a more CSS-faithful approach can use the traditional piecewise approach.
While not the most pressing issue, we should work on a shortcut to help HCT get back to CAM16 faster.
Currently, the whole thing uses bisecting to figure out the best J to use that satisfies the current L*Ch which is actually what HCT is. You can't get back to CAM16 without the right J, and J isn't used in the calculation for L* since it's from a completely different color space. Bisecting alone isn't nearly efficient enough.
The whole color space is weird in that since. It tries to take the best of CAM16 and Lab and magnifies the worst parts of CAM16, the slow conversion.
Originally we used the starting color as the output space if output_space is not specified. In retrospect, this is a bit too "magical". It is also a more expensive default as you have to convert in the species space and then convert back. If you want to keep in the same space, specify it.
This will be in 2.0 as it will be a breaking change.
So, I've realized a couple of things after running with CAM16 and HCT for a bit. We need to make some adjustments. When working through CAM16 and the derived HCT, I had not realized how important the hue actually is with achromatic colors.
With most of the LCh-like color spaces, when converting to an achromatic color, the actual hue value doesn't matter that much simply because chroma is so small. When chroma is small, like very small, the impact hue has in the calculation is so minimal, you can get away with ignoring it. But with CAM16 (and HCT which is derived from it) the chroma at times can be quite large. In the SDR region, it can get up to 2.x, maybe bigger. This value is significant enough that the hue actually has some impact.
We calculated the achromatic response in CAM16 and HCT but that response exists mainly in the 209.5x hue range. In other hue ranges, the colors are not quite achromatic, and the colors below that line, aren't necessarily achromatic as well.
For instance, consider this color in HCT, we cannot reproduce it.
```py
hct = Color('#80807f').convert('hct') hct color(--hct none 1.805 53.557 / 1) hct.convert('srgb').to_string(hex=True)
```
So, what does this mean? It means we need to restrict the achromatic response to the given colors with a hue of 209.5x
, give or take. It means that we need to restrict the achromatic none
replacement as close to that response line as possible, and we should not assume colors below that response line are achromatic.
This has slightly bigger implications. Right now, we replace none
with zero in almost every color space, but moving forward, that will not be the case. CAM16 JMh and HCT will both need to replace none
with something close to the actual hue needed. I already have this roughly working locally:
```python
hct = Color('#80807f').convert('hct') hct color(--hct 181.65 1.805 53.557 / 1) hct.convert('srgb').to_string(hex=True) '#80807f' ```
And then when we use an actual achromatic color:
```python
hct = Color('#808080').convert('hct') hct color(--hct none 1.8977 53.585 / 1) hct.to_string() 'color(--hct 209.54 1.8977 53.585)' hct.to_string(hex=True) 'color(--hct 209.54 1.8977 53.585)' ```
The people over CSS are debating dropping the gamut map requirement for certain spaces like HSL and such in order to defer gamut mapping to a later spec. Generally, HSL can round trip fine, but when dealing with interpolation, colors can give negative saturation which can make interpolation messy.
I actually don't have a problem with removing the requirement to gamut map with interpolation, but I'd at least want to tighten up the window for on some of the spaces when the lightness is very close to 1 (or 100 depending on the space's scaling). Basically, for some of the conversions, if lightness is very close 1 or 100, we count saturation as 0. This helps provide sane behavior during interpolation. It slightly affects roundtripping when very close to white, but the overall benefit that is gained when working with the colors outweighs the negative I think.
Anyways, there are couple of color spaces who's range won't quite capture resolve crazy saturation with something like oklch(1 0 0)
. We can adjust that range to give good results. We also had some spaces where we forgot to add the flag to force gamut mapping anyways (hsi
).
Generally, we can mention that some spaces may give odd results if lightness exceeds the SDR threshold we put in place, and we can suggest that people should probably fit the inputs for best results if using a finicky interpolation space that is sensitive to SDR ranges.
hsl()
and hwb()
formats respectively will resolve to numbers in the range [0, 100]. These changes reflect the latest
changes in the CSS Level 4 Color spec.color(srgb 1-0.5.4)
should parse as color(srgb 1 -0.5 0.4)
.COLOR_FORMAT
is respected.a
and b
components.comma
and none
are enabled it could make undefined alpha values show up as none
in legacy CSS
format.cam16
and cam16-jmh
respectively.cam16-ucs
, cam16-scd
, and cam16-lcd
.hct
) which combines the colorfulness and hue from CAM16 JMh and the
lightness from CIELab.fit_lch_chroma
can set DE_OPTIONS
to pass ∆E parameters.achromatic_hue()
which will
return this specific hue if needed.rec2100-hlg
transform.rec2100-hlg
color space.rec2100pq
should have been named rec2100-pq
for consistency. It has been renamed to rec2100-pq
and
serializes with the CSS ID of --rec2100-pq
. This is likely to have little impact on most users.colors css python