The NodeBox 3 architecture is based around the scene graph: a canvas containing all elements that are on the scene. You create this graph by creating and manipulating nodes. Once the scene graph is created, you can export it to a range of formats.


Nodes are a pretty vague concept, because they can mean anything. Basically, we define a node as a functional unit that optionally takes some input, does some processing, and provides output. The output is always in the form of a scene graph (internally called the Canvas).

Conceptually, nodes that don't take any input are called generator nodes. Nodes that take input, process it, and output new data are called filter nodes.

Generator nodes

Some basic generator nodes are the Rect, Oval and Text node. They don't take their input from another node, but rather from their internal parameters (which you can manipulate) such as the X and Y positions. Their output will be a new Canvas with a rectangle on it.

Note that there is a clear distinction between a node that generates a rectangle, and the actual rectangle itself. The node is something that does the processing and returns the actual rectangle, with all its parameters set. The node itself is not a rectangle: it is a processing unit that makes one.

Filter nodes

Filter nodes have one or multiple inputs. They get some or all data from their inputs, do something with it, and return it back.

A good example is the Wiggle node. Once processed, it gets all paths on the input canvas (leaving the rest of the elements untouched) and applies a point filter to them, so that each point is on a slightly different spot.

wiggle node in action


The Python node is just like any other nodes, except that you can define whether it will be a scripting or generator node. In fact, you define the inputs and parameters of this node in the script node itself. Just like all other nodes, its task is to (optionally) take some input, process it, and return a new canvas.


A filter node has a predefined set of inputs. For example, the wiggle node we discussed has one input (appropriately called "in") that serves as the source canvas. While processing, the node clones the input canvas, does something with it and sets the out canvas.

Multi-inputs are special inputs that are actually lists of inputs. An example of a node that uses a multi-input is the Merge node, which merges the output of many nodes together into one canvas.


Parameters are properties you can set on the node to influence its function. The text node, for example, has inputs for X and Y (to position the text), the text itself, a font and fontsize parameter, and so on. Parameters themselves have the following meta-data:

  • A short name, used in scripts
  • A more descriptive name, shown to the user
  • A description about what this parameter does
  • A type indicating whether it is a number, text, color.
  • A default value
  • A minimum and maximum value for the parameter


Each parameter has a type that indicates what kind of data can be contained in the parameter. This is important so that users don't input text where there should be numbers.

NodeBox infers the user interface element from the type. For example, a numeric type will show the number-drag widget, a text type will show a text input box, and a color type will show the color chooser.

The valid types are:

  • Integer numbers
  • Floating-point numbers
  • Booleans
  • Strings
  • Colors

Node Networks

Imagine you built a great set of nodes, that collectively create this really great filter. You can input any kind of text, and out comes a nice-looking composition. Now imagine that you want other people to play with your node: they should be able to input some text, set some parameters, and get their own composition. Basically, you want to create your own node. Oh, and you want to make sure that they edit the right parameters (such as the text, and maybe a few more here and there).

NodeBox allows you to package your nodes into a network. A network is a collection of nodes, where you can specify what the inputs, parameters, and output will be.

Inlets en outlets

Het principe van een node-netwerk is dat elke node in zich ook een netwerk verbergd, tot op een zeker elementair niveau. Dat wil zeggen dat zo'n netwerk-binnen-een-netwerk data moet binnenkrijgen van buitenaf, van buiten z'n eigen systeem. Dit wordt opgelost via inlets en outlets: kleine blokjes die enkel een type en een naam hebben. Je kan inlets en outlets verbinden aan respectievelijk de invoer- en uitvoer-parameters van nodes binnen je netwerk. Je kan een inlet aan zoveel parameters verbinden als je wil; een outlet kan echter maar één verbinding hebben.

Inlets en outlets zijn de invoer- en uitvoer-bolletjes die je ziet aan een node.

De script-node

Wanneer nodes niet voldoende zijn, kan je de script-node gebruiken om bepaalde bewerkingen te doen. Script-nodes hebben de volgende eigenschappen:

  • Een taal waarin het script geschreven is. Op het ogenblik is dit Python.
  • Een lijst van in- en uitvoer parameters. Die zijn vergelijkbaar met de inlets en outlets van een node netwerk, en bepalen over welke data het script beschikt, en wat er uit het netwerk zal komen.


Het is niet steeds nodig van een script te gebruiken. Soms kan een bepaalde parameter ook een berekening zijn van de uitvoer van een bepaalde node binnen het netwerk: in dat geval kan je een expressie schrijven.

Expressies zijn formules van één lijn die je kan gebruiken overal waar je normaal een nummer of string zou intypen. Je kan naar andere parameters van nodes in het netwerk verwijzen door een pad te gebruiken, net zoals een file path. De structuur is als volgt:


Hier is een voorbeeld van zo'n expressie. Deze wordt gebruikt om de breedte van een cirkel steeds het dubbel te maken van de breedte van een bepaalde ovaal.

oval1/width * 2

Je kan ook naar nodes buiten je netwerk verwijzen door absolute paden te gebruiken.


Operaties zijn onderverdeeld in classes. Over het algemeen zijn ze als volgt vergelijken:

  • IMOPs (Imaging Operations): Adobe Photoshop
  • VOPs (Vector Operations): Adobe Illustrator
  • COPs (Compositing Operations): Adobe Indesign


Image operations behandelen het laden en opslaan van beelden en alle soorten bewerkingen erop.

Image operations werken nauw samen met de Java Advanced Imaging technologie, die native implementaties heeft voor diverse routines en bijgevolg heel snel werkt.

Op mac heb je hiervoor de JAI Update van Apple nodig, die niet standaard geïnstalleerd is. De Windows- en Unixversies vind je op de JAI Download pagina.


Nog geen library gevonden. Werkt samen met java.awt.geom.GeneralPath .


Het uiteindelijk samenstellen van beelden gebeurd op een java.awt.Graphics2D context. Hiermee kunnen we ook, via iText, PDFs exporteren.


The whole system is built around a Directed Acyclic Graph. We use Piccolo to visualise these structures.

File Format

File Format

Scene Graph


NodeBox features a robust plugin structure that incorporates versioning. Different versions of plugins can be used simultaneously depending on the versioning requirements of the requesting node.

The architecture is modelled after the RubyGems model. Each plugin has a specification file (called the gemspec in RubyGems) that configures the metadata for the plugin. These are:

Important ones

  • name: The name of the plugin
  • version: The version of this plugin
  • platform: The target platform. Since we are using Java/Python code, plugins should generally be cross-platform. However, some plugins can contain binary code for a specific setup ("binary plugins"), in which the case the target platform should be set.
  • files: The list of files in this plugin
  • required_nodebox_version: The version of NodeBox required to host this plugin. Note that you can use the versioning syntax described below
    • required_plugins: A list of other required plugins and their versions

Additional ones

  • author: Author of the project
  • homepage: URL of the project
  • summary: A short description of this plugin
  • date: The datet/time this plugin was created

Plugin versioning

When nodes are using certain plugins, it is important that they specify which version of the plugin to use. Generally, they don't target a specific version, but a version that is known to work, presuming that all future versions of the plugin will work as well. This can be defined in the versioning syntax.

For example, suppose you have the following releases …

  • Version 2.1.0: Baseline
  • Version 2.2.0: Introduced some new (backward compatible) features.
  • Version 2.2.1: Removed some bugs
  • Version 2.2.2: Streamlined your code
  • Version 2.3.0: More new features (but still backwards compatible).
  • Version 3.0.0: Reworked the interface. Code written to verion 2.x might not work.

Your clients have validated that version 2.2.0 works with their nodes, but version 2.1.0 doesn't have a feature they need. You would specify the require line as follows:

require_gem 'library', '>=2.2.0'

This is an optimistic version constraint. The client assumes here that changes introduced in version 3.0 will not break compatibility with the 2.0 API. There is no assurance that this is going to be the case, but they are willing to take a guess.

Other clients of your plugins are not so optimistic. They expect that new versions of the plugin will break their nodes, so they want to guard against accidentaly using the new plugins. They can use a pessimistic version constraint to explicity exclude version 3.0 from there requirements.

require_gem 'library', '>=2.2.0'
, '<3.0'

However, you can also use the specific operator ~> (read: approximately greater than):

require_gem 'library', '~>2.2'

Here, the version number is specified with two digits. This line means we can use the plugin starting from version 2.2.0 all the way up until 2.9.9, but not 3.0 (so we drop the last specified digit and increment that all the way up).