diff --git a/crates/bevy_render/src/color.rs b/crates/bevy_render/src/color.rs index c953b997d8..80d968dc6f 100644 --- a/crates/bevy_render/src/color.rs +++ b/crates/bevy_render/src/color.rs @@ -5,95 +5,113 @@ use crate::{ renderer::{RenderResource, RenderResourceType}, }; use bevy_asset::Handle; -use bevy_core::{Byteable, Bytes}; +use bevy_core::Bytes; use bevy_math::{Vec3, Vec4}; use bevy_reflect::{Reflect, ReflectDeserialize}; use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign, Mul, MulAssign}; -// TODO: Separate types for non-linear sRGB and linear sRGB, with conversions between -// see comment on bevy issue #688 https://github.com/bevyengine/bevy/pull/688#issuecomment-711414011 -/// RGBA color in the Linear sRGB colorspace (often colloquially referred to as "linear", "RGB", or -/// "linear RGB"). -#[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] #[reflect(PartialEq, Serialize, Deserialize)] -pub struct Color { - red: f32, - green: f32, - blue: f32, - alpha: f32, +pub enum Color { + /// sRGBA color + Rgba { + /// Red component. [0.0, 1.0] + red: f32, + /// Green component. [0.0, 1.0] + green: f32, + /// Blue component. [0.0, 1.0] + blue: f32, + /// Alpha component. [0.0, 1.0] + alpha: f32, + }, + /// RGBA color in the Linear sRGB colorspace (often colloquially referred to as "linear", "RGB", or "linear RGB"). + RgbaLinear { + /// Red component. [0.0, 1.0] + red: f32, + /// Green component. [0.0, 1.0] + green: f32, + /// Blue component. [0.0, 1.0] + blue: f32, + /// Alpha component. [0.0, 1.0] + alpha: f32, + }, + /// HSL (hue, saturation, lightness) color with an alpha channel + Hsla { + /// Hue component. [0.0, 360.0] + hue: f32, + /// Saturation component. [0.0, 1.0] + saturation: f32, + /// Lightness component. [0.0, 1.0] + lightness: f32, + /// Alpha component. [0.0, 1.0] + alpha: f32, + }, } -unsafe impl Byteable for Color {} - impl Color { - pub const ALICE_BLUE: Color = Color::rgb_linear(0.94, 0.97, 1.0); - pub const ANTIQUE_WHITE: Color = Color::rgb_linear(0.98, 0.92, 0.84); - pub const AQUAMARINE: Color = Color::rgb_linear(0.49, 1.0, 0.83); - pub const AZURE: Color = Color::rgb_linear(0.94, 1.0, 1.0); - pub const BEIGE: Color = Color::rgb_linear(0.96, 0.96, 0.86); - pub const BISQUE: Color = Color::rgb_linear(1.0, 0.89, 0.77); - pub const BLACK: Color = Color::rgb_linear(0.0, 0.0, 0.0); - pub const BLUE: Color = Color::rgb_linear(0.0, 0.0, 1.0); - pub const CRIMSON: Color = Color::rgb_linear(0.86, 0.08, 0.24); - pub const CYAN: Color = Color::rgb_linear(0.0, 1.0, 1.0); - pub const DARK_GRAY: Color = Color::rgb_linear(0.25, 0.25, 0.25); - pub const DARK_GREEN: Color = Color::rgb_linear(0.0, 0.5, 0.0); - pub const FUCHSIA: Color = Color::rgb_linear(1.0, 0.0, 1.0); - pub const GOLD: Color = Color::rgb_linear(1.0, 0.84, 0.0); - pub const GRAY: Color = Color::rgb_linear(0.5, 0.5, 0.5); - pub const GREEN: Color = Color::rgb_linear(0.0, 1.0, 0.0); - pub const INDIGO: Color = Color::rgb_linear(0.29, 0.0, 0.51); - pub const LIME_GREEN: Color = Color::rgb_linear(0.2, 0.8, 0.2); - pub const MAROON: Color = Color::rgb_linear(0.5, 0.0, 0.0); - pub const MIDNIGHT_BLUE: Color = Color::rgb_linear(0.1, 0.1, 0.44); - pub const NAVY: Color = Color::rgb_linear(0.0, 0.0, 0.5); - pub const NONE: Color = Color::rgba_linear(0.0, 0.0, 0.0, 0.0); - pub const OLIVE: Color = Color::rgb_linear(0.5, 0.5, 0.0); - pub const ORANGE: Color = Color::rgb_linear(1.0, 0.65, 0.0); - pub const ORANGE_RED: Color = Color::rgb_linear(1.0, 0.27, 0.0); - pub const PINK: Color = Color::rgb_linear(1.0, 0.08, 0.58); - pub const PURPLE: Color = Color::rgb_linear(0.5, 0.0, 0.5); - pub const RED: Color = Color::rgb_linear(1.0, 0.0, 0.0); - pub const SALMON: Color = Color::rgb_linear(0.98, 0.5, 0.45); - pub const SEA_GREEN: Color = Color::rgb_linear(0.18, 0.55, 0.34); - pub const SILVER: Color = Color::rgb_linear(0.75, 0.75, 0.75); - pub const TEAL: Color = Color::rgb_linear(0.0, 0.5, 0.5); - pub const TOMATO: Color = Color::rgb_linear(1.0, 0.39, 0.28); - pub const TURQUOISE: Color = Color::rgb_linear(0.25, 0.88, 0.82); - pub const VIOLET: Color = Color::rgb_linear(0.93, 0.51, 0.93); - pub const WHITE: Color = Color::rgb_linear(1.0, 1.0, 1.0); - pub const YELLOW: Color = Color::rgb_linear(1.0, 1.0, 0.0); - pub const YELLOW_GREEN: Color = Color::rgb_linear(0.6, 0.8, 0.2); + pub const ALICE_BLUE: Color = Color::rgb(0.94, 0.97, 1.0); + pub const ANTIQUE_WHITE: Color = Color::rgb(0.98, 0.92, 0.84); + pub const AQUAMARINE: Color = Color::rgb(0.49, 1.0, 0.83); + pub const AZURE: Color = Color::rgb(0.94, 1.0, 1.0); + pub const BEIGE: Color = Color::rgb(0.96, 0.96, 0.86); + pub const BISQUE: Color = Color::rgb(1.0, 0.89, 0.77); + pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0); + pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0); + pub const CRIMSON: Color = Color::rgb(0.86, 0.08, 0.24); + pub const CYAN: Color = Color::rgb(0.0, 1.0, 1.0); + pub const DARK_GRAY: Color = Color::rgb(0.25, 0.25, 0.25); + pub const DARK_GREEN: Color = Color::rgb(0.0, 0.5, 0.0); + pub const FUCHSIA: Color = Color::rgb(1.0, 0.0, 1.0); + pub const GOLD: Color = Color::rgb(1.0, 0.84, 0.0); + pub const GRAY: Color = Color::rgb(0.5, 0.5, 0.5); + pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0); + pub const INDIGO: Color = Color::rgb(0.29, 0.0, 0.51); + pub const LIME_GREEN: Color = Color::rgb(0.2, 0.8, 0.2); + pub const MAROON: Color = Color::rgb(0.5, 0.0, 0.0); + pub const MIDNIGHT_BLUE: Color = Color::rgb(0.1, 0.1, 0.44); + pub const NAVY: Color = Color::rgb(0.0, 0.0, 0.5); + pub const NONE: Color = Color::rgba(0.0, 0.0, 0.0, 0.0); + pub const OLIVE: Color = Color::rgb(0.5, 0.5, 0.0); + pub const ORANGE: Color = Color::rgb(1.0, 0.65, 0.0); + pub const ORANGE_RED: Color = Color::rgb(1.0, 0.27, 0.0); + pub const PINK: Color = Color::rgb(1.0, 0.08, 0.58); + pub const PURPLE: Color = Color::rgb(0.5, 0.0, 0.5); + pub const RED: Color = Color::rgb(1.0, 0.0, 0.0); + pub const SALMON: Color = Color::rgb(0.98, 0.5, 0.45); + pub const SEA_GREEN: Color = Color::rgb(0.18, 0.55, 0.34); + pub const SILVER: Color = Color::rgb(0.75, 0.75, 0.75); + pub const TEAL: Color = Color::rgb(0.0, 0.5, 0.5); + pub const TOMATO: Color = Color::rgb(1.0, 0.39, 0.28); + pub const TURQUOISE: Color = Color::rgb(0.25, 0.88, 0.82); + pub const VIOLET: Color = Color::rgb(0.93, 0.51, 0.93); + pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0); + pub const YELLOW: Color = Color::rgb(1.0, 1.0, 0.0); + pub const YELLOW_GREEN: Color = Color::rgb(0.6, 0.8, 0.2); - // TODO: cant make rgb and rgba const due traits not allowed in const functions - // see issue #57563 https://github.com/rust-lang/rust/issues/57563 - /// New ``Color`` from sRGB colorspace. - pub fn rgb(r: f32, g: f32, b: f32) -> Color { - Color { + /// New `Color` from sRGB colorspace. + pub const fn rgb(r: f32, g: f32, b: f32) -> Color { + Color::Rgba { red: r, green: g, blue: b, alpha: 1.0, } - .as_nonlinear_srgb_to_linear_srgb() } - /// New ``Color`` from sRGB colorspace. - pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { - Color { + /// New `Color` from sRGB colorspace. + pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { + Color::Rgba { red: r, green: g, blue: b, alpha: a, } - .as_nonlinear_srgb_to_linear_srgb() } - /// New ``Color`` from linear colorspace. + /// New `Color` from linear RGB colorspace. pub const fn rgb_linear(r: f32, g: f32, b: f32) -> Color { - Color { + Color::RgbaLinear { red: r, green: g, blue: b, @@ -101,9 +119,9 @@ impl Color { } } - /// New ``Color`` from linear colorspace. + /// New `Color` from linear RGB colorspace. pub const fn rgba_linear(r: f32, g: f32, b: f32, a: f32) -> Color { - Color { + Color::RgbaLinear { red: r, green: g, blue: b, @@ -111,7 +129,27 @@ impl Color { } } - /// New ``Color`` from sRGB colorspace. + /// New `Color` with HSL representation in sRGB colorspace. + pub const fn hsl(hue: f32, saturation: f32, lightness: f32) -> Color { + Color::Hsla { + hue, + saturation, + lightness, + alpha: 1.0, + } + } + + /// New `Color` with HSL representation in sRGB colorspace. + pub const fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color { + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } + } + + /// New `Color` from sRGB colorspace. pub fn hex>(hex: T) -> Result { let hex = hex.as_ref(); @@ -148,14 +186,14 @@ impl Color { Err(HexColorError::Length) } - /// New ``Color`` from sRGB colorspace. + /// New `Color` from sRGB colorspace. pub fn rgb_u8(r: u8, g: u8, b: u8) -> Color { Color::rgba_u8(r, g, b, u8::MAX) } // Float operations in const fn are not stable yet // see https://github.com/rust-lang/rust/issues/57241 - /// New ``Color`` from sRGB colorspace. + /// New `Color` from sRGB colorspace. pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Color { Color::rgba( r as f32 / u8::MAX as f32, @@ -165,99 +203,292 @@ impl Color { ) } - fn as_nonlinear_srgb_to_linear_srgb(self) -> Color { - Color { - red: self.red.nonlinear_to_linear_srgb(), - green: self.green.nonlinear_to_linear_srgb(), - blue: self.blue.nonlinear_to_linear_srgb(), - alpha: self.alpha, // alpha is always linear - } - } - - // non-linear-sRGB Component Getter - /// Get red in sRGB colorspace. pub fn r(&self) -> f32 { - self.red.linear_to_nonlinear_srgb() + match self.as_rgba() { + Color::Rgba { red, .. } => red, + _ => unreachable!(), + } } /// Get green in sRGB colorspace. pub fn g(&self) -> f32 { - self.green.linear_to_nonlinear_srgb() + match self.as_rgba() { + Color::Rgba { green, .. } => green, + _ => unreachable!(), + } } /// Get blue in sRGB colorspace. pub fn b(&self) -> f32 { - self.blue.linear_to_nonlinear_srgb() + match self.as_rgba() { + Color::Rgba { blue, .. } => blue, + _ => unreachable!(), + } } - // linear-sRGB Component Getter - - /// Get red in linear colorspace. - pub fn r_linear(&self) -> f32 { - self.red - } - - /// Get green in linear colorspace. - pub fn g_linear(&self) -> f32 { - self.green - } - - /// Get blue in linear colorspace. - pub fn b_linear(&self) -> f32 { - self.blue - } - - /// Get alpha. - pub fn a(&self) -> f32 { - self.alpha - } - - // non-linear-sRGB Component Setter - /// Set red in sRGB colorspace. pub fn set_r(&mut self, r: f32) -> &mut Self { - self.red = r.nonlinear_to_linear_srgb(); + *self = self.as_rgba(); + match self { + Color::Rgba { red, .. } => *red = r, + _ => unreachable!(), + } self } /// Set green in sRGB colorspace. pub fn set_g(&mut self, g: f32) -> &mut Self { - self.green = g.nonlinear_to_linear_srgb(); + *self = self.as_rgba(); + match self { + Color::Rgba { green, .. } => *green = g, + _ => unreachable!(), + } self } /// Set blue in sRGB colorspace. pub fn set_b(&mut self, b: f32) -> &mut Self { - self.blue = b.nonlinear_to_linear_srgb(); + *self = self.as_rgba(); + match self { + Color::Rgba { blue, .. } => *blue = b, + _ => unreachable!(), + } self } - // linear-sRGB Component Setter - - /// Set red in linear colorspace. - pub fn set_r_linear(&mut self, r: f32) -> &mut Self { - self.red = r; - self - } - - /// Set green in linear colorspace. - pub fn set_g_linear(&mut self, g: f32) -> &mut Self { - self.green = g; - self - } - - /// Set blue in linear colorspace. - pub fn set_b_linear(&mut self, b: f32) -> &mut Self { - self.blue = b; - self + /// Get alpha. + pub fn a(&self) -> f32 { + match self { + Color::Rgba { alpha, .. } + | Color::RgbaLinear { alpha, .. } + | Color::Hsla { alpha, .. } => *alpha, + } } /// Set alpha. pub fn set_a(&mut self, a: f32) -> &mut Self { - self.alpha = a; + match self { + Color::Rgba { alpha, .. } + | Color::RgbaLinear { alpha, .. } + | Color::Hsla { alpha, .. } => { + *alpha = a; + } + } self } + + /// Converts a `Color` to variant `Color::Rgba` + pub fn as_rgba(self: &Color) -> Color { + match self { + Color::Rgba { .. } => *self, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red.linear_to_nonlinear_srgb(), + green: green.linear_to_nonlinear_srgb(), + blue: blue.linear_to_nonlinear_srgb(), + alpha: *alpha, + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let [red, green, blue] = + HslRepresentation::hsl_to_nonlinear_srgb(*hue, *saturation, *lightness); + Color::Rgba { + red, + green, + blue, + alpha: *alpha, + } + } + } + } + + /// Converts a `Color` to variant `Color::RgbaLinear` + pub fn as_rgba_linear(self: &Color) -> Color { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red.nonlinear_to_linear_srgb(), + green: green.nonlinear_to_linear_srgb(), + blue: blue.nonlinear_to_linear_srgb(), + alpha: *alpha, + }, + Color::RgbaLinear { .. } => *self, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let [red, green, blue] = + HslRepresentation::hsl_to_nonlinear_srgb(*hue, *saturation, *lightness); + Color::RgbaLinear { + red: red.nonlinear_to_linear_srgb(), + green: green.nonlinear_to_linear_srgb(), + blue: blue.nonlinear_to_linear_srgb(), + alpha: *alpha, + } + } + } + } + + /// Converts a `Color` to variant `Color::Hsla` + pub fn as_hsla(self: &Color) -> Color { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([*red, *green, *blue]); + Color::Hsla { + hue, + saturation, + lightness, + alpha: *alpha, + } + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + let (hue, saturation, lightness) = HslRepresentation::nonlinear_srgb_to_hsl([ + red.linear_to_nonlinear_srgb(), + green.linear_to_nonlinear_srgb(), + blue.linear_to_nonlinear_srgb(), + ]); + Color::Hsla { + hue, + saturation, + lightness, + alpha: *alpha, + } + } + Color::Hsla { .. } => *self, + } + } + + /// Converts a `Color` to a `[f32; 4]` from sRBG colorspace + pub fn as_rgba_f32(self: Color) -> [f32; 4] { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => [red, green, blue, alpha], + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => [ + red.linear_to_nonlinear_srgb(), + green.linear_to_nonlinear_srgb(), + blue.linear_to_nonlinear_srgb(), + alpha, + ], + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let [red, green, blue] = + HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + [red, green, blue, alpha] + } + } + } + + /// Converts a `Color` to a `[f32; 4]` from linear RBG colorspace + pub fn as_linear_rgba_f32(self: Color) -> [f32; 4] { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => [ + red.nonlinear_to_linear_srgb(), + green.nonlinear_to_linear_srgb(), + blue.nonlinear_to_linear_srgb(), + alpha, + ], + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => [red, green, blue, alpha], + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let [red, green, blue] = + HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + [ + red.nonlinear_to_linear_srgb(), + green.nonlinear_to_linear_srgb(), + blue.nonlinear_to_linear_srgb(), + alpha, + ] + } + } + } + + /// Converts a `Color` to a `[f32; 4]` from HLS colorspace + pub fn as_hlsa_f32(self: Color) -> [f32; 4] { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([red, green, blue]); + [hue, saturation, lightness, alpha] + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + let (hue, saturation, lightness) = HslRepresentation::nonlinear_srgb_to_hsl([ + red.linear_to_nonlinear_srgb(), + green.linear_to_nonlinear_srgb(), + blue.linear_to_nonlinear_srgb(), + ]); + [hue, saturation, lightness, alpha] + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => [hue, saturation, lightness, alpha], + } + } } impl Default for Color { @@ -268,11 +499,43 @@ impl Default for Color { impl AddAssign for Color { fn add_assign(&mut self, rhs: Color) { - *self = Color { - red: self.red + rhs.red, - green: self.green + rhs.green, - blue: self.blue + rhs.blue, - alpha: self.alpha + rhs.alpha, + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + let rhs = rhs.as_rgba_f32(); + *red += rhs[0]; + *green += rhs[1]; + *blue += rhs[2]; + *alpha += rhs[3]; + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + let rhs = rhs.as_linear_rgba_f32(); + *red += rhs[0]; + *green += rhs[1]; + *blue += rhs[2]; + *alpha += rhs[3]; + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let rhs = rhs.as_linear_rgba_f32(); + *hue += rhs[0]; + *saturation += rhs[1]; + *lightness += rhs[2]; + *alpha += rhs[3]; + } } } } @@ -281,31 +544,72 @@ impl Add for Color { type Output = Color; fn add(self, rhs: Color) -> Self::Output { - Color { - red: self.red + rhs.red, - green: self.green + rhs.green, - blue: self.blue + rhs.blue, - alpha: self.alpha + rhs.alpha, + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + let rhs = rhs.as_rgba_f32(); + Color::Rgba { + red: red + rhs[0], + green: green + rhs[1], + blue: blue + rhs[2], + alpha: alpha + rhs[3], + } + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + let rhs = rhs.as_linear_rgba_f32(); + Color::RgbaLinear { + red: red + rhs[0], + green: green + rhs[1], + blue: blue + rhs[2], + alpha: alpha + rhs[3], + } + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let rhs = rhs.as_linear_rgba_f32(); + Color::Hsla { + hue: hue + rhs[0], + saturation: saturation + rhs[1], + lightness: lightness + rhs[2], + alpha: alpha + rhs[3], + } + } } } } +impl AddAssign for Color { + fn add_assign(&mut self, rhs: Vec4) { + let rhs: Color = rhs.into(); + *self += rhs + } +} + impl Add for Color { type Output = Color; fn add(self, rhs: Vec4) -> Self::Output { - Color { - red: self.red + rhs.x, - green: self.green + rhs.y, - blue: self.blue + rhs.z, - alpha: self.alpha + rhs.w, - } + let rhs: Color = rhs.into(); + self + rhs } } impl From for [f32; 4] { fn from(color: Color) -> Self { - [color.r(), color.g(), color.b(), color.a()] + color.as_rgba_f32() } } @@ -317,7 +621,8 @@ impl From<[f32; 4]> for Color { impl From for Vec4 { fn from(color: Color) -> Self { - Vec4::new(color.r(), color.g(), color.b(), color.a()) + let color: [f32; 4] = color.into(); + Vec4::new(color[0], color[1], color[2], color[3]) } } @@ -331,15 +636,72 @@ impl Mul for Color { type Output = Color; fn mul(self, rhs: f32) -> Self::Output { - Color::rgba(self.r() * rhs, self.g() * rhs, self.b() * rhs, self.a()) + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red * rhs, + green: green * rhs, + blue: blue * rhs, + alpha, + }, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::RgbaLinear { + red: red * rhs, + green: green * rhs, + blue: blue * rhs, + alpha, + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => Color::Hsla { + hue: hue * rhs, + saturation: saturation * rhs, + lightness: lightness * rhs, + alpha, + }, + } } } impl MulAssign for Color { fn mul_assign(&mut self, rhs: f32) { - self.set_r(self.r() * rhs); - self.set_g(self.g() * rhs); - self.set_b(self.b() * rhs); + match self { + Color::Rgba { + red, green, blue, .. + } => { + *red *= rhs; + *green *= rhs; + *blue *= rhs; + } + Color::RgbaLinear { + red, green, blue, .. + } => { + *red *= rhs; + *green *= rhs; + *blue *= rhs; + } + Color::Hsla { + hue, + saturation, + lightness, + .. + } => { + *hue *= rhs; + *saturation *= rhs; + *lightness *= rhs; + } + } } } @@ -347,21 +709,81 @@ impl Mul for Color { type Output = Color; fn mul(self, rhs: Vec4) -> Self::Output { - Color::rgba( - self.r() * rhs.x, - self.g() * rhs.y, - self.b() * rhs.z, - self.a() * rhs.w, - ) + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red * rhs.x, + green: green * rhs.y, + blue: blue * rhs.z, + alpha: alpha * rhs.w, + }, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::RgbaLinear { + red: red * rhs.x, + green: green * rhs.y, + blue: blue * rhs.z, + alpha: alpha * rhs.w, + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => Color::Hsla { + hue: hue * rhs.x, + saturation: saturation * rhs.y, + lightness: lightness * rhs.z, + alpha: alpha * rhs.w, + }, + } } } impl MulAssign for Color { fn mul_assign(&mut self, rhs: Vec4) { - self.set_r(self.r() * rhs.x); - self.set_g(self.g() * rhs.y); - self.set_b(self.b() * rhs.z); - self.set_a(self.a() * rhs.w); + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + *red *= rhs.x; + *green *= rhs.y; + *blue *= rhs.z; + *alpha *= rhs.w; + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + *red *= rhs.x; + *green *= rhs.y; + *blue *= rhs.z; + *alpha *= rhs.w; + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + *hue *= rhs.x; + *saturation *= rhs.y; + *lightness *= rhs.z; + *alpha *= rhs.w; + } + } } } @@ -369,99 +791,279 @@ impl Mul for Color { type Output = Color; fn mul(self, rhs: Vec3) -> Self::Output { - Color::rgba( - self.r() * rhs.x, - self.g() * rhs.y, - self.b() * rhs.z, - self.a(), - ) + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red * rhs.x, + green: green * rhs.y, + blue: blue * rhs.z, + alpha, + }, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::RgbaLinear { + red: red * rhs.x, + green: green * rhs.y, + blue: blue * rhs.z, + alpha, + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => Color::Hsla { + hue: hue * rhs.x, + saturation: saturation * rhs.y, + lightness: lightness * rhs.z, + alpha, + }, + } } } impl MulAssign for Color { fn mul_assign(&mut self, rhs: Vec3) { - self.set_r(self.r() * rhs.x); - self.set_g(self.g() * rhs.y); - self.set_b(self.b() * rhs.z); + match self { + Color::Rgba { + red, green, blue, .. + } => { + *red *= rhs.x; + *green *= rhs.y; + *blue *= rhs.z; + } + Color::RgbaLinear { + red, green, blue, .. + } => { + *red *= rhs.x; + *green *= rhs.y; + *blue *= rhs.z; + } + Color::Hsla { + hue, + saturation, + lightness, + .. + } => { + *hue *= rhs.x; + *saturation *= rhs.y; + *lightness *= rhs.z; + } + } } } impl Mul<[f32; 4]> for Color { type Output = Color; - fn mul(self, [r, g, b, a]: [f32; 4]) -> Self::Output { - Color::rgba(self.r() * r, self.g() * g, self.b() * b, self.a() * a) + fn mul(self, rhs: [f32; 4]) -> Self::Output { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red * rhs[0], + green: green * rhs[1], + blue: blue * rhs[2], + alpha: alpha * rhs[3], + }, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::RgbaLinear { + red: red * rhs[0], + green: green * rhs[1], + blue: blue * rhs[2], + alpha: alpha * rhs[3], + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => Color::Hsla { + hue: hue * rhs[0], + saturation: saturation * rhs[1], + lightness: lightness * rhs[2], + alpha: alpha * rhs[3], + }, + } } } impl MulAssign<[f32; 4]> for Color { - fn mul_assign(&mut self, [r, g, b, a]: [f32; 4]) { - self.set_r(self.r() * r); - self.set_g(self.g() * g); - self.set_b(self.b() * b); - self.set_a(self.a() * a); + fn mul_assign(&mut self, rhs: [f32; 4]) { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + *red *= rhs[0]; + *green *= rhs[1]; + *blue *= rhs[2]; + *alpha *= rhs[3]; + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + *red *= rhs[0]; + *green *= rhs[1]; + *blue *= rhs[2]; + *alpha *= rhs[3]; + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + *hue *= rhs[0]; + *saturation *= rhs[1]; + *lightness *= rhs[2]; + *alpha *= rhs[3]; + } + } } } impl Mul<[f32; 3]> for Color { type Output = Color; - fn mul(self, [r, g, b]: [f32; 3]) -> Self::Output { - Color::rgba(self.r() * r, self.g() * g, self.b() * b, self.a()) + fn mul(self, rhs: [f32; 3]) -> Self::Output { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => Color::Rgba { + red: red * rhs[0], + green: green * rhs[1], + blue: blue * rhs[2], + alpha, + }, + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => Color::RgbaLinear { + red: red * rhs[0], + green: green * rhs[1], + blue: blue * rhs[2], + alpha, + }, + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => Color::Hsla { + hue: hue * rhs[0], + saturation: saturation * rhs[1], + lightness: lightness * rhs[2], + alpha, + }, + } } } impl MulAssign<[f32; 3]> for Color { - fn mul_assign(&mut self, [r, g, b]: [f32; 3]) { - self.set_r(self.r() * r); - self.set_g(self.g() * g); - self.set_b(self.b() * b); + fn mul_assign(&mut self, rhs: [f32; 3]) { + match self { + Color::Rgba { + red, green, blue, .. + } => { + *red *= rhs[0]; + *green *= rhs[1]; + *blue *= rhs[2]; + } + Color::RgbaLinear { + red, green, blue, .. + } => { + *red *= rhs[0]; + *green *= rhs[1]; + *blue *= rhs[2]; + } + Color::Hsla { + hue, + saturation, + lightness, + .. + } => { + *hue *= rhs[0]; + *saturation *= rhs[1]; + *lightness *= rhs[2]; + } + } } } -impl Bytes for ColorSource { +impl Bytes for Color { fn write_bytes(&self, buffer: &mut [u8]) { match *self { - ColorSource::Color(ref color) => color.write_bytes(buffer), - ColorSource::Texture(_) => {} // Texture is not a uniform + Color::Rgba { + red, + green, + blue, + alpha, + } => { + red.nonlinear_to_linear_srgb().write_bytes(buffer); + green + .nonlinear_to_linear_srgb() + .write_bytes(&mut buffer[std::mem::size_of::()..]); + blue.nonlinear_to_linear_srgb() + .write_bytes(&mut buffer[2 * std::mem::size_of::()..]); + alpha.write_bytes(&mut buffer[3 * std::mem::size_of::()..]); + } + Color::RgbaLinear { + red, + green, + blue, + alpha, + } => { + red.write_bytes(buffer); + green.write_bytes(&mut buffer[std::mem::size_of::()..]); + blue.write_bytes(&mut buffer[2 * std::mem::size_of::()..]); + alpha.write_bytes(&mut buffer[3 * std::mem::size_of::()..]); + } + Color::Hsla { + hue, + saturation, + lightness, + alpha, + } => { + let [red, green, blue] = + HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + red.nonlinear_to_linear_srgb().write_bytes(buffer); + green + .nonlinear_to_linear_srgb() + .write_bytes(&mut buffer[std::mem::size_of::()..]); + blue.nonlinear_to_linear_srgb() + .write_bytes(&mut buffer[std::mem::size_of::() * 2..]); + alpha.write_bytes(&mut buffer[std::mem::size_of::() * 3..]); + } } } fn byte_len(&self) -> usize { - match *self { - ColorSource::Color(ref color) => color.byte_len(), - ColorSource::Texture(_) => 0, // Texture is not a uniform - } - } -} - -/// A source of color -pub enum ColorSource { - Color(Color), - Texture(Handle), -} - -impl From<[f32; 4]> for ColorSource { - fn from(f32s: [f32; 4]) -> Self { - ColorSource::Color(f32s.into()) - } -} - -impl From for ColorSource { - fn from(vec4: Vec4) -> Self { - ColorSource::Color(vec4.into()) - } -} - -impl From for ColorSource { - fn from(color: Color) -> Self { - ColorSource::Color(color) - } -} - -impl From> for ColorSource { - fn from(texture: Handle) -> Self { - ColorSource::Texture(texture) + std::mem::size_of::() * 4 } } @@ -500,135 +1102,129 @@ fn decode_rgba(data: &[u8]) -> Result { } } -#[test] -fn test_color_components_roundtrip() { - let mut color = Color::NONE; - color.set_r(0.5).set_g(0.5).set_b(0.5).set_a(0.5); - const EPS: f32 = 0.001; - assert!((color.r() - 0.5).abs() < EPS); - assert!((color.g() - 0.5).abs() < EPS); - assert!((color.b() - 0.5).abs() < EPS); - assert!((color.a() - 0.5).abs() < EPS); -} - -#[test] -fn test_hex_color() { - assert_eq!(Color::hex("FFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); - assert_eq!(Color::hex("000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); - assert!(Color::hex("---").is_err()); - - assert_eq!(Color::hex("FFFF").unwrap(), Color::rgba(1.0, 1.0, 1.0, 1.0)); - assert_eq!(Color::hex("0000").unwrap(), Color::rgba(0.0, 0.0, 0.0, 0.0)); - assert!(Color::hex("----").is_err()); - - assert_eq!(Color::hex("FFFFFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); - assert_eq!(Color::hex("000000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); - assert!(Color::hex("------").is_err()); - - assert_eq!( - Color::hex("FFFFFFFF").unwrap(), - Color::rgba(1.0, 1.0, 1.0, 1.0) - ); - assert_eq!( - Color::hex("00000000").unwrap(), - Color::rgba(0.0, 0.0, 0.0, 0.0) - ); - assert!(Color::hex("--------").is_err()); - - assert!(Color::hex("1234567890").is_err()); -} - -#[test] -fn test_conversions_vec4() { - let starting_vec4 = Vec4::new(0.4, 0.5, 0.6, 1.0); - let starting_color = Color::from(starting_vec4); - - assert_eq!(starting_vec4, Vec4::from(starting_color),); - - let transformation = Vec4::new(0.5, 0.5, 0.5, 1.0); - - assert_eq!( - starting_color * transformation, - Color::from(starting_vec4 * transformation), - ); -} - -#[test] -fn test_mul_and_mulassign_f32() { - let transformation = 0.5; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.5, 0.5 * 0.5, 0.6 * 0.5, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); -} - -#[test] -fn test_mul_and_mulassign_f32by3() { - let transformation = [0.4, 0.5, 0.6]; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); -} - -#[test] -fn test_mul_and_mulassign_f32by4() { - let transformation = [0.4, 0.5, 0.6, 0.9]; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0 * 0.9), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); -} - -#[test] -fn test_mul_and_mulassign_vec3() { - let transformation = Vec3::new(0.2, 0.3, 0.4); - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); -} - -#[test] -fn test_mul_and_mulassign_vec4() { - let transformation = Vec4::new(0.2, 0.3, 0.4, 0.5); - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0 * 0.5), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_color() { + assert_eq!(Color::hex("FFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); + assert_eq!(Color::hex("000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); + assert!(Color::hex("---").is_err()); + + assert_eq!(Color::hex("FFFF").unwrap(), Color::rgba(1.0, 1.0, 1.0, 1.0)); + assert_eq!(Color::hex("0000").unwrap(), Color::rgba(0.0, 0.0, 0.0, 0.0)); + assert!(Color::hex("----").is_err()); + + assert_eq!(Color::hex("FFFFFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); + assert_eq!(Color::hex("000000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); + assert!(Color::hex("------").is_err()); + + assert_eq!( + Color::hex("FFFFFFFF").unwrap(), + Color::rgba(1.0, 1.0, 1.0, 1.0) + ); + assert_eq!( + Color::hex("00000000").unwrap(), + Color::rgba(0.0, 0.0, 0.0, 0.0) + ); + assert!(Color::hex("--------").is_err()); + + assert!(Color::hex("1234567890").is_err()); + } + + #[test] + fn conversions_vec4() { + let starting_vec4 = Vec4::new(0.4, 0.5, 0.6, 1.0); + let starting_color = Color::from(starting_vec4); + + assert_eq!(starting_vec4, Vec4::from(starting_color),); + + let transformation = Vec4::new(0.5, 0.5, 0.5, 1.0); + + assert_eq!( + starting_color * transformation, + Color::from(starting_vec4 * transformation), + ); + } + + #[test] + fn mul_and_mulassign_f32() { + let transformation = 0.5; + let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); + + assert_eq!( + starting_color * transformation, + Color::rgba(0.4 * 0.5, 0.5 * 0.5, 0.6 * 0.5, 1.0), + ); + + let mut mutated_color = starting_color; + mutated_color *= transformation; + + assert_eq!(starting_color * transformation, mutated_color,); + } + + #[test] + fn mul_and_mulassign_f32by3() { + let transformation = [0.4, 0.5, 0.6]; + let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); + + assert_eq!( + starting_color * transformation, + Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0), + ); + + let mut mutated_color = starting_color; + mutated_color *= transformation; + + assert_eq!(starting_color * transformation, mutated_color,); + } + + #[test] + fn mul_and_mulassign_f32by4() { + let transformation = [0.4, 0.5, 0.6, 0.9]; + let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); + + assert_eq!( + starting_color * transformation, + Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0 * 0.9), + ); + + let mut mutated_color = starting_color; + mutated_color *= transformation; + + assert_eq!(starting_color * transformation, mutated_color,); + } + + #[test] + fn mul_and_mulassign_vec3() { + let transformation = Vec3::new(0.2, 0.3, 0.4); + let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); + + assert_eq!( + starting_color * transformation, + Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0), + ); + + let mut mutated_color = starting_color; + mutated_color *= transformation; + + assert_eq!(starting_color * transformation, mutated_color,); + } + + #[test] + fn mul_and_mulassign_vec4() { + let transformation = Vec4::new(0.2, 0.3, 0.4, 0.5); + let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); + + assert_eq!( + starting_color * transformation, + Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0 * 0.5), + ); + + let mut mutated_color = starting_color; + mutated_color *= transformation; + + assert_eq!(starting_color * transformation, mutated_color,); + } } diff --git a/crates/bevy_render/src/colorspace.rs b/crates/bevy_render/src/colorspace.rs index 53d99aa2dd..677b115276 100644 --- a/crates/bevy_render/src/colorspace.rs +++ b/crates/bevy_render/src/colorspace.rs @@ -29,19 +29,173 @@ impl SrgbColorSpace for f32 { } } -#[test] -fn test_srgb_full_roundtrip() { - let u8max: f32 = u8::max_value() as f32; - for color in 0..u8::max_value() { - let color01 = color as f32 / u8max; - let color_roundtrip = color01 - .linear_to_nonlinear_srgb() - .nonlinear_to_linear_srgb(); - // roundtrip is not perfect due to numeric precision, even with f64 - // so ensure the error is at least ready for u8 (where sRGB is used) - assert_eq!( - (color01 * u8max).round() as u8, - (color_roundtrip * u8max).round() as u8 - ); +pub struct HslRepresentation; +impl HslRepresentation { + /// converts a color in HLS space to sRGB space + pub fn hsl_to_nonlinear_srgb(hue: f32, saturation: f32, lightness: f32) -> [f32; 3] { + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB + let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation; + let hue_prime = hue / 60.0; + let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs()); + let (r_temp, g_temp, b_temp) = if hue_prime < 1.0 { + (chroma, largest_component, 0.0) + } else if hue_prime < 2.0 { + (largest_component, chroma, 0.0) + } else if hue_prime < 3.0 { + (0.0, chroma, largest_component) + } else if hue_prime < 4.0 { + (0.0, largest_component, chroma) + } else if hue_prime < 5.0 { + (largest_component, 0.0, chroma) + } else { + (chroma, 0.0, largest_component) + }; + let lightness_match = lightness - chroma / 2.0; + + [ + r_temp + lightness_match, + g_temp + lightness_match, + b_temp + lightness_match, + ] + } + + /// converts a color in sRGB space to HLS space + pub fn nonlinear_srgb_to_hsl([red, green, blue]: [f32; 3]) -> (f32, f32, f32) { + // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB + let x_max = red.max(green.max(blue)); + let x_min = red.min(green.min(blue)); + let chroma = x_max - x_min; + let lightness = (x_max + x_min) / 2.0; + let hue = if chroma == 0.0 { + 0.0 + } else if red > green && red > blue { + 60.0 * (green - blue) / chroma + } else if green > red && green > blue { + 60.0 * (2.0 + (blue - red) / chroma) + } else { + 60.0 * (4.0 + (red - green) / chroma) + }; + let hue = if hue < 0.0 { 360.0 + hue } else { hue }; + let saturation = if lightness <= 0.0 || lightness >= 1.0 { + 0.0 + } else { + (x_max - lightness) / lightness.min(1.0 - lightness) + }; + + (hue, saturation, lightness) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn srgb_linear_full_roundtrip() { + let u8max: f32 = u8::max_value() as f32; + for color in 0..u8::max_value() { + let color01 = color as f32 / u8max; + let color_roundtrip = color01 + .linear_to_nonlinear_srgb() + .nonlinear_to_linear_srgb(); + // roundtrip is not perfect due to numeric precision, even with f64 + // so ensure the error is at least ready for u8 (where sRGB is used) + assert_eq!( + (color01 * u8max).round() as u8, + (color_roundtrip * u8max).round() as u8 + ); + } + } + + #[test] + fn hsl_to_srgb() { + // "truth" from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples + + // black + let (hue, saturation, lightness) = (0.0, 0.0, 0.0); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 0); + assert_eq!((g * 100.0).round() as u32, 0); + assert_eq!((b * 100.0).round() as u32, 0); + + // white + let (hue, saturation, lightness) = (0.0, 0.0, 1.0); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 100); + assert_eq!((g * 100.0).round() as u32, 100); + assert_eq!((b * 100.0).round() as u32, 100); + + let (hue, saturation, lightness) = (300.0, 0.5, 0.5); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 75); + assert_eq!((g * 100.0).round() as u32, 25); + assert_eq!((b * 100.0).round() as u32, 75); + + // a red + let (hue, saturation, lightness) = (283.7, 0.775, 0.543); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 70); + assert_eq!((g * 100.0).round() as u32, 19); + assert_eq!((b * 100.0).round() as u32, 90); + + // a green + let (hue, saturation, lightness) = (162.4, 0.779, 0.447); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 10); + assert_eq!((g * 100.0).round() as u32, 80); + assert_eq!((b * 100.0).round() as u32, 59); + + // a blue + let (hue, saturation, lightness) = (251.1, 0.832, 0.511); + let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); + assert_eq!((r * 100.0).round() as u32, 25); + assert_eq!((g * 100.0).round() as u32, 10); + assert_eq!((b * 100.0).round() as u32, 92); + } + + #[test] + fn srgb_to_hsl() { + // "truth" from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples + + // black + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([0.0, 0.0, 0.0]); + assert_eq!(hue.round() as u32, 0); + assert_eq!((saturation * 100.0).round() as u32, 0); + assert_eq!((lightness * 100.0).round() as u32, 0); + + // white + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([1.0, 1.0, 1.0]); + assert_eq!(hue.round() as u32, 0); + assert_eq!((saturation * 100.0).round() as u32, 0); + assert_eq!((lightness * 100.0).round() as u32, 100); + + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([0.75, 0.25, 0.75]); + assert_eq!(hue.round() as u32, 300); + assert_eq!((saturation * 100.0).round() as u32, 50); + assert_eq!((lightness * 100.0).round() as u32, 50); + + // a red + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([0.704, 0.187, 0.897]); + assert_eq!(hue.round() as u32, 284); + assert_eq!((saturation * 100.0).round() as u32, 78); + assert_eq!((lightness * 100.0).round() as u32, 54); + + // a green + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([0.099, 0.795, 0.591]); + assert_eq!(hue.round() as u32, 162); + assert_eq!((saturation * 100.0).round() as u32, 78); + assert_eq!((lightness * 100.0).round() as u32, 45); + + // a blue + let (hue, saturation, lightness) = + HslRepresentation::nonlinear_srgb_to_hsl([0.255, 0.104, 0.918]); + assert_eq!(hue.round() as u32, 251); + assert_eq!((saturation * 100.0).round() as u32, 83); + assert_eq!((lightness * 100.0).round() as u32, 51); } } diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index a9400632b6..320751fc77 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,9 +1,6 @@ use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph}; use bevy_reflect::TypeUuid; -use bevy_render::{ - color::Color, - texture::{Extent3d, Texture, TextureDimension, TextureFormat}, -}; +use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; #[derive(Debug, TypeUuid)] #[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] @@ -28,25 +25,12 @@ impl Font { }); // TODO: make this texture grayscale - let color = Color::WHITE; - let color_u8 = [ - (color.r() * 255.0) as u8, - (color.g() * 255.0) as u8, - (color.b() * 255.0) as u8, - ]; Texture::new( Extent3d::new(width as u32, height as u32, 1), TextureDimension::D2, alpha .iter() - .map(|a| { - vec![ - color_u8[0], - color_u8[1], - color_u8[2], - (color.a() * a * 255.0) as u8, - ] - }) + .map(|a| vec![255, 255, 255, (*a * 255.0) as u8]) .flatten() .collect::>(), TextureFormat::Rgba8UnormSrgb, diff --git a/crates/bevy_wgpu/src/wgpu_type_converter.rs b/crates/bevy_wgpu/src/wgpu_type_converter.rs index f8b35c1b69..94c1714e35 100644 --- a/crates/bevy_wgpu/src/wgpu_type_converter.rs +++ b/crates/bevy_wgpu/src/wgpu_type_converter.rs @@ -127,11 +127,12 @@ impl<'a> From<&'a OwnedWgpuVertexBufferLayout> for wgpu::VertexBufferLayout<'a> impl WgpuFrom for wgpu::Color { fn from(color: Color) -> Self { + let linear = color.as_linear_rgba_f32(); wgpu::Color { - r: color.r_linear() as f64, - g: color.g_linear() as f64, - b: color.b_linear() as f64, - a: color.a() as f64, + r: linear[0] as f64, + g: linear[1] as f64, + b: linear[2] as f64, + a: linear[3] as f64, } } } diff --git a/examples/2d/contributors.rs b/examples/2d/contributors.rs index 179234dbe1..2ba11c06c4 100644 --- a/examples/2d/contributors.rs +++ b/examples/2d/contributors.rs @@ -29,7 +29,7 @@ struct SelectTimer; struct ContributorDisplay; struct Contributor { - color: [f32; 3], + hue: f32, } struct Velocity { @@ -40,8 +40,11 @@ struct Velocity { const GRAVITY: f32 = -9.821 * 100.0; const SPRITE_SIZE: f32 = 75.0; -const COL_DESELECTED: Color = Color::rgba_linear(0.03, 0.03, 0.03, 0.92); -const COL_SELECTED: Color = Color::WHITE; +const SATURATION_DESELECTED: f32 = 0.3; +const LIGHTNESS_DESELECTED: f32 = 0.2; +const SATURATION_SELECTED: f32 = 0.9; +const LIGHTNESS_SELECTED: f32 = 0.7; +const ALPHA: f32 = 0.92; const SHOWCASE_TIMER_SECS: f32 = 3.0; @@ -69,7 +72,7 @@ fn setup( let pos = (rnd.gen_range(-400.0..400.0), rnd.gen_range(0.0..400.0)); let dir = rnd.gen_range(-1.0..1.0); let velocity = Vec3::new(dir * 500.0, 0.0, 0.0); - let col = gen_color(&mut rnd); + let hue = rnd.gen_range(0.0..=360.0); // some sprites should be flipped let flipped = rnd.gen_bool(0.5); @@ -77,7 +80,7 @@ fn setup( let transform = Transform::from_xyz(pos.0, pos.1, 0.0); commands - .spawn((Contributor { color: col },)) + .spawn((Contributor { hue },)) .with(Velocity { translation: velocity, rotation: -dir * 5.0, @@ -90,12 +93,12 @@ fn setup( ..Default::default() }, material: materials.add(ColorMaterial { - color: COL_DESELECTED * col, + color: Color::hsla(hue, SATURATION_DESELECTED, LIGHTNESS_DESELECTED, ALPHA), texture: Some(texture_handle.clone()), }), + transform, ..Default::default() - }) - .with(transform); + }); let e = commands.current_entity().unwrap(); @@ -180,15 +183,8 @@ fn select_system( let (name, e) = &sel.order[sel.idx]; if let Ok((c, handle, mut tr)) = q.get_mut(*e) { - for mut text in dq.iter_mut() { - select( - &mut *materials, - handle.clone(), - c, - &mut *tr, - &mut *text, - name, - ); + if let Some(mut text) = dq.iter_mut().next() { + select(&mut *materials, handle, c, &mut *tr, &mut *text, name); } } } @@ -197,14 +193,14 @@ fn select_system( /// bring the object to the front and display the name. fn select( materials: &mut Assets, - mat_handle: Handle, + mat_handle: &Handle, cont: &Contributor, trans: &mut Transform, text: &mut Text, name: &str, ) -> Option<()> { let mat = materials.get_mut(mat_handle)?; - mat.color = COL_SELECTED * cont.color; + mat.color = Color::hsla(cont.hue, SATURATION_SELECTED, LIGHTNESS_SELECTED, ALPHA); trans.translation.z = 100.0; @@ -224,7 +220,7 @@ fn deselect( trans: &mut Transform, ) -> Option<()> { let mat = materials.get_mut(mat_handle)?; - mat.color = COL_DESELECTED * cont.color; + mat.color = Color::hsla(cont.hue, SATURATION_DESELECTED, LIGHTNESS_DESELECTED, ALPHA); trans.translation.z = 0.0; @@ -321,20 +317,3 @@ fn contributors() -> Contributors { .filter_map(|x| x.ok()) .collect() } - -/// Generate a color modulation -/// -/// Because there is no `Mul for Color` instead `[f32; 3]` is -/// used. -fn gen_color(rng: &mut impl Rng) -> [f32; 3] { - loop { - let rgb = rng.gen(); - if luminance(rgb) >= 0.6 { - break rgb; - } - } -} - -fn luminance([r, g, b]: [f32; 3]) -> f32 { - 0.299 * r + 0.587 * g + 0.114 * b -} diff --git a/examples/tools/bevymark.rs b/examples/tools/bevymark.rs index c00f257cda..d2b9f699f8 100644 --- a/examples/tools/bevymark.rs +++ b/examples/tools/bevymark.rs @@ -5,7 +5,7 @@ use bevy::{ use rand::Rng; const BIRDS_PER_SECOND: u32 = 1000; -const BASE_COLOR: Color = Color::rgb_linear(5.0, 5.0, 5.0); +const BASE_COLOR: Color = Color::rgb(5.0, 5.0, 5.0); const GRAVITY: f32 = -9.8 * 100.0; const MAX_VELOCITY: f32 = 750.; const BIRD_SCALE: f32 = 0.15; diff --git a/examples/ui/text.rs b/examples/ui/text.rs index 585819c24e..5ce60f8c51 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -106,11 +106,11 @@ fn text_color_system(time: Res