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.12k stars 1.6k forks source link

Image alpha blending seems additive since 0.20 #2648

Open nywles opened 1 year ago

nywles commented 1 year ago

Describe the bug Alpha blending of images seems to have become additive since egui 0.20. I have tested manual Glow integration as well as eframe 0.20 and the result is the same, painting images with an alpha channel only lightens the image. I've tried lowering the alpha of the image using the .tint() to see if i can compensate but the effect stays.

To Reproduce A minimal example based on eframe, runs on 0.19 and 0.20 with visual difference.

use std::io::Cursor;
use eframe::{egui, HardwareAcceleration};
use eframe::egui::{Color32, ColorImage, Image, Pos2, Rect, Vec2};
use image::ImageFormat;

fn main() {
    let mut native_options = eframe::NativeOptions::default();
    native_options.initial_window_size = Some(Vec2::new(300.0, 300.0));
    eframe::run_native("My egui App", native_options, Box::new(|_| Box::new(MyEguiApp::default())));
}

#[derive(Default)]
struct MyEguiApp {
    back: Option<egui::TextureHandle>,
    over: Option<egui::TextureHandle>,
}

impl eframe::App for MyEguiApp {
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            let back: &egui::TextureHandle = self.back.get_or_insert_with(|| {
                let img_image = image::load(&mut Cursor::new(include_bytes!("background.png")), ImageFormat::Png).unwrap();
                let img_size = [img_image.width() as _, img_image.height() as _];
                let img_buffer = img_image.to_rgba8();
                let img_pixels = img_buffer.as_flat_samples();
                let background = ColorImage::from_rgba_unmultiplied(img_size, img_pixels.as_slice());
                ctx.load_texture("back", background, Default::default())
            });
            let over: &egui::TextureHandle = self.over.get_or_insert_with(|| {
                let img_image = image::load(&mut Cursor::new(include_bytes!("overlay.png")), ImageFormat::Png).unwrap();
                let img_size = [img_image.width() as _, img_image.height() as _];
                let img_buffer = img_image.to_rgba8();
                let img_pixels = img_buffer.as_flat_samples();
                let background = ColorImage::from_rgba_unmultiplied(img_size, img_pixels.as_slice());
                ctx.load_texture("over", background, Default::default())
            });

            let size = Vec2::new(300.0, 300.0);
            ui.put(Rect::from_min_max(Pos2::ZERO, size.to_pos2()), Image::new(back, size));
            ui.put(Rect::from_min_max(Pos2::ZERO, size.to_pos2()), Image::new(over, size));
        });
    }
}

Expected behavior I expect the images to be alpha blended in the way 0.19 did it, this is also how image editors and html blend them.

Screenshots Version 0.19: (Sharp line around boxes with a faint flow)

blending-0 19

Version 0.20: (Thick line around boxes)

blending-0 20

Background image: background Alpha blended overlay: overlay

Desktop (please complete the following information):

emilk commented 1 year ago

This is weird. Perhaps from_rgba_unmultiplied needs to do the multiplication in gamma space.

nywles commented 1 year ago

If I replace the let a_lin = linear_f32_from_linear_u8(a); with let a_lin = linear_f32_from_gamma_u8(a); in from_rgba_unmultiplied the results are definitely closer. It looks alright but it's not the same as it was. I don't mind since this looks fine, I'm just not sure if this is the right thing to do and doesn't break anything else. Any pointers what I can check to make sure?

egui 0.19: Slightly brighter and less contrast

blending-0 19

egui master with the gamma space alpha:

blending-master-gamma