Skip to content

Commit 8e52309

Browse files
YohYamasakiTrueDoctorcubic-dev-ai[bot]
authored
Render raster images as outlines in Outline mode (#3831)
* Render raster images as outlines in Outline mode * Draw a transformed unit-rectangle stroke instead of raster pixels * Skip creating blend layers for a raster image in Outline mode when only blend mode would trigger them * Rename variable names * Minor refactor to reduce nesting * Extract shared outline drawing helper * Update node-graph/libraries/rendering/src/renderer.rs Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
1 parent 3a7a5f5 commit 8e52309

1 file changed

Lines changed: 69 additions & 29 deletions

File tree

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ use graphic_types::vector_types::subpath::Subpath;
1818
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
1919
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
2020
use graphic_types::{Artboard, Graphic};
21-
use kurbo::Affine;
22-
use kurbo::Shape;
21+
use kurbo::{Affine, Cap, Join, Shape};
2322
use num_traits::Zero;
2423
use std::collections::{HashMap, HashSet};
2524
use std::fmt::Write;
@@ -262,6 +261,36 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
262261
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)
263262
}
264263

264+
fn get_outline_styles(render_params: &RenderParams) -> (kurbo::Stroke, peniko::Color) {
265+
use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;
266+
267+
let outline_stroke = kurbo::Stroke {
268+
width: LAYER_OUTLINE_STROKE_WEIGHT / if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. },
269+
miter_limit: 4.,
270+
join: Join::Miter,
271+
start_cap: Cap::Butt,
272+
end_cap: Cap::Butt,
273+
dash_pattern: Default::default(),
274+
dash_offset: 0.,
275+
};
276+
277+
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background);
278+
let outline_color_peniko = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]);
279+
280+
(outline_stroke, outline_color_peniko)
281+
}
282+
283+
fn draw_raster_outline(scene: &mut Scene, outline_transform: &DAffine2, render_params: &RenderParams) {
284+
use graphic_types::vector_types::vector::PointId;
285+
286+
let (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);
287+
288+
let mut outline_path = Subpath::<PointId>::new_rectangle(DVec2::ZERO, DVec2::ONE).to_bezpath();
289+
outline_path.apply_affine(Affine::new(outline_transform.to_cols_array()));
290+
291+
scene.stroke(&outline_stroke, Affine::IDENTITY, outline_color_peniko, None, &outline_path);
292+
}
293+
265294
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
266295
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
267296
#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
@@ -935,10 +964,7 @@ impl Render for Table<Vector> {
935964
}
936965

937966
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
938-
use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;
939967
use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};
940-
use vello::kurbo::{Cap, Join};
941-
use vello::peniko;
942968

943969
for row in self.iter() {
944970
use graphic_types::vector_types::vector;
@@ -1111,20 +1137,9 @@ impl Render for Table<Vector> {
11111137
// Render the path
11121138
match render_params.render_mode {
11131139
RenderMode::Outline => {
1114-
let outline_stroke = kurbo::Stroke {
1115-
width: LAYER_OUTLINE_STROKE_WEIGHT / if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. },
1116-
miter_limit: 4.,
1117-
join: Join::Miter,
1118-
start_cap: Cap::Butt,
1119-
end_cap: Cap::Butt,
1120-
dash_pattern: Default::default(),
1121-
dash_offset: 0.,
1122-
};
1123-
1124-
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background);
1125-
let outline_color = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]);
1140+
let (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);
11261141

1127-
scene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color, None, &path);
1142+
scene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color_peniko, None, &path);
11281143
}
11291144
_ => {
11301145
if use_layer {
@@ -1375,8 +1390,6 @@ impl Render for Table<Raster<CPU>> {
13751390
}
13761391

13771392
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
1378-
use vello::peniko;
1379-
13801393
for row in self.iter() {
13811394
let image = &row.element;
13821395
if image.data.is_empty() {
@@ -1389,7 +1402,7 @@ impl Render for Table<Raster<CPU>> {
13891402
let opacity = alpha_blending.opacity(render_params.for_mask);
13901403
let mut layer = false;
13911404

1392-
if (opacity < 1. || alpha_blending.blend_mode != BlendMode::default())
1405+
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()))
13931406
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
13941407
{
13951408
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
@@ -1398,6 +1411,19 @@ impl Render for Table<Raster<CPU>> {
13981411
layer = true;
13991412
}
14001413

1414+
if let RenderMode::Outline = render_params.render_mode {
1415+
let outline_transform = transform * *row.transform;
1416+
draw_raster_outline(scene, &outline_transform, render_params);
1417+
1418+
if layer {
1419+
scene.pop_layer();
1420+
}
1421+
1422+
continue;
1423+
}
1424+
1425+
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
1426+
14011427
let image_brush = peniko::ImageBrush::new(peniko::ImageData {
14021428
data: image.to_flat_u8().0.into(),
14031429
format: peniko::ImageFormat::Rgba8,
@@ -1406,7 +1432,6 @@ impl Render for Table<Raster<CPU>> {
14061432
alpha_type: peniko::ImageAlphaType::Alpha,
14071433
})
14081434
.with_extend(peniko::Extend::Repeat);
1409-
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
14101435

14111436
scene.draw_image(&image_brush, kurbo::Affine::new(image_transform.to_cols_array()));
14121437

@@ -1441,21 +1466,36 @@ impl Render for Table<Raster<GPU>> {
14411466
log::warn!("tried to render texture as an svg");
14421467
}
14431468

1444-
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
1445-
use vello::peniko;
1446-
1469+
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
14471470
for row in self.iter() {
1448-
let blend_mode = *row.alpha_blending;
1471+
let alpha_blending = *row.alpha_blending;
1472+
let blend_mode = match render_params.render_mode {
1473+
RenderMode::Outline => peniko::Mix::Normal,
1474+
_ => alpha_blending.blend_mode.to_peniko(),
1475+
};
1476+
14491477
let mut layer = false;
1450-
if blend_mode != Default::default()
1478+
1479+
if (render_params.render_mode != RenderMode::Outline && alpha_blending != Default::default())
14511480
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
14521481
{
1453-
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
1482+
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
14541483
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
1455-
scene.push_layer(peniko::Fill::NonZero, blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
1484+
scene.push_layer(peniko::Fill::NonZero, blending, alpha_blending.opacity, kurbo::Affine::IDENTITY, &rect);
14561485
layer = true;
14571486
}
14581487

1488+
if let RenderMode::Outline = render_params.render_mode {
1489+
let outline_transform = transform * *row.transform;
1490+
draw_raster_outline(scene, &outline_transform, render_params);
1491+
1492+
if layer {
1493+
scene.pop_layer();
1494+
}
1495+
1496+
continue;
1497+
}
1498+
14591499
let width = row.element.data().width();
14601500
let height = row.element.data().height();
14611501
let image = peniko::ImageBrush::new(peniko::ImageData {

0 commit comments

Comments
 (0)