Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add color math operations (Add, Sub) and Animatable support for cylindrical color spaces #12617

Closed
alice-i-cecile opened this issue Mar 21, 2024 · 10 comments
Labels
A-Math Fundamental domain-agnostic mathematical operations A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible X-Controversial There is active debate or serious implications around merging this PR
Milestone

Comments

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Mar 21, 2024

What problem does this solve or what need does it fill?

Many of our existing color spaces have a robust set of operations that can be used for blending, animating and otherwise mixing colors:

  1. Mathematical operations, for raw operations.
  2. The Mix trait.
  3. The Point trait, for interop with the splines in bevy_math.
  4. Animatable, for interop with bevy_animation.

However, this is only currently done for "straightforwardly linear" color spaces. Some (like Srgba) are neither perceptually nor physically linear, and therefore are simply not a good choice to interpolate through.

But others, like Hsla, Hsva or Lcha have a different problem: their "hue" parameter loops around on itself. Rather than representing the space of colors as a straighforward "cube", with three independent orthogonal linear axes, the third axis behaves like an angle. For this reason, they are called "cylindrical color spaces": the hue parameter acts as a polar coordinate.

We should be able to define an intuitive set of linear operations over this space: it's simply trickier and will require the use of the .rem_euclid family of methods to perform modular arithmetic on floats.

Interpolating colors in these spaces is a nice quality of life feature (as they're often quite intuitive to work in and are exported by a wide range of art software). But it also provides new functionality for users: being able to interpolate between orange and purple the "short way around" (through red), rather than the "long way around" (through green).

What solution would you like?

For each of the cylindrical color spaces, implement:

  1. Add, Sub, AddAssign, SubAssign and f32 multiplication.
  2. The Point trait from bevy_math, to enable use with splines.
  3. The Animatable trait, to enable color blending via animation graphs.

EDIT: As noted below, we cannot implement float multiplication in a reasonable way on these types, and so cannot use splines.

Be sure to add tests to ensure that the appropriate properties of addition, subtraction and multiplication behave as you might expect in a linear space.

After that PR, the only color types that don't implement these would be Srgba (really terrible to interpolate through) and the universal enum Color (very implicit as to which color space is used).

What alternative(s) have you considered?

Convert to a flat linear color space before interpolating. This won't always give you the behavior you want though: being able to cycle around the red -> purple edge of the hues is a reasonable artistic choice that is not easily done in the existing flat color spaces, even if it's not particularly physical.

We could also implement these traits for Srgba (please, separate PR) and simply warn users that this is a probably a bad idea in the docs.

Additional context

This is a follow-up to #12202, and was split out to reduce controversy.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen A-Math Fundamental domain-agnostic mathematical operations S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! labels Mar 21, 2024
@IQuick143
Copy link
Contributor

The only way I can see this working is to consider the cylinder as being unwrapped and periodically repeating.

This means that we make the hue axis span (-inf, +inf) but each 2pi period the colours de-facto repeat.

If you desire to interpolate through the repeat, you use a colour from the next period over.

ie: If you want to go from Red through the entire rainbow and back to Red, you interpolate between:
hue: 0 and hue: 2pi, if you want to loop through n times, you interpolate between 0 and 2pi * n

This brings the obvious caveats: Colours can be represented with many different hues, but this hue information is relevant for interpolation. IIRC colour-conversions do not (and likely cannot) account for this, at the very least they'd lose the period information (because it does not affect the resulting colour).

You cannot use any sort of modular arithmetic to define operations on a vector space, because linear algebra tells us, that all real n-dimensional vector spaces are isomorphic to R^n (think Vec[n]), but addition with modulo isn't isomorphic to addition of real numbers.

Example of how it would break down:
Say we have a colour represented by a hue modulo 2pi, then in order to implement a vector space the following identity must hold:
(ab) * hue = a * (b * hue)
Let's pick hue = 1, b = 2pi, a = 1/2pi
We see the left hand side evaluates to:
(1/2pi * 2pi) * 1 = 1 * 1 = 1 mod 2pi
Meanwhile the right hand side:
1/2pi * (2pi * 1) = 1/2pi * 0 = 0 mod 2pi
(2pi is the same hue as 0 because of modular arithmetic)
(Other channels were left out from this calculation, but they wouldn't fix the fact that you end up with different hues, breaking this identity.)

Note: Addition mod 2pi works just fine, is commutative and associative and it has negatives and 0 and all that, the problem is with scalar multiplication, which cannot be defined in such a way as to be compatible with the modular addition and form a vector space.

@alice-i-cecile
Copy link
Member Author

alice-i-cecile commented Mar 21, 2024

Aha, that makes sense. I think we should be able to do Add, Sub and Animatable, but will not be able to add spline support then. I don't think that the additional complexity of an infinite tiling color space is worth it.

@alice-i-cecile alice-i-cecile changed the title Add color math operations (Add, Sub, f32 multiplication), spline and Animatable support for cylindrical color spaces Add color math operations (Add, Sub) and Animatable support for cylindrical color spaces Mar 21, 2024
@alice-i-cecile
Copy link
Member Author

BTW, one final comment on the issue of math and color: even though srgb isn't "linear", there are circumstances where a gradient calculated linearly in srgb can look quite good. Specifically, if you are doing RGB color sliders, or even an alpha slider, doing the interpolation in linear RGB actually looks wrong, whereas in srgb it looks like what you would expect. Interpolating in linear RGB, what you see is most of the slider filled with a flat, unchanging color, with all of the changes crowded up on the right side. And this makes sense, because if your slider represents an RGB value, then the gradient that is displayed should match the color space you are editing. That being said, these kinds of gradients will generally be interpolated in .wgsl code, not using Rust code such as bevy_color.

LinearRgba:
image

Srgba:
image

Apparently implementing this on Srgba colors is also probably fine too, as there appear to be use cases. Different PR though please.

@alice-i-cecile alice-i-cecile added this to the 0.14 milestone Mar 21, 2024
@viridia
Copy link
Contributor

viridia commented Mar 21, 2024

Yes, I would agree that color math on Srgba would be useful, and not too surprising. While it's not "linear" in the mathematical sense, it has a continuity which matches user expectations in some circumstances.

@IQuick143
Copy link
Contributor

Personally not a huge fan of hue getting treated specially like this, it goes against all the other implementations of Add and Sub. Just like other channels don't/shouldn't clamp, hue shouldn't wrap.

Also for this reason Add and Sub (without Mul) is not very useful, as adding two HSL colours will produce an object with a valid Hue, but most likely have some other invalid channel, the only operations that are guaranteed to work are convex combinations (on account of the gamut being a convex set in its induced geometry).

@IQuick143
Copy link
Contributor

Yes, I would agree that color math on Srgba would be useful, and not too surprising. While it's not "linear" in the mathematical sense, it has a continuity which matches user expectations in some circumstances.

This brings me to a question (not necesarily aimed at @viridia, just in general), what do we mean when we say that a colour space is "linear".

When I talk about linear spaces, it means that we endow it with an Euclidean geometry which is backed by a vector space, which has certain linearity requirements on its operations (This then lets us do things like draw lines or splines in this space).

Technical Note: We don't even endow it with an entire euclidean geometry, only make it an affine space, the distinction is that euclidean geometry takes place in an affine space, but also has measurements of lengths and angles. (And these measurements follow Euclid's axioms.)
Our implementations of Add and Mul do not define a way to measure distances between points and angles between vectors or lines.
An affine space simply defines a flat space where we can have points (colours) and we can translate them using vectors (difference of two points) and these operations have nice properties.
I'm saying this so we don't get the wrong idea that by specifying an affine space (inside of which is a convex subset of valid colours) for each space, we would implicitly define a measure of distance (which in most spaces would not reflect how colours work at all).

@alice-i-cecile alice-i-cecile added X-Controversial There is active debate or serious implications around merging this PR and removed S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! labels Mar 21, 2024
@NthTensor
Copy link
Contributor

The only way I can see this working is to consider the cylinder as being unwrapped and periodically repeating.

This has finally made me see the merits of the ColorVec<ColorSpace> approach. When converting from color vec back to normal color space, the proper linear axies would get clamped to bounds, and the cylindrical axies would be wrapped.

Mix lets us do interpolations within a convex color space. Arbitrary math requires the full affine space, but once we are done we must map the result them back to the set of colors. I like how ColorVec would make this conversion explicit on the type level.

github-merge-queue bot pushed a commit that referenced this issue Mar 22, 2024
# Objective

- Implements maths and `Animatable` for `Srgba` as suggested
[here](#12617 (comment)).

## Solution

- Implements `Animatable` and maths for `Srgba` just like their
implemented for other colors.

---

## Changelog

- Updated the example to mention `Srgba`.

## Migration Guide

- The previously existing implementation of mul/div for `Srgba` did not
modify `alpha` but these operations do modify `alpha` now. Users need to
be aware of this change.
@viridia
Copy link
Contributor

viridia commented Mar 28, 2024

@alice-i-cecile Can we consider this "done"?

@NthTensor
Copy link
Contributor

I'd still like to try the ColorVec approach for Hsla and similar.

@NthTensor
Copy link
Contributor

Looked into this a bit more and changed my mind. There's no reason to do algebra in a cylindrical model of a space when you have a linear model of the same space available.

I think we can close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

No branches or pull requests

4 participants