Skip to content

Commit

Permalink
sRGB awareness for Color (#616)
Browse files Browse the repository at this point in the history
Color is now sRGB aware, added SrgbColorSpace trait for f32
  • Loading branch information
Julian Heinken authored Oct 8, 2020
1 parent e89301a commit a92790c
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 67 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@

- [Another fast compile flag for macOS][552]

### Changed

- Breaking Change: [sRGB awareness for `Color`][616]
- Color is now assumed to be provided in the non-linear sRGB colorspace, and constructors such as `Color::rgb` and `Color::rgba` will be converted to linear sRGB under-the-hood.
- This allows drop-in use of colors from most applications.
- New methods `Color::rgb_linear` and `Color::rgba_linear` will accept colors already in linear sRGB (the old behavior)
- Individual color-components must now be accessed through setters and getters: `.r`, `.g`, `.b`, `.a`, `.set_r`, `.set_g`, `.set_b`, `.set_a`, and the corresponding methods with the `*_linear` suffix.

[552]: https://github.com/bevyengine/bevy/pull/552
[616]: https://github.com/bevyengine/bevy/pull/616

## Version 0.2.1 (2020-9-20)

Expand Down
236 changes: 181 additions & 55 deletions crates/bevy_render/src/color.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::texture::Texture;
use crate::{
colorspace::*,
impl_render_resource_bytes,
renderer::{RenderResource, RenderResourceType},
};
Expand All @@ -10,32 +11,68 @@ use bevy_property::Property;
use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign, Mul, MulAssign};

/// A RGBA color
/// 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, Property)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
red: f32,
green: f32,
blue: f32,
alpha: f32,
}

unsafe impl Byteable for Color {}

impl Color {
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 GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
pub const NONE: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
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 GREEN: Color = Color::rgb_linear(0.0, 1.0, 0.0);
pub const NONE: Color = Color::rgba_linear(0.0, 0.0, 0.0, 0.0);
pub const RED: Color = Color::rgb_linear(1.0, 0.0, 0.0);
pub const WHITE: Color = Color::rgb_linear(1.0, 1.0, 1.0);

// 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 {
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 {
red: r,
green: g,
blue: b,
alpha: a,
}
.as_nonlinear_srgb_to_linear_srgb()
}

pub const fn rgb(r: f32, g: f32, b: f32) -> Color {
Color { r, g, b, a: 1.0 }
/// New ``Color`` from linear colorspace.
pub const fn rgb_linear(r: f32, g: f32, b: f32) -> Color {
Color {
red: r,
green: g,
blue: b,
alpha: 1.0,
}
}

pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
Color { r, g, b, a }
/// New ``Color`` from linear colorspace.
pub const fn rgba_linear(r: f32, g: f32, b: f32, a: f32) -> Color {
Color {
red: r,
green: g,
blue: b,
alpha: a,
}
}

pub fn hex<T: AsRef<str>>(hex: T) -> Result<Color, HexColorError> {
Expand Down Expand Up @@ -74,12 +111,14 @@ impl Color {
Err(HexColorError::Length)
}

/// 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.
pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Color {
Color::rgba(
r as f32 / u8::MAX as f32,
Expand All @@ -88,6 +127,82 @@ impl Color {
a as f32 / u8::MAX as f32,
)
}

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
pub fn r(&self) -> f32 {
self.red.linear_to_nonlinear_srgb()
}

pub fn g(&self) -> f32 {
self.red.linear_to_nonlinear_srgb()
}

pub fn b(&self) -> f32 {
self.red.linear_to_nonlinear_srgb()
}

// linear-sRGB Component Getter
pub fn g_linear(&self) -> f32 {
self.green
}

pub fn r_linear(&self) -> f32 {
self.red
}

pub fn b_linear(&self) -> f32 {
self.blue
}

pub fn a(&self) -> f32 {
self.alpha
}

// non-linear-sRGB Component Setter
pub fn set_r(&mut self, r: f32) -> &mut Self {
self.red = r.nonlinear_to_linear_srgb();
self
}

pub fn set_g(&mut self, g: f32) -> &mut Self {
self.green = g.nonlinear_to_linear_srgb();
self
}

pub fn set_b(&mut self, b: f32) -> &mut Self {
self.blue = b.nonlinear_to_linear_srgb();
self
}

// linear-sRGB Component Setter
pub fn set_r_linear(&mut self, r: f32) -> &mut Self {
self.red = r;
self
}

pub fn set_g_linear(&mut self, g: f32) -> &mut Self {
self.green = g;
self
}

pub fn set_b_linear(&mut self, b: f32) -> &mut Self {
self.blue = b;
self
}

pub fn set_a(&mut self, a: f32) -> &mut Self {
self.alpha = a;
self
}
}

impl Default for Color {
Expand All @@ -99,10 +214,10 @@ impl Default for Color {
impl AddAssign<Color> for Color {
fn add_assign(&mut self, rhs: Color) {
*self = Color {
r: self.r + rhs.r,
g: self.g + rhs.g,
b: self.b + rhs.b,
a: self.a + rhs.a,
red: self.red + rhs.red,
green: self.green + rhs.green,
blue: self.blue + rhs.blue,
alpha: self.alpha + rhs.alpha,
}
}
}
Expand All @@ -112,10 +227,10 @@ impl Add<Color> for Color {

fn add(self, rhs: Color) -> Self::Output {
Color {
r: self.r + rhs.r,
g: self.g + rhs.g,
b: self.b + rhs.b,
a: self.a + rhs.a,
red: self.red + rhs.red,
green: self.green + rhs.green,
blue: self.blue + rhs.blue,
alpha: self.alpha + rhs.alpha,
}
}
}
Expand All @@ -125,49 +240,49 @@ impl Add<Vec4> for Color {

fn add(self, rhs: Vec4) -> Self::Output {
Color {
r: self.r + rhs.x(),
g: self.g + rhs.y(),
b: self.b + rhs.z(),
a: self.a + rhs.w(),
red: self.red + rhs.x(),
green: self.green + rhs.y(),
blue: self.blue + rhs.z(),
alpha: self.alpha + rhs.w(),
}
}
}

impl From<Vec4> for Color {
fn from(vec4: Vec4) -> Self {
Color {
r: vec4.x(),
g: vec4.y(),
b: vec4.z(),
a: vec4.w(),
red: vec4.x(),
green: vec4.y(),
blue: vec4.z(),
alpha: vec4.w(),
}
}
}

impl Into<[f32; 4]> for Color {
fn into(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
[self.red, self.green, self.blue, self.alpha]
}
}
impl Mul<f32> for Color {
type Output = Color;

fn mul(self, rhs: f32) -> Self::Output {
Color {
r: self.r * rhs,
g: self.g * rhs,
b: self.b * rhs,
a: self.a * rhs,
red: self.red * rhs,
green: self.green * rhs,
blue: self.blue * rhs,
alpha: self.alpha * rhs,
}
}
}

impl MulAssign<f32> for Color {
fn mul_assign(&mut self, rhs: f32) {
self.r *= rhs;
self.g *= rhs;
self.b *= rhs;
self.a *= rhs;
self.red *= rhs;
self.green *= rhs;
self.blue *= rhs;
self.alpha *= rhs;
}
}

Expand All @@ -176,20 +291,20 @@ impl Mul<Vec4> for Color {

fn mul(self, rhs: Vec4) -> Self::Output {
Color {
r: self.r * rhs.x(),
g: self.g * rhs.y(),
b: self.b * rhs.z(),
a: self.a * rhs.w(),
red: self.red * rhs.x(),
green: self.green * rhs.y(),
blue: self.blue * rhs.z(),
alpha: self.alpha * rhs.w(),
}
}
}

impl MulAssign<Vec4> for Color {
fn mul_assign(&mut self, rhs: Vec4) {
self.r *= rhs.x();
self.g *= rhs.y();
self.b *= rhs.z();
self.a *= rhs.w();
self.red *= rhs.x();
self.green *= rhs.y();
self.blue *= rhs.z();
self.alpha *= rhs.w();
}
}

Expand All @@ -198,19 +313,19 @@ impl Mul<Vec3> for Color {

fn mul(self, rhs: Vec3) -> Self::Output {
Color {
r: self.r * rhs.x(),
g: self.g * rhs.y(),
b: self.b * rhs.z(),
a: self.a,
red: self.red * rhs.x(),
green: self.green * rhs.y(),
blue: self.blue * rhs.z(),
alpha: self.alpha,
}
}
}

impl MulAssign<Vec3> for Color {
fn mul_assign(&mut self, rhs: Vec3) {
self.r *= rhs.x();
self.g *= rhs.y();
self.b *= rhs.z();
self.red *= rhs.x();
self.green *= rhs.y();
self.blue *= rhs.z();
}
}

Expand Down Expand Up @@ -289,6 +404,17 @@ fn decode_rgba(data: &[u8]) -> Result<Color, HexColorError> {
}
}

#[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));
Expand Down
Loading

0 comments on commit a92790c

Please sign in to comment.