You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-color-5] color-mix to allow more than two colors? #4711
Because of 1., it has fallback language to say how to handle the percentages not summing to 100
This issue is to record the discrepancy. Do we want to allow multiple colors? Do we want to allow percent to be omitted (defaults to equal mix of each color?)
Yes, definitely, we should align with cross-fade()'s N-ary mixing. Otherwise mixing multiple colors requires doing some non-trivial math to reverse the desired final percentages into binary mixing amounts.
I have no opinion on "omit %s, assume equal" as a use-case, but feel strongly that we should align with cross-fade(), which allows exactly that.
Should the syntax then be like: color-mix(red 40%, blue 20%, pink 40%)?
Yeah as soon as I started thinking of multiple colors, it seemed like the percentages or other modifiers needed to be grouped closely with the colors they were affecting.
I think it would also be nice to have the option of omiting the percentage to denote an equal distribution
if the %s don't add up to 100% (over or under), normalize them to 100%.
if some %s aren't specified, add up what %s are, then distribute the leftover % to the unspecified ones equally (so mix(foo 50%, bar, baz) would be equivalent to mix(foo 50%, bar 25%, baz 25%)).
Reacted by Chris Lilley, Sebastian Zartner and Krzysztof Maczyński
I moved the colorspace to the front, as that's more standard practice when a function takes both options and a list (see the shape or gradient functions).
The color-adjusters used must be consistent within a specific colorspace; you can't adjust "hue" and "red" at the same time. The colorspace in play is inferred from the adjusters used, defaulting to lch if possible. A plain % is shorthand for applying all the relevant adjusters from that colorspace.
N-ary mixing is then applied per-adjuster, using the same logic as cross-fade for omitted adjusters (taking the leftover %, and distributing it equally among the unspecified items).
Reacted by Chris Lilley, Adam Argyle and Krzysztof Maczyński
Ah, sorry, actually the omitter logic is a touch more complicated - if you've specified any channel adjuster for a color, the omitted adjusters default to 0%. You only distribute leftover % if there's no adjuster at all.
That is, if we write color-mix(canvas, var(--bg-color) a(100%)) to apply the variable's alpha to the canvas color, we want the rest of the var's adjusters to default to 0% (so you use canvas for all the actual color channels).
As @svgeesus pointed out in #5277, before we dive into the specifics of how to make this work, we should first see if there are any actual use cases for multiple colors. @una@argyleink what use cases did you have in mind?
? mix against white then add some complimentary color for additional contrast
? lightening || darkening then cooling || warming
? fun color-mix(var(--c1), var(--c2), var(--c3))
Just a thought that might simplify implementation:
What if color-mix only took 2 colors and a percentage for the mixing?
i.e. if color-mix(red 60%, blue) means 60% red and 40% blue, then:
color-mix(color-mix(red 60%, blue), pink 40%) means 60% of the total is a mixture of 60% red and 40% blue, the rest of 40% pink. Overall values would be: 36% red, 24% blue, 40% pink.
This is mostly a discussion of multiple color mixing.
The TL;DR is: Outside of a completely new space for the purpose, n-color mixing needs to be a cascade, where we only mix two colors at any one time. Conveniently this also means mix percentages can apply to color(1) only. The previous color-mix() will always be color(2).
...And.... I just saw that una is suggesting something similar to what I wrote below. Doh me. At the end of the post though is an outline of some other color-mix() things, probably for separate issues.
RGB is not paint
In additive tristimulus color models (i.e. RGB), colors do not mix like "paint."
In an additive model like RGB, the more random colors you add together at once, the closer the result color is to an achromatic grey.
To create a means for color mixing of n-colors that functions in a useful and intuitive way is non-trivial, and also will be opinionated — oil pigments mix very differently than watercolors, which are different than printer inks, and different than...etc etc.
Tristimulus Mixing
In an sRGB monitor, only three "colors" are emitted. A blueViolet, a limeGreen, and a redOrange. These "primary colors" are not chosen to be at the peak sensitivity of either the L, M, or S cones, but chosen to be as far apart as practical so they each primary stimulates one of the L,M,S cones as "independently as possible" (which is harder than you might think).
Thus, all the monitor/system is doing is selectively stimulating the LMS cones to create a particular color sensation. When you see YELLOW on the computer monitor, the monitor is only emitting GREEN and RED, at the same time in close proximity. This stimulates the L and M cones in a manner similar to actual yellow, which wavelength-wise is "between" red and green.
In the example above, the yellow bar is nothing more than red and green at the same time and location. The red and green does not "mix in the air" and in fact the red and green do not mix on the eye's retina, but later through the neurologically based opponent color process and visual cortex color processing.
I.e.: it mixes only in your head
So then the question is how to predict a desired perceptual effect of mixing 2+ colors. And here again the answer is opinionated depending on the specific use case: is the goal to emulate natural linear light? Or the perception of tinting a pigment? Is the goal to affect the perceived brightness (Q)? Or the perceived lightness(J) or only the hue without affecting the readability contrast...
Each use case just mentioned needs a different approach. Just performing this in a usefully predictable manner with only two colors is already challenging. But there are plenty of use cases where an end result does need more than two colors mixed.
Use Case Examples
A simple example is the challenge of developing a dynamic color palette that maintains appropriate contrast values, and also properly accommodates the many focus or state changes in interactive content. That is, a button might be a color mix of two colors, but will also subsequently need an adjustment by mixing in a third color to indicate a state change.
Another example: A number of color-mix() colors are used throughout a page. There is the option to adjust the entire page, such as giving it a more sepia tone by adding the sepia adjust as another color mix to the other mixed colors.
Solution: A Cascade of Colors
A solution would be instead of attempting to mix n-colors all at once, is to cascade the color mix iterations, only doing two at a time... typically an artist will only be adding one bit of pigment at a time to a color mix, and doing so here should have more predictable and useable results. SO:
So, 30% aqua + 70% purple makes lavender, then 25% yellow + 75% lavender makes a less saturated and lighter lavender.
Percentages and adding up to 100%
If we limit to two colors at a time, then only one percentage value is needed, for color(1) only.
Possibly one percentage value per color(1) component, so if in LCh, 10% 40% 50% means putting the hue halfway between C1 and C2, but mostly disregarding C1's lightness, etc.
color-mix() Concerns Not Examined in This Post
Some things I didn't get into, but may discuss elsewhere include concerns such as these: (placing these here as reminders to me, or to stir some thoughts, or something)
Missing "mix" spaces??:
xyY
LUV and LChuv and Lsh
Jab & JCh
Separating working space from color property space
While it may be convenient to specify the color property in LCh, there are a number of legitimate reasons to perform a mix in a different space
needing to mix the color in a linearized space like XYZ or linear RGB
wanting to use the straight line interpolation of the LAB cartesian space
Similarly, a page might use hex values for colors normally, but want to perform the mix or blend in LCh or XYZ.
Missing linearizing for RGB spaces?
can RGB colors be linearized prior to mixing?
and then returned to sRGB?
Gamut mapping choices? For color-mix(), I didn't see choices for:
gamut mapping type (perceptual, absolute, sat, lightness, etc)
soft clipping,
range, or hi or low LIMITING
Fallback color
What happens if a color-mix() fails? Is there a way to specify the fall-back?
I think all of us (@una@svgeesus@argyleink and me) recently discussed this and resolved that since there is no obvious way that N-ary mixing should work and way too many gotchas, we should keep color-mix() simple and only allow two colors. Authors can always nest color-mix() calls to mix multiple colors, per what was suggested already.
If I do remember this correctly, we should probably close this.
@Myndex we don't need to do anything specific to "allow" color-mix() to be nested. That's how functions work, since it accepts <color> values and produces <color> values, it can be nested.
@Myndex we don't need to do anything specific to "allow" color-mix() to be nested. That's how functions work, since it accepts <color> values and produces <color> values, it can be nested.
Got it, as it is CSS, I wasn't certain if there was any issue — I have occasionally tried something in CSS that "seems like obvious syntax" but, uh, not...
As this is going to close... I had a brief outline of some other issues — are they dead now too? Or handled elsewhere? (before I compose another essay in a dark alley, LOL)
I had a brief outline of some other issues — are they dead now too? Or handled elsewhere?
In general, (and this is based on many years experience with mailing lists, before issue management was moved to GitHub) please resist the temptation to post long, expository, multi-issue essays in GitHub issues. Issues work best when each one is closely focused on a single issue. Unrelated side points are easily lost.
Outside of a completely new space for the purpose, n-color mixing needs to be a cascade, where we only mix two colors at any one time.
Yes, that is also the conclusion the editors came to.
I dud look into how 3D graphics handles a three-way color interpolation (for example, flat-shading a triangular mesh where each vertex has a color, and vertex normals are ignored). The algorithm proceeds by picking two colors, and interpolating them to produce the gradient along one edge. Then there is a second, again two-color interpolation step where the third color is interpolated with the color of a particular point along the gradient. So, two cascaded two-color interpolations.
Activity
tabatkins commentedon Jan 28, 2020
Yes, definitely, we should align with cross-fade()'s N-ary mixing. Otherwise mixing multiple colors requires doing some non-trivial math to reverse the desired final percentages into binary mixing amounts.
I have no opinion on "omit %s, assume equal" as a use-case, but feel strongly that we should align with cross-fade(), which allows exactly that.
[css-color-5] inline #4711
una commentedon Feb 6, 2020
Interesting to mix multiple colors! I like this idea as it relates to thinking of color like painting.
Should the syntax then be like:
color-mix(red 40%, blue 20%, pink 40%)?What happens if these colors do not add up to 100%?
I think it would also be nice to have the option of omiting the percentage to denote an equal distribution, i.e:
color-mix(red, blue)would mix red and blue by 50% eachcolor-mix(red, blue, pink)would mix each by 33.333% eachTo add on, if you only have one percentage, it could look like this:
color-mix(red 40%, blue)-- a 40% red and 60% bluecolor-mix(red, blue, pink 20%)-- a 40% red, 40% blue, 20% pinksvgeesus commentedon Feb 6, 2020
Yeah as soon as I started thinking of multiple colors, it seemed like the percentages or other modifiers needed to be grouped closely with the colors they were affecting.
I agree.
tabatkins commentedon Feb 8, 2020
It should work just like cross-fade():
mix(foo 50%, bar, baz)would be equivalent tomix(foo 50%, bar 25%, baz 25%)).tabatkins commentedon May 19, 2020
Specifically, the grammar should be:
I moved the colorspace to the front, as that's more standard practice when a function takes both options and a list (see the shape or gradient functions).
The color-adjusters used must be consistent within a specific colorspace; you can't adjust "hue" and "red" at the same time. The colorspace in play is inferred from the adjusters used, defaulting to lch if possible. A plain % is shorthand for applying all the relevant adjusters from that colorspace.
N-ary mixing is then applied per-adjuster, using the same logic as cross-fade for omitted adjusters (taking the leftover %, and distributing it equally among the unspecified items).
tabatkins commentedon May 20, 2020
Ah, sorry, actually the omitter logic is a touch more complicated - if you've specified any channel adjuster for a color, the omitted adjusters default to 0%. You only distribute leftover % if there's no adjuster at all.
That is, if we write
color-mix(canvas, var(--bg-color) a(100%))to apply the variable's alpha to thecanvascolor, we want the rest of the var's adjusters to default to 0% (so you usecanvasfor all the actual color channels).svgeesus commentedon May 24, 2020
I plan to try to integrate @tabatkins suggestion in the next week, so if anyone has comments r corrections against the proposal now is a good time.
argyleink commentedon Jun 25, 2020
it's awesome / lovely 👍
fantasai commentedon Jul 1, 2020
@svgeesus Would be good to get these edits in (and WD updated), given Blink already sent out intent to prototype.
1 remaining item
LeaVerou commentedon Jul 3, 2020
As @svgeesus pointed out in #5277, before we dive into the specifics of how to make this work, we should first see if there are any actual use cases for multiple colors. @una @argyleink what use cases did you have in mind?
argyleink commentedon Jul 6, 2020
? mix against white then add some complimentary color for additional contrast
? lightening || darkening then cooling || warming
? fun
color-mix(var(--c1), var(--c2), var(--c3))una commentedon Jul 6, 2020
Just a thought that might simplify implementation:
What if
color-mixonly took 2 colors and a percentage for the mixing?i.e. if
color-mix(red 60%, blue)means 60% red and 40% blue, then:color-mix(color-mix(red 60%, blue), pink 40%)means 60% of the total is a mixture of 60% red and 40% blue, the rest of 40% pink. Overall values would be: 36% red, 24% blue, 40% pink.Myndex commentedon Mar 1, 2021
Multi-Color Mixing Mayhem
This is mostly a discussion of multiple color mixing.
The TL;DR is: Outside of a completely new space for the purpose, n-color mixing needs to be a cascade, where we only mix two colors at any one time. Conveniently this also means mix percentages can apply to color(1) only. The previous color-mix() will always be color(2).
...And.... I just saw that una is suggesting something similar to what I wrote below. Doh me. At the end of the post though is an outline of some other color-mix() things, probably for separate issues.
RGB is not paint
In additive tristimulus color models (i.e. RGB), colors do not mix like "paint."
In an additive model like RGB, the more random colors you add together at once, the closer the result color is to an achromatic grey.
To create a means for color mixing of n-colors that functions in a useful and intuitive way is non-trivial, and also will be opinionated — oil pigments mix very differently than watercolors, which are different than printer inks, and different than...etc etc.
Tristimulus Mixing
In an sRGB monitor, only three "colors" are emitted. A blueViolet, a limeGreen, and a redOrange. These "primary colors" are not chosen to be at the peak sensitivity of either the L, M, or S cones, but chosen to be as far apart as practical so they each primary stimulates one of the L,M,S cones as "independently as possible" (which is harder than you might think).
Thus, all the monitor/system is doing is selectively stimulating the LMS cones to create a particular color sensation. When you see YELLOW on the computer monitor, the monitor is only emitting GREEN and RED, at the same time in close proximity. This stimulates the L and M cones in a manner similar to actual yellow, which wavelength-wise is "between" red and green.
In the example above, the yellow bar is nothing more than red and green at the same time and location. The red and green does not "mix in the air" and in fact the red and green do not mix on the eye's retina, but later through the neurologically based opponent color process and visual cortex color processing.
I.e.: it mixes only in your head
So then the question is how to predict a desired perceptual effect of mixing 2+ colors. And here again the answer is opinionated depending on the specific use case: is the goal to emulate natural linear light? Or the perception of tinting a pigment? Is the goal to affect the perceived brightness (Q)? Or the perceived lightness(J) or only the hue without affecting the readability contrast...
Each use case just mentioned needs a different approach. Just performing this in a usefully predictable manner with only two colors is already challenging. But there are plenty of use cases where an end result does need more than two colors mixed.
Use Case Examples
A simple example is the challenge of developing a dynamic color palette that maintains appropriate contrast values, and also properly accommodates the many focus or state changes in interactive content. That is, a button might be a color mix of two colors, but will also subsequently need an adjustment by mixing in a third color to indicate a state change.
Another example: A number of color-mix() colors are used throughout a page. There is the option to adjust the entire page, such as giving it a more sepia tone by adding the sepia adjust as another color mix to the other mixed colors.
Solution: A Cascade of Colors
A solution would be instead of attempting to mix n-colors all at once, is to cascade the color mix iterations, only doing two at a time... typically an artist will only be adding one bit of pigment at a time to a color mix, and doing so here should have more predictable and useable results. SO:
Allow color-mix() to be Recursive.
Example:
color-mix( yellow 25%, color-mix(aqua 30%, purple))So, 30% aqua + 70% purple makes lavender, then 25% yellow + 75% lavender makes a less saturated and lighter lavender.
Percentages and adding up to 100%
color-mix() Concerns Not Examined in This Post
Some things I didn't get into, but may discuss elsewhere include concerns such as these:
(placing these here as reminders to me, or to stir some thoughts, or something)
Missing "mix" spaces??:
Separating working space from color property space
Missing linearizing for RGB spaces?
Gamut mapping choices? For color-mix(), I didn't see choices for:
Fallback color
Andy
Andrew Somers
W3 AGWG Invited Expert
LeaVerou commentedon Mar 1, 2021
I think all of us (@una @svgeesus @argyleink and me) recently discussed this and resolved that since there is no obvious way that N-ary mixing should work and way too many gotchas, we should keep
color-mix()simple and only allow two colors. Authors can always nestcolor-mix()calls to mix multiple colors, per what was suggested already.If I do remember this correctly, we should probably close this.
@Myndex we don't need to do anything specific to "allow"
color-mix()to be nested. That's how functions work, since it accepts<color>values and produces<color>values, it can be nested.Myndex commentedon Mar 2, 2021
Hi Lea @LeaVerou
Got it, as it is CSS, I wasn't certain if there was any issue — I have occasionally tried something in CSS that "seems like obvious syntax" but, uh, not...
As this is going to close... I had a brief outline of some other issues — are they dead now too? Or handled elsewhere? (before I compose another essay in a dark alley, LOL)
Thank you!
Andy
svgeesus commentedon Mar 2, 2021
In general, (and this is based on many years experience with mailing lists, before issue management was moved to GitHub) please resist the temptation to post long, expository, multi-issue essays in GitHub issues. Issues work best when each one is closely focused on a single issue. Unrelated side points are easily lost.
svgeesus commentedon Mar 2, 2021
Yes, that is also the conclusion the editors came to.
I dud look into how 3D graphics handles a three-way color interpolation (for example, flat-shading a triangular mesh where each vertex has a color, and vertex normals are ignored). The algorithm proceeds by picking two colors, and interpolating them to produce the gradient along one edge. Then there is a second, again two-color interpolation step where the third color is interpolated with the color of a particular point along the gradient. So, two cascaded two-color interpolations.
So I think we can close this issue.
[css-color-5] Closed #4711
Myndex commentedon Mar 2, 2021
Oh I do agree... just trying to get "in sync" with how things are at the moment...