Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sRGB awareness for Color #616

Merged
merged 11 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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