Skip to content

[css-color-5] color-mix to allow more than two colors? #4711

Closed

Description

@svgeesus

color-mix takes two colors and one mandatory percentage (the percentage for the other color is 100-that percentage).

From the Toronto minutes:

RESOLVED: Add color-mix(), try to align with cross-fade()

Looking at the definition for cross-fade I notice:

  1. It allows an arbitrary number of arguments
  2. 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?)

Activity

  1. tabatkins commented on Jan 28, 2020

    @tabatkins
    Member

    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.

  2. added a commit that references this issue on Feb 4, 2020
  3. una commented on Feb 6, 2020

    @una
    Contributor

    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% each
    • color-mix(red, blue, pink) would mix each by 33.333% each

    To add on, if you only have one percentage, it could look like this:

    • color-mix(red 40%, blue) -- a 40% red and 60% blue
    • color-mix(red, blue, pink 20%) -- a 40% red, 40% blue, 20% pink
  4. svgeesus commented on Feb 6, 2020

    @svgeesus
    CollaboratorAuthor

    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

    I agree.

  5. tabatkins commented on Feb 8, 2020

    @tabatkins
    Member

    It should work just like cross-fade():

    • 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%)).
  6. tabatkins commented on May 19, 2020

    @tabatkins
    Member

    Specifically, the grammar should be:

    color-mix( <<colorspace>>?,  [ <<color-adjuster>>? && <<color>> ]# )
    <<color-adjuster>> = <<percentage>> | hue(<<percentage>>) | ...
    

    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).

  7. tabatkins commented on May 20, 2020

    @tabatkins
    Member

    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).

  8. svgeesus commented on May 24, 2020

    @svgeesus
    CollaboratorAuthor

    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.

  9. argyleink commented on Jun 25, 2020

    @argyleink

    it's awesome / lovely 👍

  10. fantasai commented on Jul 1, 2020

    @fantasai
    Collaborator

    @svgeesus Would be good to get these edits in (and WD updated), given Blink already sent out intent to prototype.

  11. 1 remaining item

  12. LeaVerou commented on Jul 3, 2020

    @LeaVerou
    Member

    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?

  13. argyleink commented on Jul 6, 2020

    @argyleink

    ? 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))

  14. una commented on Jul 6, 2020

    @una
    Contributor

    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.

  15. Myndex commented on Mar 1, 2021

    @Myndex
    Member

    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

    1. In additive tristimulus color models (i.e. RGB), colors do not mix like "paint."

    2. 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.

    3. 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.

    redgreenyellow

    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%

    • 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?

    Andy

    Andrew Somers
    W3 AGWG Invited Expert

  16. LeaVerou commented on Mar 1, 2021

    @LeaVerou
    Member

    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.

  17. Myndex commented on Mar 2, 2021

    @Myndex
    Member

    Hi Lea @LeaVerou

    @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)

    Thank you!

    Andy

  18. svgeesus commented on Mar 2, 2021

    @svgeesus
    CollaboratorAuthor

    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.

  19. svgeesus commented on Mar 2, 2021

    @svgeesus
    CollaboratorAuthor

    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.

    So I think we can close this issue.

  20. added
    Closed as RetractedWhen the person who raised the issue thinks that there's no issue after all.
    Commenter SatisfiedCommenter has indicated satisfaction with the resolution / edits.
    and removed on Mar 2, 2021
  21. added a commit that references this issue on Mar 2, 2021
    4fdf55a
  22. Myndex commented on Mar 2, 2021

    @Myndex
    Member

    Issues work best when each one is closely focused on a single issue. Unrelated side points are easily lost.

    Oh I do agree... just trying to get "in sync" with how things are at the moment...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Closed as RetractedWhen the person who raised the issue thinks that there's no issue after all.Commenter SatisfiedCommenter has indicated satisfaction with the resolution / edits.css-color-5Color modification

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions