* Update GPU and quantization to new node system
Squashed commit of the following:
commit 3b69bdafed79f0bb1279609537a8eeead3f06830
Author: Dennis Kobert <dennis@kobert.dev>
Date: Sun Mar 5 11:37:17 2023 +0100
Disable dev tools by default
commit dbbbedd68e48d1162442574ad8877c9922d40e4a
Merge: b1018eb5 a8f6e11e
Author: Dennis Kobert <dennis@kobert.dev>
Date: Sun Mar 5 10:45:00 2023 +0100
Merge branch 'vite' into tauri-restructure-lite
commit b1018eb5ee56c2d23f9d5a4f034608ec684bd746
Merge: 3195833e 0512cb24
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 17:06:21 2023 +0100
Merge branch 'master' into tauri-restructure-lite
commit 3195833e4088a4ed7984955c72617b27b7e39bfc
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 17:06:02 2023 +0100
Bump number of samples
commit 3e57e1e3280759cf4f75726635e31d2b8e9387f9
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 16:55:52 2023 +0100
Move part of quantization code to gcore
commit 10c15b0bc6ffb51e2bf2d94cd4eb0e24d761fb6f
Merge: 2b3db45a 8fe8896c
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 14:28:56 2023 +0100
Merge remote-tracking branch 'origin/master' into tauri-restructure-lite
commit 2b3db45aee44a20660f0b1204666bb81e5a7e4b6
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 14:17:11 2023 +0100
Fix types in node registry
commit 9122f35c0ba9a86255709680d744a48d3c7dcac4
Merge: 26eefc43 2cf4ee0f
Author: Dennis Kobert <dennis@kobert.dev>
Date: Fri Mar 3 01:04:55 2023 +0100
Merge remote-tracking branch 'origin/master' into tauri-restructure-lite
commit 26eefc437eaad873f8d38fdb1fae0a1e3ec189e4
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Mar 2 23:05:53 2023 +0100
Add Quantize node to document_node_types
commit 3f7606a91329200b2c025010d4a0cffee840a11c
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Mar 2 17:47:51 2023 +0100
Add quantization nodes to node registry
commit 22d8e477ef79eef5b57b1dc9805e41bbf81cae43
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Mar 2 17:13:28 2023 +0100
Introduce scopes (#1053)
* Implement let binding
* Add lambda inputs
* Fix tests
* Fix proto network formatting
* Generate a template Scoped network by default
* Add comment to explain the lambda parameter
* Move binding wrapping out of the template
* Fix errors cause by image frames
commit 9e0c29d92a164d4a4063e93480e1e289ef5243fe
Author: Alexandru Ică <alexandru@seyhanlee.com>
Date: Thu Mar 2 15:55:10 2023 +0200
Make use of ImageFrame in the node system more extensively (#1055) (#1062)
Make the node system use ImageFrame more extensively (#1055)
commit 5912ef9a1a807917eeb90c1f4835bd8a5de9c821
Author: Dennis Kobert <dennis@kobert.dev>
Date: Wed Mar 1 16:15:21 2023 +0100
Split quantization into multiple nodes
commit 285d7b76c176b3e2679ea24eecb38ef867a79f3b
Author: Dennis Kobert <dennis@kobert.dev>
Date: Mon Feb 27 12:35:57 2023 +0100
Fix gpu support
commit e0b6327eebba8caf7545c4fedc6670abc4c3652e
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Feb 16 22:08:53 2023 +0100
Don't watch frontend files when using tauri
commit 58ae146f6da935cfd37afbd25e1c331b615252da
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Feb 16 21:48:54 2023 +0100
Migrate vue code base to vite
commit f996390cc312618a60f98ccb9cd515f1bae5006d
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Feb 16 19:34:33 2023 +0100
Start migrating vue to use vite
commit 29d752f47cfd1c74ee51fac6f3d75557a378471c
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Feb 16 19:00:53 2023 +0100
Kill cargo watch process automatically
commit 4d1c76b07acadbf609dbab7d57d9a7769b81d4b5
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Feb 16 17:37:27 2023 +0100
Start playing around with vite infrastructure
commit 8494f5e9227aa433fd5ca75b268a6a96b2706b36
Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
Date: Thu Jan 19 18:40:46 2023 +0000
Fix import style and eslint rules
commit 92490f7774a7351bb40091bcec78f79c28704768
Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
Date: Thu Jan 19 18:25:09 2023 +0000
Fix icons
commit dc67821abad87f8ff780b12ae96668af2f7bb355
Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
Date: Thu Jan 19 18:20:48 2023 +0000
Add license generator with rollup
commit 441e339d31b76dac4f91321d39a39900b5a79bc1
Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
Date: Thu Jan 19 18:14:22 2023 +0000
Use eslint --fix to fix TS-in-svelte type imports. Now it compiles.
commit 2e847d339e7dcd51ed4c4677ed337c1e20636724
Author: Locria Cyber <74560659+locriacyber@users.noreply.github.com>
Date: Thu Jan 19 17:31:49 2023 +0000
Remove webpack and plugins
commit 3adab1b7f40ff17b91163e7ca47a403ef3c02fbc
Author: Dennis Kobert <dennis@kobert.dev>
Date: Thu Mar 2 16:10:19 2023 +0100
Fix errors cause by image frames
commit 4e5f838995e213b4696225a473b9c56c0084e7a8
Author: Alexandru Ică <alexandru@seyhanlee.com>
Date: Thu Mar 2 15:55:10 2023 +0200
Make use of ImageFrame in the node system more extensively (#1055) (#1062)
Make the node system use ImageFrame more extensively (#1055)
commit 1d4b0e29c693a53c068f1a30f0e857a9c1a59587
Author: Dennis Kobert <dennis@kobert.dev>
Date: Wed Mar 1 15:13:51 2023 +0100
Update node graph guide readme with new syntax (#1061)
commit 6735d8c61f5709e22d2b22abd037bab417e868d6
Author: Rob Nadal <Robnadal44@gmail.com>
Date: Tue Feb 28 18:59:06 2023 -0500
Bezier-rs: Add function to smoothly join bezier curves (#1037)
* Added bezier join
* Stylistic changes per review
commit cd1d7aa7fbcce39fbbf7762d131ee16ad9cb46dd
Author: Dennis Kobert <dennis@kobert.dev>
Date: Wed Feb 22 23:42:32 2023 +0100
Implement let binding
Add lambda inputs
Fix tests
Fix proto network formatting
Generate a template Scoped network by default
Add comment to explain the lambda parameter
Move binding wrapping out of the template
* Update package-lock.json
* Regenerate package-lock.json and fix lint errors
* Readd git keep dir
* Revert change to panic.ts
* Fix clippy warnings
* Apply code review
* Clean up node_registry
* Fix test / spriv -> spirv typos
|
||
|---|---|---|
| .. | ||
| borrow_stack | ||
| compilation-client | ||
| compilation-server | ||
| future-executor | ||
| gcore | ||
| gpu-compiler | ||
| graph-craft | ||
| gstd | ||
| interpreted-executor | ||
| node-macro | ||
| vulkan-executor | ||
| wgpu-executor | ||
| LICENSE | ||
| README.md | ||
README.md
Creating Nodes In Graphite
Purpose of Nodes
Graphite is an image editor which is centred around a node based editing workflow, which allows operations to be visually connected in a graph. This is flexible as it allows all operations to be viewed or modified at any time without losing original data. The node system has been designed to be as general as possible with all data types being representable and a broad selection of nodes for a variety of use cases being planned.
The Document Graph
The graph that is presented to users in the editor is known as the document graph. Each node that has been placed in this graph has the following properties:
pub struct DocumentNode {
// An identifier used to display in the editor and to display the appropriate properties.
pub name: String,
// A NodeId (number), a value, or a specifier that this node is in a nested network and receives input from the outer network
pub inputs: Vec<NodeInput>,
// A nested document network or a proto-node identifier
pub implementation: DocumentNodeImplementation,
// Contains the position of the node
pub metadata: DocumentNodeMetadata,
}
You can define your own type of document node in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs. We currently just store document node types in a static slice but this will become dynamic in future. A sample document node type definition for the opacity node is shown:
DocumentNodeType {
name: "Opacity",
category: "Image Adjustments",
identifier: NodeImplementation::proto("graphene_core::raster::OpacityNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::Image(Image::empty()), true),
DocumentInputType::value("Factor", TaggedValue::F64(100.), false),
],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::multiply_opacity,
},
The identifier here must be the same as that of the proto-node which will be discussed soon and is usually the path to the node implementation.
The input names are shown in the graph when an input is exposed (with a dot in the properties panel). The default input is used when a node is first created or when a link is disconnected. An input is comprised from a TaggedValue (allowing serialisation of a dynamic type with serde) in addition to an exposed boolean, which defines if the input is shown as a dot in the node graph UI by default. In the opacity node, the "Color" input is shown but the "Factor" input is hidden from the graph by default, allowing for a less cluttered graph.
The properties field is a function that defines a number input, which can be seen by selecting the gamma node in the graph. The code for this property is shown below:
pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let factor = number_widget(document_node, node_id, 1, "Factor", NumberInput::default().min(0.).max(100.).unit("%"), true);
vec![LayoutGroup::Row { widgets: factor }]
}
Node Implementation
Defining the actual implementation for a node is done by implementing the Node trait. The Node trait has one function called eval that takes one generic input. A node implementation for the opacity node is seen below:
#[derive(Debug, Clone, Copy)]
pub struct OpacityNode<O> {
opacity_multiplier: O,
}
impl<'i, N: Node<'i, (), Output = f64> + 'i> Node<'i, Color> for OpacityNode<N> {
type Output = Color;
fn eval<'s: 'i>(&'s self, color: Color) -> Color {
let opacity_multiplier = self.opacity_multiplier.eval(()) as f32 / 100.;
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
}
}
impl<N> OpacityNode<N> {
pub fn new(node: N) -> Self {
Self { opacity_multiplier: node }
}
}
The eval function can only take one input. To support more than one input, the node struct can contain references to other nodes (it is the references that implement the Node trait). If the input is a value, then a node that simply evaluates to its field will be referenced. If the input is a node, then the relevant proto-node will be referenced. To use these secondary inputs in the implementation, they are evaluated with the input of () to give an output an f64 as specified by the generics. A helper function to create a new node struct is also defined here.
This process can be made more concise using the node_macro macro, which can be applied to a function like image_opacity with an attribute of the name of the node:
#[derive(Debug, Clone, Copy)]
pub struct OpacityNode<O> {
opacity_multiplier: O,
}
#[node_macro::node_fn(OpacityNode)]
fn image_opacity(color: Color, opacity_multiplier: f64) -> Color {
let opacity_multiplier = opacity_multiplier as f32 / 100.;
Color::from_rgbaf32_unchecked(color.r(), color.g(), color.b(), color.a() * opacity_multiplier)
}
Inserting the Proto-Node
When the document graph is executed, it is first converted to a proto-graph, which has all of the nested node graphs flattened as well as separating out the primary input from the secondary inputs. The secondary inputs are stored as a list of node ids in the construction arguments field of the ProtoNode. The newly created ProtoNodes are then converted into the corresponding dynamic rust functions using the mapping defined in node-graph/interpreted-executor/src/node_registry.rs. The resolved functions are then stored in a BorrowTree, which allows previous proto-nodes to be referenced as inputs by later nodes. The BorrowTree ensures nodes can't be removed while being referenced by other nodes.
raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),
Nodes in the borrow stack take a Box<dyn DynAny> as input and output another Box<dyn DynAny>, to allow for any type. To use this as the field for our OpacityNode, we must downcast these types so the input is a () and the output is a f64. This can be achieved by the DowncastBothNode.
The new OpacityNode that has been constructed must then be made to have a dynamic input and output using the DynAnyNode.
If the primary input to the node comes from the output of another node, then the ProtoNodeInput will be set to a node id. This node is found and then chained with the opacity node using the .then() function. If there is no primary input then we simply return the node.
Finally we call .into_type_erased() on the result and that is inserted into the borrow stack.
Conclusion
Defining some basic nodes to allow for a simple image editing workflow would be invaluable. Currently defining nodes is quite a laborious process however efforts at simplification are being discussed. Any contributions you might have would be greatly appreciated. If any parts of this guide are outdated or difficult to understand, please feel free to ask for help in the Graphite Discord. We are very happy to answer any questions :)