Skip to content

Commit 2f4aef3

Browse files
authored
Add Table<Color> as a graphical type (#3033)
* Reduce code duplication in bounding box impls on Table * Working Table<Color> rendering in the graph * Implement color and fix other rendering with Vello and polish
1 parent 81abfe1 commit 2f4aef3

File tree

24 files changed

+458
-194
lines changed

24 files changed

+458
-194
lines changed

editor/src/messages/portfolio/document/node_graph/utility_types.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@ use std::borrow::Cow;
88
pub enum FrontendGraphDataType {
99
#[default]
1010
General,
11-
Raster,
12-
Vector,
1311
Number,
14-
Graphic,
1512
Artboard,
13+
Graphic,
14+
Raster,
15+
Vector,
16+
Color,
1617
}
1718

1819
impl FrontendGraphDataType {
1920
pub fn from_type(input: &Type) -> Self {
2021
match TaggedValue::from_type_or_none(input) {
21-
TaggedValue::Raster(_) => Self::Raster,
22-
TaggedValue::Vector(_) => Self::Vector,
2322
TaggedValue::U32(_)
2423
| TaggedValue::U64(_)
2524
| TaggedValue::F64(_)
@@ -28,8 +27,11 @@ impl FrontendGraphDataType {
2827
| TaggedValue::VecF64(_)
2928
| TaggedValue::VecDVec2(_)
3029
| TaggedValue::DAffine2(_) => Self::Number,
31-
TaggedValue::Graphic(_) => Self::Graphic,
3230
TaggedValue::Artboard(_) => Self::Artboard,
31+
TaggedValue::Graphic(_) => Self::Graphic,
32+
TaggedValue::Raster(_) => Self::Raster,
33+
TaggedValue::Vector(_) => Self::Vector,
34+
TaggedValue::ColorTable(_) | TaggedValue::Color(_) | TaggedValue::OptionalColor(_) => Self::Color,
3335
_ => Self::General,
3436
}
3537
}

editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl TableRowLayout for Graphic {
158158
Self::Vector(vector) => vector.identifier(),
159159
Self::RasterCPU(_) => "Raster (on CPU)".to_string(),
160160
Self::RasterGPU(_) => "Raster (on GPU)".to_string(),
161+
Self::Color(_) => "Color".to_string(),
161162
}
162163
}
163164
// Don't put a breadcrumb for Graphic
@@ -170,6 +171,12 @@ impl TableRowLayout for Graphic {
170171
Self::Vector(table) => table.layout_with_breadcrumb(data),
171172
Self::RasterCPU(_) => label("Raster is not supported"),
172173
Self::RasterGPU(_) => label("Raster is not supported"),
174+
Self::Color(color) => {
175+
let rows = vec![vec![
176+
TextLabel::new(format!("Colors:\n{}", color.iter().map(|color| color.element.to_rgba_hex_srgb()).collect::<Vec<_>>().join("\n"))).widget_holder(),
177+
]];
178+
vec![LayoutGroup::Table { rows }]
179+
}
173180
}
174181
}
175182
}

editor/src/node_graph_executor.rs

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use graphene_std::text::FontCache;
1515
use graphene_std::transform::Footprint;
1616
use graphene_std::vector::Vector;
1717
use graphene_std::vector::style::ViewMode;
18+
use graphene_std::wasm_application_io::RenderOutputType;
1819
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
1920

2021
mod runtime_io;
@@ -33,7 +34,7 @@ pub struct ExecutionResponse {
3334
execution_id: u64,
3435
result: Result<TaggedValue, String>,
3536
responses: VecDeque<FrontendMessage>,
36-
transform: DAffine2,
37+
footprint: Footprint,
3738
vector_modify: HashMap<NodeId, Vector>,
3839
/// The resulting value from the temporary inspected during execution
3940
inspect_result: Option<InspectResult>,
@@ -223,7 +224,7 @@ impl NodeGraphExecutor {
223224

224225
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
225226
let TaggedValue::RenderOutput(RenderOutput {
226-
data: graphene_std::wasm_application_io::RenderOutputType::Svg { svg, .. },
227+
data: RenderOutputType::Svg { svg, .. },
227228
..
228229
}) = node_graph_output
229230
else {
@@ -263,7 +264,7 @@ impl NodeGraphExecutor {
263264
execution_id,
264265
result,
265266
responses: existing_responses,
266-
transform,
267+
footprint,
267268
vector_modify,
268269
inspect_result,
269270
} = execution_response;
@@ -286,9 +287,9 @@ impl NodeGraphExecutor {
286287
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
287288
if let Some(export_config) = execution_context.export_config {
288289
// Special handling for exporting the artwork
289-
self.export(node_graph_output, export_config, responses)?
290+
self.export(node_graph_output, export_config, responses)?;
290291
} else {
291-
self.process_node_graph_output(node_graph_output, transform, responses)?
292+
self.process_node_graph_output(node_graph_output, footprint, responses)?;
292293
}
293294
responses.add_front(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
294295

@@ -332,12 +333,12 @@ impl NodeGraphExecutor {
332333
Ok(())
333334
}
334335

335-
fn debug_render(render_object: impl Render, transform: DAffine2, responses: &mut VecDeque<Message>) {
336+
fn debug_render(render_object: impl Render, footprint: Footprint, responses: &mut VecDeque<Message>) {
336337
// Setup rendering
337338
let mut render = SvgRender::new();
338339
let render_params = RenderParams {
339340
view_mode: ViewMode::Normal,
340-
culling_bounds: None,
341+
footprint,
341342
thumbnail: false,
342343
hide_artboards: false,
343344
for_export: false,
@@ -349,24 +350,25 @@ impl NodeGraphExecutor {
349350
render_object.render_svg(&mut render, &render_params);
350351

351352
// Concatenate the defs and the SVG into one string
352-
render.wrap_with_transform(transform, None);
353+
render.wrap_with_transform(footprint.transform, None);
353354
let svg = render.svg.to_svg_string();
354355

355356
// Send to frontend
356357
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
357358
}
358359

359-
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
360+
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, footprint: Footprint, responses: &mut VecDeque<Message>) -> Result<(), String> {
360361
let mut render_output_metadata = RenderMetadata::default();
362+
361363
match node_graph_output {
362364
TaggedValue::RenderOutput(render_output) => {
363365
match render_output.data {
364-
graphene_std::wasm_application_io::RenderOutputType::Svg { svg, image_data } => {
366+
RenderOutputType::Svg { svg, image_data } => {
365367
// Send to frontend
366368
responses.add(FrontendMessage::UpdateImageData { image_data });
367369
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
368370
}
369-
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
371+
RenderOutputType::CanvasFrame(frame) => {
370372
let matrix = format_transform_matrix(frame.transform);
371373
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
372374
let svg = format!(
@@ -375,29 +377,23 @@ impl NodeGraphExecutor {
375377
);
376378
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
377379
}
378-
graphene_std::wasm_application_io::RenderOutputType::Texture { .. } => {}
379-
_ => {
380-
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
381-
}
380+
RenderOutputType::Texture { .. } => {}
381+
_ => return Err(format!("Invalid node graph output type: {:#?}", render_output.data)),
382382
}
383383

384384
render_output_metadata = render_output.metadata;
385385
}
386-
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
387-
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
388-
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
389-
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
390-
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
391-
TaggedValue::Vector(render_object) => Self::debug_render(render_object, transform, responses),
392-
TaggedValue::Graphic(render_object) => Self::debug_render(render_object, transform, responses),
393-
TaggedValue::Raster(render_object) => Self::debug_render(render_object, transform, responses),
394-
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
395-
_ => {
396-
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
397-
}
386+
TaggedValue::Bool(render_object) => Self::debug_render(render_object, footprint, responses),
387+
TaggedValue::F64(render_object) => Self::debug_render(render_object, footprint, responses),
388+
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, footprint, responses),
389+
TaggedValue::String(render_object) => Self::debug_render(render_object, footprint, responses),
390+
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, footprint, responses),
391+
TaggedValue::Palette(render_object) => Self::debug_render(render_object, footprint, responses),
392+
_ => return Err(format!("Invalid node graph output type: {node_graph_output:#?}")),
398393
};
394+
399395
let graphene_std::renderer::RenderMetadata {
400-
upstream_footprints: footprints,
396+
upstream_footprints,
401397
local_transforms,
402398
first_element_source_id,
403399
click_targets,
@@ -406,7 +402,7 @@ impl NodeGraphExecutor {
406402

407403
// Run these update state messages immediately
408404
responses.add(DocumentMessage::UpdateUpstreamTransforms {
409-
upstream_footprints: footprints,
405+
upstream_footprints,
410406
local_transforms,
411407
first_element_source_id,
412408
});

editor/src/node_graph_executor/runtime.rs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use graph_craft::proto::GraphErrors;
88
use graph_craft::wasm_application_io::EditorPreferences;
99
use graph_craft::{ProtoNodeIdentifier, concrete};
1010
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
11+
use graphene_std::bounds::RenderBoundingBox;
1112
use graphene_std::memo::IORecord;
1213
use graphene_std::renderer::{Render, RenderParams, SvgRender};
1314
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
1415
use graphene_std::table::{Table, TableRow};
1516
use graphene_std::text::FontCache;
17+
use graphene_std::transform::RenderQuality;
1618
use graphene_std::vector::Vector;
1719
use graphene_std::vector::style::ViewMode;
1820
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
@@ -202,8 +204,6 @@ impl NodeRuntime {
202204
});
203205
}
204206
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
205-
let transform = render_config.viewport.transform;
206-
207207
let result = self.execute_network(render_config).await;
208208
let mut responses = VecDeque::new();
209209
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
@@ -227,7 +227,7 @@ impl NodeRuntime {
227227
execution_id,
228228
result,
229229
responses,
230-
transform,
230+
footprint: render_config.viewport,
231231
vector_modify: self.vector_modify.clone(),
232232
inspect_result,
233233
});
@@ -292,51 +292,49 @@ impl NodeRuntime {
292292
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
293293
continue;
294294
}
295+
295296
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
296297
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
297298
warn!("Monitor node has invalid node id");
298-
299299
continue;
300300
};
301301

302-
// Extract the monitor node's stored `Graphic` data.
302+
// Extract the monitor node's stored `Graphic` data
303303
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
304304
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
305305
#[cfg(debug_assertions)]
306306
warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err());
307-
308307
continue;
309308
};
310309

310+
// Graphic table: thumbnail
311311
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
312-
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
313-
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
314-
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
315-
// Insert the vector modify if we are dealing with vector data
316-
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
312+
if update_thumbnails {
313+
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
314+
}
315+
}
316+
// Artboard table: thumbnail
317+
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
318+
if update_thumbnails {
319+
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
320+
}
321+
}
322+
// Vector table: vector modifications
323+
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
324+
// Insert the vector modify
317325
let default = TableRow::default();
318326
self.vector_modify
319-
.insert(parent_network_node_id, record.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
320-
} else {
327+
.insert(parent_network_node_id, io.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
328+
}
329+
// Other
330+
else {
321331
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
322332
}
323333
}
324334
}
325335

326-
// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
327-
fn process_graphic(
328-
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
329-
parent_network_node_id: NodeId,
330-
graphic: &impl Render,
331-
responses: &mut VecDeque<FrontendMessage>,
332-
update_thumbnails: bool,
333-
) {
334-
// RENDER THUMBNAIL
335-
336-
if !update_thumbnails {
337-
return;
338-
}
339-
336+
/// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
337+
fn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque<FrontendMessage>) {
340338
// Skip thumbnails if the layer is too complex (for performance)
341339
if graphic.render_complexity() > 1000 {
342340
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
@@ -349,12 +347,21 @@ impl NodeRuntime {
349347
return;
350348
}
351349

352-
let bounds = graphic.bounding_box(DAffine2::IDENTITY, true);
350+
let bounds = match graphic.bounding_box(DAffine2::IDENTITY, true) {
351+
RenderBoundingBox::None => return,
352+
RenderBoundingBox::Infinite => [DVec2::ZERO, DVec2::new(300., 200.)],
353+
RenderBoundingBox::Rectangle(bounds) => bounds,
354+
};
355+
let footprint = Footprint {
356+
transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)),
357+
resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32),
358+
quality: RenderQuality::Full,
359+
};
353360

354361
// Render the thumbnail from a `Graphic` into an SVG string
355362
let render_params = RenderParams {
356363
view_mode: ViewMode::Normal,
357-
culling_bounds: bounds,
364+
footprint,
358365
thumbnail: true,
359366
hide_artboards: false,
360367
for_export: false,
@@ -365,8 +372,7 @@ impl NodeRuntime {
365372
graphic.render_svg(&mut render, &render_params);
366373

367374
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
368-
let [min, max] = bounds.unwrap_or_default();
369-
render.format_svg(min, max);
375+
render.format_svg(bounds[0], bounds[1]);
370376

371377
// UPDATE FRONTEND THUMBNAIL
372378

frontend/src/components/Editor.svelte

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,18 @@
113113
114114
--color-data-general: #cfcfcf;
115115
--color-data-general-dim: #8a8a8a;
116+
--color-data-number: #c9a699;
117+
--color-data-number-dim: #886b60;
118+
--color-data-artboard: #fbf9eb;
119+
--color-data-artboard-dim: #b9b9a9;
120+
--color-data-graphic: #66b195;
121+
--color-data-graphic-dim: #3d725e;
116122
--color-data-raster: #e4bb72;
117123
--color-data-raster-dim: #8b7752;
118124
--color-data-vector: #65bbe5;
119-
--color-data-vector-dim: #4b778c;
120-
--color-data-graphic: #66b195;
121-
--color-data-graphic-dim: #3d725e;
122-
--color-data-artboard: #fbf9eb;
123-
--color-data-artboard-dim: #b9b9a9;
124-
--color-data-number: #c9a699;
125-
--color-data-number-dim: #886b60;
125+
--color-data-vector-dim: #417892;
126+
--color-data-color: #af81eb;
127+
--color-data-color-dim: #6c489b;
126128
127129
--color-none: white;
128130
--color-none-repeat: no-repeat;

frontend/src/messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export type ContextMenuInformation = {
192192
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
193193
};
194194

195-
export type FrontendGraphDataType = "General" | "Raster" | "Vector" | "Number" | "Graphic" | "Artboard";
195+
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color";
196196

197197
export class Node {
198198
readonly index!: bigint;

node-graph/gbrush/src/brush.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::brush_cache::BrushCache;
22
use crate::brush_stroke::{BrushStroke, BrushStyle};
33
use glam::{DAffine2, DVec2};
44
use graphene_core::blending::BlendMode;
5-
use graphene_core::bounds::BoundingBox;
5+
use graphene_core::bounds::{BoundingBox, RenderBoundingBox};
66
use graphene_core::color::{Alpha, Color, Pixel, Sample};
77
use graphene_core::generic::FnNode;
88
use graphene_core::math::bbox::{AxisAlignedBbox, Bbox};
@@ -186,7 +186,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
186186
// TODO: Find a way to handle more than one row
187187
let table_row = image_frame_table.iter().next().expect("Expected the one row we just pushed").into_cloned();
188188

189-
let [start, end] = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
189+
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
190+
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
190191
let image_bbox = AxisAlignedBbox { start, end };
191192
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
192193
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };

0 commit comments

Comments
 (0)