Skip to content

Convert colors from srgb to linear srgb automatically #585

Closed

Description

@CleanCut

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
}

Activity

  1. CleanCut commented on Sep 26, 2020

    @CleanCut
    MemberAuthor

    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)
    }
  2. julhe commented on Sep 27, 2020

    @julhe

    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. 🤔

  3. added
    C-FeatureA new feature, making something new possible
    A-RenderingDrawing game state to the screen
    on Sep 27, 2020
  4. kettle11 commented on Sep 27, 2020

    @kettle11
    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)
        }
    }
  5. Svintooo commented on Sep 27, 2020

    @Svintooo

    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();
    }
  6. CleanCut commented on Sep 27, 2020

    @CleanCut
    MemberAuthor

    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.

  7. Svintooo commented on Sep 27, 2020

    @Svintooo

    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.

  8. cart commented on Oct 12, 2020

    @cart
    Member

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

  9. jamesmichaeltate1 commented on Mar 1, 2023

    @jamesmichaeltate1

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-FeatureA new feature, making something new possible

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions