From a6466b318eec4d9857f895108cd9b2e4b5bd5bb8 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Thu, 5 Feb 2026 20:02:26 +0000 Subject: [PATCH 1/8] contrast --- .../libraries/rendering/src/render_ext.rs | 9 ++++-- .../libraries/rendering/src/renderer.rs | 31 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index 1b5a5063ae..97af98dae4 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -1,5 +1,5 @@ -use crate::renderer::{RenderParams, format_transform_matrix}; -use core_types::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT}; +use crate::renderer::{RenderParams, contrasting_outline_color, format_transform_matrix}; +use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT; use core_types::uuid::generate_uuid; use glam::DAffine2; use graphic_types::vector_types::gradient::{Gradient, GradientType}; @@ -167,7 +167,10 @@ impl RenderExt for PathStyle { match render_mode { RenderMode::Outline => { let fill_attribute = Fill::None.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); - let mut outline_stroke = Stroke::new(Some(LAYER_OUTLINE_STROKE_COLOR), LAYER_OUTLINE_STROKE_WEIGHT); + + let outline_color = contrasting_outline_color(render_params.artboard_background); + + let mut outline_stroke = Stroke::new(Some(outline_color), LAYER_OUTLINE_STROKE_WEIGHT); // Outline strokes should be non-scaling by default outline_stroke.non_scaling = true; let stroke_attribute = outline_stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 6da8a86081..37f97744d9 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -182,6 +182,7 @@ pub struct RenderParams { pub alignment_parent_transform: Option, pub aligned_strokes: bool, pub override_paint_order: bool, + pub artboard_background: Option, } impl Hash for RenderParams { @@ -198,6 +199,7 @@ impl Hash for RenderParams { } self.aligned_strokes.hash(state); self.override_paint_order.hash(state); + self.artboard_background.hash(state); } } @@ -235,6 +237,16 @@ fn max_scale(transform: DAffine2) -> f64 { (sx + sy).sqrt() } +pub fn contrasting_outline_color(background: Option) -> Color { + use core_types::consts::LAYER_OUTLINE_STROKE_COLOR; + if let Some(bg) = background { + let luminance = 0.2126 * bg.r() + 0.7152 * bg.g() + 0.0722 * bg.b(); + if luminance > 0.5 { Color::BLACK } else { Color::WHITE } + } else { + LAYER_OUTLINE_STROKE_COLOR + } +} + pub fn to_transform(transform: DAffine2) -> usvg::Transform { let cols = transform.to_cols_array(); usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32) @@ -440,7 +452,9 @@ impl Render for Artboard { }, // Artwork content |render| { - self.content.render_svg(render, render_params); + let mut render_params = render_params.clone(); + render_params.artboard_background = Some(self.background); + self.content.render_svg(render, &render_params); }, ); } @@ -462,7 +476,9 @@ impl Render for Artboard { } // Since the content's transform is right multiplied in when rendering the content, we just need to right multiply by the artboard offset here. let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2()); - self.content.render_to_vello(scene, child_transform, context, render_params); + let mut render_params = render_params.clone(); + render_params.artboard_background = Some(self.background); + self.content.render_to_vello(scene, child_transform, context, &render_params); if self.clip { scene.pop_layer(); } @@ -881,7 +897,7 @@ impl Render for Table { } fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { - use core_types::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT}; + use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT; use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin}; use vello::kurbo::{Cap, Join}; use vello::peniko; @@ -1065,12 +1081,9 @@ impl Render for Table { dash_pattern: Default::default(), dash_offset: 0., }; - let outline_color = peniko::Color::new([ - LAYER_OUTLINE_STROKE_COLOR.r(), - LAYER_OUTLINE_STROKE_COLOR.g(), - LAYER_OUTLINE_STROKE_COLOR.b(), - LAYER_OUTLINE_STROKE_COLOR.a(), - ]); + + let outline_color = contrasting_outline_color(render_params.artboard_background); + let outline_color = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]); scene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color, None, &path); } From 861b2fc771243c905bc935a2536c8f95e47bf9ea Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Thu, 5 Feb 2026 21:27:38 +0000 Subject: [PATCH 2/8] Update --- .../libraries/rendering/src/renderer.rs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 37f97744d9..e9708db9f1 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -239,9 +239,41 @@ fn max_scale(transform: DAffine2) -> f64 { pub fn contrasting_outline_color(background: Option) -> Color { use core_types::consts::LAYER_OUTLINE_STROKE_COLOR; + if let Some(bg) = background { - let luminance = 0.2126 * bg.r() + 0.7152 * bg.g() + 0.0722 * bg.b(); - if luminance > 0.5 { Color::BLACK } else { Color::WHITE } + let alpha = bg.a(); + + if alpha.abs() < f32::EPSILON { + return Color::BLACK; + } + + // Convert Linear Float to sRGB Float + let linear_to_srgb = |x: f32| -> f32 { if x <= 0.0031308 { x * 12.92 } else { 1.055 * x.powf(1.0 / 2.4) - 0.055 } }; + + // Convert sRGB Float to Linear Float + let srgb_to_linear = |x: f32| -> f32 { if x <= 0.04045 { x / 12.92 } else { ((x + 0.055) / 1.055).powf(2.4) } }; + + let r_lin = bg.r() / alpha; + let g_lin = bg.g() / alpha; + let b_lin = bg.b() / alpha; + + let r_srgb = linear_to_srgb(r_lin); + let g_srgb = linear_to_srgb(g_lin); + let b_srgb = linear_to_srgb(b_lin); + + let r_comp_srgb = r_srgb * alpha + (1.0 - alpha); + let g_comp_srgb = g_srgb * alpha + (1.0 - alpha); + let b_comp_srgb = b_srgb * alpha + (1.0 - alpha); + + let r_final = srgb_to_linear(r_comp_srgb); + let g_final = srgb_to_linear(g_comp_srgb); + let b_final = srgb_to_linear(b_comp_srgb); + + let luminance = 0.2126 * r_final + 0.7152 * g_final + 0.0722 * b_final; +9 + let threshold = (1.05 * 0.05f32).sqrt() - 0.05; + + if luminance > threshold { Color::BLACK } else { Color::WHITE } } else { LAYER_OUTLINE_STROKE_COLOR } From 96fc64871251cb10924d1ca7c7aca23df2cbc7a2 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Thu, 5 Feb 2026 21:31:32 +0000 Subject: [PATCH 3/8] error corrected --- node-graph/libraries/rendering/src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index e9708db9f1..f5c975dc9f 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -270,7 +270,7 @@ pub fn contrasting_outline_color(background: Option) -> Color { let b_final = srgb_to_linear(b_comp_srgb); let luminance = 0.2126 * r_final + 0.7152 * g_final + 0.0722 * b_final; -9 + let threshold = (1.05 * 0.05f32).sqrt() - 0.05; if luminance > threshold { Color::BLACK } else { Color::WHITE } From 156e2cee67d96cdd3ba29e5e86090d3444712c89 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Fri, 6 Feb 2026 19:37:00 +0000 Subject: [PATCH 4/8] changes --- node-graph/libraries/rendering/src/renderer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index f5c975dc9f..a7ea4008a9 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -261,9 +261,10 @@ pub fn contrasting_outline_color(background: Option) -> Color { let g_srgb = linear_to_srgb(g_lin); let b_srgb = linear_to_srgb(b_lin); - let r_comp_srgb = r_srgb * alpha + (1.0 - alpha); - let g_comp_srgb = g_srgb * alpha + (1.0 - alpha); - let b_comp_srgb = b_srgb * alpha + (1.0 - alpha); + let canvas_srgb = 0x22 as f32 / 255.0; + let r_comp_srgb = r_srgb * alpha + canvas_srgb * (1.0 - alpha); + let g_comp_srgb = g_srgb * alpha + canvas_srgb * (1.0 - alpha); + let b_comp_srgb = b_srgb * alpha + canvas_srgb * (1.0 - alpha); let r_final = srgb_to_linear(r_comp_srgb); let g_final = srgb_to_linear(g_comp_srgb); From 8cb18ce9d5bedbfbda3ace477ba174db35ab5d7e Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Mon, 16 Feb 2026 20:19:56 +0000 Subject: [PATCH 5/8] Fix --- .../libraries/rendering/src/renderer.rs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index a7ea4008a9..07de1188a2 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -243,28 +243,24 @@ pub fn contrasting_outline_color(background: Option) -> Color { if let Some(bg) = background { let alpha = bg.a(); - if alpha.abs() < f32::EPSILON { - return Color::BLACK; - } - // Convert Linear Float to sRGB Float let linear_to_srgb = |x: f32| -> f32 { if x <= 0.0031308 { x * 12.92 } else { 1.055 * x.powf(1.0 / 2.4) - 0.055 } }; // Convert sRGB Float to Linear Float let srgb_to_linear = |x: f32| -> f32 { if x <= 0.04045 { x / 12.92 } else { ((x + 0.055) / 1.055).powf(2.4) } }; - let r_lin = bg.r() / alpha; - let g_lin = bg.g() / alpha; - let b_lin = bg.b() / alpha; - - let r_srgb = linear_to_srgb(r_lin); - let g_srgb = linear_to_srgb(g_lin); - let b_srgb = linear_to_srgb(b_lin); + let (r_srgb, g_srgb, b_srgb) = if alpha.abs() > f32::EPSILON { + let r_lin = bg.r() / alpha; + let g_lin = bg.g() / alpha; + let b_lin = bg.b() / alpha; + (linear_to_srgb(r_lin), linear_to_srgb(g_lin), linear_to_srgb(b_lin)) + } else { + (0., 0., 0.) + }; - let canvas_srgb = 0x22 as f32 / 255.0; - let r_comp_srgb = r_srgb * alpha + canvas_srgb * (1.0 - alpha); - let g_comp_srgb = g_srgb * alpha + canvas_srgb * (1.0 - alpha); - let b_comp_srgb = b_srgb * alpha + canvas_srgb * (1.0 - alpha); + let r_comp_srgb = r_srgb * alpha; + let g_comp_srgb = g_srgb * alpha; + let b_comp_srgb = b_srgb * alpha; let r_final = srgb_to_linear(r_comp_srgb); let g_final = srgb_to_linear(g_comp_srgb); From 614a243318d5bfae582e759eb46e2d0d9c697c94 Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Mon, 16 Feb 2026 21:53:20 +0000 Subject: [PATCH 6/8] fix-2 --- .../libraries/rendering/src/render_ext.rs | 4 +- .../libraries/rendering/src/renderer.rs | 52 ++++++++----------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index 97af98dae4..079ec65e1a 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -1,4 +1,4 @@ -use crate::renderer::{RenderParams, contrasting_outline_color, format_transform_matrix}; +use crate::renderer::{RenderParams, black_or_white_for_best_contrast, format_transform_matrix}; use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT; use core_types::uuid::generate_uuid; use glam::DAffine2; @@ -168,7 +168,7 @@ impl RenderExt for PathStyle { RenderMode::Outline => { let fill_attribute = Fill::None.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); - let outline_color = contrasting_outline_color(render_params.artboard_background); + let outline_color = black_or_white_for_best_contrast(render_params.artboard_background); let mut outline_stroke = Stroke::new(Some(outline_color), LAYER_OUTLINE_STROKE_WEIGHT); // Outline strokes should be non-scaling by default diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index fce1c7d128..eaa16863dd 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -237,43 +237,37 @@ fn max_scale(transform: DAffine2) -> f64 { (sx + sy).sqrt() } -pub fn contrasting_outline_color(background: Option) -> Color { +pub fn black_or_white_for_best_contrast(background: Option) -> Color { use core_types::consts::LAYER_OUTLINE_STROKE_COLOR; - if let Some(bg) = background { - let alpha = bg.a(); + let Some(bg) = background else { + return LAYER_OUTLINE_STROKE_COLOR; + }; - // Convert Linear Float to sRGB Float - let linear_to_srgb = |x: f32| -> f32 { if x <= 0.0031308 { x * 12.92 } else { 1.055 * x.powf(1.0 / 2.4) - 0.055 } }; + let alpha = bg.a(); - // Convert sRGB Float to Linear Float - let srgb_to_linear = |x: f32| -> f32 { if x <= 0.04045 { x / 12.92 } else { ((x + 0.055) / 1.055).powf(2.4) } }; - - let (r_srgb, g_srgb, b_srgb) = if alpha.abs() > f32::EPSILON { - let r_lin = bg.r() / alpha; - let g_lin = bg.g() / alpha; - let b_lin = bg.b() / alpha; - (linear_to_srgb(r_lin), linear_to_srgb(g_lin), linear_to_srgb(b_lin)) - } else { - (0., 0., 0.) - }; + let (r_srgb, g_srgb, b_srgb) = if alpha.abs() > f32::EPSILON { + let r_lin = bg.r() / alpha; + let g_lin = bg.g() / alpha; + let b_lin = bg.b() / alpha; + (Color::linear_to_srgb(r_lin), Color::linear_to_srgb(g_lin), Color::linear_to_srgb(b_lin)) + } else { + (0., 0., 0.) + }; - let r_comp_srgb = r_srgb * alpha; - let g_comp_srgb = g_srgb * alpha; - let b_comp_srgb = b_srgb * alpha; + let r_comp_srgb = r_srgb * alpha; + let g_comp_srgb = g_srgb * alpha; + let b_comp_srgb = b_srgb * alpha; - let r_final = srgb_to_linear(r_comp_srgb); - let g_final = srgb_to_linear(g_comp_srgb); - let b_final = srgb_to_linear(b_comp_srgb); + let r_final = Color::srgb_to_linear(r_comp_srgb); + let g_final = Color::srgb_to_linear(g_comp_srgb); + let b_final = Color::srgb_to_linear(b_comp_srgb); - let luminance = 0.2126 * r_final + 0.7152 * g_final + 0.0722 * b_final; + let luminance = 0.2126 * r_final + 0.7152 * g_final + 0.0722 * b_final; - let threshold = (1.05 * 0.05f32).sqrt() - 0.05; + let threshold = (1.05 * 0.05f32).sqrt() - 0.05; - if luminance > threshold { Color::BLACK } else { Color::WHITE } - } else { - LAYER_OUTLINE_STROKE_COLOR - } + if luminance > threshold { Color::BLACK } else { Color::WHITE } } pub fn to_transform(transform: DAffine2) -> usvg::Transform { @@ -1112,7 +1106,7 @@ impl Render for Table { dash_offset: 0., }; - let outline_color = contrasting_outline_color(render_params.artboard_background); + let outline_color = black_or_white_for_best_contrast(render_params.artboard_background); let outline_color = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]); scene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color, None, &path); From af56ae9143265386281ba653883fcf0daa9e58ee Mon Sep 17 00:00:00 2001 From: Kulratan Thapar Date: Mon, 16 Feb 2026 21:56:28 +0000 Subject: [PATCH 7/8] cleanup-2 --- .../libraries/rendering/src/renderer.rs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index eaa16863dd..be82db6af3 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -246,28 +246,19 @@ pub fn black_or_white_for_best_contrast(background: Option) -> Color { let alpha = bg.a(); - let (r_srgb, g_srgb, b_srgb) = if alpha.abs() > f32::EPSILON { - let r_lin = bg.r() / alpha; - let g_lin = bg.g() / alpha; - let b_lin = bg.b() / alpha; - (Color::linear_to_srgb(r_lin), Color::linear_to_srgb(g_lin), Color::linear_to_srgb(b_lin)) + // Un-premultiply, then convert to gamma sRGB + let srgb = if alpha.abs() > f32::EPSILON { + Color::from_rgbaf32_unchecked(bg.r() / alpha, bg.g() / alpha, bg.b() / alpha, alpha).to_gamma_srgb() } else { - (0., 0., 0.) + Color::from_rgbaf32_unchecked(0., 0., 0., 0.) }; - let r_comp_srgb = r_srgb * alpha; - let g_comp_srgb = g_srgb * alpha; - let b_comp_srgb = b_srgb * alpha; - - let r_final = Color::srgb_to_linear(r_comp_srgb); - let g_final = Color::srgb_to_linear(g_comp_srgb); - let b_final = Color::srgb_to_linear(b_comp_srgb); - - let luminance = 0.2126 * r_final + 0.7152 * g_final + 0.0722 * b_final; + // Composite over black in sRGB space, then convert back to linear for luminance + let composited = Color::from_rgbaf32_unchecked(srgb.r() * alpha, srgb.g() * alpha, srgb.b() * alpha, 1.).to_linear_srgb(); let threshold = (1.05 * 0.05f32).sqrt() - 0.05; - if luminance > threshold { Color::BLACK } else { Color::WHITE } + if composited.luminance_srgb() > threshold { Color::BLACK } else { Color::WHITE } } pub fn to_transform(transform: DAffine2) -> usvg::Transform { From 441f970de935399572fa124fe0a978e13991b41c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 16 Feb 2026 14:57:26 -0800 Subject: [PATCH 8/8] Formatting --- node-graph/libraries/rendering/src/render_ext.rs | 2 +- node-graph/libraries/rendering/src/renderer.rs | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index 079ec65e1a..a7eb895f54 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -169,10 +169,10 @@ impl RenderExt for PathStyle { let fill_attribute = Fill::None.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); let outline_color = black_or_white_for_best_contrast(render_params.artboard_background); - let mut outline_stroke = Stroke::new(Some(outline_color), LAYER_OUTLINE_STROKE_WEIGHT); // Outline strokes should be non-scaling by default outline_stroke.non_scaling = true; + let stroke_attribute = outline_stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); format!("{fill_attribute}{stroke_attribute}") } diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index be82db6af3..74372383ac 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -238,19 +238,15 @@ fn max_scale(transform: DAffine2) -> f64 { } pub fn black_or_white_for_best_contrast(background: Option) -> Color { - use core_types::consts::LAYER_OUTLINE_STROKE_COLOR; - - let Some(bg) = background else { - return LAYER_OUTLINE_STROKE_COLOR; - }; + let Some(bg) = background else { return core_types::consts::LAYER_OUTLINE_STROKE_COLOR }; let alpha = bg.a(); // Un-premultiply, then convert to gamma sRGB - let srgb = if alpha.abs() > f32::EPSILON { + let srgb = if alpha > f32::EPSILON { Color::from_rgbaf32_unchecked(bg.r() / alpha, bg.g() / alpha, bg.b() / alpha, alpha).to_gamma_srgb() } else { - Color::from_rgbaf32_unchecked(0., 0., 0., 0.) + Color::TRANSPARENT }; // Composite over black in sRGB space, then convert back to linear for luminance