Fix click targets (in, e.g., the boolean node) by resolving footprints from render output (#1946)

* add NodeId (u64) and Footprint to Graphic Group

* Render Output footprints

* Small bug fixes

* Commented out render output click targets/footprints

* Run graph when deleting

* Switch to node path

* Add upstream clicktargets for boolean operation

* Fix boolean operations

* Fix grouped layers

* Add click targets to vello render

* Add cache to artwork

* Fix demo artwork

* Improve recursion

* Code review

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-09-15 18:26:59 -07:00
committed by GitHub
parent ef007736f5
commit ca0d102296
28 changed files with 1003 additions and 670 deletions

View File

@@ -42,7 +42,7 @@ fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperat
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> {
// Ensure all non vector data in the graphic group is converted to vector data
let vector_data = graphic_group.iter().map(union_vector_data);
let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element));
// Apply the transform from the parent graphic group
let transformed_vector_data = vector_data.map(|mut vector_data| {
vector_data.transform = graphic_group.transform * vector_data.transform;
@@ -174,7 +174,15 @@ fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperat
}
// The first index is the bottom of the stack
boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation)
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation);
let transform = boolean_operation_result.transform;
VectorData::transform(&mut boolean_operation_result, transform);
boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY);
boolean_operation_result.transform = DAffine2::IDENTITY;
boolean_operation_result.upstream_graphic_group = Some(group_of_paths);
boolean_operation_result
}
fn to_svg_string(vector: &VectorData, transform: DAffine2) -> String {

View File

@@ -1,4 +1,5 @@
pub use graph_craft::document::value::RenderOutput;
use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType;
pub use graph_craft::wasm_application_io::*;
#[cfg(target_arch = "wasm32")]
use graphene_core::application_io::SurfaceHandle;
@@ -7,6 +8,7 @@ use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::Image;
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint;
use graphene_core::Node;
@@ -16,6 +18,7 @@ use graphene_core::{Color, WasmNotSend};
use base64::Engine;
#[cfg(target_arch = "wasm32")]
use glam::DAffine2;
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::Clamped;
@@ -86,7 +89,7 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
image
}
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput {
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType {
if !data.contains_artboard() && !render_params.hide_artboards {
render.leaf_tag("rect", |attributes| {
attributes.push("x", "0");
@@ -102,42 +105,43 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
}
data.render_svg(&mut render, &render_params);
render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2()));
RenderOutput::Svg(render.svg.to_svg_string())
RenderOutputType::Svg(render.svg.to_svg_string())
}
#[cfg(feature = "vello")]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutput {
async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutputType {
use graphene_core::SurfaceFrame;
if let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() {
use vello::*;
let footprint = render_config.viewport;
let mut scene = Scene::new();
let mut child = Scene::new();
let mut context = wgpu_executor::RenderContext::default();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context)
.await
.expect("Failed to render Vello scene");
} else {
let footprint = render_config.viewport;
let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else {
unreachable!("Attempted to render with Vello when no GPU executor is available");
}
};
use vello::*;
let mut scene = Scene::new();
let mut child = Scene::new();
let mut context = wgpu_executor::RenderContext::default();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context)
.await
.expect("Failed to render Vello scene");
let frame = SurfaceFrame {
surface_id: surface_handle.window_id,
resolution: render_config.viewport.resolution,
transform: glam::DAffine2::IDENTITY,
};
RenderOutput::CanvasFrame(frame)
RenderOutputType::CanvasFrame(frame)
}
#[cfg(target_arch = "wasm32")]
@@ -225,13 +229,23 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata {
footprints: HashMap::new(),
click_targets: HashMap::new(),
vector_data: HashMap::new(),
};
data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format;
match output_format {
let data = match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
ExportFormat::Canvas => {
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
return render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await;
return RenderOutput {
data: render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await,
metadata,
};
#[cfg(not(all(feature = "vello", target_arch = "wasm32")))]
render_svg(data, SvgRender::new(), render_params, footprint)
} else {
@@ -239,5 +253,6 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
}
}
_ => todo!("Non-SVG render output for {output_format:?}"),
}
};
RenderOutput { data, metadata }
}