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>
This commit is contained in:
parent
3a7a5f5953
commit
8e52309bb0
|
|
@ -18,8 +18,7 @@ use graphic_types::vector_types::subpath::Subpath;
|
||||||
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
|
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
|
||||||
use graphic_types::{Artboard, Graphic};
|
use graphic_types::{Artboard, Graphic};
|
||||||
use kurbo::Affine;
|
use kurbo::{Affine, Cap, Join, Shape};
|
||||||
use kurbo::Shape;
|
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
@ -262,6 +261,36 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
|
||||||
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_outline_styles(render_params: &RenderParams) -> (kurbo::Stroke, peniko::Color) {
|
||||||
|
use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;
|
||||||
|
|
||||||
|
let outline_stroke = kurbo::Stroke {
|
||||||
|
width: LAYER_OUTLINE_STROKE_WEIGHT / if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. },
|
||||||
|
miter_limit: 4.,
|
||||||
|
join: Join::Miter,
|
||||||
|
start_cap: Cap::Butt,
|
||||||
|
end_cap: Cap::Butt,
|
||||||
|
dash_pattern: Default::default(),
|
||||||
|
dash_offset: 0.,
|
||||||
|
};
|
||||||
|
|
||||||
|
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background);
|
||||||
|
let outline_color_peniko = peniko::Color::new([outline_color.r(), outline_color.g(), outline_color.b(), outline_color.a()]);
|
||||||
|
|
||||||
|
(outline_stroke, outline_color_peniko)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_raster_outline(scene: &mut Scene, outline_transform: &DAffine2, render_params: &RenderParams) {
|
||||||
|
use graphic_types::vector_types::vector::PointId;
|
||||||
|
|
||||||
|
let (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);
|
||||||
|
|
||||||
|
let mut outline_path = Subpath::<PointId>::new_rectangle(DVec2::ZERO, DVec2::ONE).to_bezpath();
|
||||||
|
outline_path.apply_affine(Affine::new(outline_transform.to_cols_array()));
|
||||||
|
|
||||||
|
scene.stroke(&outline_stroke, Affine::IDENTITY, outline_color_peniko, None, &outline_path);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
||||||
// 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.
|
// 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.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
|
|
@ -935,10 +964,7 @@ impl Render for Table<Vector> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;
|
|
||||||
use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};
|
||||||
use vello::kurbo::{Cap, Join};
|
|
||||||
use vello::peniko;
|
|
||||||
|
|
||||||
for row in self.iter() {
|
for row in self.iter() {
|
||||||
use graphic_types::vector_types::vector;
|
use graphic_types::vector_types::vector;
|
||||||
|
|
@ -1111,20 +1137,9 @@ impl Render for Table<Vector> {
|
||||||
// Render the path
|
// Render the path
|
||||||
match render_params.render_mode {
|
match render_params.render_mode {
|
||||||
RenderMode::Outline => {
|
RenderMode::Outline => {
|
||||||
let outline_stroke = kurbo::Stroke {
|
let (outline_stroke, outline_color_peniko) = get_outline_styles(render_params);
|
||||||
width: LAYER_OUTLINE_STROKE_WEIGHT / if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. },
|
|
||||||
miter_limit: 4.,
|
|
||||||
join: Join::Miter,
|
|
||||||
start_cap: Cap::Butt,
|
|
||||||
end_cap: Cap::Butt,
|
|
||||||
dash_pattern: Default::default(),
|
|
||||||
dash_offset: 0.,
|
|
||||||
};
|
|
||||||
|
|
||||||
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background);
|
scene.stroke(&outline_stroke, kurbo::Affine::new(element_transform.to_cols_array()), outline_color_peniko, None, &path);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if use_layer {
|
if use_layer {
|
||||||
|
|
@ -1375,8 +1390,6 @@ impl Render for Table<Raster<CPU>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
|
||||||
|
|
||||||
for row in self.iter() {
|
for row in self.iter() {
|
||||||
let image = &row.element;
|
let image = &row.element;
|
||||||
if image.data.is_empty() {
|
if image.data.is_empty() {
|
||||||
|
|
@ -1389,7 +1402,7 @@ impl Render for Table<Raster<CPU>> {
|
||||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
if (opacity < 1. || alpha_blending.blend_mode != BlendMode::default())
|
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()))
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
|
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
|
|
@ -1398,6 +1411,19 @@ impl Render for Table<Raster<CPU>> {
|
||||||
layer = true;
|
layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let RenderMode::Outline = render_params.render_mode {
|
||||||
|
let outline_transform = transform * *row.transform;
|
||||||
|
draw_raster_outline(scene, &outline_transform, render_params);
|
||||||
|
|
||||||
|
if layer {
|
||||||
|
scene.pop_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||||
|
|
||||||
let image_brush = peniko::ImageBrush::new(peniko::ImageData {
|
let image_brush = peniko::ImageBrush::new(peniko::ImageData {
|
||||||
data: image.to_flat_u8().0.into(),
|
data: image.to_flat_u8().0.into(),
|
||||||
format: peniko::ImageFormat::Rgba8,
|
format: peniko::ImageFormat::Rgba8,
|
||||||
|
|
@ -1406,7 +1432,6 @@ impl Render for Table<Raster<CPU>> {
|
||||||
alpha_type: peniko::ImageAlphaType::Alpha,
|
alpha_type: peniko::ImageAlphaType::Alpha,
|
||||||
})
|
})
|
||||||
.with_extend(peniko::Extend::Repeat);
|
.with_extend(peniko::Extend::Repeat);
|
||||||
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
|
||||||
|
|
||||||
scene.draw_image(&image_brush, kurbo::Affine::new(image_transform.to_cols_array()));
|
scene.draw_image(&image_brush, kurbo::Affine::new(image_transform.to_cols_array()));
|
||||||
|
|
||||||
|
|
@ -1441,21 +1466,36 @@ impl Render for Table<Raster<GPU>> {
|
||||||
log::warn!("tried to render texture as an svg");
|
log::warn!("tried to render texture as an svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
|
||||||
|
|
||||||
for row in self.iter() {
|
for row in self.iter() {
|
||||||
let blend_mode = *row.alpha_blending;
|
let alpha_blending = *row.alpha_blending;
|
||||||
|
let blend_mode = match render_params.render_mode {
|
||||||
|
RenderMode::Outline => peniko::Mix::Normal,
|
||||||
|
_ => alpha_blending.blend_mode.to_peniko(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
if blend_mode != Default::default()
|
|
||||||
|
if (render_params.render_mode != RenderMode::Outline && alpha_blending != Default::default())
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
|
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||||
scene.push_layer(peniko::Fill::NonZero, blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
|
scene.push_layer(peniko::Fill::NonZero, blending, alpha_blending.opacity, kurbo::Affine::IDENTITY, &rect);
|
||||||
layer = true;
|
layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let RenderMode::Outline = render_params.render_mode {
|
||||||
|
let outline_transform = transform * *row.transform;
|
||||||
|
draw_raster_outline(scene, &outline_transform, render_params);
|
||||||
|
|
||||||
|
if layer {
|
||||||
|
scene.pop_layer();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let width = row.element.data().width();
|
let width = row.element.data().width();
|
||||||
let height = row.element.data().height();
|
let height = row.element.data().height();
|
||||||
let image = peniko::ImageBrush::new(peniko::ImageData {
|
let image = peniko::ImageBrush::new(peniko::ImageData {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue