Skip to content

[css-color-5] calc(infinity) in relative color and interpolation #14088

Open

Description

@romainmenke

Also see: #14027
https://codepen.io/romainmenke/pen/wBgqvrR?editors=1111

color(from color(srgb calc(Infinity) 0.2 0.3) display-p3 r g b)

Chrome:

  • renders as white (this is correct)
  • computed color(display-p3 calc(NaN) calc(NaN) calc(NaN))

Firefox:

  • renders as black
  • computed color(display-p3 0 0 0)

Safari:

  • renders as black
  • computed color(display-p3 0 0 0)

Currently there are also no WPT tests for calc(infinity) in relative color syntax as far as I can tell.


I get color(display-p3 1979527133.8978488 519643834.0040926 393998297.8977633) as a result of converting Infinity to 2_147_483_647.

https://drafts.csswg.org/css-values-4/#css-infinity

Infinities do not escape a top-level calculation; they’re clamped to the minimum or maximum value allowed in the context, as defined in § 10.12 Range Checking.

After gamut mapping this would display as white.

But what is the computed value and serialization?
We probably don't want to leak the internal maximum value that is used to clamp infinities.

Activity

  1. romainmenke commented on Jun 27, 2026

    @romainmenke
    MemberAuthor

    This is also observable with interpolation.

    in xyz in Firefox:
    https://codepen.io/romainmenke/pen/myRMRQj

    The examples have a control gradient with a reasonable but large value, followed by a gradient with calc(Infinity) in the same channel

    Image

    and in Safari:

    Image

    in lch in Firefox:
    https://codepen.io/romainmenke/pen/ZYLJLqQ

    Image

    Cylindrical polar color models do better here because they have more clamping. hue is always normalized to [0, 360] and lightness to [0, 100]

    (chrome just fails to render things with infinities, so unknown what their outcome would look like)

  2. changed the title [-][css-color-5] `calc(infinity)` in relative color[/-] [+][css-color-5] `calc(infinity)` in relative color and interpolation[/+] on Jun 27, 2026
  3. svgeesus commented on Jun 27, 2026

    @svgeesus
    Collaborator

    Tagging @tabatkins who I think introduced this "coerce to plus infinity" concept. Also tagging @LeaVerou for comment.

  4. Loirooriol commented on Jun 27, 2026

    @Loirooriol
    Contributor

    We probably don't want to leak the internal maximum value that is used to clamp infinities.

    Why not? Isn't that exposed in width: calc(1px / 0) and so on?

  5. weinig commented on Jun 27, 2026

    @weinig
    Contributor

    I think the underlying question here is actually how should color conversions work in the face of maximum-finite-value component values.

    For example, in your example above:

    color(from color(srgb calc(Infinity) 0.2 0.3) display-p3 r g b)
    

    to resolve this, the origin color, color(srgb calc(Infinity) 0.2 0.3), must be converted to display-p3 so it can be used by the relative color syntax.

    But, is that color conversion well defined?

    The first step seems well defined, and its evaluating the calc expression, turning color(srgb calc(Infinity) 0.2 0.3) into color(srgb {max-finite-value} 0.2 0.3) ("Infinities do not escape a top-level calculation; they’re clamped to the minimum or maximum value allowed in the context, as defined in § 10.12 Range Checking." - https://drafts.csswg.org/css-values-4/#css-infinity)

    The second step would be converting color(srgb {max-finite-value} 0.2 0.3) to linear-srgb by applying the inverse gamma function on each component. In WebKit, that function looks something like:

    float toLinearSRGB(float c)
    {
            auto sign = std::signbit(c) ? -1.0f : 1.0f;
            c = std::abs(c);
    
            if (c <= 0.04045f)
                return c / 12.92f * sign;
            return std::pow((c + 0.055f) / 1.055f, 2.4f) * sign;
    }
    

    For that first component that started as calc(Infinity) and is now {max-finite-value} (or FLT_MAX in WebKit), the call to toLinearSRGB will return INFINITY.

    So, the question becomes, does that INFINITY also get clamped to {max-finite-value}? Or should the rest of the color conversion math continue with the infinity in place?

    @romainmenke I am curious how your implementation deals with this? How do you avoid saturating to +infinity during conversion to display-p3?

  6. romainmenke commented on Jun 27, 2026

    @romainmenke
    MemberAuthor

    @romainmenke I am curious how your implementation deals with this? How do you avoid saturating to +infinity during conversion to display-p3?

    I use 2_147_483_647 instead of the maximum float value in JavaScript.

    My thinking at the time was that this would avoid getting another infinity after conversion while still producing a value that is excessively large for any component.


    So, the question becomes, does that INFINITY also get clamped to {max-finite-value}? Or should the rest of the color conversion math continue with the infinity in place?

    I also see some parallels with powerless components/none in that maybe these should carry over and be re-inserted as {max-finite-value} or calc(Infinity) in analogous components.

  7. svgeesus commented on Jun 29, 2026

    @svgeesus
    Collaborator

    So, the question becomes, does that INFINITY also get clamped to {max-finite-value}?

    That would be my assumption, yes

  8. tabatkins commented on Jun 29, 2026

    @tabatkins
    Member

    It is indeed up to the implementation (in general, intentionally undefined, because we don't specify exactly what supported value ranges are) how precisely to treat infinity. The spec only mandates that math functions clamp to the allowed range, whatever that is in the given calculation context. There is intentionally zero difference between calc(infinity) and 1e1000; both are too large to actually be stored, and will instead be treated as some maximum value.

    (We keep getting these sort of questions about calc(infinity) in various contexts. What needs to be fixed in the spec to make it clearer that it's exactly identical to just using an incredibly large value, and thus already has well-defined behavior?)

  9. romainmenke commented on Jun 29, 2026

    @romainmenke
    MemberAuthor

    I agree that the spec isn't unclear on calc(infinity)

    But leaving it up to implementations to pick a value, makes it harder to have WPT tests and the lack of WPT tests leads to differences in implementations 🤷

    My initial question:

    what is the computed value and serialization?

    was also asked because I wanted to add tests for values like: color(from color(srgb calc(Infinity) 0.2 0.3) display-p3 r g b).

    But maybe we can test that implementations do the right thing without checking for a specific computed value?

  10. tabatkins commented on Jun 29, 2026

    @tabatkins
    Member

    But leaving it up to implementations to pick a value, makes it harder to have WPT tests and the lack of WPT tests leads to differences in implementations 🤷

    Sure, but again, this is exactly the same problem as using an ordinary 1e1000 value, as in color(from color(srgb 1e1000 0.2 0.3) display-p3 r g b). We can't strictly specify behavior without incidentally strictly specifying the internal numeric representation, which we've previously rejected doing.

    But maybe we can test that implementations do the right thing without checking for a specific computed value?

    You could, for example, check that using infinity and using a very large number give the same result, whatever it is.

  11. romainmenke commented on Jun 30, 2026

    @romainmenke
    MemberAuthor

    🤔 I don't know if/how that would work.
    On my end for example 1e1000 already parses as a number that is Infinity in JavaScript.
    So comparing that to calc(Infinity) would obviously yield the same result, but not for the right reasons.

    Any bugs that exist against calc(Infinity) would also exist for 1e1000.

    Maybe this is different in browser implementations?

  12. romainmenke commented on Sep 4, 2026

    @romainmenke
    MemberAuthor

    Maybe tests could check a series of asserts instead of actual channel values?

    For example:

    • r > g
    • r > b
    • g > r
    • g > b
    • ...
    • r == g
    • r == b
    • g == r
    • g == b
    • ...

    And that a test passes if the computed value passes or fails the same asserts as a reference value?

    Maybe a couple of special cases like is black or is white to catch cases where all channels equal each other and would pass the asserts listed above.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions