Skip to content

Commit

Permalink
Adds back in way to convert color to u8 array, implemented for the tw…
Browse files Browse the repository at this point in the history
…o 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 #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>
  • Loading branch information
gagnus and gagnus committed Jun 10, 2024
1 parent c50a4d8 commit 298b01f
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 45 deletions.
7 changes: 6 additions & 1 deletion crates/bevy_color/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_color/src/color_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
63 changes: 49 additions & 14 deletions crates/bevy_color/src/linear_rgba.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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())
}
}

Expand Down Expand Up @@ -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<LinearRgba> for wgpu_types::Color {
fn from(color: LinearRgba) -> Self {
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 24 additions & 13 deletions crates/bevy_color/src/srgba.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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),
Expand All @@ -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
Expand All @@ -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).
Expand Down Expand Up @@ -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 {
#[inline]
fn from(value: LinearRgba) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/fog.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
17 changes: 4 additions & 13 deletions crates/bevy_pbr/src/volumetric_fog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/mesh2d/color_material.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down

0 comments on commit 298b01f

Please sign in to comment.