Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
7 changes: 5 additions & 2 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
};
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/tool/tool_messages/pen_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
14 changes: 10 additions & 4 deletions editor/src/messages/tool/tool_messages/select_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/tool/tool_messages/text_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
13 changes: 5 additions & 8 deletions node-graph/graph-craft/src/document/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,12 @@ impl TaggedValue {
fn to_color(input: &str) -> Option<Color> {
// 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)
Expand Down
73 changes: 60 additions & 13 deletions node-graph/libraries/no-std-types/src/color/color_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// ```
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -922,11 +941,27 @@ 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<Color> {
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()?;

Some(Color::from_rgb8_srgb(r, g, b))
}

/// Creates a color from a 8-character RGBA hex string (without a # prefix).
/// 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();
Expand All @@ -935,12 +970,12 @@ impl Color {
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(Color::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).
Expand All @@ -953,11 +988,23 @@ impl Color {
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(Color::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`.
/// Returns `None` for invalid or unrecognized strings.
#[cfg(feature = "std")]
pub fn from_hex_str(hex: &str) -> Option<Color> {
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.
Expand Down
9 changes: 9 additions & 0 deletions node-graph/nodes/gcore/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -37,6 +38,14 @@ fn read_color(ctx: impl Ctx + ExtractVarArgs) -> Table<Color> {
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<GradientStops> {
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,
Expand Down
42 changes: 42 additions & 0 deletions node-graph/nodes/math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Color> {
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<Color> {
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<Color> {
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<Color> {
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<GradientStops>) -> Table<GradientStops> {
Expand Down
11 changes: 3 additions & 8 deletions node-graph/nodes/raster/src/image_color_palette.rs
Original file line number Diff line number Diff line change
@@ -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<Raster<CPU>>,
#[hard_min(1.)]
#[soft_max(28.)]
max_size: u32,
) -> Table<Color> {
async fn image_color_palette(_: impl Ctx, image: Table<Raster<CPU>>, #[default(4)] count: IntegerCount) -> Table<Color> {
const GRID: f32 = 3.;

let bins = GRID * GRID * GRID;
Expand All @@ -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];

Expand Down