Filling cached shapes with a different color. (#5752)

Fixes #5188

Added a new method `ShapeOps::recolorize` which changes color depending on values on r, g, b channels. It should be explained more in the docs. It will allow us using colored cached icons in the Component Browser.
This commit is contained in:
Adam Obuchowicz 2023-02-27 11:38:47 +01:00 committed by GitHub
parent 6dfbde5afd
commit d1a0f5a543
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 13 deletions

View File

@ -81,7 +81,7 @@ define_icons! {
}
}
/// Four rounded rectangles in different colors aranged in a grid.
/// Four rounded rectangles in different colors arranged in a grid.
pub mod libraries(Libraries) {
ensogl_core::shape! {
above = [grid_view::selectable::highlight::shape, crate::entry::background];

View File

@ -142,6 +142,41 @@ where for<'t> &'t Self: IntoOwned<Owned = Self> {
Fill(self, color)
}
/// Change the shape color depending on RGB components.
///
/// ### How The New Color Is Defined
///
/// Assuming `s.color` is a previous shape premultiplied color (i.e. the alpha component is
/// applied to each channel), a new color is defined as:
/// `r * s.color.r + b * s.color.b + g * s.color.g`.
///
/// ### Usage
///
/// The main case for this function is coloring a complex shape serving as a
/// template - the best example are [cached
/// shapes](crate::display::shape::primitive::system::cached), which cannot be
/// parameterized.
///
/// When the only colors in that template are [full red](color::Rgba::red),
/// [full green](color::Rgba::green), or [full blue](color::Rgba::blue), this method will
/// replace the template colors with the specialized ones.
///
/// A real-world example is an icon (cached in the texture) which should change color on mouse
/// hover or click.
fn recolorize<RColor, GColor, BColor>(
&self,
r: RColor,
g: GColor,
b: BColor,
) -> Recolorize<Self>
where
RColor: Into<Var<color::Rgba>>,
GColor: Into<Var<color::Rgba>>,
BColor: Into<Var<color::Rgba>>,
{
Recolorize(self, r, g, b)
}
/// Makes the borders of the shape crisp. Please note that it removes any form of antialiasing
/// and can cause distortions especially with round surfaces.
fn pixel_snap(&self) -> PixelSnap<Self> {

View File

@ -122,6 +122,7 @@ define_modifiers! {
Difference difference (child1,child2) ()
Intersection intersection (child1,child2) ()
Fill fill (child) (color:Rgba)
Recolorize recolorize (child) (r: Rgba, g: Rgba, b: Rgba)
PixelSnap pixel_snap (child) ()
Grow grow (child) (value:f32)
Shrink shrink (child) (value:f32)

View File

@ -403,6 +403,8 @@ Shape intersection_no_blend (Shape s1, Shape s2) {
}
Shape set_color(Shape shape, Rgba t) {
// The [`alpha`] field is applied on [`color`] only if we are not in
// `DISPLAY_MODE_CACHED_SHAPES_TEXTURE`. See [`color`] docs for explanation.
if (input_display_mode != DISPLAY_MODE_CACHED_SHAPES_TEXTURE) {
t.raw.a *= shape.alpha;
}
@ -410,6 +412,24 @@ Shape set_color(Shape shape, Rgba t) {
return shape;
}
/// Change the shape color depending on RGB components.
///
/// See documentation of [`ShapeOps::colorize`] for detailed explanation and
/// usage examples.
Shape recolorize(Shape shape, Rgba r, Rgba g, Rgba b) {
PremultipliedColor r_prem = premultiply(Color(r));
PremultipliedColor g_prem = premultiply(Color(g));
PremultipliedColor b_prem = premultiply(Color(b));
vec4 r_component = premultiply(Color(r)).repr.raw * shape.color.repr.raw.r;
vec4 g_component = premultiply(Color(g)).repr.raw * shape.color.repr.raw.g;
vec4 b_component = premultiply(Color(b)).repr.raw * shape.color.repr.raw.b;
Rgba new = rgba(r_component + g_component + b_component);
// The original shape's color components had the [`alpha`] field already applied, so we don't need to apply it
// again here.
shape.color = PremultipliedColor(new);
return shape;
}
Shape with_infinite_bounds (Shape s) {
BoundSdf sdf = s.sdf;
sdf.bounds = infinite();

View File

@ -240,8 +240,42 @@ impl Canvas {
self.if_not_defined(num, |this| {
let color: Glsl = color.into().glsl();
this.add_current_function_code_line(format!("Shape shape = {};", s.getter()));
this.add_current_function_code_line(format!("Srgba color = srgba({color});"));
this.new_shape_from_expr("return set_color(shape,rgba(color));")
this.add_current_function_code_line(format!("Rgba color = rgba({color});"));
this.new_shape_from_expr("return set_color(shape, color);")
})
}
/// Change the shape color depending on RGB components.
///
/// Assuming `s.color` is a previous shape premultiplied color (i.e. the alpha component is
/// applied to each channel), a new color is defined as:
/// `r * s.color.r + b * s.color.b + g * s.color.g`.
///
/// See [`ShapeOps` counterpart
/// documentation](crate::display::shape::class::ShapeOps::recolorize) for usage examples.
pub fn recolorize<RColor, GColor, BColor>(
&mut self,
num: usize,
s: Shape,
r: RColor,
g: GColor,
b: BColor,
) -> Shape
where
RColor: Into<Var<color::Rgba>>,
GColor: Into<Var<color::Rgba>>,
BColor: Into<Var<color::Rgba>>,
{
self.if_not_defined(num, |this| {
let r: Glsl = r.into().glsl();
let g: Glsl = g.into().glsl();
let b: Glsl = b.into().glsl();
this.add_current_function_code_line(format!("Shape shape = {};", s.getter()));
this.add_current_function_code_line(format!("Rgba r = rgba({r});"));
this.add_current_function_code_line(format!("Rgba g = rgba({g});"));
this.add_current_function_code_line(format!("Rgba b = rgba({b});"));
this.new_shape_from_expr("return recolorize(shape, r, g, b);")
})
}

View File

@ -6,6 +6,7 @@
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::let_and_return)]
use ensogl_core::data::color;
use ensogl_core::display::shape::*;
use ensogl_core::display::world::*;
use ensogl_core::prelude::*;
@ -24,8 +25,8 @@ mod icon1 {
use super::*;
ensogl_core::cached_shape! { 32 x 32;
(_style: Style) {
let shape = Circle(16.px()).fill(color::Rgba::green());
shape.into()
let shape = Circle(16.px()).fill(color::Rgba::red());
shape.recolorize(color::Rgba::green(), color::Rgba::red(), color::Rgba::blue()).into()
}
}
}
@ -87,9 +88,12 @@ pub mod data_input {
mod shape {
use super::*;
ensogl_core::shape! {
(_style: Style, shape: cached::AnyCachedShape) {
(_style: Style, shape: cached::AnyCachedShape, color: Vector4) {
let bg = Rect((100.px(), 100.px())).fill(color::Rgba::white());
let with_bg = &bg + &shape;
let vivid_color: Var<color::Rgba> = color.into();
let dull_color = vivid_color.clone().multiply_alpha(&Var::Static(0.5));
let colored = shape.recolorize(vivid_color, dull_color, color::Rgba::default());
let with_bg = &bg + &colored;
with_bg.into()
}
}
@ -174,18 +178,27 @@ pub fn main() {
world.default_scene.add_child(&texture_preview);
world.default_scene.layers.main.add(&texture_preview);
let shapes = [shape::View::new(), shape::View::new(), shape::View::new()];
for shape in &shapes {
shape.set_size(Vector2(100.0, 100.0));
let shapes = [
shape::View::new(),
shape::View::new(),
shape::View::new(),
shape::View::new(),
shape::View::new(),
];
for (index, shape) in shapes.iter().enumerate() {
shape.set_x(-100.0 + 50.0 * index as f32);
shape.set_size(Vector2(40.0, 40.0));
shape.color.set(color::Rgba::black().into());
world.default_scene.add_child(shape);
world.default_scene.layers.main.add(shape);
}
shapes[0].set_xy((-60.0, 0.0));
shapes[0].shape.set(icon1::Shape::any_cached_shape_parameter());
shapes[1].set_xy((60.0, 0.0));
shapes[1].shape.set(icon2::Shape::any_cached_shape_parameter());
shapes[2].set_xy((180.0, 0.0));
shapes[2].shape.set(data_input::Shape::any_cached_shape_parameter());
shapes[3].shape.set(data_input::Shape::any_cached_shape_parameter());
shapes[3].color.set(color::Rgb::from_base_255(43.0, 117.0, 239.0).with_alpha(1.0).into());
shapes[4].shape.set(data_input::Shape::any_cached_shape_parameter());
shapes[4].color.set(color::Rgb::from_base_255(134.0, 135.0, 43.0).with_alpha(1.0).into());
let icon1 = icon1::View::new();
icon1.set_size(Vector2(100.0, 100.0));