From 45681b05c79e72741c4ca20fe3e2caa354578b19 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 26 Feb 2026 17:16:37 -0800 Subject: [PATCH 1/2] New nodes: RGBA to Color, HSVA to Color, Hex to Color, and Read Gradient --- .../document/overlays/utility_types_native.rs | 10 ++- .../document/overlays/utility_types_web.rs | 10 ++- .../portfolio/document/utility_types/misc.rs | 2 +- .../messages/tool/tool_messages/path_tool.rs | 7 +- .../messages/tool/tool_messages/pen_tool.rs | 2 +- .../tool/tool_messages/select_tool.rs | 14 +++- .../messages/tool/tool_messages/text_tool.rs | 2 +- node-graph/graph-craft/src/document/value.rs | 13 ++-- .../no-std-types/src/color/color_types.rs | 78 +++++++++++++++++-- node-graph/nodes/gcore/src/context.rs | 9 +++ node-graph/nodes/math/src/lib.rs | 42 ++++++++++ .../nodes/raster/src/image_color_palette.rs | 11 +-- 12 files changed, 165 insertions(+), 35 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_native.rs b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs index c5d3d96865..139ed6ef69 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_native.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_native.rs @@ -784,7 +784,10 @@ impl OverlayContextInternal { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { let sign = scale.signum(); - let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.05) + .to_rgba_hex_srgb(); fill_color.insert(0, '#'); let fill_color = Some(fill_color.as_str()); self.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None); @@ -817,7 +820,10 @@ impl OverlayContextInternal { // Hover ring if show_hover_ring { - let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.5) + .to_rgba_hex_srgb(); fill_color.insert(0, '#'); let circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius); diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs index 43757cf51b..77b0d7296a 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs @@ -698,7 +698,10 @@ impl OverlayContext { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { let sign = scale.signum(); - let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.05) + .to_rgba_hex_srgb(); fill_color.insert(0, '#'); let fill_color = Some(fill_color.as_str()); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); @@ -735,7 +738,10 @@ impl OverlayContext { // Hover ring if show_hover_ring { - let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.5) + .to_rgba_hex_srgb(); fill_color.insert(0, '#'); self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 2394dc73b0..076030db5a 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -229,7 +229,7 @@ impl Default for GridSnapping { isometric_y_spacing: 1., isometric_angle_a: 30., isometric_angle_b: 30., - grid_color: Color::from_rgb_str(COLOR_OVERLAY_GRAY.strip_prefix('#').unwrap()).unwrap(), + grid_color: Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_GRAY.strip_prefix('#').unwrap()).unwrap(), dot_display: false, } } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index e264348f94..f8a50450c4 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1888,7 +1888,7 @@ impl Fsm for PathToolFsmState { } } Self::Drawing { selection_shape } => { - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + let mut fill_color = graphene_std::Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) .unwrap() .with_alpha(0.05) .to_rgba_hex_srgb(); @@ -1978,7 +1978,10 @@ impl Fsm for PathToolFsmState { let viewport_diagonal = viewport.size().into_dvec2().length(); let faded = |color: &str| { - let mut color = graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); + let mut color = graphene_std::Color::from_rgb_hex_for_overlays(color.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.25) + .to_rgba_hex_srgb(); color.insert(0, '#'); color }; diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 25730b4751..d5d905cc71 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1783,7 +1783,7 @@ impl Fsm for PenToolFsmState { }) .collect(); - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + let mut fill_color = graphene_std::Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) .unwrap() .with_alpha(0.05) .to_rgba_hex_srgb(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index bf30e782b0..1fb99f443a 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -689,7 +689,7 @@ impl Fsm for SelectToolFsmState { .parent(document.metadata()) .is_some_and(|parent| selected.selected_layers_contains(parent, document.metadata())) }) { - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + let mut fill_color = graphene_std::Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) .unwrap() .with_alpha(0.5) .to_rgba_hex_srgb(); @@ -903,7 +903,10 @@ impl Fsm for SelectToolFsmState { let color = if !hover { color } else { - let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); + let color_string = &graphene_std::Color::from_rgb_hex_for_overlays(color.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.25) + .to_rgba_hex_srgb(); &format!("#{color_string}") }; let line_center = tool_data.line_center; @@ -927,7 +930,10 @@ impl Fsm for SelectToolFsmState { } else { (COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED) }; - let mut perp_color = graphene_std::Color::from_rgb_str(perp_color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); + let mut perp_color = graphene_std::Color::from_rgb_hex_for_overlays(perp_color.strip_prefix('#').unwrap()) + .unwrap() + .with_alpha(0.25) + .to_rgba_hex_srgb(); perp_color.insert(0, '#'); let perp_color = perp_color.as_str(); overlay_context.line(origin - edge * viewport_diagonal, origin + edge * viewport_diagonal, Some(edge_color), None); @@ -972,7 +978,7 @@ impl Fsm for SelectToolFsmState { } // Update the selection box - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + let mut fill_color = graphene_std::Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) .unwrap() .with_alpha(0.05) .to_rgba_hex_srgb(); diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 1bd12b61ac..dd1d87b90e 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -570,7 +570,7 @@ impl Fsm for TextToolFsmState { .. } = transition_data; let font_cache = &persistent_data.font_cache; - let fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) + let fill_color = graphene_std::Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) .unwrap() .with_alpha(0.05) .to_rgba_hex_srgb(); diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 7c092f70f0..1bc10ec050 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -297,15 +297,12 @@ impl TaggedValue { fn to_color(input: &str) -> Option { // String syntax (e.g. "000000ff") if input.starts_with('"') && input.ends_with('"') { - let color = input.trim().trim_matches('"').trim().trim_start_matches('#'); - match color.len() { - 6 => return Color::from_rgb_str(color), - 8 => return Color::from_rgba_str(color), - _ => { - log::error!("Invalid default value color string: {input}"); - return None; - } + let hex = input.trim().trim_matches('"').trim().trim_start_matches('#'); + let color = Color::from_hex_str(hex); + if color.is_none() { + log::error!("Invalid default value color string: {input}"); } + return color; } // Color constant syntax (e.g. Color::BLACK) diff --git a/node-graph/libraries/no-std-types/src/color/color_types.rs b/node-graph/libraries/no-std-types/src/color/color_types.rs index 76b9911d6e..28d809c54e 100644 --- a/node-graph/libraries/no-std-types/src/color/color_types.rs +++ b/node-graph/libraries/no-std-types/src/color/color_types.rs @@ -435,7 +435,7 @@ impl Color { Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha) } - /// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1) + /// Create a [Color] from a hue, saturation, lightness, and alpha (all between 0 and 1) /// /// # Examples /// ``` @@ -477,6 +477,25 @@ impl Color { Color { red, green, blue, alpha } } + /// Create a [Color] from hue, saturation, value, and alpha (all between 0 and 1). + pub fn from_hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Color { + let h_prime = (hue * 6.) % 6.; + let i = h_prime as i32; + let f = h_prime - i as f32; + let p = value * (1. - saturation); + let q = value * (1. - f * saturation); + let t = value * (1. - (1. - f) * saturation); + let (red, green, blue) = match i % 6 { + 0 => (value, t, p), + 1 => (q, value, p), + 2 => (p, value, t), + 3 => (p, q, value), + 4 => (t, p, value), + _ => (value, p, q), + }; + Color { red, green, blue, alpha } + } + /// Return the `red` component. /// /// # Examples @@ -922,16 +941,42 @@ impl Color { [hue, saturation, lightness, self.alpha] } - // TODO: Readd formatting + // TODO: This incorrectly handles gamma/linear and premultiplied alpha. For now, this can only be used for overlay drawing, not artwork. + // TODO: Remove this function and have overlays directly use the hex colors and not use the `Color` struct at all. + /// Creates a color from a 6-character RGB hex string (without a # prefix). + /// + /// ``` + /// use core_types::color::Color; + /// let color = Color::from_rgb_hex_for_overlays("7C67FA").unwrap(); + /// ``` + pub fn from_rgb_hex_for_overlays(color_str: &str) -> Option { + if color_str.len() != 6 { + return None; + } + let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; + let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; + let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; - /// Creates a color from a 8-character RGBA hex string (without a # prefix). + Some(Color::from_rgb8_srgb(r, g, b)) + } + + /// Creates a color from an 8-character RGBA hex string (without a # prefix). /// - /// # Examples /// ``` /// use core_types::color::Color; /// let color = Color::from_rgba_str("7C67FA61").unwrap(); /// ``` pub fn from_rgba_str(color_str: &str) -> Option { + let from_rgba8_srgb = |red, green, blue, alpha| { + let map_range = |int_color| int_color as f32 / 255.; + + let red = map_range(red); + let green = map_range(green); + let blue = map_range(blue); + let alpha = map_range(alpha); + Color { red, green, blue, alpha } + }; + if color_str.len() != 8 { return None; } @@ -940,7 +985,7 @@ impl Color { let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; let a = u8::from_str_radix(&color_str[6..8], 16).ok()?; - Some(Color::from_rgba8_srgb(r, g, b, a)) + Some(from_rgba8_srgb(r, g, b, a)) } /// Creates a color from a 6-character RGB hex string (without a # prefix). @@ -950,6 +995,15 @@ impl Color { /// let color = Color::from_rgb_str("7C67FA").unwrap(); /// ``` pub fn from_rgb_str(color_str: &str) -> Option { + let from_rgb8_srgb = |red, green, blue| { + let map_range = |int_color| int_color as f32 / 255.; + + let red = map_range(red); + let green = map_range(green); + let blue = map_range(blue); + Color { red, green, blue, alpha: 1. } + }; + if color_str.len() != 6 { return None; } @@ -957,7 +1011,19 @@ impl Color { let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; - Some(Color::from_rgb8_srgb(r, g, b)) + Some(from_rgb8_srgb(r, g, b)) + } + + /// Creates a color from a hex color code string with an optional `#` prefix, such as `#RRGGBB`, `RRGGBB`, `#RRGGBBAA`, or `RRGGBBAA`. + /// Returns `None` for invalid or unrecognized strings. + #[cfg(feature = "std")] + pub fn from_hex_str(hex: &str) -> Option { + let hex = hex.trim().trim_start_matches('#'); + match hex.len() { + 6 => Color::from_rgb_str(hex), + 8 => Color::from_rgba_str(hex), + _ => None, + } } /// Linearly interpolates between two colors based on t. diff --git a/node-graph/nodes/gcore/src/context.rs b/node-graph/nodes/gcore/src/context.rs index eee22bb4d8..a5527fe1d6 100644 --- a/node-graph/nodes/gcore/src/context.rs +++ b/node-graph/nodes/gcore/src/context.rs @@ -2,6 +2,7 @@ use core_types::table::Table; use core_types::{Color, ExtractVarArgs}; use core_types::{Ctx, ExtractIndex, ExtractPosition}; use glam::DVec2; +use graphic_types::vector_types::GradientStops; use graphic_types::{Graphic, Vector}; use raster_types::{CPU, Raster}; @@ -37,6 +38,14 @@ fn read_color(ctx: impl Ctx + ExtractVarArgs) -> Table { var_arg.downcast_ref().cloned().unwrap_or_default() } +#[node_macro::node(category("Context"), path(graphene_core::vector))] +fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> Table { + let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; + let var_arg = var_arg as &dyn std::any::Any; + + var_arg.downcast_ref().cloned().unwrap_or_default() +} + #[node_macro::node(category("Context"), path(core_types::vector))] async fn read_position( ctx: impl Ctx + ExtractPosition, diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 60c9515310..a5d92ec1ac 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -765,6 +765,48 @@ fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Table< color } +/// Constructs a color value from red, green, blue, and alpha components given as numbers from 0 to 1. +#[node_macro::node(category("Color"), name("RGBA to Color"))] +fn rgba_to_color(_: impl Ctx, _primary: (), red: Fraction, green: Fraction, blue: Fraction, #[default(1.)] alpha: Fraction) -> Table { + let red = (red as f32).clamp(0., 1.); + let green = (green as f32).clamp(0., 1.); + let blue = (blue as f32).clamp(0., 1.); + let alpha = (alpha as f32).clamp(0., 1.); + + Table::new_from_element(Color::from_rgbaf32_unchecked(red, green, blue, alpha)) +} + +/// Constructs a color value from hue, saturation, value, and alpha components given as numbers from 0 to 1. +#[node_macro::node(category("Color"), name("HSVA to Color"))] +fn hsva_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(1.)] value: Fraction, #[default(1.)] alpha: Fraction) -> Table { + let hue = (hue as f32) - (hue as f32).floor(); + let saturation = (saturation as f32).clamp(0., 1.); + let value = (value as f32).clamp(0., 1.); + let alpha = (alpha as f32).clamp(0., 1.); + + Table::new_from_element(Color::from_hsva(hue, saturation, value, alpha)) +} + +/// Constructs a color value from hue, saturation, lightness, and alpha components given as numbers from 0 to 1. +#[node_macro::node(category("Color"), name("HSLA to Color"))] +fn hsla_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] saturation: Fraction, #[default(0.5)] lightness: Fraction, #[default(1.)] alpha: Fraction) -> Table { + let hue = (hue as f32) - (hue as f32).floor(); + let saturation = (saturation as f32).clamp(0., 1.); + let lightness = (lightness as f32).clamp(0., 1.); + let alpha = (alpha as f32).clamp(0., 1.); + + Table::new_from_element(Color::from_hsla(hue, saturation, lightness, alpha)) +} + +/// Constructs a color value from an sRGB color code string, such as `#RRGGBB` or `#RRGGBBAA`. Invalid hex code strings produce no color. +#[node_macro::node(category("Color"), name("Hex to Color"))] +fn hex_to_color(_: impl Ctx, hex_code: String) -> Table { + match Color::from_hex_str(&hex_code) { + Some(c) => Table::new_from_element(c), + None => Table::new(), + } +} + /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. #[node_macro::node(category("Value"))] fn gradient_value(_: impl Ctx, _primary: (), gradient: Table) -> Table { diff --git a/node-graph/nodes/raster/src/image_color_palette.rs b/node-graph/nodes/raster/src/image_color_palette.rs index 65d6555f3a..ef20170591 100644 --- a/node-graph/nodes/raster/src/image_color_palette.rs +++ b/node-graph/nodes/raster/src/image_color_palette.rs @@ -1,16 +1,11 @@ use core_types::color::Color; use core_types::context::Ctx; +use core_types::registry::types::IntegerCount; use core_types::table::{Table, TableRow}; use raster_types::{CPU, Raster}; #[node_macro::node(category("Color"))] -async fn image_color_palette( - _: impl Ctx, - image: Table>, - #[hard_min(1.)] - #[soft_max(28.)] - max_size: u32, -) -> Table { +async fn image_color_palette(_: impl Ctx, image: Table>, #[default(4)] count: IntegerCount) -> Table { const GRID: f32 = 3.; let bins = GRID * GRID * GRID; @@ -35,7 +30,7 @@ async fn image_color_palette( shorted .iter() - .take(max_size as usize) + .take(count as usize) .flat_map(|&i| { let list = &color_bins[i]; From db1e1303a87e4b3760297c2e03a650cd98c39654 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 26 Feb 2026 18:11:04 -0800 Subject: [PATCH 2/2] Simplify --- .../no-std-types/src/color/color_types.rs | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/node-graph/libraries/no-std-types/src/color/color_types.rs b/node-graph/libraries/no-std-types/src/color/color_types.rs index 28d809c54e..4b4d7f00be 100644 --- a/node-graph/libraries/no-std-types/src/color/color_types.rs +++ b/node-graph/libraries/no-std-types/src/color/color_types.rs @@ -967,25 +967,15 @@ impl Color { /// let color = Color::from_rgba_str("7C67FA61").unwrap(); /// ``` pub fn from_rgba_str(color_str: &str) -> Option { - let from_rgba8_srgb = |red, green, blue, alpha| { - let map_range = |int_color| int_color as f32 / 255.; - - let red = map_range(red); - let green = map_range(green); - let blue = map_range(blue); - let alpha = map_range(alpha); - Color { red, green, blue, alpha } - }; - if color_str.len() != 8 { return None; } - let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; - let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; - let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; - let a = u8::from_str_radix(&color_str[6..8], 16).ok()?; + let red = u8::from_str_radix(&color_str[0..2], 16).ok()? as f32 / 255.; + let green = u8::from_str_radix(&color_str[2..4], 16).ok()? as f32 / 255.; + let blue = u8::from_str_radix(&color_str[4..6], 16).ok()? as f32 / 255.; + let alpha = u8::from_str_radix(&color_str[6..8], 16).ok()? as f32 / 255.; - Some(from_rgba8_srgb(r, g, b, a)) + Some(Color { red, green, blue, alpha }) } /// Creates a color from a 6-character RGB hex string (without a # prefix). @@ -995,23 +985,14 @@ impl Color { /// let color = Color::from_rgb_str("7C67FA").unwrap(); /// ``` pub fn from_rgb_str(color_str: &str) -> Option { - let from_rgb8_srgb = |red, green, blue| { - let map_range = |int_color| int_color as f32 / 255.; - - let red = map_range(red); - let green = map_range(green); - let blue = map_range(blue); - Color { red, green, blue, alpha: 1. } - }; - if color_str.len() != 6 { return None; } - let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; - let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; - let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; + let red = u8::from_str_radix(&color_str[0..2], 16).ok()? as f32 / 255.; + let green = u8::from_str_radix(&color_str[2..4], 16).ok()? as f32 / 255.; + let blue = u8::from_str_radix(&color_str[4..6], 16).ok()? as f32 / 255.; - Some(from_rgb8_srgb(r, g, b)) + Some(Color { red, green, blue, alpha: 1. }) } /// Creates a color from a hex color code string with an optional `#` prefix, such as `#RRGGBB`, `RRGGBB`, `#RRGGBBAA`, or `RRGGBBAA`.