From 298b01f10da4535ff70b026f960a9eca5b6901f0 Mon Sep 17 00:00:00 2001 From: Gagnus <20407779+gagnus@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:03:46 +0100 Subject: [PATCH] Adds back in way to convert color to u8 array, implemented for the two RGB color types, also renames Color::linear to Color::to_linear. (#13759) # Objective One thing missing from the new Color implementation in 0.14 is the ability to easily convert to a u8 representation of the rgb color. (note this is a redo of PR https://github.com/bevyengine/bevy/pull/13739 as I needed to move the source branch ## Solution I have added to_u8_array and to_u8_array_no_alpha to a new trait called ColorToPacked to mirror the f32 conversions in ColorToComponents and implemented the new trait for Srgba and LinearRgba. To go with those I also added matching from_u8... functions and converted a couple of cases that used ad-hoc implementations of that conversion to use these. After discussion on Discord of the experience of using the API I renamed Color::linear to Color::to_linear, as without that it looks like a constructor (like Color::rgb). I also added to_srgba which is the other commonly converted to type of color (for UI and 2D) to match to_linear. Removed a redundant extra implementation of to_f32_array for LinearColor as it is also supplied in ColorToComponents (I'm surprised that's allowed?) ## Testing Ran all tests and manually tested. Added to_and_from_u8 to linear_rgba::tests ## Changelog visible change is Color::linear becomes Color::to_linear. --------- Co-authored-by: John Payne <20407779+johngpayne@users.noreply.github.com> --- crates/bevy_color/src/color.rs | 7 ++- crates/bevy_color/src/color_ops.rs | 12 ++++ crates/bevy_color/src/linear_rgba.rs | 63 ++++++++++++++----- crates/bevy_color/src/srgba.rs | 37 +++++++---- crates/bevy_pbr/src/fog.rs | 2 +- crates/bevy_pbr/src/render/light.rs | 1 + crates/bevy_pbr/src/volumetric_fog/mod.rs | 17 ++--- .../bevy_sprite/src/mesh2d/color_material.rs | 2 +- crates/bevy_sprite/src/render/mod.rs | 2 +- crates/bevy_ui/src/render/mod.rs | 2 +- 10 files changed, 100 insertions(+), 45 deletions(-) diff --git a/crates/bevy_color/src/color.rs b/crates/bevy_color/src/color.rs index 4ddc9599a1d3b..385c3582aa8ea 100644 --- a/crates/bevy_color/src/color.rs +++ b/crates/bevy_color/src/color.rs @@ -73,7 +73,12 @@ impl StandardColor for Color {} impl Color { /// Return the color as a linear RGBA color. - pub fn linear(&self) -> LinearRgba { + pub fn to_linear(&self) -> LinearRgba { + (*self).into() + } + + /// Return the color as an SRGBA color. + pub fn to_srgba(&self) -> Srgba { (*self).into() } diff --git a/crates/bevy_color/src/color_ops.rs b/crates/bevy_color/src/color_ops.rs index e731a6f33c5a7..32fdb83ba2ede 100644 --- a/crates/bevy_color/src/color_ops.rs +++ b/crates/bevy_color/src/color_ops.rs @@ -115,6 +115,18 @@ pub trait ColorToComponents { fn from_vec3(color: Vec3) -> Self; } +/// Trait with methods for converting colors to packed non-color types +pub trait ColorToPacked { + /// Convert to [u8; 4] where that makes sense (Srgba is most relevant) + fn to_u8_array(self) -> [u8; 4]; + /// Convert to [u8; 3] where that makes sense (Srgba is most relevant) + fn to_u8_array_no_alpha(self) -> [u8; 3]; + /// Convert from [u8; 4] where that makes sense (Srgba is most relevant) + fn from_u8_array(color: [u8; 4]) -> Self; + /// Convert to [u8; 3] where that makes sense (Srgba is most relevant) + fn from_u8_array_no_alpha(color: [u8; 3]) -> Self; +} + /// Utility function for interpolating hue values. This ensures that the interpolation /// takes the shortest path around the color wheel, and that the result is always between /// 0 and 360. diff --git a/crates/bevy_color/src/linear_rgba.rs b/crates/bevy_color/src/linear_rgba.rs index 041c039d9abb0..2dafa15eed798 100644 --- a/crates/bevy_color/src/linear_rgba.rs +++ b/crates/bevy_color/src/linear_rgba.rs @@ -1,6 +1,6 @@ use crate::{ color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents, - Gray, Luminance, Mix, StandardColor, + ColorToPacked, Gray, Luminance, Mix, StandardColor, }; use bevy_math::{Vec3, Vec4}; use bevy_reflect::prelude::*; @@ -149,24 +149,12 @@ impl LinearRgba { } } - /// Converts the color into a [f32; 4] array in RGBA order. - /// - /// This is useful for passing the color to a shader. - pub fn to_f32_array(&self) -> [f32; 4] { - [self.red, self.green, self.blue, self.alpha] - } - /// Converts this color to a u32. /// /// Maps the RGBA channels in RGBA order to a little-endian byte array (GPUs are little-endian). /// `A` will be the most significant byte and `R` the least significant. pub fn as_u32(&self) -> u32 { - u32::from_le_bytes([ - (self.red * 255.0) as u8, - (self.green * 255.0) as u8, - (self.blue * 255.0) as u8, - (self.alpha * 255.0) as u8, - ]) + u32::from_le_bytes(self.to_u8_array()) } } @@ -310,6 +298,25 @@ impl ColorToComponents for LinearRgba { } } +impl ColorToPacked for LinearRgba { + fn to_u8_array(self) -> [u8; 4] { + [self.red, self.green, self.blue, self.alpha] + .map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) + } + + fn to_u8_array_no_alpha(self) -> [u8; 3] { + [self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) + } + + fn from_u8_array(color: [u8; 4]) -> Self { + Self::from_f32_array(color.map(|u| u as f32 / 255.0)) + } + + fn from_u8_array_no_alpha(color: [u8; 3]) -> Self { + Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0)) + } +} + #[cfg(feature = "wgpu-types")] impl From for wgpu_types::Color { fn from(color: LinearRgba) -> Self { @@ -416,6 +423,34 @@ mod tests { assert_eq!(a.distance_squared(&b), 1.0); } + #[test] + fn to_and_from_u8() { + // from_u8_array + let a = LinearRgba::from_u8_array([255, 0, 0, 255]); + let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0); + assert_eq!(a, b); + + // from_u8_array_no_alpha + let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]); + let b = LinearRgba::rgb(1.0, 1.0, 0.0); + assert_eq!(a, b); + + // to_u8_array + let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array(); + let b = [0, 0, 255, 255]; + assert_eq!(a, b); + + // to_u8_array_no_alpha + let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha(); + let b = [0, 255, 255]; + assert_eq!(a, b); + + // clamping + let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha(); + let b = [0, 255, 0]; + assert_eq!(a, b); + } + #[test] fn darker_lighter() { // Darker and lighter should be commutative. diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index 9de33883af82d..f8396a93944ec 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -1,7 +1,7 @@ use crate::color_difference::EuclideanDistance; use crate::{ - impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, LinearRgba, Luminance, Mix, - StandardColor, Xyza, + impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba, + Luminance, Mix, StandardColor, Xyza, }; use bevy_math::{Vec3, Vec4}; use bevy_reflect::prelude::*; @@ -168,10 +168,7 @@ impl Srgba { /// Convert this color to CSS-style hexadecimal notation. pub fn to_hex(&self) -> String { - let r = (self.red * 255.0).round() as u8; - let g = (self.green * 255.0).round() as u8; - let b = (self.blue * 255.0).round() as u8; - let a = (self.alpha * 255.0).round() as u8; + let [r, g, b, a] = self.to_u8_array(); match a { 255 => format!("#{:02X}{:02X}{:02X}", r, g, b), _ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a), @@ -189,7 +186,7 @@ impl Srgba { /// See also [`Srgba::new`], [`Srgba::rgba_u8`], [`Srgba::hex`]. /// pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self { - Self::rgba_u8(r, g, b, u8::MAX) + Self::from_u8_array_no_alpha([r, g, b]) } // Float operations in const fn are not stable yet @@ -206,12 +203,7 @@ impl Srgba { /// See also [`Srgba::new`], [`Srgba::rgb_u8`], [`Srgba::hex`]. /// pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { - Self::new( - r as f32 / u8::MAX as f32, - g as f32 / u8::MAX as f32, - b as f32 / u8::MAX as f32, - a as f32 / u8::MAX as f32, - ) + Self::from_u8_array([r, g, b, a]) } /// Converts a non-linear sRGB value to a linear one via [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction). @@ -373,6 +365,25 @@ impl ColorToComponents for Srgba { } } +impl ColorToPacked for Srgba { + fn to_u8_array(self) -> [u8; 4] { + [self.red, self.green, self.blue, self.alpha] + .map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) + } + + fn to_u8_array_no_alpha(self) -> [u8; 3] { + [self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) + } + + fn from_u8_array(color: [u8; 4]) -> Self { + Self::from_f32_array(color.map(|u| u as f32 / 255.0)) + } + + fn from_u8_array_no_alpha(color: [u8; 3]) -> Self { + Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0)) + } +} + impl From for Srgba { #[inline] fn from(value: LinearRgba) -> Self { diff --git a/crates/bevy_pbr/src/fog.rs b/crates/bevy_pbr/src/fog.rs index 8ed7533a6c043..5567783274f00 100644 --- a/crates/bevy_pbr/src/fog.rs +++ b/crates/bevy_pbr/src/fog.rs @@ -1,4 +1,4 @@ -use bevy_color::{Color, LinearRgba}; +use bevy_color::{Color, ColorToComponents, LinearRgba}; use bevy_ecs::prelude::*; use bevy_math::Vec3; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index ded628c5b33ee..0f17c2cabc8f9 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,4 +1,5 @@ use bevy_asset::AssetId; +use bevy_color::ColorToComponents; use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT; use bevy_ecs::entity::EntityHashSet; use bevy_ecs::prelude::*; diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index acc3b303c7445..170d38cb21f23 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -31,7 +31,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; -use bevy_color::Color; +use bevy_color::{Color, ColorToComponents}; use bevy_core_pipeline::{ core_3d::{ graph::{Core3d, Node3d}, @@ -617,18 +617,9 @@ pub fn prepare_volumetric_fog_uniforms( for (entity, volumetric_fog_settings) in view_targets.iter() { let offset = writer.write(&VolumetricFogUniform { - fog_color: Vec3::from_slice( - &volumetric_fog_settings.fog_color.linear().to_f32_array()[0..3], - ), - light_tint: Vec3::from_slice( - &volumetric_fog_settings.light_tint.linear().to_f32_array()[0..3], - ), - ambient_color: Vec3::from_slice( - &volumetric_fog_settings - .ambient_color - .linear() - .to_f32_array()[0..3], - ), + fog_color: volumetric_fog_settings.fog_color.to_linear().to_vec3(), + light_tint: volumetric_fog_settings.light_tint.to_linear().to_vec3(), + ambient_color: volumetric_fog_settings.ambient_color.to_linear().to_vec3(), ambient_intensity: volumetric_fog_settings.ambient_intensity, step_count: volumetric_fog_settings.step_count, max_depth: volumetric_fog_settings.max_depth, diff --git a/crates/bevy_sprite/src/mesh2d/color_material.rs b/crates/bevy_sprite/src/mesh2d/color_material.rs index 9bf6581b8848f..78061ce06d0c6 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.rs +++ b/crates/bevy_sprite/src/mesh2d/color_material.rs @@ -1,7 +1,7 @@ use crate::{Material2d, Material2dPlugin, MaterialMesh2dBundle}; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; +use bevy_color::{Color, ColorToComponents, LinearRgba}; use bevy_math::Vec4; use bevy_reflect::prelude::*; use bevy_render::{ diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 64dda440be2a0..0e7efc1f75c23 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -5,7 +5,7 @@ use crate::{ ComputedTextureSlices, Sprite, WithSprite, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; -use bevy_color::LinearRgba; +use bevy_color::{ColorToComponents, LinearRgba}; use bevy_core_pipeline::{ core_2d::Transparent2d, tonemapping::{ diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 33f7443787225..d05c0b541e00e 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -2,7 +2,7 @@ mod pipeline; mod render_pass; mod ui_material_pipeline; -use bevy_color::{Alpha, LinearRgba}; +use bevy_color::{Alpha, ColorToComponents, LinearRgba}; use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};