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

Convert colors from srgb to linear srgb automatically #585

Closed
CleanCut opened this issue Sep 26, 2020 · 8 comments
Closed

Convert colors from srgb to linear srgb automatically #585

CleanCut opened this issue Sep 26, 2020 · 8 comments
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible

Comments

@CleanCut
Copy link
Member

As per this Discord conversation and #419 (comment), assume people are providing raw color values in the non-linear srgb colorspace. Bevy should then convert the provided colors to linear srgb for internal use, for example in the constructors in bevy_render/src/color.rs), and automatically convert the colors to linear srgb when it makes sense under the hood.

We could have constructors for both non-linear and linear sources as long as we document them wherever there could be ambiguity.

Workaround

Right now this can be worked around by manually converting the colors to linear srgb colorspace before using them with the engine like this:

// # Cargo.toml
// [dependencies]
// palette = "0.5.0"

use palette::Srgb;
use bevy::prelude::Color;

fn some_function() {
    // (0.592, 0.337, 0.157) is a dark brown in the non-linear srgb colorspace
    // Use `palette` to convert from non-linear to linear
    let color_intermediate = Srgb::new(0.592, 0.337, 0.157).into_linear();  
    // Convert to Bevy's `Color` struct
    let color_linear = Color::rgb(color_intermediate.red, color_intermediate.green, color_intermediate.blue) ;
    // Now use `color_linear` with Bevy
}
@CleanCut
Copy link
Member Author

Here's some helper workaround functions I made for my little project to make things more ergonomic:

use bevy::prelude::Color;
use palette::Srgb;

/// Take a color in non-linear srgb and convert it to the linear srgb format Bevy currently needs
pub fn color_from_f32(r: f32, g: f32, b: f32) -> Color {
    let l = Srgb::new(r, g, b).into_linear();
    Color::rgb(l.red, l.green, l.blue)
}

/// Take a color in non-linear srgb and convert it to the linear srgb format Bevy currently needs
pub fn color_from_u8(r: u8, g: u8, b: u8) -> Color {
    color_from_f32(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
}

@julhe
Copy link
Contributor

julhe commented Sep 27, 2020

I would say we make the default color constructors sRGB aware, and offer additonal linear color constructors.

I'm not sure on adding a palette dependency, as this would mean +10k lines (according to https://lib.rs/crates/palette) of addtional dependencies. The features it offers don't come across as something you need in every day game-dev, at least from my experience. 🤔

@GabCampbell GabCampbell added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen labels Sep 27, 2020
@kettle11
Copy link
Contributor

The formulas for linear to sRGB and vice-versa aren't much code. A full dependency on palette isn't needed.

Here's an (untested) transcription of the formulas from the Wikipedia article about sRGB:

fn linear_to_srgb(u: f32) -> f32 {
    if u <= 0.0031308 {
        12.92 * u
    } else {
        1.055 * f32::powf(u,0.416666) - 0.055
    }
}

fn srgb_to_linear(u: f32) -> f32 {
    if u <= 0.04045 {
        u / 12.92
    } else {
        f32::powf((u + 0.055) / 1.055, 2.4)
    }
}

@Svintooo
Copy link

I agree that the needed formula is simple enough to not warrant a dependency on an external crate.

Here follows an example solution with a macro (using the same formula from wikipedia). I like this solution because the needed code change is very clear and minimal.

use bevy::prelude::*;
use bevy::render::pass::ClearColor;

macro_rules! color_rgb {
    ( $( $x:expr ),* ) => {
        Color::rgb( $(
            // Without converting srgb to linear
            //$x
            // With converting srgb to linear
            if $x <= 0.04045 {
                $x / 12.92
            } else {
                ((($x as f32) + 0.055) / 1.055).powf(2.4)
            }
        ),* )
    };
}

fn main() {
    App::build()
        .add_default_plugins()
        //.add_resource(ClearColor(Color::rgb(0.83, 0.474, 0.206)))
        .add_resource(ClearColor(color_rgb!(0.83, 0.474, 0.206)))
        .run();
}

@CleanCut
Copy link
Member Author

Oh good, to be clear I didn't want an additional dependency -- but I also didn't want to figure out the math myself when I was tired. 😄 I'm glad the math ended up being so simple.

@Svintooo
Copy link

I found one possible problem with my solution above. Using GIMP colorpicker on the resulting color gives me the equivalent of 0.831, 0.475, 0.208, with differ slightly from the values in the code: 0.83, 0.474, 0.206.

Maybe this is a precision/rounding problem? Or maybe hardware specific? For reference I'm using an Nvidia graphics card in Linux.

@cart
Copy link
Member

cart commented Oct 12, 2020

I think we can close this out now that #616 landed

@jamesmichaeltate1
Copy link

As per this Discord conversation and #419 (comment), assume people are providing raw color values in the non-linear srgb colorspace. Bevy should then convert the provided colors to linear srgb for internal use, for example in the constructors in bevy_render/src/color.rs), and automatically convert the colors to linear srgb when it makes sense under the hood.

We could have constructors for both non-linear and linear sources as long as we document them wherever there could be ambiguity.

Workaround

Right now this can be worked around by manually converting the colors to linear srgb colorspace before using them with the engine like this:

// # Cargo.toml
// [dependencies]
// palette = "0.5.0"

use palette::Srgb;
use bevy::prelude::Color;

fn some_function() {
    // (0.592, 0.337, 0.157) is a dark brown in the non-linear srgb colorspace
    // Use `palette` to convert from non-linear to linear
    let color_intermediate = Srgb::new(0.592, 0.337, 0.157).into_linear();  
    // Convert to Bevy's `Color` struct
    let color_linear = Color::rgb(color_intermediate.red, color_intermediate.green, color_intermediate.blue) ;
    // Now use [jamesmichaeltate1/mikeytate](url)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible
Projects
None yet
Development

No branches or pull requests

7 participants