Skip to content

Fix semi-transparent colors appearing too bright#5824

Merged
emilk merged 15 commits intomasterfrom
emilk/fix-transparency-blending
Mar 21, 2025
Merged

Fix semi-transparent colors appearing too bright#5824
emilk merged 15 commits intomasterfrom
emilk/fix-transparency-blending

Conversation

@emilk
Copy link
Owner

@emilk emilk commented Mar 19, 2025

The bug was in Color32::from_rgba_unmultiplied and by extension affects:

  • Color32::from_rgba_unmultiplied
  • hex_color!
  • HexColor
  • ColorImage::from_rgba_unmultiplied
  • All images with transparency (png, webp, …)
  • Color32::from_white_alpha

The bug caused translucent colors to appear too bright.

More

Color is hard.

When I started out egui I thought "linear space is objectively better, for everything!" and then I've been slowly walking that back for various reasons:

  • sRGB textures not available everywhere
  • gamma-space is more perceptually even, so it makes sense to use for anti-aliasing
  • other applications do everything in gamma space, so that's what people expect (this PR)

Similarly, pre-multiplied alpha makes sense for blending colors. It also enables additive colors, which is nice. But it does complicate things. Especially when mixed with sRGB/gamma (As @karhu points out).

Related

TODO

  • I broke the RGBA u8 color picker. Fix it

@emilk emilk added visuals Renderings / graphics releated egui ecolor epaint labels Mar 19, 2025
@emilk emilk marked this pull request as draft March 19, 2025 16:26
@github-actions
Copy link

Preview available at https://egui-pr-preview.github.io/pr/5824-emilkfix-transparency-blending
Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed.

@emilk emilk force-pushed the emilk/fix-transparency-blending branch from 3732629 to 7abb45c Compare March 19, 2025 16:53
@emilk emilk marked this pull request as ready for review March 19, 2025 16:58
@emilk emilk added the bug Something is broken label Mar 19, 2025
@Wumpf Wumpf self-requested a review March 19, 2025 17:27
Copy link
Collaborator

@Wumpf Wumpf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this blending in gamma space business drives me nuts, but apparently this produces the results everyone expects so great catch! 👍

@karhu
Copy link
Contributor

karhu commented Mar 19, 2025

As mentioned in one of the line comment threads I think we need to properly specify and document what Color32 stores in respect to a linear, unmultiplied rgba value:

a) to_gamma(to_premultiplied(some_linear_rgba_unmultiplied_value))
or
b) to_premultiplied(to_gamma(some_linear_rgba_unmultiplied_value))

Or expressed differently: Do we gamma encode in linear space or in premultiplied space?

If I understand your changes to from_rgba_unmultiplied correctly, this flips the order of the two operations, so we now do b) instead of previously a).

For what it is worth, I think b) is the right way to go about this, as a) behaves weirdly when blending between non-opaque colors. However: From going through the egui codebase, my impression is that code pretty consistently assumes that we are doing a).

This includes conversions between Color32 and Rgba. For example this quick test fails now:

    let rgba_unmult: [u8; 4] = [200, 150, 100, 150];

    let as_color32 = egui::Color32::from_rgba_unmultiplied(
        rgba_unmult[0],
        rgba_unmult[1],
        rgba_unmult[2],
        rgba_unmult[3],
    );

    let as_rgba = egui::Rgba::from(as_color32);

    let rgba_unmult_after = as_rgba.to_srgba_unmultiplied();
    assert!(
        rgba_unmult == rgba_unmult_after,
        "Values differ: {rgba_unmult:?} != {rgba_unmult_after:?}"
    );

with Values differ: [200, 150, 100, 150] != [151, 113, 77, 150] (to be fair, it also fails on master but only due to rounding errors).

Sidenote: Should from_rgba_unmultiplied be renamed from_srgba_unmultiplied to be in line with to_srgba_unmultiplied ?

Another place is the shader code, which currently directly transforms between gamma and linear encoding, but would have to be changed to transform between premult / unmult before doing any gamma transforms. Also in the shader code, the texture read (which currently happens from an sRGBA aware texture) might be affected. From a first glance the automatic conversion on the driver level works with a), but not b).

Phew, sorry for the wall of text. My brain is smoking, and I concur with @Wumpf that this is driving me nuts, so please take all of it with a big pile of salt.

emilk and others added 2 commits March 20, 2025 09:35
Co-authored-by: Andreas Reich <andreas@rerun.io>
@emilk emilk marked this pull request as draft March 20, 2025 15:03
@emilk emilk force-pushed the emilk/fix-transparency-blending branch from c493103 to d9b5b74 Compare March 20, 2025 15:28
@emilk emilk marked this pull request as ready for review March 20, 2025 18:38
@emilk
Copy link
Owner Author

emilk commented Mar 20, 2025

@karhu you are right to point that out… I'll try to make things a bit more consistent.

EDIT: now the Color32 <-> Rgba conversions does the alpha unmultiply… it's not pretty, but it works.

@emilk emilk force-pushed the emilk/fix-transparency-blending branch from e0f1cec to 69a54a1 Compare March 20, 2025 20:03
@emilk emilk added the style visuals and theming label Mar 20, 2025
@Wumpf Wumpf self-requested a review March 21, 2025 08:34
Copy link
Collaborator

@Wumpf Wumpf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, feels a looot more rigorous now!

emilk and others added 2 commits March 21, 2025 10:37
Co-authored-by: Andreas Reich <andreas@rerun.io>
Co-authored-by: Andreas Reich <andreas@rerun.io>
@emilk emilk merged commit 3f731ec into master Mar 21, 2025
46 checks passed
@emilk emilk deleted the emilk/fix-transparency-blending branch March 21, 2025 09:45
MichaelGrupp added a commit to MichaelGrupp/maps that referenced this pull request Jul 25, 2025
Without any conversion, an old saved value appears as a completely
different color because it's assumed to be a gamma-premultiplied
value (which it wasn't when serialized with egui < 0.32).
This then lead e.g. the color picker to do wrong conversions and
mess up the tint color.

We now check if the session was written with the latest or an old
version and fix the misinterpreted color values if needed.

Note that while the values are correct after conversion, the image
can still appear darker due to the blending changes in egui.

Relates to: emilk/egui#5824
Relates to: emilk/egui#7311
darkwater pushed a commit to darkwater/egui that referenced this pull request Aug 24, 2025
The bug was in `Color32::from_rgba_unmultiplied` and by extension
affects:

* `Color32::from_rgba_unmultiplied`
* `hex_color!`
* `HexColor`
* `ColorImage::from_rgba_unmultiplied`
* All images with transparency (png, webp, …)
* `Color32::from_white_alpha`

The bug caused translucent colors to appear too bright.

## More
Color is hard.

When I started out egui I thought "linear space is objectively better,
for everything!" and then I've been slowly walking that back for various
reasons:

* sRGB textures not available everywhere
* gamma-space is more _perceptually_ even, so it makes sense to use for
anti-aliasing
* other applications do everything in gamma space, so that's what people
expect (this PR)

Similarly, pre-multiplied alpha _makes sense_ for blending colors. It
also enables additive colors, which is nice. But it does complicate
things. Especially when mixed with sRGB/gamma (As @karhu [points
out](emilk#5824 (comment))).

## Related
* Closes emilk#5751
* Closes emilk#5771 ? (probably; hard to
tell without a repro)
* But not emilk#5810

## TODO
* [x] I broke the RGBA u8 color picker. Fix it

---------

Co-authored-by: Andreas Reich <andreas@rerun.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is broken ecolor egui epaint style visuals and theming visuals Renderings / graphics releated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rgba::From<Color32> does not consider alpha Semi-transparent pixels of image appear too bright?

3 participants