egui icon indicating copy to clipboard operation
egui copied to clipboard

Correct color on wide-gamut displays

Open elliottslaughter opened this issue 2 years ago • 6 comments

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.

elliottslaughter avatar Feb 10 '23 20:02 elliottslaughter

Interesting!

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

emilk avatar Feb 10 '23 23:02 emilk

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.

elliottslaughter avatar Feb 10 '23 23:02 elliottslaughter

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
  • https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3.txt (OpenGL extension)
  • https://chromium.googlesource.com/angle/angle/+/refs/heads/main/src/libANGLE/validationEGL.cpp#416 (libANGLE - used in Chrome and Firefox)
  • https://github.com/flutter/flutter/issues/55092#issuecomment-1140780268 (Flutter issue, references Chromium and Skia)
  • https://github.com/flutter/engine/pull/39111 (Flutter PR)
  • https://github.com/dosbox-staging/dosbox-staging/issues/1793 (dosbox issue)
  • https://www.reddit.com/r/opengl/comments/d12vmn/comment/ezj1hcz/ (detailed comment in /r/opengl)
  • https://github.com/glfw/glfw/issues/890 (glfw issue)

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.

bancek avatar Jun 28 '23 19:06 bancek

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?

virtualritz avatar Oct 07 '24 10:10 virtualritz

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.

Wumpf avatar Oct 23 '24 12:10 Wumpf

Something that needs to be mentioned in this discussion is the role of ColorSync and how colorspace is configured for the Metal layer. At least as far as MacOS goes, rendering to sRGB and relying on the OS to do the color conversion seems a perfectly serviceable solution, but it relies on colorspace being set properly. I haven't checked, but my guess is that's the only problem in the glow implementation. wgpu is behaving properly, and misbehaves in the same way as glow once I change colorspace to nil (there's no option in wgpu, I changed the source code).

The interesting thing that I've observed is that a lot of applications get this wrong. I spent quite a while trying to figure out why wgpu was displaying different results compared to a few image editors I have. It's not wgpu that's wrong, it's the image editors. The most reliable option here is to use the "Digital Color Meter" on MacOS, switch to "Display in sRGB", and check if those numbers are correct.

It can seem like wgpu is wrong, because when sRGB is displayed correctly on a P3 display, it looks desaturated (to my eye). As far as I can tell, this is an intended outcome. Additionally, taking screenshots on a P3 display and pasting into an RGB image editor may yield bad results, further adding to confusion.

jabuwu avatar Oct 17 '25 17:10 jabuwu