Color maths 4 (#12575)

# Objective

- Fixes #12202 

## Solution

- This PR implements componentwise (including alpha) addition,
subtraction and scalar multiplication/division for some color types.
- The mentioned color types are `Laba`, `Oklaba`, `LinearRgba` and
`Xyza` as all of them are either physically or perceptually linear as
mentioned by @alice-i-cecile in the issue.

---

## Changelog

- Scalar mul/div for `LinearRgba` may modify alpha now.

## Migration Guide

- Users of scalar mul/div for `LinearRgba` need to be aware of the
change and maybe use the `.clamp()` methods or manually set the `alpha`
channel.
This commit is contained in:
Lynn 2024-03-19 23:46:33 +01:00 committed by GitHub
parent e7a31d000e
commit d7372f2c75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 50 deletions

View file

@ -1,6 +1,6 @@
use crate::{
Alpha, ClampColor, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor,
Xyza,
impl_componentwise_point, Alpha, ClampColor, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix,
Oklaba, Srgba, StandardColor, Xyza,
};
use bevy_reflect::prelude::*;
use serde::{Deserialize, Serialize};
@ -25,6 +25,8 @@ pub struct Laba {
impl StandardColor for Laba {}
impl_componentwise_point!(Laba, [lightness, a, b, alpha]);
impl Laba {
/// Construct a new [`Laba`] color from components.
///

View file

@ -65,6 +65,12 @@
//! types in this crate. This is useful when you need to store a color in a data structure
//! that can't be generic over the color type.
//!
//! Color types that are either physically or perceptually linear also implement `Add<Self>`, `Sub<Self>`, `Mul<f32>` and `Div<f32>`
//! allowing you to use them with splines.
//!
//! Please note that most often adding or subtracting colors is not what you may want.
//! Please have a look at other operations like blending, lightening or mixing colors using e.g. [`Mix`] or [`Luminance`] instead.
//!
//! # Example
//!
//! ```
@ -151,3 +157,61 @@ where
Self: Alpha,
{
}
macro_rules! impl_componentwise_point {
($ty: ident, [$($element: ident),+]) => {
impl std::ops::Add<Self> for $ty {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::Output {
$($element: self.$element + rhs.$element,)+
}
}
}
impl std::ops::Sub<Self> for $ty {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self::Output {
$($element: self.$element - rhs.$element,)+
}
}
}
impl std::ops::Mul<f32> for $ty {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self::Output {
$($element: self.$element * rhs,)+
}
}
}
impl std::ops::Mul<$ty> for f32 {
type Output = $ty;
fn mul(self, rhs: $ty) -> Self::Output {
Self::Output {
$($element: self * rhs.$element,)+
}
}
}
impl std::ops::Div<f32> for $ty {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self::Output {
$($element: self.$element / rhs,)+
}
}
}
impl bevy_math::cubic_splines::Point for $ty {}
};
}
pub(crate) use impl_componentwise_point;

View file

@ -1,7 +1,6 @@
use std::ops::{Div, Mul};
use crate::{
color_difference::EuclideanDistance, Alpha, ClampColor, Luminance, Mix, StandardColor,
color_difference::EuclideanDistance, impl_componentwise_point, Alpha, ClampColor, Luminance,
Mix, StandardColor,
};
use bevy_math::Vec4;
use bevy_reflect::prelude::*;
@ -29,6 +28,8 @@ pub struct LinearRgba {
impl StandardColor for LinearRgba {}
impl_componentwise_point!(LinearRgba, [red, green, blue, alpha]);
impl LinearRgba {
/// A fully black color with full alpha.
pub const BLACK: Self = Self {
@ -299,48 +300,6 @@ impl From<LinearRgba> for wgpu::Color {
}
}
/// All color channels are scaled directly,
/// but alpha is unchanged.
///
/// Values are not clamped.
impl Mul<f32> for LinearRgba {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
red: self.red * rhs,
green: self.green * rhs,
blue: self.blue * rhs,
alpha: self.alpha,
}
}
}
impl Mul<LinearRgba> for f32 {
type Output = LinearRgba;
fn mul(self, rhs: LinearRgba) -> LinearRgba {
rhs * self
}
}
/// All color channels are scaled directly,
/// but alpha is unchanged.
///
/// Values are not clamped.
impl Div<f32> for LinearRgba {
type Output = Self;
fn div(self, rhs: f32) -> Self {
Self {
red: self.red / rhs,
green: self.green / rhs,
blue: self.blue / rhs,
alpha: self.alpha,
}
}
}
// [`LinearRgba`] is intended to be used with shaders
// So it's the only color type that implements [`ShaderType`] to make it easier to use inside shaders
impl encase::ShaderType for LinearRgba {

View file

@ -1,6 +1,6 @@
use crate::{
color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hwba, Lcha, LinearRgba,
Luminance, Mix, Srgba, StandardColor, Xyza,
color_difference::EuclideanDistance, impl_componentwise_point, Alpha, ClampColor, Hsla, Hsva,
Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
};
use bevy_reflect::prelude::*;
use serde::{Deserialize, Serialize};
@ -25,6 +25,8 @@ pub struct Oklaba {
impl StandardColor for Oklaba {}
impl_componentwise_point!(Oklaba, [lightness, a, b, alpha]);
impl Oklaba {
/// Construct a new [`Oklaba`] color from components.
///

View file

@ -1,4 +1,6 @@
use crate::{Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor};
use crate::{
impl_componentwise_point, Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor,
};
use bevy_reflect::prelude::*;
use serde::{Deserialize, Serialize};
@ -22,6 +24,8 @@ pub struct Xyza {
impl StandardColor for Xyza {}
impl_componentwise_point!(Xyza, [x, y, z, alpha]);
impl Xyza {
/// Construct a new [`Xyza`] color from components.
///