Make Brush tool use per-stroke options and improve its performance (#1242)

* Laid groundwork for per-stroke brush parameters.

* Added new spacing parameter.

* Added back interpolation, using spacing parameter.

* Move bounding box code into core.

* Initial working prototype of per-stroke styles.

* Removed now useless brush node properties.

* Made default spacing 50% for performance comparison.

* Quick and dirty prototype for BlitNode copied from blend.

* Fixed error after rebase.

* Optimized the blitting loop.

* Pretty big optimization for into_flat_u8.

* Insert brush node for images

* Fix starting position transform

* UX polish

* Code review nits

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Orson Peters
2023-05-27 22:55:49 +02:00
committed by Keavon Chambers
co-authored by 0hypercube Keavon Chambers
parent 0586d52f3a
commit 7148b199ec
15 changed files with 491 additions and 337 deletions
@@ -1,6 +1,7 @@
use crate::messages::prelude::*;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::ManipulatorPointId;
@@ -46,6 +47,10 @@ pub enum GraphOperationMessage {
layer: LayerIdentifier,
modification: VectorDataModification,
},
Brush {
layer: LayerIdentifier,
strokes: Vec<BrushStroke>,
},
}
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
@@ -5,12 +5,13 @@ use document_legacy::document::Document;
use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Stroke};
use transform_utils::LayerBounds;
use glam::{DAffine2, DVec2};
mod transform_utils;
pub mod transform_utils;
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler;
@@ -220,6 +221,16 @@ impl<'a> ModifyInputsContext<'a> {
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
}
fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
self.modify_inputs("Brush", false, |inputs| {
if matches!(inputs[0], NodeInput::Node { .. }) {
inputs[1] = core::mem::replace(&mut inputs[0], NodeInput::value(TaggedValue::None, false));
}
inputs[0] = NodeInput::value(TaggedValue::None, false);
inputs[3] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
});
}
}
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
@@ -244,7 +255,6 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
responses.add(Operation::SetLayerStroke { path: layer, stroke });
}
}
GraphOperationMessage::TransformChange {
layer,
transform,
@@ -298,12 +308,16 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
let pivot = pivot.into();
responses.add(Operation::SetPivot { layer_path: layer, pivot });
}
GraphOperationMessage::Vector { layer, modification } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.vector_modify(modification);
}
}
GraphOperationMessage::Brush { layer, strokes } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.brush_modify(strokes);
}
}
}
}
@@ -639,17 +639,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentInputType::value("None", TaggedValue::None, false),
DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Bounds", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Trace", TaggedValue::VecDVec2((0..2).map(|x| DVec2::new(x as f64 * 10., 0.)).collect()), true),
DocumentInputType::value("Diameter", TaggedValue::F64(40.), false),
DocumentInputType::value("Hardness", TaggedValue::F64(50.), false),
DocumentInputType::value("Flow", TaggedValue::F64(100.), false),
DocumentInputType::value("Color", TaggedValue::Color(Color::BLACK), false),
DocumentInputType::value("Trace", TaggedValue::BrushStrokes(Vec::new()), false),
],
outputs: vec![DocumentOutputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
}],
properties: node_properties::brush_node_properties,
properties: node_properties::no_properties,
},
DocumentNodeType {
name: "Extract Vector Points",
@@ -1,21 +1,17 @@
use super::document_node_types::NodePropertiesContext;
use super::FrontendGraphDataType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::Operation;
use glam::{DVec2, IVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
use graphene_core::text::Font;
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
use graphene_core::{Cow, Type, TypeDescriptor};
use super::document_node_types::NodePropertiesContext;
use super::FrontendGraphDataType;
use glam::{DVec2, IVec2};
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
let widget = WidgetHolder::text_widget(text);
@@ -129,7 +125,7 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
widgets
}
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, unit: &str, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, false);
assist(&mut widgets);
@@ -143,13 +139,13 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
WidgetHolder::unrelated_separator(),
NumberInput::new(Some(vec2.x))
.label(x)
.unit(" px")
.unit(unit)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index))
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(vec2.y))
.label(y)
.unit(" px")
.unit(unit)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index))
.widget_holder(),
]);
@@ -165,14 +161,14 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
NumberInput::new(Some(vec2.x as f64))
.int()
.label(x)
.unit(" px")
.unit(unit)
.on_update(update_value(update_x, node_id, index))
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(vec2.y as f64))
.int()
.label(y)
.unit(" px")
.unit(unit)
.on_update(update_value(update_y, node_id, index))
.widget_holder(),
]);
@@ -707,16 +703,6 @@ pub fn blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _con
vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }]
}
pub fn brush_node_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let color = color_widget(document_node, node_id, 7, "Color", ColorInput::default().allow_none(false), true);
let size = number_widget(document_node, node_id, 4, "Diameter", NumberInput::default().min(1.).max(100.).unit(" px"), true);
let hardness = number_widget(document_node, node_id, 5, "Hardness", NumberInput::default().min(0.).max(100.).unit("%"), true);
let flow = number_widget(document_node, node_id, 6, "Flow", NumberInput::default().min(1.).max(100.).unit("%"), true);
vec![color, LayoutGroup::Row { widgets: size }, LayoutGroup::Row { widgets: hardness }, LayoutGroup::Row { widgets: flow }]
}
pub fn adjust_threshold_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let thereshold_min = number_widget(document_node, node_id, 1, "Min Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
let thereshold_max = number_widget(document_node, node_id, 2, "Max Luminance", NumberInput::default().min(0.).max(100.).unit("%"), true);
@@ -945,7 +931,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
add_blank_assist(widgets);
}
};
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", translation_assist);
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", translation_assist);
let rotation = {
let index = 2;
@@ -972,7 +958,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
LayoutGroup::Row { widgets }
};
let scale = vec2_widget(document_node, node_id, 3, "Scale", "X", "Y", add_blank_assist);
let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", add_blank_assist);
vec![translation, rotation, scale]
}
@@ -1658,8 +1644,8 @@ pub fn layer_properties(document_node: &DocumentNode, node_id: NodeId, _context:
]
}
pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", add_blank_assist);
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", add_blank_assist);
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", " px", add_blank_assist);
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", " px", add_blank_assist);
let background = color_widget(document_node, node_id, 3, "Background", ColorInput::default().allow_none(false), true);
vec![location, dimensions, background]
}