emilk / egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
https://www.egui.rs/
Apache License 2.0
22.61k stars 1.62k forks source link

Correct color on wide-gamut displays #2712

Open elliottslaughter opened 1 year ago

elliottslaughter commented 1 year ago

Some displays support color gamuts larger than sRGB. E.g., many recent Mac laptops use the Display P3 color space. As best I can tell, egui/eframe naively assume the display's color gamut is sRGB, and this causes colors to be drawn incorrectly on devices with wider gamuts.

The easiest way to test this to set up a comparison with a web browser. Safari and Firefox are both gamut-aware and will correctly draw colors in sRGB when viewed on a wide-gamut display. Note that the display-p3 syntax is specific to Safari:

<html>
<head>
<style>
body {
    /* sRGB: renders correctly in Firefox and Safari */
    background-color: steelblue;
    /* this is equivalent in rgb(...) notation: */
    /* background-color: rgb(70, 130, 180); */

    /* Display P3: requires Safari */
    /* background-color: color(display-p3 0.27450980392156865 0.5098039215686274 0.7058823529411765); */
}
</style>
</head>
<body>
</body>
</html>

For comparison, you can set up a simple app with eframe_template and draw a rect with Color32::from_rgb(70, 130, 180). Note that the color that is shown matches the Display P3 color test, not sRGB.

In contrast, when I run the same eframe_template app via trunk and open it in Firefox, the colors are rendered correctly. It must be that Firefox performs color correction in WebGL contexts and ensures that sRGB colors are displayed correctly.

emilk commented 1 year ago

Interesting!

Do you know anyone with a wide-gamut display that can help fix this? :)

elliottslaughter commented 1 year ago

I have a wide-gamut display, but I'm not sure how I'd go about a fix.

I assume that pushing gamut support all the way through egui/eframe would be a huge amount of work and not necessarily desirable.

But if there's a way to, say, set up the OpenGL context (or whatever backend we use these days) to use sRGB, maybe there's a simpler fix that wouldn't involve too much churn. We could perhaps put it behind a flag/setting so that interested users could still access Display P3 colors if they wanted to. But it would make it so that for everyone else, things would render more consistently across platforms. The fact that egui with WebGL already works this way makes me hopeful that it's maybe not too much work. But even so, I really don't know where to start.

bancek commented 1 year ago

On macOS Ventura (13.4), Wgpu renderer and web (Chrome 114) produce correct colors. Glow produces too bright colors. Using Wgpu solves this for me.

Wgpu:

egui-wgpu

Glow:

egui-glow

web (Chrome):

egui-web

Code from Android docs:

std::vector<EGLint> attributes;
attributes.push_back(EGL_GL_COLORSPACE_KHR);
attributes.push_back(EGL_GL_COLORSPACE_DISPLAY_P3_EXT);
attributes.push_back(EGL_NONE);
engine->surface_ = eglCreateWindowSurface(
    engine->display_, config, engine->app->window, attributes.data());

This would probably need to be done in eframe where gl_surface is built:

let surface_attributes =
    glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
        .build(window.raw_window_handle(), width, height);

let gl_surface = unsafe {
    self.gl_config
        .display()
        .create_window_surface(&self.gl_config, &surface_attributes)?
};

Although by looking in glutin sources, cgl_backend is used on macOS and surface_attributes are ignored altogether and raw_window_handle is used.

virtualritz commented 1 month ago

On this note: the docs do not say what color space the linear RGB (color model) used in egui::Rgba is. "Linear RGB" is pretty meaninless w/o this information.

Working on an app that needs to display images and stuff like e.g. color swatches 'correctly' (the RGB color is e.g. in linear ACEScg). My assumption is that this is simply treated as linear sRGB (i.e. sRGB primaries but no gamma-encoding). I.e. egui will just apply the gamma-encoding when converting these to 8bit/channel for display. Is this correct?

P.S.: I'd be interested in hearing if there is any new insights on this issue by anyone since the last comment?

Wumpf commented 1 month ago

The way egui::Rgba is used across egui implies it's Bt.709/sRGB primaries prior to OETF, i.e. in optically linear units. That, paired with the fact that egui always assumes 8bit sRGB output (post-OETF, i.e. "with applied gamma") means that your conclusion is correct :) That this is not speced properly can be regarded as a bug that needs fixing!

no movement on the general issue. Color is particularly cursed on WebGL, and nothing has been done yet to support P3 and other output formats. I think in a nutshell what I think needs to happen to move this forward, is that egui's rendering backends (each individually!) need to take those Bt.709 floating point linear colors that and then apply the correct color-space-conversion + OETF (both!) depending on the expected output. Since egui has only a single shader, it should be realtively straight forward to do this entirely in software which limits exposure to the more cumbersome (and error prone!) automatic driver sided conversions like EGL_GL_COLORSPACE_KHR. Btw. that's already the preferred mode of operation in egui. This comes mostly from the fact egui wants to do blending in a more perceptual space, for details see this older pr.