Implement the Brush without relying on a stamp texture
Test Plan: Test the BrushNode in the editor Reviewers: Keavon Reviewed By: Keavon Pull Request: https://github.com/GraphiteEditor/Graphite/pull/1184
This commit is contained in:
parent
5d9c0cb4d5
commit
1020eb6835
|
|
@ -232,9 +232,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.16"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "113713495a32dd0ab52baf5c10044725aa3aec00b31beda84218e469029b72a3"
|
||||
checksum = "b70caf9f1b0c045f7da350636435b775a9733adf2df56e8aa2a29210fbc335d4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
|
|
@ -1122,12 +1122,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.6.2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2266,8 +2266,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.9.3"
|
||||
source = "git+https://github.com/linebender/kurbo.git#615ed7ede2d80ae0f3509165c5a2dee6cef69251"
|
||||
version = "0.9.4"
|
||||
source = "git+https://github.com/linebender/kurbo.git#01b52cd85c3a9b1be2c6e39ab97fcf401a8911c4"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"serde",
|
||||
|
|
@ -2342,9 +2342,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.4"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
|
||||
checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
|
|
@ -2493,15 +2493,6 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
|
@ -2864,9 +2855,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.51"
|
||||
version = "0.10.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3"
|
||||
checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
|
|
@ -2896,9 +2887,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.86"
|
||||
version = "0.9.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69"
|
||||
checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
|
@ -3169,7 +3160,7 @@ dependencies = [
|
|||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.7.1",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3457,9 +3448,9 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
||||
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
|
|
@ -3564,9 +3555,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.14"
|
||||
version = "0.37.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
|
||||
checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
|
@ -4319,9 +4310,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.6"
|
||||
version = "0.12.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
|
||||
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
|
|
@ -4670,9 +4661,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.27.0"
|
||||
version = "1.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
||||
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
|
|
@ -4684,14 +4675,14 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -4721,9 +4712,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
|
|
@ -4806,11 +4797,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
|||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
|
|
@ -4819,13 +4809,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "9725ddbfe40183
|
|||
] }
|
||||
xxhash-rust = { version = "0.8.4", features = ["xxh3"] }
|
||||
|
||||
|
||||
[profile.dev.package.graphite-editor]
|
||||
opt-level = 1
|
||||
|
||||
|
|
@ -54,6 +53,9 @@ opt-level = 3
|
|||
[profile.dev.package.image]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.png]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.xxhash-rust]
|
||||
opt-level = 3
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct ModifyInputsContext<'a> {
|
|||
impl<'a> ModifyInputsContext<'a> {
|
||||
/// Get the node network from the document
|
||||
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
|
||||
document.layer_mut(&layer).ok().and_then(|layer| layer.as_node_graph_mut().ok()).map(|network| Self {
|
||||
document.layer_mut(layer).ok().and_then(|layer| layer.as_node_graph_mut().ok()).map(|network| Self {
|
||||
network,
|
||||
node_graph,
|
||||
responses,
|
||||
|
|
|
|||
|
|
@ -1048,19 +1048,19 @@ pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
|||
let nodes = vec![
|
||||
resolve_document_node_type("Begin Scope")
|
||||
.expect("Begin Scope node type not found")
|
||||
.to_document_node(vec![input_type.clone()], DocumentNodeMetadata::default()),
|
||||
.to_document_node(vec![input_type], DocumentNodeMetadata::default()),
|
||||
inner_network,
|
||||
resolve_document_node_type("End Scope")
|
||||
.expect("End Scope node type not found")
|
||||
.to_document_node(vec![NodeInput::node(0, 0), NodeInput::node(1, 0)], DocumentNodeMetadata::default()),
|
||||
];
|
||||
let network = NodeNetwork {
|
||||
|
||||
NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(2, 0)],
|
||||
nodes: nodes.into_iter().enumerate().map(|(id, node)| (id as NodeId, node)).collect(),
|
||||
..Default::default()
|
||||
};
|
||||
network
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
|
||||
|
|
@ -1121,8 +1121,8 @@ pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
|
|||
text_generator.to_document_node(
|
||||
[
|
||||
NodeInput::Network(concrete!(graphene_core::EditorApi)),
|
||||
NodeInput::value(TaggedValue::String(text.clone()), false),
|
||||
NodeInput::value(TaggedValue::Font(font.clone()), false),
|
||||
NodeInput::value(TaggedValue::String(text), false),
|
||||
NodeInput::value(TaggedValue::Font(font), false),
|
||||
NodeInput::value(TaggedValue::F64(size), false),
|
||||
],
|
||||
DocumentNodeMetadata::position((0, 4)),
|
||||
|
|
|
|||
|
|
@ -149,10 +149,10 @@ impl ShapeState {
|
|||
/// Move the selected points by dragging the mouse.
|
||||
pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for (layer_path, state) in &self.selected_shape_state {
|
||||
let Ok(layer) = document.layer(&layer_path) else { continue };
|
||||
let Ok(layer) = document.layer(layer_path) else { continue };
|
||||
let Some(vector_data) = layer.as_vector_data() else { continue };
|
||||
|
||||
let transform = document.multiply_transforms(&layer_path).unwrap_or_default();
|
||||
let transform = document.multiply_transforms(layer_path).unwrap_or_default();
|
||||
let delta = transform.inverse().transform_vector2(delta);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, response
|
|||
};
|
||||
let mut network = NodeNetwork::value_network(brush_node);
|
||||
network.push_output_node();
|
||||
graph_modification_utils::new_custom_layer(network, layer_path.clone(), responses);
|
||||
graph_modification_utils::new_custom_layer(network, layer_path, responses);
|
||||
}
|
||||
|
||||
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>)> {
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses
|
|||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_data.primary_color, data.weight),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ impl Fsm for LineToolFsmState {
|
|||
tool_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -273,11 +273,9 @@ impl Fsm for PathToolFsmState {
|
|||
if tool_data.opposing_handle_lengths.is_none() {
|
||||
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
}
|
||||
} else {
|
||||
if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
}
|
||||
} else if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
}
|
||||
|
||||
// Move the selected points by the mouse position
|
||||
|
|
@ -291,8 +289,7 @@ impl Fsm for PathToolFsmState {
|
|||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point)
|
||||
.clone();
|
||||
.map(|(_, nearest_point)| nearest_point);
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ impl PenToolData {
|
|||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
|
||||
return Some(DocumentMessage::CommitTransaction);
|
||||
Some(DocumentMessage::CommitTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -762,7 +762,7 @@ impl Fsm for SelectToolFsmState {
|
|||
if let Some(path) = intersection.last() {
|
||||
// let folders: Vec<_> = (1..path.len() + 1).map(|i| &path[0..i]).collect();
|
||||
// let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !folders.contains(&layer)).map(|path| path.to_vec()).collect();
|
||||
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(&layer)).map(|path| path.to_vec()).collect();
|
||||
let replacement_selected_layers: Vec<_> = document.selected_layers().filter(|&layer| !path.starts_with(layer)).map(|path| path.to_vec()).collect();
|
||||
|
||||
tool_data.layers_dragging.clear();
|
||||
tool_data.layers_dragging.append(replacement_selected_layers.clone().as_mut());
|
||||
|
|
@ -999,7 +999,7 @@ fn drag_shallowest_manipulation(
|
|||
// Checks if the incoming layer's root parent is already selected
|
||||
// If so we need to update the selected layer to the deeper of the two
|
||||
let mut layers_without_incoming_parent: Vec<Vec<u64>> = document.selected_layers().filter(|&layer| layer != [incoming_parent].as_slice()).map(|path| path.to_vec()).collect();
|
||||
if layers.contains(&&[incoming_parent].as_slice()) {
|
||||
if layers.contains(&[incoming_parent].as_slice()) {
|
||||
// Add incoming layer
|
||||
tool_data.layers_dragging.clear();
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
|
@ -1028,7 +1028,7 @@ fn drag_shallowest_manipulation(
|
|||
|
||||
// Check if the intersected layer path is already selected
|
||||
let previous_parents: Vec<_> = (0..layers.len()).map(|i| &layers.get(i).unwrap()[..1]).collect();
|
||||
let already_selected_parent = previous_parents.contains(&&[incoming_parent].as_slice());
|
||||
let already_selected_parent = previous_parents.contains(&[incoming_parent].as_slice());
|
||||
|
||||
let selected_layers: Vec<_> = document.selected_layers().collect();
|
||||
let mut search = previous_layer_path.to_vec();
|
||||
|
|
@ -1042,7 +1042,7 @@ fn drag_shallowest_manipulation(
|
|||
selected_layer_path_parent = selected_layer_path_parent[..selected_layer_path_parent.len() - 1].to_vec();
|
||||
}
|
||||
|
||||
while selected_layer_path_parent.len() > 0 && !is_parent && !recursive_found {
|
||||
while !selected_layer_path_parent.is_empty() && !is_parent && !recursive_found {
|
||||
let selected_children_layer_paths = document.document_legacy.folder_children_paths(&selected_layer_path_parent);
|
||||
for child in selected_children_layer_paths {
|
||||
if child == *incoming_layer_path_vector {
|
||||
|
|
@ -1062,7 +1062,7 @@ fn drag_shallowest_manipulation(
|
|||
|
||||
// Check if new layer is already selected
|
||||
let mut already_selected = false;
|
||||
if selected_layers.contains(&search.clone().as_slice()) {
|
||||
if selected_layers.contains(&search.as_slice()) {
|
||||
already_selected = true;
|
||||
}
|
||||
|
||||
|
|
@ -1086,7 +1086,7 @@ fn drag_shallowest_manipulation(
|
|||
} else {
|
||||
// Previous selected layers with the intersect layer path appended to it
|
||||
let mut combined_layers = selected_layers.clone();
|
||||
let intersection_temp = intersection.clone();
|
||||
let intersection_temp = intersection;
|
||||
let intersection_temp_slice = intersection_temp.as_slice();
|
||||
combined_layers.push(intersection_temp_slice);
|
||||
let layers_iter = combined_layers.into_iter();
|
||||
|
|
@ -1162,7 +1162,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, interse
|
|||
let incoming_parent = *intersect_layer_path.first().unwrap();
|
||||
let previous_parents: Vec<_> = (0..selected_layers.len()).map(|i| &selected_layers.get(i).unwrap()[..1]).collect();
|
||||
let mut incoming_parent_selected = false;
|
||||
if previous_parents.contains(&&[incoming_parent].as_slice()) {
|
||||
if previous_parents.contains(&[incoming_parent].as_slice()) {
|
||||
incoming_parent_selected = true;
|
||||
}
|
||||
if incoming_parent_selected {
|
||||
|
|
@ -1225,11 +1225,9 @@ fn recursive_search(document: &DocumentMessageHandler, layer_path: &Vec<u64>, in
|
|||
for path in layer_paths {
|
||||
if path == *incoming_layer_path_vector {
|
||||
return true;
|
||||
} else if document.document_legacy.is_folder(path.clone()) {
|
||||
if recursive_search(document, &path, incoming_layer_path_vector) {
|
||||
return true;
|
||||
}
|
||||
} else if document.document_legacy.is_folder(path.clone()) && recursive_search(document, &path, incoming_layer_path_vector) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ impl TextToolData {
|
|||
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let network = new_text_network(String::new(), editing_text.font.clone(), editing_text.font_size as f64);
|
||||
let network = new_text_network(String::new(), editing_text.font.clone(), editing_text.font_size);
|
||||
|
||||
responses.add(Operation::AddFrame {
|
||||
path: self.layer_path.clone(),
|
||||
|
|
@ -320,7 +320,7 @@ impl TextToolData {
|
|||
resize_overlays(&mut self.overlays, responses, 1);
|
||||
|
||||
let editing_text = self.editing_text.as_ref()?;
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(&data));
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||
let far = graphene_core::text::bounding_box(&self.new_text, buzz_face, editing_text.font_size, None);
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
|
||||
|
|
@ -337,8 +337,8 @@ impl TextToolData {
|
|||
|
||||
fn get_bounds(&self, text: &str, render_data: &RenderData) -> Option<[DVec2; 2]> {
|
||||
let editing_text = self.editing_text.as_ref()?;
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(&data));
|
||||
let subpaths = graphene_core::text::to_path(&text, buzz_face, editing_text.font_size, None);
|
||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||
let subpaths = graphene_core::text::to_path(text, buzz_face, editing_text.font_size, None);
|
||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box());
|
||||
let combined_bounds = bounds.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]).unwrap_or_default();
|
||||
Some(combined_bounds)
|
||||
|
|
@ -383,8 +383,8 @@ fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolDa
|
|||
let node_id = get_text_node_id(node_graph)?;
|
||||
let document_node = node_graph.nodes.get(&node_id)?;
|
||||
let (text, font, font_size) = TextToolData::extract_text_node_inputs(document_node)?;
|
||||
let buzz_face = render_data.font_cache.get(font).map(|data| load_face(&data));
|
||||
let far = graphene_core::text::bounding_box(&text, buzz_face, font_size, None);
|
||||
let buzz_face = render_data.font_cache.get(font).map(|data| load_face(data));
|
||||
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None);
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let multiplied = document.document_legacy.multiply_transforms(path).ok()? * quad;
|
||||
Some(multiplied.bounding_box())
|
||||
|
|
@ -474,7 +474,7 @@ impl Fsm for TextToolFsmState {
|
|||
tool_data.new_text = String::new();
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
|
||||
tool_data.interact(state, input.mouse.position, document, &render_data, responses)
|
||||
tool_data.interact(state, input.mouse.position, document, render_data, responses)
|
||||
}
|
||||
(state, TextToolMessage::EditSelected) => {
|
||||
if let Some(layer_path) = can_edit_selected(document) {
|
||||
|
|
|
|||
|
|
@ -65,10 +65,10 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
}
|
||||
|
||||
if using_path_tool {
|
||||
if let Ok(layer) = document.document_legacy.layer(&selected_layers[0]) {
|
||||
if let Ok(layer) = document.document_legacy.layer(selected_layers[0]) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
let viewspace = &mut document.document_legacy.generate_transform_relative_to_viewport(&selected_layers[0]).ok().unwrap_or_default();
|
||||
let viewspace = &mut document.document_legacy.generate_transform_relative_to_viewport(selected_layers[0]).ok().unwrap_or_default();
|
||||
|
||||
let mut point_count: usize = 0;
|
||||
let count_point = |position| {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ fn handle_message(message: String) -> String {
|
|||
for image in image_data {
|
||||
let path = image.path.clone();
|
||||
let mime = image.mime.clone();
|
||||
let transform = image.transform.clone();
|
||||
let transform = image.transform;
|
||||
images.insert(format!("{:?}_{}", &image.path, document_id), image);
|
||||
stub_data.push(FrontendImageData {
|
||||
path,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,13 @@ impl<ManipulatorGroupId: crate::Identifier> Hash for ManipulatorGroup<Manipulato
|
|||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.in_handle.is_some().hash(state);
|
||||
self.in_handle.map(|in_handle| in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state)));
|
||||
if let Some(in_handle) = self.in_handle {
|
||||
in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.out_handle.is_some().hash(state);
|
||||
self.out_handle.map(|out_handle| out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state)));
|
||||
if let Some(out_handle) = self.out_handle {
|
||||
out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub struct PushConstants {
|
|||
impl Sample for SampledImage<Image2d> {
|
||||
type Pixel = Color;
|
||||
|
||||
fn sample(&self, pos: glam::DVec2) -> Option<Self::Pixel> {
|
||||
fn sample(&self, pos: glam::DVec2, _area: glam::DVec2) -> Option<Self::Pixel> {
|
||||
let color = self.sample(pos);
|
||||
Color::from_rgbaf32(color.x, color.y, color.z, color.w)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ mod test {
|
|||
let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32));
|
||||
assert_eq!(value.eval(()), Ok(&4u32));
|
||||
//let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
|
||||
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| x.clone())));
|
||||
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| *x)));
|
||||
//et type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
|
||||
assert_eq!(map_result.eval(Ok(&4u32)), Ok(4u32));
|
||||
let fst = value.then(map_result);
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ pub trait Luminance {
|
|||
pub trait Sample {
|
||||
type Pixel: Pixel;
|
||||
// TODO: Add an area parameter
|
||||
fn sample(&self, pos: DVec2) -> Option<Self::Pixel>;
|
||||
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel>;
|
||||
}
|
||||
|
||||
// TODO: We might rename this to Bitmap at some point
|
||||
|
|
|
|||
|
|
@ -715,6 +715,9 @@ impl Color {
|
|||
}
|
||||
|
||||
pub fn to_unassociated_alpha(&self) -> Self {
|
||||
if self.alpha == 0. {
|
||||
return *self;
|
||||
}
|
||||
let unmultiply = 1. / self.alpha;
|
||||
Self {
|
||||
red: self.red * unmultiply,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ mod base64_serde {
|
|||
{
|
||||
use serde::de::Error;
|
||||
|
||||
let color_from_chunk = |chunk: &[u8]| P::from_bytes(chunk.try_into().unwrap()).clone();
|
||||
let color_from_chunk = |chunk: &[u8]| P::from_bytes(chunk.try_into().unwrap());
|
||||
|
||||
let colors_from_bytes = |bytes: Vec<u8>| bytes.chunks_exact(P::byte_size()).map(color_from_chunk).collect();
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ where
|
|||
pub fn into_flat_u8(self) -> (Vec<u8>, u32, u32) {
|
||||
let Image { width, height, data } = self;
|
||||
|
||||
let to_gamma = |x| SRGBGammaFloat::from_linear(x);
|
||||
let to_gamma = SRGBGammaFloat::from_linear;
|
||||
let to_u8 = |x| (num_cast::<_, f32>(x).unwrap() * 255.) as u8;
|
||||
|
||||
let result_bytes = data
|
||||
|
|
@ -201,7 +201,8 @@ pub struct ImageFrame<P: Pixel> {
|
|||
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
type Pixel = P;
|
||||
|
||||
fn sample(&self, pos: DVec2) -> Option<Self::Pixel> {
|
||||
// TODO: Improve sampling logic
|
||||
fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> {
|
||||
let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64);
|
||||
let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos);
|
||||
if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ mod test {
|
|||
fn test_ref_eval() {
|
||||
let value = ValueNode::new(5);
|
||||
|
||||
assert_eq!((&value).eval(()), &5);
|
||||
assert_eq!(value.eval(()), &5);
|
||||
let id = IdNode::new();
|
||||
|
||||
let compose = ComposeNode::new(&value, &id);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub struct IntNode<const N: u32>;
|
|||
|
||||
impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
|
||||
type Output = u32;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
N
|
||||
}
|
||||
|
|
@ -17,6 +18,7 @@ pub struct ValueNode<T>(pub T);
|
|||
|
||||
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
|
||||
type Output = &'i T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
&self.0
|
||||
}
|
||||
|
|
@ -45,6 +47,7 @@ pub struct ClonedNode<T: Clone>(pub T);
|
|||
|
||||
impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
self.0.clone()
|
||||
}
|
||||
|
|
@ -62,11 +65,34 @@ impl<T: Clone> From<T> for ClonedNode<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
/// The DebugClonedNode logs every time it is evaluated.
|
||||
/// This is useful for debugging.
|
||||
pub struct DebugClonedNode<T: Clone>(pub T);
|
||||
|
||||
impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("DebugClonedNode::eval");
|
||||
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> DebugClonedNode<T> {
|
||||
pub const fn new(value: T) -> ClonedNode<T> {
|
||||
ClonedNode(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CopiedNode<T: Copy>(pub T);
|
||||
|
||||
impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
self.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ fn set_vector_data_fill(
|
|||
positions: Vec<(f64, Option<Color>)>,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_fill(match fill_type {
|
||||
FillType::None | FillType::Solid => solid_color.map_or(Fill::None, |solid_color| Fill::Solid(solid_color)),
|
||||
FillType::None | FillType::Solid => solid_color.map_or(Fill::None, Fill::Solid),
|
||||
FillType::Gradient => Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -169,8 +169,7 @@ pub struct UniformNode<Executor> {
|
|||
|
||||
#[node_macro::node_fn(UniformNode)]
|
||||
fn uniform_node<T: ToUniformBuffer, E: GpuExecutor>(data: T, executor: &'any_input E) -> ShaderInput<E::BufferHandle> {
|
||||
let handle = executor.create_uniform_buffer(data).unwrap();
|
||||
handle
|
||||
executor.create_uniform_buffer(data).unwrap()
|
||||
}
|
||||
|
||||
pub struct StorageNode<Executor> {
|
||||
|
|
@ -179,7 +178,7 @@ pub struct StorageNode<Executor> {
|
|||
|
||||
#[node_macro::node_fn(StorageNode)]
|
||||
fn storage_node<T: ToStorageBuffer, E: GpuExecutor>(data: T, executor: &'any_input E) -> ShaderInput<E::BufferHandle> {
|
||||
let handle = executor
|
||||
executor
|
||||
.create_storage_buffer(
|
||||
data,
|
||||
StorageBufferOptions {
|
||||
|
|
@ -188,8 +187,7 @@ fn storage_node<T: ToStorageBuffer, E: GpuExecutor>(data: T, executor: &'any_inp
|
|||
cpu_readable: false,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
handle
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub struct PushNode<Value> {
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ impl NodeNetwork {
|
|||
}
|
||||
|
||||
pub fn input_types<'a>(&'a self) -> impl Iterator<Item = Type> + 'a {
|
||||
self.inputs.iter().map(move |id| self.nodes[id].inputs.get(0).map(|i| i.ty().clone()).unwrap_or(concrete!(())))
|
||||
self.inputs.iter().map(move |id| self.nodes[id].inputs.get(0).map(|i| i.ty()).unwrap_or(concrete!(())))
|
||||
}
|
||||
|
||||
/// An empty graph
|
||||
|
|
@ -500,7 +500,7 @@ impl NodeNetwork {
|
|||
}
|
||||
FlowIter {
|
||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||
network: &self,
|
||||
network: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::raster::{Color, Image, ImageFrame, RasterMut};
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::raster::{Alpha, Color, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_core::Node;
|
||||
use node_macro::node_fn;
|
||||
|
|
@ -73,8 +73,49 @@ fn vector_points(vector: VectorData) -> Vec<DVec2> {
|
|||
vector.subpaths.iter().flat_map(|subpath| subpath.manipulator_groups().iter().map(|group| group.anchor)).collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct BrushStampGenerator<P: Pixel + Alpha> {
|
||||
color: P,
|
||||
feather_exponent: f32,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
impl<P: Pixel + Alpha> Transform for BrushStampGenerator<P> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel + Alpha> TransformMut for BrushStampGenerator<P> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
|
||||
type Pixel = P;
|
||||
|
||||
#[inline]
|
||||
fn sample(&self, position: DVec2, area: DVec2) -> Option<P> {
|
||||
let position = self.transform.inverse().transform_point2(position);
|
||||
let area = self.transform.inverse().transform_vector2(area);
|
||||
let center = DVec2::splat(0.5);
|
||||
|
||||
let distance = (position + area / 2. - center).length() as f32 * 2.;
|
||||
|
||||
let result = if distance < 1. {
|
||||
1. - distance.powf(self.feather_exponent)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
use graphene_core::raster::Channel;
|
||||
Some(self.color.multiplied_alpha(P::AlphaChannel::from_f32(result)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct BrushTextureNode<ColorNode, Hardness, Flow> {
|
||||
pub struct BrushStampGeneratorNode<ColorNode, Hardness, Flow> {
|
||||
pub color: ColorNode,
|
||||
pub hardness: Hardness,
|
||||
pub flow: Flow,
|
||||
|
|
@ -92,17 +133,14 @@ fn erase(input: (Color, Color), flow: f64) -> Color {
|
|||
Color::from_unassociated_alpha(input.r(), input.g(), input.b(), alpha)
|
||||
}
|
||||
|
||||
#[node_fn(BrushTextureNode)]
|
||||
fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> ImageFrame<Color> {
|
||||
#[node_fn(BrushStampGeneratorNode)]
|
||||
fn brush_stamp_generator_node(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
// Diameter
|
||||
let radius = diameter / 2.;
|
||||
// TODO: Remove the 4px padding after figuring out why the brush stamp gets randomly offset by 1px up/down/left/right when clicking with the Brush tool
|
||||
let dimension = diameter.ceil() as u32 + 4;
|
||||
let center = DVec2::splat(radius + (dimension as f64 - diameter) / 2.);
|
||||
|
||||
// Hardness
|
||||
let hardness = hardness / 100.;
|
||||
let feather_exponent = 1. / (1. - hardness);
|
||||
let feather_exponent = 1. / (1. - hardness) as f32;
|
||||
|
||||
// Flow
|
||||
let flow = flow / 100.;
|
||||
|
|
@ -110,33 +148,8 @@ fn brush_texture(diameter: f64, color: Color, hardness: f64, flow: f64) -> Image
|
|||
// Color
|
||||
let color = color.apply_opacity(flow as f32);
|
||||
|
||||
// Initial transparent image
|
||||
let mut image = Image::new(dimension, dimension, Color::TRANSPARENT);
|
||||
|
||||
for y in 0..dimension {
|
||||
for x in 0..dimension {
|
||||
let summation = MULTISAMPLE_GRID.iter().fold(0., |acc, (offset_x, offset_y)| {
|
||||
let position = DVec2::new(x as f64 + offset_x, y as f64 + offset_y);
|
||||
let distance = (position - center).length();
|
||||
|
||||
if distance < radius {
|
||||
acc + (1. - (distance / radius).powf(feather_exponent)).clamp(0., 1.)
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
|
||||
let pixel_fill = summation / MULTISAMPLE_GRID.len() as f64;
|
||||
|
||||
let pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
*pixel = color.apply_opacity(pixel_fill as f32);
|
||||
}
|
||||
}
|
||||
|
||||
ImageFrame {
|
||||
image,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::splat(dimension as f64), 0., -DVec2::splat(radius)),
|
||||
}
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(diameter), 0., -DVec2::splat(radius));
|
||||
BrushStampGenerator { color, feather_exponent, transform }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
@ -183,19 +196,17 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_brush_texture() {
|
||||
let brush_texture_node = BrushTextureNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
||||
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.));
|
||||
let size = 20.;
|
||||
let image = brush_texture_node.eval(size);
|
||||
assert_eq!(image.image.width, size.ceil() as u32 + 4);
|
||||
assert_eq!(image.image.height, size.ceil() as u32 + 4);
|
||||
assert_eq!(image.transform, DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil() + 4.), 0., -DVec2::splat(size / 2.)));
|
||||
assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.)));
|
||||
// center pixel should be BLACK
|
||||
assert_eq!(image.image.get_pixel(11, 11), Some(Color::BLACK));
|
||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_brush() {
|
||||
let brush_texture_node = BrushTextureNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(1.0), ClonedNode::new(1.0));
|
||||
let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(1.0), ClonedNode::new(1.0));
|
||||
let image = brush_texture_node.eval(20.);
|
||||
let trace = vec![DVec2::new(0.0, 0.0), DVec2::new(10.0, 0.0)];
|
||||
let trace = ClonedNode::new(trace.into_iter());
|
||||
|
|
@ -203,7 +214,6 @@ mod test {
|
|||
let frames = MapNode::new(ValueNode::new(translate_node));
|
||||
let frames = trace.then(frames).eval(()).collect::<Vec<_>>();
|
||||
assert_eq!(frames.len(), 2);
|
||||
assert_eq!(frames[0].image.width, 24);
|
||||
let background_bounds = ReduceNode::new(ClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new()));
|
||||
let background_bounds = background_bounds.eval(frames.clone().into_iter());
|
||||
let background_bounds = ClonedNode::new(background_bounds.unwrap().to_transform());
|
||||
|
|
@ -211,8 +221,8 @@ mod test {
|
|||
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(1.0));
|
||||
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
|
||||
let final_image = final_image.eval(frames.into_iter());
|
||||
assert_eq!(final_image.image.height, 24);
|
||||
assert_eq!(final_image.image.width, 34);
|
||||
assert_eq!(final_image.image.height, 20);
|
||||
assert_eq!(final_image.image.width, 30);
|
||||
drop(final_image);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ where
|
|||
|
||||
if let Some((_, cached_value, keep)) = self.cache.iter().find(|(h, _, _)| *h == hash) {
|
||||
keep.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
return cached_value;
|
||||
cached_value
|
||||
} else {
|
||||
trace!("Cache miss");
|
||||
let output = self.node.eval(input);
|
||||
let index = self.cache.push((hash, output, AtomicBool::new(true)));
|
||||
return &self.cache[index].1;
|
||||
&self.cache[index].1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ where
|
|||
|
||||
fn serialize(&self) -> Option<String> {
|
||||
let output = self.output.lock().unwrap();
|
||||
(&*output).as_ref().map(|output| serde_json::to_string(output).ok()).flatten()
|
||||
(*output).as_ref().and_then(|output| serde_json::to_string(output).ok())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ impl<'i, T: 'i + Hash> Node<'i, Option<T>> for LetNode<T> {
|
|||
}
|
||||
trace!("Cache miss");
|
||||
let index = self.cache.push((hash, input));
|
||||
return &self.cache[index].1;
|
||||
&self.cache[index].1
|
||||
}
|
||||
None => &self.cache.iter().last().expect("Let node was not initialized").1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use dyn_any::{DynAny, StaticType};
|
|||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::raster::{Alpha, Channel, Image, ImageFrame, Luminance, Pixel, RasterMut, Sample};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::{ClonedNode, ValueNode};
|
||||
|
||||
use graphene_core::Node;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
|
@ -240,6 +240,7 @@ fn mask_image<
|
|||
// Transforms a point from the background image to the forground image
|
||||
let bg_to_fg = image.transform() * DAffine2::from_scale(1. / image_size);
|
||||
|
||||
let area = bg_to_fg.transform_point2(DVec2::new(1., 1.)) - bg_to_fg.transform_point2(DVec2::ZERO);
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let image_point = DVec2::new(x as f64, y as f64);
|
||||
|
|
@ -247,8 +248,8 @@ fn mask_image<
|
|||
let local_mask_point = stencil.transform().inverse().transform_point2(mask_point);
|
||||
mask_point = stencil.transform().transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
|
||||
|
||||
let image_pixel = image.get_pixel_mut(x as u32, y as u32).unwrap();
|
||||
if let Some(mask_pixel) = stencil.sample(mask_point) {
|
||||
let image_pixel = image.get_pixel_mut(x, y).unwrap();
|
||||
if let Some(mask_pixel) = stencil.sample(mask_point, area) {
|
||||
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().to_channel());
|
||||
}
|
||||
}
|
||||
|
|
@ -258,20 +259,20 @@ fn mask_image<
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlendImageTupleNode<P, MapFn> {
|
||||
pub struct BlendImageTupleNode<P, Fg, MapFn> {
|
||||
map_fn: MapFn,
|
||||
_p: PhantomData<P>,
|
||||
_fg: PhantomData<Fg>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(BlendImageTupleNode<_P>)]
|
||||
fn blend_image_tuple<_P: Pixel + Debug, MapFn>(images: (ImageFrame<_P>, ImageFrame<_P>), map_fn: &'any_input MapFn) -> ImageFrame<_P>
|
||||
#[node_macro::node_fn(BlendImageTupleNode<_P, _Fg>)]
|
||||
fn blend_image_tuple<_P: Pixel + Debug, MapFn, _Fg: Sample<Pixel = _P> + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'any_input MapFn) -> ImageFrame<_P>
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone,
|
||||
{
|
||||
let (background, foreground) = images;
|
||||
|
||||
let node = BlendImageNode::new(ClonedNode::new(background), ValueNode::new(map_fn.clone()));
|
||||
node.eval(foreground)
|
||||
blend_image(foreground, background, map_fn)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -283,13 +284,20 @@ pub struct BlendImageNode<P, Background, MapFn> {
|
|||
|
||||
// TODO: Implement proper blending
|
||||
#[node_macro::node_fn(BlendImageNode<_P>)]
|
||||
fn blend_image<_P: Clone, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform>(
|
||||
fn blend_image_node<_P: Clone, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform>(
|
||||
foreground: Frame,
|
||||
mut background: Background,
|
||||
background: Background,
|
||||
map_fn: &'any_input MapFn,
|
||||
) -> Background
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input,
|
||||
{
|
||||
blend_image(foreground, background, map_fn)
|
||||
}
|
||||
|
||||
fn blend_image<_P: Clone, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: RasterMut<Pixel = _P> + Transform>(foreground: Frame, mut background: Background, map_fn: &MapFn) -> Background
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P>,
|
||||
{
|
||||
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
|
||||
|
||||
|
|
@ -303,12 +311,13 @@ where
|
|||
let start = (bg_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
let end = (bg_aabb.end * background_size).min(background_size).as_uvec2();
|
||||
|
||||
let area = bg_to_fg.transform_point2(DVec2::new(1., 1.)) - bg_to_fg.transform_point2(DVec2::ZERO);
|
||||
for y in start.y..end.y {
|
||||
for x in start.x..end.x {
|
||||
let bg_point = DVec2::new(x as f64, y as f64);
|
||||
let fg_point = bg_to_fg.transform_point2(bg_point);
|
||||
|
||||
if let Some(src_pixel) = foreground.sample(fg_point) {
|
||||
if let Some(src_pixel) = foreground.sample(fg_point, area) {
|
||||
if let Some(dst_pixel) = background.get_pixel_mut(x, y) {
|
||||
*dst_pixel = map_fn.eval((src_pixel, dst_pixel.clone()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,8 +176,13 @@ impl BorrowTree {
|
|||
}
|
||||
|
||||
pub fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), String> {
|
||||
let ProtoNode { construction_args, identifier, .. } = proto_node;
|
||||
self.source_map.insert(proto_node.document_node_path, id);
|
||||
let ProtoNode {
|
||||
construction_args,
|
||||
identifier,
|
||||
document_node_path,
|
||||
..
|
||||
} = proto_node;
|
||||
self.source_map.insert(document_node_path, id);
|
||||
|
||||
match construction_args {
|
||||
ConstructionArgs::Value(value) => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
|||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::*;
|
||||
use graphene_core::structural::Then;
|
||||
use graphene_core::value::{ClonedNode, ForgetNode, ValueNode};
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ForgetNode, ValueNode};
|
||||
use graphene_core::{Node, NodeIO, NodeIOTypes};
|
||||
use graphene_std::brush::*;
|
||||
use graphene_std::raster::*;
|
||||
|
|
@ -175,6 +175,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
vec![(
|
||||
NodeIdentifier::new("graphene_std::brush::BrushNode"),
|
||||
|args| {
|
||||
use graphene_core::value::*;
|
||||
use graphene_std::brush::*;
|
||||
|
||||
let trace: DowncastBothNode<(), Vec<DVec2>> = DowncastBothNode::new(args[0]);
|
||||
|
|
@ -183,24 +184,23 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
let flow: DowncastBothNode<(), f64> = DowncastBothNode::new(args[3]);
|
||||
let color: DowncastBothNode<(), Color> = DowncastBothNode::new(args[4]);
|
||||
|
||||
let stamp = BrushTextureNode::new(color, ClonedNode::new(hardness.eval(())), ClonedNode::new(flow.eval(())));
|
||||
let stamp = BrushStampGeneratorNode::new(color, CopiedNode::new(hardness.eval(())), CopiedNode::new(flow.eval(())));
|
||||
let stamp = stamp.eval(diameter.eval(()));
|
||||
|
||||
let frames = TranslateNode::new(ClonedNode::new(stamp));
|
||||
let frames = TranslateNode::new(CopiedNode::new(stamp));
|
||||
let frames = MapNode::new(ValueNode::new(frames));
|
||||
let frames = frames.eval(trace.eval(()).into_iter()).collect::<Vec<_>>();
|
||||
|
||||
let background_bounds = ReduceNode::new(ClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new()));
|
||||
let background_bounds = ReduceNode::new(DebugClonedNode::new(None), ValueNode::new(MergeBoundingBoxNode::new()));
|
||||
let background_bounds = background_bounds.eval(frames.clone().into_iter());
|
||||
let background_bounds = ClonedNode::new(background_bounds.unwrap().to_transform());
|
||||
let background_bounds = DebugClonedNode::new(background_bounds.unwrap().to_transform());
|
||||
|
||||
let background_image = background_bounds.then(EmptyImageNode::new(ClonedNode::new(Color::TRANSPARENT)));
|
||||
let background_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)));
|
||||
|
||||
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(BlendMode::Normal), ClonedNode::new(100.));
|
||||
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
|
||||
|
||||
let final_image = ReduceNode::new(background_image, ValueNode::new(BlendImageTupleNode::new(ValueNode::new(blend_node))));
|
||||
let final_image = final_image.eval(frames.into_iter());
|
||||
let final_image = ClonedNode::new(final_image);
|
||||
let final_image = DebugClonedNode::new(frames.into_iter()).then(final_image);
|
||||
|
||||
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
|
||||
Box::pin(any)
|
||||
|
|
@ -241,7 +241,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
|
||||
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
|
||||
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
|
||||
let blend_node = graphene_core::raster::BlendNode::new(ClonedNode::new(blend_mode.eval(())), ClonedNode::new(opacity.eval(())));
|
||||
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(())), CopiedNode::new(opacity.eval(())));
|
||||
let node = graphene_std::raster::BlendImageNode::new(image, ValueNode::new(blend_node));
|
||||
let _ = &node as &dyn for<'i> Node<'i, ImageFrame<Color>, Output = ImageFrame<Color>>;
|
||||
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
|
||||
|
|
|
|||
|
|
@ -207,13 +207,13 @@ impl WasmSubpath {
|
|||
// Line between pivot and start point on curve
|
||||
let original_dashed_line = format!(
|
||||
r#"<line x1="{pivot_x}" y1="{pivot_y}" x2="{}" y2="{}" stroke="{ORANGE}" stroke-dasharray="0, 4" stroke-width="2" stroke-linecap="round"/>"#,
|
||||
self.0.iter().nth(0).unwrap().start().x,
|
||||
self.0.iter().nth(0).unwrap().start().y
|
||||
self.0.iter().next().unwrap().start().x,
|
||||
self.0.iter().next().unwrap().start().y
|
||||
);
|
||||
let rotated_dashed_line = format!(
|
||||
r#"<line x1="{pivot_x}" y1="{pivot_y}" x2="{}" y2="{}" stroke="{ORANGE}" stroke-dasharray="0, 4" stroke-width="2" stroke-linecap="round"/>"#,
|
||||
rotated_subpath.iter().nth(0).unwrap().start().x,
|
||||
rotated_subpath.iter().nth(0).unwrap().start().y
|
||||
rotated_subpath.iter().next().unwrap().start().x,
|
||||
rotated_subpath.iter().next().unwrap().start().y
|
||||
);
|
||||
|
||||
wrap_svg_tag(format!("{subpath_svg}{rotated_subpath_svg}{pivot}{original_dashed_line}{rotated_dashed_line}"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue