From 1069ad8496aa03a2d29e1cbb2deb7ad4870d4b83 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 13 Sep 2020 09:28:54 +0200 Subject: [PATCH] add color test window to troubleshoot your Egui painter backend egui_glium passes the test, but the egui_web WebGL painter fails it. --- TODO.md | 16 +- egui/src/demos.rs | 2 + egui/src/demos/app.rs | 30 ++- egui/src/demos/color_test.rs | 359 +++++++++++++++++++++++++++++++ egui/src/math/vec2.rs | 2 +- egui/src/paint/color.rs | 26 ++- egui/src/paint/command.rs | 45 ++++ egui/src/ui.rs | 2 +- egui/src/widgets/color_picker.rs | 14 +- egui/src/widgets/image.rs | 20 +- egui_web/src/webgl.rs | 1 + 11 files changed, 490 insertions(+), 27 deletions(-) create mode 100644 egui/src/demos/color_test.rs diff --git a/TODO.md b/TODO.md index 48b5c5a9f..b142cb889 100644 --- a/TODO.md +++ b/TODO.md @@ -17,9 +17,10 @@ TODO-list for the Egui project. If you looking for something to do, look here. * [x] Color edit button with popup color picker * [x] Gamma for value (brightness) slider * [x] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`) - * [ ] RGB editing without alpha + * [x] RGB editing without alpha * [ ] Additive blending aware color picker * [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit. + * [ ] Hue wheel * Containers * [ ] Scroll areas * [x] Vertical scrolling @@ -53,19 +54,17 @@ TODO-list for the Egui project. If you looking for something to do, look here. * [ ] Positioning preference: `window.preference(Top, Right)` * [ ] Keeping right/bottom on expand. Maybe cover jitteryness with quick animation? * [ ] Make auto-positioning of windows respect permanent side-bars. -* [/] Image support +* [x] Image support * [x] Show user textures - * [ ] API for creating a texture managed by Egui - * Backend-agnostic. Good for people doing Egui-apps (games etc). - * [ ] Convert font texture to RGBA, or communicate format in initialization? - * [ ] Generalized font atlas + * [x] API for creating a texture managed by `egui::app::Backend` * Visuals * [x] Pixel-perfect painting (round positions to nearest pixel). * [x] Fix `aa_size`: should be 1, currently fudged at 1.5 * [x] Fix thin rounded corners rendering bug (too bright) * [x] Smoother animation (e.g. ease-out)? NO: animation are too brief for subtelty - * [ ] Veriy alpha and sRGB correctness + * [x] Veriy alpha and sRGB correctness * [x] sRGBA decode in fragment shader + * [ ] Fix alpha blending / sRGB weirdness in WebGL (EXT_sRGB) * [ ] Thin circles look bad * [ ] Allow adding multiple tooltips to the same widget, showing them all one after the other. * Math @@ -86,7 +85,8 @@ TODO-list for the Egui project. If you looking for something to do, look here. * [x] Scroll input * [x] Change to resize cursor on hover * [x] Port most code to Rust -* [x] Read url fragment and redirect to a subpage (e.g. different examples apps) +* [x] Read url fragment and redirect to a subpage (e.g. different examples apps)] +* [ ] Fix WebGL colors/beldning (try EXT_sRGB) * [ ] Embeddability * [ ] Support canvas that does NOT cover entire screen. * [ ] Support multiple eguis in one web page. diff --git a/egui/src/demos.rs b/egui/src/demos.rs index c2edd6307..ba33b7d7c 100644 --- a/egui/src/demos.rs +++ b/egui/src/demos.rs @@ -2,10 +2,12 @@ //! //! The demo-code is also used in benchmarks and tests. mod app; +mod color_test; mod fractal_clock; pub mod toggle_switch; pub use { app::{DemoApp, DemoWindow}, + color_test::ColorTest, fractal_clock::FractalClock, }; diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index 8eed65fc6..489fc6505 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{app, color::*, containers::*, demos::FractalClock, paint::*, widgets::*, *}; +use crate::{app, color::*, containers::*, demos::*, paint::*, widgets::*, *}; // ---------------------------------------------------------------------------- @@ -17,6 +17,9 @@ pub struct DemoApp { demo_window: DemoWindow, fractal_clock: FractalClock, num_frames_painted: u64, + #[serde(skip)] + color_test: ColorTest, + show_color_test: bool, } impl DemoApp { @@ -189,6 +192,12 @@ impl DemoApp { self.num_frames_painted += 1; ui.label(format!("Total frames painted: {}", self.num_frames_painted)); + + ui.separator(); + ui.checkbox( + "Show color blend test (debug backend painter)", + &mut self.show_color_test, + ); } } @@ -198,6 +207,25 @@ impl app::App for DemoApp { self.backend_ui(ui, backend); }); + let Self { + show_color_test, + color_test, + .. + } = self; + + if *show_color_test { + let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| { + backend.new_texture_srgba_premultiplied(size, pixels) + }; + Window::new("Color Test") + .default_size(vec2(1024.0, 1024.0)) + .scroll(true) + .open(show_color_test) + .show(ui.ctx(), |ui| { + color_test.ui(ui, &mut tex_loader); + }); + } + let web_info = backend.web_info(); let web_location_hash = web_info .as_ref() diff --git a/egui/src/demos/color_test.rs b/egui/src/demos/color_test.rs new file mode 100644 index 000000000..7c7ee2c25 --- /dev/null +++ b/egui/src/demos/color_test.rs @@ -0,0 +1,359 @@ +use crate::widgets::color_picker::show_color; +use crate::*; +use color::*; +use std::collections::HashMap; + +pub type TextureLoader<'a> = dyn FnMut((usize, usize), &[crate::Srgba]) -> TextureId + 'a; + +const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0); + +pub struct ColorTest { + tex_mngr: TextureManager, + vertex_gradients: bool, + texture_gradients: bool, + srgb: bool, +} + +impl Default for ColorTest { + fn default() -> Self { + Self { + tex_mngr: Default::default(), + vertex_gradients: true, + texture_gradients: true, + srgb: false, + } + } +} + +impl ColorTest { + pub fn ui(&mut self, ui: &mut Ui, tex_loader: &mut TextureLoader<'_>) { + ui.label("This is made to test if your Egui painter backend is set up correctly"); + ui.label("It is meant to ensure you do proper sRGBA decoding of both texture and vertex colors, and blend using premultiplied alpha."); + ui.label("If everything is set up correctly, all groups of gradients will look uniform"); + + ui.checkbox("Vertex gradients", &mut self.vertex_gradients); + ui.checkbox("Texture gradients", &mut self.texture_gradients); + ui.checkbox("Show naive sRGBA horror", &mut self.srgb); + + ui.heading("sRGB color test"); + ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500"); + ui.add_custom(|ui| { + ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients + let g = Gradient::one_color(Srgba::new(255, 165, 0, 255)); + self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g); + self.tex_gradient( + ui, + tex_loader, + "orange rgb(255, 165, 0) - texture", + WHITE, + &g, + ); + }); + + ui.separator(); + + ui.label("Test that vertex color times texture color is done in linear space:"); + ui.add_custom(|ui| { + ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients + + let tex_color = Rgba::new(1.0, 0.25, 0.25, 1.0); + let vertex_color = Rgba::new(0.5, 0.75, 0.75, 1.0); + + ui.horizontal_centered(|ui| { + let color_size = vec2(2.0, 1.0) * ui.style().spacing.clickable_diameter; + ui.label("texture"); + show_color(ui, tex_color, color_size); + ui.label(" * "); + show_color(ui, vertex_color, color_size); + ui.label(" vertex color ="); + }); + { + let g = Gradient::one_color(Srgba::from(tex_color * vertex_color)); + self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g); + self.tex_gradient(ui, tex_loader, "Ground truth (texture)", WHITE, &g); + } + ui.horizontal_centered(|ui| { + let g = Gradient::one_color(Srgba::from(tex_color)); + let tex = self.tex_mngr.get(tex_loader, &g); + let texel_offset = 0.5 / (g.0.len() as f32); + let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); + ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv)) + .tooltip_text(format!("A texture that is {} texels wide", g.0.len())); + ui.label("GPU result"); + }); + }); + + ui.separator(); + + ui.separator(); + + // TODO: test color multiplication (image tint), + // to make sure vertex and texture color multiplication is done in linear space. + + self.show_gradients(ui, tex_loader, WHITE, (RED, GREEN)); + if self.srgb { + ui.label("Notice the darkening in the center of the naive sRGB interpolation."); + } + + ui.separator(); + + self.show_gradients(ui, tex_loader, RED, (TRANSPARENT, GREEN)); + + ui.separator(); + + self.show_gradients(ui, tex_loader, WHITE, (TRANSPARENT, GREEN)); + if self.srgb { + ui.label( + "Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.", + ); + } + + ui.separator(); + + // TODO: another ground truth where we do the alpha-blending against the background also. + // TODO: exactly the same thing, but with vertex colors (no textures) + self.show_gradients(ui, tex_loader, WHITE, (TRANSPARENT, BLACK)); + ui.separator(); + self.show_gradients(ui, tex_loader, BLACK, (TRANSPARENT, WHITE)); + ui.separator(); + + ui.label("Additive blending: add more and more blue to the red background:"); + self.show_gradients(ui, tex_loader, RED, (TRANSPARENT, Srgba::new(0, 0, 255, 0))); + + ui.separator(); + } + + fn show_gradients( + &mut self, + ui: &mut Ui, + tex_loader: &mut TextureLoader<'_>, + bg_fill: Srgba, + (left, right): (Srgba, Srgba), + ) { + let is_opaque = left.is_opaque() && right.is_opaque(); + + ui.horizontal_centered(|ui| { + let color_size = vec2(2.0, 1.0) * ui.style().spacing.clickable_diameter; + if !is_opaque { + ui.label("Background:"); + show_color(ui, bg_fill, color_size); + } + ui.label("gradient"); + show_color(ui, left, color_size); + ui.label("-"); + show_color(ui, right, color_size); + }); + + ui.add_custom(|ui| { + ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients + if is_opaque { + let g = Gradient::ground_truth_linear_gradient(left, right); + self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g); + self.tex_gradient( + ui, + tex_loader, + "Ground Truth (CPU gradient) - texture", + bg_fill, + &g, + ); + } else { + let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill); + self.vertex_gradient( + ui, + "Ground Truth (CPU gradient, CPU blending) - vertices", + bg_fill, + &g, + ); + self.tex_gradient( + ui, + tex_loader, + "Ground Truth (CPU gradient, CPU blending) - texture", + bg_fill, + &g, + ); + let g = Gradient::ground_truth_linear_gradient(left, right); + self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g); + self.tex_gradient( + ui, + tex_loader, + "CPU gradient, GPU blending - texture", + bg_fill, + &g, + ); + } + + let g = Gradient::texture_gradient(left, right); + self.vertex_gradient( + ui, + "Triangle mesh of width 2 (test vertex decode and interpolation)", + bg_fill, + &g, + ); + self.tex_gradient( + ui, + tex_loader, + "Texture of width 2 (test texture sampler)", + bg_fill, + &g, + ); + + if self.srgb { + let g = + Gradient::ground_truth_bad_srgba_gradient(left, right).with_bg_fill(bg_fill); + self.vertex_gradient( + ui, + "Triangle mesh with naive sRGBA interpolation (WRONG)", + bg_fill, + &g, + ); + self.tex_gradient( + ui, + tex_loader, + "Naive sRGBA interpolation (WRONG)", + bg_fill, + &g, + ); + } + }); + } + + fn tex_gradient( + &mut self, + ui: &mut Ui, + tex_loader: &mut TextureLoader<'_>, + label: &str, + bg_fill: Srgba, + gradient: &Gradient, + ) { + if !self.texture_gradients { + return; + } + ui.horizontal_centered(|ui| { + let tex = self.tex_mngr.get(tex_loader, gradient); + let texel_offset = 0.5 / (gradient.0.len() as f32); + let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); + ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv)) + .tooltip_text(format!( + "A texture that is {} texels wide", + gradient.0.len() + )); + ui.label(label); + }); + } + + fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Srgba, gradient: &Gradient) { + if !self.vertex_gradients { + return; + } + ui.horizontal_centered(|ui| { + vertex_gradient(ui, bg_fill, gradient).tooltip_text(format!( + "A triangle mesh that is {} vertices wide", + gradient.0.len() + )); + ui.label(label); + }); + } +} + +fn vertex_gradient(ui: &mut Ui, bg_fill: Srgba, gradient: &Gradient) -> Response { + use crate::paint::*; + let rect = ui.allocate_space(GRADIENT_SIZE); + if bg_fill != Default::default() { + let mut triangles = Triangles::default(); + triangles.add_colored_rect(rect, bg_fill); + ui.painter().add(PaintCmd::Triangles(triangles)); + } + { + let n = gradient.0.len(); + assert!(n >= 2); + let mut triangles = Triangles::default(); + for (i, &color) in gradient.0.iter().enumerate() { + let t = i as f32 / (n as f32 - 1.0); + let x = lerp(rect.range_x(), t); + triangles.colored_vertex(pos2(x, rect.top()), color); + triangles.colored_vertex(pos2(x, rect.bottom()), color); + if i < n - 1 { + let i = i as u32; + triangles.add_triangle(2 * i, 2 * i + 1, 2 * i + 2); + triangles.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3); + } + } + ui.painter().add(PaintCmd::Triangles(triangles)); + } + ui.interact_hover(rect) +} + +#[derive(Clone, Hash, PartialEq, Eq)] +struct Gradient(pub Vec); + +impl Gradient { + pub fn one_color(srgba: Srgba) -> Self { + Self(vec![srgba, srgba]) + } + pub fn texture_gradient(left: Srgba, right: Srgba) -> Self { + Self(vec![left, right]) + } + pub fn ground_truth_linear_gradient(left: Srgba, right: Srgba) -> Self { + let left = Rgba::from(left); + let right = Rgba::from(right); + + let n = 255; + Self( + (0..=n) + .map(|i| { + let t = i as f32 / n as f32; + Srgba::from(lerp(left..=right, t)) + }) + .collect(), + ) + } + /// This is how a bad person blends `sRGBA` + pub fn ground_truth_bad_srgba_gradient(left: Srgba, right: Srgba) -> Self { + let n = 255; + Self( + (0..=n) + .map(|i| { + let t = i as f32 / n as f32; + Srgba([ + lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8, // Don't ever do this please! + lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8, // Don't ever do this please! + lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8, // Don't ever do this please! + lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8, // Don't ever do this please! + ]) + }) + .collect(), + ) + } + + /// Do premultiplied alpha-aware blending of the gradient on top of the fill color + pub fn with_bg_fill(self, bg: Srgba) -> Self { + let bg = Rgba::from(bg); + Self( + self.0 + .into_iter() + .map(|fg| { + let fg = Rgba::from(fg); + Srgba::from(bg * (1.0 - fg.a()) + fg) + }) + .collect(), + ) + } + + pub fn to_pixel_row(&self) -> Vec { + self.0.clone() + } +} + +#[derive(Default)] +struct TextureManager(HashMap); + +impl TextureManager { + fn get(&mut self, tex_loader: &mut TextureLoader<'_>, gradient: &Gradient) -> TextureId { + *self.0.entry(gradient.clone()).or_insert_with(|| { + let pixels = gradient.to_pixel_row(); + let width = pixels.len(); + let height = 1; + tex_loader((width, height), &pixels) + }) + } +} diff --git a/egui/src/math/vec2.rs b/egui/src/math/vec2.rs index e67e12208..dac13fd4c 100644 --- a/egui/src/math/vec2.rs +++ b/egui/src/math/vec2.rs @@ -13,7 +13,7 @@ pub struct Vec2 { } #[inline(always)] -pub fn vec2(x: f32, y: f32) -> Vec2 { +pub const fn vec2(x: f32, y: f32) -> Vec2 { Vec2 { x, y } } diff --git a/egui/src/paint/color.rs b/egui/src/paint/color.rs index 404e9a200..ba85853dc 100644 --- a/egui/src/paint/color.rs +++ b/egui/src/paint/color.rs @@ -9,7 +9,7 @@ use crate::math::clamp; /// Alpha channel is in linear space. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Srgba(pub [u8; 4]); +pub struct Srgba(pub(crate) [u8; 4]); impl std::ops::Index for Srgba { type Output = u8; @@ -49,9 +49,8 @@ impl Srgba { Self([l, l, l, 0]) } - /// Returns an opaque version of self - pub fn to_opaque(self) -> Self { - Rgba::from(self).to_opaque().into() + pub fn is_opaque(&self) -> bool { + self.a() == 255 } pub fn r(&self) -> u8 { @@ -67,6 +66,11 @@ impl Srgba { self.0[3] } + /// Returns an opaque version of self + pub fn to_opaque(self) -> Self { + Rgba::from(self).to_opaque().into() + } + pub fn to_array(&self) -> [u8; 4] { [self.r(), self.g(), self.b(), self.a()] } @@ -94,7 +98,7 @@ pub const LIGHT_BLUE: Srgba = srgba(140, 160, 255, 255); /// 0-1 linear space `RGBA` color with premultiplied alpha. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Rgba(pub [f32; 4]); +pub struct Rgba(pub(crate) [f32; 4]); impl std::ops::Index for Rgba { type Output = f32; @@ -200,6 +204,18 @@ impl std::ops::Add for Rgba { } } +impl std::ops::Mul for Rgba { + type Output = Rgba; + fn mul(self, other: Rgba) -> Rgba { + Rgba([ + self[0] * other[0], + self[1] * other[1], + self[2] * other[2], + self[3] * other[3], + ]) + } +} + impl std::ops::Mul for Rgba { type Output = Rgba; fn mul(self, factor: f32) -> Rgba { diff --git a/egui/src/paint/command.rs b/egui/src/paint/command.rs index 389bc1601..0753af764 100644 --- a/egui/src/paint/command.rs +++ b/egui/src/paint/command.rs @@ -43,6 +43,51 @@ pub enum PaintCmd { Triangles(Triangles), } +impl PaintCmd { + pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { + Self::LineSegment { + points, + stroke: stroke.into(), + } + } + + pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into) -> Self { + Self::Circle { + center, + radius, + fill: fill_color.into(), + stroke: Default::default(), + } + } + + pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into) -> Self { + Self::Circle { + center, + radius, + fill: Default::default(), + stroke: stroke.into(), + } + } + + pub fn rect_filled(rect: Rect, corner_radius: f32, fill_color: impl Into) -> Self { + Self::Rect { + rect, + corner_radius, + fill: fill_color.into(), + stroke: Default::default(), + } + } + + pub fn rect_stroke(rect: Rect, corner_radius: f32, stroke: impl Into) -> Self { + Self::Rect { + rect, + corner_radius, + fill: Default::default(), + stroke: stroke.into(), + } + } +} + #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Stroke { diff --git a/egui/src/ui.rs b/egui/src/ui.rs index c12b76214..7d73e350d 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -578,7 +578,7 @@ impl Ui { self.allocate_space(child_ui.bounding_size()) } - /// Create a child ui + /// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance. pub fn add_custom(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) { let child_rect = self.available(); let mut child_ui = self.child_ui(child_rect); diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 041cf8148..d67e6aa6f 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -38,14 +38,18 @@ fn background_checkers(painter: &Painter, rect: Rect) { painter.add(PaintCmd::Triangles(triangles)); } -fn show_color(ui: &mut Ui, color: Srgba, desired_size: Vec2) -> Response { +pub fn show_color(ui: &mut Ui, color: impl Into, desired_size: Vec2) -> Response { + show_srgba(ui, color.into(), desired_size) +} + +fn show_srgba(ui: &mut Ui, srgba: Srgba, desired_size: Vec2) -> Response { let rect = ui.allocate_space(desired_size); background_checkers(ui.painter(), rect); ui.painter().add(PaintCmd::Rect { rect, corner_radius: 2.0, - fill: color, - stroke: Stroke::new(3.0, color.to_opaque()), + fill: srgba, + stroke: Stroke::new(3.0, srgba.to_opaque()), }); ui.interact_hover(rect) } @@ -191,9 +195,9 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma) { ui.style().spacing.clickable_diameter * 2.0, ); - show_color(ui, (*hsva).into(), current_color_size).tooltip_text("Current color"); + show_color(ui, *hsva, current_color_size).tooltip_text("Current color"); - show_color(ui, HsvaGamma { a: 1.0, ..*hsva }.into(), current_color_size) + show_color(ui, HsvaGamma { a: 1.0, ..*hsva }, current_color_size) .tooltip_text("Current color (opaque)"); let opaque = HsvaGamma { a: 1.0, ..*hsva }; diff --git a/egui/src/widgets/image.rs b/egui/src/widgets/image.rs index 910da4f8c..1846e49b2 100644 --- a/egui/src/widgets/image.rs +++ b/egui/src/widgets/image.rs @@ -3,6 +3,7 @@ use crate::*; #[derive(Clone, Copy, Debug, Default)] pub struct Image { texture_id: TextureId, + uv: Rect, desired_size: Vec2, bg_fill: Srgba, tint: Srgba, @@ -12,21 +13,28 @@ impl Image { pub fn new(texture_id: TextureId, desired_size: Vec2) -> Self { Self { texture_id, + uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), desired_size, tint: color::WHITE, ..Default::default() } } + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. + pub fn uv(mut self, uv: impl Into) -> Self { + self.uv = uv.into(); + self + } + /// A solid color to put behind the image. Useful for transparent images. - pub fn bg_fill(mut self, bg_fill: Srgba) -> Self { - self.bg_fill = bg_fill; + pub fn bg_fill(mut self, bg_fill: impl Into) -> Self { + self.bg_fill = bg_fill.into(); self } /// Multiply image color with this. Default is WHITE (no tint). - pub fn tint(mut self, tint: Srgba) -> Self { - self.tint = tint; + pub fn tint(mut self, tint: impl Into) -> Self { + self.tint = tint.into(); self } } @@ -36,6 +44,7 @@ impl Widget for Image { use paint::*; let Self { texture_id, + uv, desired_size, bg_fill, tint, @@ -48,9 +57,8 @@ impl Widget for Image { } { // TODO: builder pattern for Triangles - let uv = [pos2(0.0, 0.0), pos2(1.0, 1.0)]; let mut triangles = Triangles::with_texture(texture_id); - triangles.add_rect_with_uv(rect, uv.into(), tint); + triangles.add_rect_with_uv(rect, uv, tint); ui.painter().add(PaintCmd::Triangles(triangles)); } diff --git a/egui_web/src/webgl.rs b/egui_web/src/webgl.rs index b17bcc9a6..5da347a26 100644 --- a/egui_web/src/webgl.rs +++ b/egui_web/src/webgl.rs @@ -207,6 +207,7 @@ impl Painter { gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture)); // TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB + // https://www.khronos.org/registry/webgl/extensions/EXT_sRGB/ let level = 0; let internal_format = Gl::RGBA; let border = 0;