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>
This commit is contained in:
Gagnus 2024-06-10 14:03:46 +01:00 committed by GitHub
parent c50a4d8821
commit 298b01f10d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 100 additions and 45 deletions

View file

@ -73,7 +73,12 @@ impl StandardColor for Color {}
impl Color { impl Color {
/// Return the color as a linear RGBA 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() (*self).into()
} }

View file

@ -115,6 +115,18 @@ pub trait ColorToComponents {
fn from_vec3(color: Vec3) -> Self; 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 /// 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 /// takes the shortest path around the color wheel, and that the result is always between
/// 0 and 360. /// 0 and 360.

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents, 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_math::{Vec3, Vec4};
use bevy_reflect::prelude::*; 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. /// Converts this color to a u32.
/// ///
/// Maps the RGBA channels in RGBA order to a little-endian byte array (GPUs are little-endian). /// 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. /// `A` will be the most significant byte and `R` the least significant.
pub fn as_u32(&self) -> u32 { pub fn as_u32(&self) -> u32 {
u32::from_le_bytes([ u32::from_le_bytes(self.to_u8_array())
(self.red * 255.0) as u8,
(self.green * 255.0) as u8,
(self.blue * 255.0) as u8,
(self.alpha * 255.0) as u8,
])
} }
} }
@ -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")] #[cfg(feature = "wgpu-types")]
impl From<LinearRgba> for wgpu_types::Color { impl From<LinearRgba> for wgpu_types::Color {
fn from(color: LinearRgba) -> Self { fn from(color: LinearRgba) -> Self {
@ -416,6 +423,34 @@ mod tests {
assert_eq!(a.distance_squared(&b), 1.0); 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] #[test]
fn darker_lighter() { fn darker_lighter() {
// Darker and lighter should be commutative. // Darker and lighter should be commutative.

View file

@ -1,7 +1,7 @@
use crate::color_difference::EuclideanDistance; use crate::color_difference::EuclideanDistance;
use crate::{ use crate::{
impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, LinearRgba, Luminance, Mix, impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba,
StandardColor, Xyza, Luminance, Mix, StandardColor, Xyza,
}; };
use bevy_math::{Vec3, Vec4}; use bevy_math::{Vec3, Vec4};
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
@ -168,10 +168,7 @@ impl Srgba {
/// Convert this color to CSS-style hexadecimal notation. /// Convert this color to CSS-style hexadecimal notation.
pub fn to_hex(&self) -> String { pub fn to_hex(&self) -> String {
let r = (self.red * 255.0).round() as u8; let [r, g, b, a] = self.to_u8_array();
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;
match a { match a {
255 => format!("#{:02X}{:02X}{:02X}", r, g, b), 255 => format!("#{:02X}{:02X}{:02X}", r, g, b),
_ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a), _ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a),
@ -189,7 +186,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgba_u8`], [`Srgba::hex`]. /// See also [`Srgba::new`], [`Srgba::rgba_u8`], [`Srgba::hex`].
/// ///
pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self { 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 // Float operations in const fn are not stable yet
@ -206,12 +203,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgb_u8`], [`Srgba::hex`]. /// See also [`Srgba::new`], [`Srgba::rgb_u8`], [`Srgba::hex`].
/// ///
pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new( Self::from_u8_array([r, g, b, a])
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,
)
} }
/// Converts a non-linear sRGB value to a linear one via [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction). /// 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<LinearRgba> for Srgba { impl From<LinearRgba> for Srgba {
#[inline] #[inline]
fn from(value: LinearRgba) -> Self { fn from(value: LinearRgba) -> Self {

View file

@ -1,4 +1,4 @@
use bevy_color::{Color, LinearRgba}; use bevy_color::{Color, ColorToComponents, LinearRgba};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_math::Vec3; use bevy_math::Vec3;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};

View file

@ -1,4 +1,5 @@
use bevy_asset::AssetId; use bevy_asset::AssetId;
use bevy_color::ColorToComponents;
use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT; use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
use bevy_ecs::entity::EntityHashSet; use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;

View file

@ -31,7 +31,7 @@
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle}; use bevy_asset::{load_internal_asset, Handle};
use bevy_color::Color; use bevy_color::{Color, ColorToComponents};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::{ core_3d::{
graph::{Core3d, Node3d}, graph::{Core3d, Node3d},
@ -617,18 +617,9 @@ pub fn prepare_volumetric_fog_uniforms(
for (entity, volumetric_fog_settings) in view_targets.iter() { for (entity, volumetric_fog_settings) in view_targets.iter() {
let offset = writer.write(&VolumetricFogUniform { let offset = writer.write(&VolumetricFogUniform {
fog_color: Vec3::from_slice( fog_color: volumetric_fog_settings.fog_color.to_linear().to_vec3(),
&volumetric_fog_settings.fog_color.linear().to_f32_array()[0..3], light_tint: volumetric_fog_settings.light_tint.to_linear().to_vec3(),
), ambient_color: volumetric_fog_settings.ambient_color.to_linear().to_vec3(),
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],
),
ambient_intensity: volumetric_fog_settings.ambient_intensity, ambient_intensity: volumetric_fog_settings.ambient_intensity,
step_count: volumetric_fog_settings.step_count, step_count: volumetric_fog_settings.step_count,
max_depth: volumetric_fog_settings.max_depth, max_depth: volumetric_fog_settings.max_depth,

View file

@ -1,7 +1,7 @@
use crate::{Material2d, Material2dPlugin, MaterialMesh2dBundle}; use crate::{Material2d, Material2dPlugin, MaterialMesh2dBundle};
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; 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_math::Vec4;
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_render::{ use bevy_render::{

View file

@ -5,7 +5,7 @@ use crate::{
ComputedTextureSlices, Sprite, WithSprite, SPRITE_SHADER_HANDLE, ComputedTextureSlices, Sprite, WithSprite, SPRITE_SHADER_HANDLE,
}; };
use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
use bevy_color::LinearRgba; use bevy_color::{ColorToComponents, LinearRgba};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_2d::Transparent2d, core_2d::Transparent2d,
tonemapping::{ tonemapping::{

View file

@ -2,7 +2,7 @@ mod pipeline;
mod render_pass; mod render_pass;
mod ui_material_pipeline; 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_2d::graph::{Core2d, Node2d};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};