mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
sRGB awareness for Color (#616)
Color is now sRGB aware, added SrgbColorSpace trait for f32
This commit is contained in:
parent
e89301ad29
commit
a92790c011
6 changed files with 253 additions and 67 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::texture::Texture;
|
||||
use crate::{
|
||||
colorspace::*,
|
||||
impl_render_resource_bytes,
|
||||
renderer::{RenderResource, RenderResourceType},
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
pub const fn rgb(r: f32, g: f32, b: f32) -> Color {
|
||||
Color { r, g, b, a: 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()
|
||||
}
|
||||
|
||||
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
|
||||
Color { r, g, b, a }
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,10 +240,10 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,17 +251,17 @@ impl Add<Vec4> for Color {
|
|||
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 {
|
||||
|
@ -154,20 +269,20 @@ impl Mul<f32> for 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
|
50
crates/bevy_render/src/colorspace.rs
Normal file
50
crates/bevy_render/src/colorspace.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
// sRGB
|
||||
//==================================================================================================
|
||||
pub trait SrgbColorSpace {
|
||||
fn linear_to_nonlinear_srgb(self) -> Self;
|
||||
fn nonlinear_to_linear_srgb(self) -> Self;
|
||||
}
|
||||
|
||||
//source: https://entropymine.com/imageworsener/srgbformula/
|
||||
impl SrgbColorSpace for f32 {
|
||||
fn linear_to_nonlinear_srgb(self) -> f32 {
|
||||
if self <= 0.0 {
|
||||
return self;
|
||||
}
|
||||
|
||||
if self <= 0.0031308 {
|
||||
self * 12.92 // linear falloff in dark values
|
||||
} else {
|
||||
(1.055 * self.powf(1.0 / 2.4)) - 0.055 //gamma curve in other area
|
||||
}
|
||||
}
|
||||
|
||||
fn nonlinear_to_linear_srgb(self) -> f32 {
|
||||
if self <= 0.0 {
|
||||
return self;
|
||||
}
|
||||
if self <= 0.04045 {
|
||||
self / 12.92 // linear falloff in dark values
|
||||
} else {
|
||||
((self + 0.055) / 1.055).powf(2.4) //gamma curve in other area
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_srgb_full_roundtrip() {
|
||||
let u8max: f32 = u8::max_value() as f32;
|
||||
for color in 0..u8::max_value() {
|
||||
let color01 = color as f32 / u8max;
|
||||
let color_roundtrip = color01
|
||||
.linear_to_nonlinear_srgb()
|
||||
.nonlinear_to_linear_srgb();
|
||||
// roundtrip is not perfect due to numeric precision, even with f64
|
||||
// so ensure the error is at least ready for u8 (where sRGB is used)
|
||||
assert_eq!(
|
||||
(color01 * u8max).round() as u8,
|
||||
(color_roundtrip * u8max).round() as u8
|
||||
);
|
||||
}
|
||||
}
|
||||
//==================================================================================================
|
|
@ -1,6 +1,7 @@
|
|||
pub mod batch;
|
||||
pub mod camera;
|
||||
pub mod color;
|
||||
pub mod colorspace;
|
||||
pub mod draw;
|
||||
pub mod entity;
|
||||
pub mod mesh;
|
||||
|
|
|
@ -30,9 +30,9 @@ impl Font {
|
|||
// TODO: make this texture grayscale
|
||||
let color = Color::WHITE;
|
||||
let color_u8 = [
|
||||
(color.r * 255.0) as u8,
|
||||
(color.g * 255.0) as u8,
|
||||
(color.b * 255.0) as u8,
|
||||
(color.r() * 255.0) as u8,
|
||||
(color.g() * 255.0) as u8,
|
||||
(color.b() * 255.0) as u8,
|
||||
];
|
||||
Texture::new(
|
||||
Vec2::new(width as f32, height as f32),
|
||||
|
@ -43,7 +43,7 @@ impl Font {
|
|||
color_u8[0],
|
||||
color_u8[1],
|
||||
color_u8[2],
|
||||
(color.a * a * 255.0) as u8,
|
||||
(color.a() * a * 255.0) as u8,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
|
@ -75,9 +75,9 @@ impl Font {
|
|||
);
|
||||
|
||||
let color_u8 = [
|
||||
(color.r * 255.0) as u8,
|
||||
(color.g * 255.0) as u8,
|
||||
(color.b * 255.0) as u8,
|
||||
(color.r() * 255.0) as u8,
|
||||
(color.g() * 255.0) as u8,
|
||||
(color.b() * 255.0) as u8,
|
||||
];
|
||||
|
||||
// TODO: this offset is a bit hackey
|
||||
|
@ -108,7 +108,7 @@ impl Font {
|
|||
color_u8[0],
|
||||
color_u8[1],
|
||||
color_u8[2],
|
||||
(color.a * a * 255.0) as u8,
|
||||
(color.a() * a * 255.0) as u8,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
|
|
|
@ -124,10 +124,10 @@ impl<'a> From<&'a OwnedWgpuVertexBufferDescriptor> for wgpu::VertexBufferDescrip
|
|||
impl WgpuFrom<Color> for wgpu::Color {
|
||||
fn from(color: Color) -> Self {
|
||||
wgpu::Color {
|
||||
r: color.r as f64,
|
||||
g: color.g as f64,
|
||||
b: color.b as f64,
|
||||
a: color.a as f64,
|
||||
r: color.r_linear() as f64,
|
||||
g: color.g_linear() as f64,
|
||||
b: color.b_linear() as f64,
|
||||
a: color.a() as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue