documented new node classes

This commit is contained in:
itsmattkc
2019-07-17 03:05:25 -04:00
parent 39256fd6f9
commit 9f6d0f9dc7
20 changed files with 435 additions and 7 deletions
+10
View File
@@ -2,6 +2,16 @@
#define CLAMP_H
template<typename T>
/**
* @brief Clamp a value between a minimum and a maximum value
*
* Similar to using min() and max() functions, but performs both at once. If value is less than minimum, this returns
* minimum. If it is more than maximum, this returns maximum. Otherwise it returns value as-is.
*
* @return
*
* Will always return a value between minimum and maximum (inclusive).
*/
T clamp(T value, T minimum, T maximum) {
if (value < minimum) {
return minimum;
+3
View File
@@ -2,6 +2,9 @@
#define LERP_H
template<typename T>
/**
* @brief Linearly interpolate a value between a and b using t
*/
T lerp(T a, T b, double t) {
return (a * (1.0 - t)) + (b * t);
}
+3
View File
@@ -25,6 +25,9 @@
#include "decoder/decoder.h"
/**
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
*/
class FFmpegDecoder : public Decoder
{
public:
+3 -1
View File
@@ -30,7 +30,9 @@ extern "C" {
#include "common/rational.h"
/**
* @brief Abstraction from AVFrame. Currently a simple AVFrame wrapper.
* @brief Video frame data or audio sample data from a Decoder
*
* Abstraction from AVFrame. Currently a simple AVFrame wrapper.
*
* This class does not support copying at this time.
*/
+16
View File
@@ -26,12 +26,28 @@
class NodeOutput;
class NodeInput;
/**
* @brief A connection between two node parameters (a NodeOutput and a NodeInput)
*
* To simplify memory management, it's recommended to use NodeEdgePtr instead of raw pointers when working with
* NodeEdge.
*/
class NodeEdge
{
public:
/**
* @brief Create a node edge connecting an output to an input
*/
NodeEdge(NodeOutput* output, NodeInput* input);
/**
* @brief Return the output parameter this edge is connected to
*/
NodeOutput* output();
/**
* @brief Return the input parameter this edge is connected to
*/
NodeInput* input();
private:
+5
View File
@@ -17,6 +17,11 @@ QString SolidGenerator::Name()
return tr("Solid");
}
QString SolidGenerator::id()
{
return "org.olivevideoeditor.Olive.solidgenerator";
}
QString SolidGenerator::Category()
{
return tr("Generator");
+4
View File
@@ -5,6 +5,9 @@
#include "node/node.h"
/**
* @brief A node that generates a solid color
*/
class SolidGenerator : public Node
{
Q_OBJECT
@@ -12,6 +15,7 @@ public:
SolidGenerator();
virtual QString Name() override;
virtual QString id() override;
virtual QString Category() override;
virtual QString Description() override;
+29
View File
@@ -25,21 +25,50 @@
#include "node/node.h"
/**
* @brief A collection of nodes
*/
class NodeGraph : public QObject
{
Q_OBJECT
public:
/**
* @brief NodeGraph Constructor
*/
NodeGraph();
/**
* @brief Add a node to this graph
*
* The node will get added to this graph. It is not automatically connected to anything, any connections will need to
* be made manually after the node is added. The graph takes ownership of the Node.
*/
void AddNode(Node* node);
/**
* @brief Return the name of this graph (user-defined)
*/
const QString& name();
/**
* @brief Set the name of this graph (user-defined)
*/
void set_name(const QString& name);
/**
* @brief Retrieve a complete list of the nodes belonging to this graph
*/
QList<Node*> nodes();
signals:
/**
* @brief Signal emitted when a member node of this graph has been connected to another (creating an "edge")
*/
void EdgeAdded(NodeEdgePtr edge);
/**
* @brief Signal emitted when a member node of this graph has been disconnected from another (removing an "edge")
*/
void EdgeRemoved(NodeEdgePtr edge);
private:
+68
View File
@@ -24,34 +24,102 @@
#include "keyframe.h"
#include "param.h"
/**
* @brief A node parameter designed to take either user input or data from another node
*/
class NodeInput : public NodeParam
{
public:
NodeInput();
/**
* @brief Returns kInput
*/
virtual Type type() override;
/**
* @brief Add a data type that this input accepts
*
* While an input will usually only accept one data type, NodeInput supports several. Use this to add a data type that
* this input can accept.
*/
void add_data_input(const DataType& data_type);
/**
* @brief Return whether an input can accept a certain type based on its list of data types
*
* The input checks its list of acceptable data types (added by add_data_input()) to determine whether a certain
* data type can be connected to this input.
*/
bool can_accept_type(const DataType& data_type);
/**
* @brief Return whether this parameter accepts multiple inputs (false by default)
*
* While an input will usually only accept one connection from an output at any given time, NodeInput does support
* more than one. By default this is false, but can be set to true on any input object. If this is true, get_value()
* returns QList<QVariant> rather than just a QVariant (but casted to QVariant).
*/
bool can_accept_multiple_inputs();
/**
* @brief \see can_accept_multiple_inputs().
*/
void set_can_accept_multiple_inputs(bool b);
/**
* @brief Get the value at a given time
*
* This function will automatically retrieve the correct value for this input at the given time.
*
* If an output is connected to this input, a request is made to that output for its value at this time. If multiple
* outputs are connected (\see can_accept_multiple_inputs()), a QList<QVariant> (casted to a QVariant) is returned
* instead, listing all the outputs' values currently connected.
*
* If no output is connected, this will return a user-defined value, either a static value if this input is not
* keyframed, or an interpolated value between the keyframes at this time.
*/
QVariant get_value(const rational &time);
/**
* @brief Return whether keyframing is enabled on this input or not
*/
bool keyframing();
/**
* @brief Set whether keyframing is enabled on this input or not
*/
void set_keyframing(bool k);
/**
* @brief A list of input data types accepted by this parameter
*/
const QList<DataType>& inputs();
private:
/**
* @brief Internal list of accepted data types
*
* Use can_accept_type() to check if a type is in this list
*/
QList<DataType> inputs_;
/**
* @brief Internal keyframe array
*
* All internal/user-defined data is stored in this array. Even if keyframing is not enabled, this array will contain
* one entry which will be used, and its time value will be ignored.
*/
QList<NodeKeyframe> keyframes_;
/**
* @brief Internal keyframing enabled setting
*/
bool keyframing_;
/**
* @brief Internal multiple inputs accepted setting
*/
bool can_accept_multiple_inputs_;
};
+5
View File
@@ -13,6 +13,11 @@ QString ImageInput::Name()
return tr("Image");
}
QString ImageInput::id()
{
return "org.olivevideoeditor.Olive.imageinput";
}
QString ImageInput::Category()
{
return tr("Input");
+7
View File
@@ -5,6 +5,12 @@
#include "node/node.h"
/**
* @brief A node that imports an image
*
* FIXME: This will likely be replaced by the Media node as the Media node will be set up to pull from various decoders
* from the beginning.
*/
class ImageInput : public Node
{
Q_OBJECT
@@ -12,6 +18,7 @@ public:
ImageInput();
virtual QString Name() override;
virtual QString id() override;
virtual QString Category() override;
virtual QString Description() override;
+18
View File
@@ -25,24 +25,42 @@
#include "common/rational.h"
/**
* @brief A point of data to be used at a certain time and interpolated with other data
*/
class NodeKeyframe
{
public:
/**
* @brief Methods of interpolation to use with this keyframe
*/
enum Type {
kLinear,
kHold,
kBezier
};
/**
* @brief NodeKeyframe Constructor
*/
NodeKeyframe();
/**
* @brief The time this keyframe is set at
*/
const rational& time();
void set_time(const rational& time);
/**
* @brief The value of this keyframe (i.e. the value to use at this keyframe's time)
*/
const QVariant& value();
void set_value(const QVariant &value);
/**
* @brief The method of interpolation to use with this keyframe
*/
const Type& type();
void set_type(const Type& type);
+80
View File
@@ -27,16 +27,68 @@
#include "node/input.h"
#include "node/output.h"
/**
* @brief A single processing unit that can be connected with others to create intricate processing systems
*
* A cornerstone of "visual programming", a node is a single "function" that takes input and returns an output that can
* be connected to other nodes. Inputs can be either user-set or retrieved from the output of another node. By joining
* several nodes together, intricate, highly customizable, and infinitely extensible systems can be made for processing
* data. It can also all be exposed to the user without forcing them to write code or compile anything.
*
* A major example in Olive is the entire rendering workflow. To render a frame, Olive will work through a node graph
* that can be infinitely customized by the user to create images.
*
* This is a simple base class designed to contain all the functionality for this kind of processing connective unit.
* It is an abstract class intended to be subclassed to create nodes with actual functionality.
*/
class Node : public QObject
{
Q_OBJECT
public:
Node();
/**
* @brief Return the name of the node
*
* This is the node's name shown to the user. This must be overridden by subclasses, and preferably run through the
* translator.
*/
virtual QString Name() = 0;
/**
* @brief Return the unique identifier of the node
*
* This is used in save files and any other times a specific node must be picked out at runtime. This must be an ID
* completely unique to this node, and preferably in bundle identifier format (e.g. "org.company.Name"). This string
* should NOT be translated.
*/
virtual QString id() = 0;
/**
* @brief Return the category this node is in (optional for subclassing, but recommended)
*
* In any organized node menus, show the node in this category. If this node should be in a subfolder of a subfolder,
* use a "/" to separate categories (e.g. "Distort/Noise"). The string should not start with a "/" as this will be
* interpreted as an empty string category. This value should be run through a translator as its largely user
* oriented.
*/
virtual QString Category();
/**
* @brief Return a description of this node's purpose (optional for subclassing, but recommended)
*
* A short (1-2 sentence) description of what this node should do to help the user understand its purpose. This should
* be run through a translator.
*/
virtual QString Description();
/**
* @brief Add a parameter to this node
*
* The Node takes ownership of this parameter.
*
* This can be either an output or an input at any time. Parameters will always appear in the order they're added.
*/
void AddParameter(NodeParam* param);
/**
@@ -72,10 +124,38 @@ public:
int IndexOfParameter(NodeParam* param);
public slots:
/**
* @brief The main processing function
*
* The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you
* create, this is where the code for that goes.
*
* Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may
* output different values depending on the time, and even if not, it will likely be receiving different input
* depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass
* the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a
* corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior
* of the NodeParam objects will handle everything related to it automatically.
*/
virtual void Process(const rational& time) = 0;
signals:
/**
* @brief Signal emitted when a node is connected to another node (creating an "edge")
*
* @param edge
*
* The edge that was added
*/
void EdgeAdded(NodeEdgePtr edge);
/**
* @brief Signal emitted when a node is disconnected from another node (removing an "edge")
*
* @param edge
*
* The edge that was removed
*/
void EdgeRemoved(NodeEdgePtr edge);
};
+35
View File
@@ -23,17 +23,52 @@
#include "param.h"
/**
* @brief A node parameter designed to serve data to the input of another node
*/
class NodeOutput : public NodeParam
{
public:
/**
* @brief NodeOutput Constructor
*/
NodeOutput();
/**
* @brief Returns kOutput
*/
virtual Type type() override;
/**
* @brief The data type this parameter outputs
*
* This can be used in conjunction with NodeInput::can_accept_type() to determine whether this parameter can be
* connected to it.
*/
const DataType& data_type();
void set_data_type(const DataType& type);
/**
* @brief Get the value of this output at a given tie
*
* This function is intended to primarily be called by any connected NodeInputs.
*
* The first thing this function does is request the parent Node object to Process() at this time. The Node should
* then perform whatever actions necessary (usually taking data from inputs and creating output data) to set the
* correct value that this output should have at this time (the Node should use set_value() for this). This function
* will then return the value that was set after Process() returned.
*
* In many cases for efficiency, the Node can also ignore this request if it knows the output data will not change
* (i.e. if the time has not changed from the last Process()).
*/
virtual const QVariant& get_value(const rational &time);
/**
* @brief Set the current value of this output
*
* Intended to only be set by parent Node objects in their Node::Process() function. Whatever result data is intended
* for use later in the pipeline should be set here (\see get_value()).
*/
virtual void set_value(const QVariant& value);
private:
+5
View File
@@ -15,6 +15,11 @@ QString ViewerOutput::Name()
return tr("Viewer");
}
QString ViewerOutput::id()
{
return "org.olivevideoeditor.Olive.vieweroutput";
}
QString ViewerOutput::Category()
{
return tr("Output");
+6
View File
@@ -4,6 +4,11 @@
#include "node/node.h"
#include "panel/viewer/viewer.h"
/**
* @brief A bridge between a node system and a ViewerPanel
*
* Receives update/time change signals from ViewerPanels and responds by sending them a texture of that frame
*/
class ViewerOutput : public Node
{
Q_OBJECT
@@ -11,6 +16,7 @@ public:
ViewerOutput();
virtual QString Name() override;
virtual QString id() override;
virtual QString Category() override;
virtual QString Description() override;
+2 -2
View File
@@ -113,7 +113,7 @@ bool NodeParam::AreDataTypesCompatible(const DataType &output_type, const QList<
NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input)
{
// If the input can only accept one input (the default) and has one already, disconnect it
FreeSpaceForEdgeFromInput(input);
DisconnectForNewOutput(input);
// Make sure it's not a duplicate of an edge that already exists
foreach (NodeEdgePtr existing, input->edges()) {
@@ -155,7 +155,7 @@ void NodeParam::DisconnectEdge(NodeOutput *output, NodeInput *input)
}
}
NodeEdgePtr NodeParam::FreeSpaceForEdgeFromInput(NodeInput *input)
NodeEdgePtr NodeParam::DisconnectForNewOutput(NodeInput *input)
{
// If the input can only accept one input (the default) and has one already, disconnect it
if (!input->edges_.isEmpty() && !input->can_accept_multiple_inputs()) {
+132 -3
View File
@@ -29,15 +29,29 @@
class Node;
/**
* @brief A base parameter of a Node
*
* The main data points of a Node. NodeParams are added to Nodes so that Node::Process() can use data acquired either
* directly as a value set by the user, or through the output of another NodeParam.
*
* This is an abstract base class. In most cases you'll want NodeInput or NodeOutput.
*/
class NodeParam : public QObject
{
Q_OBJECT
public:
/**
* @brief The type of parameter this is
*/
enum Type {
kInput,
kOutput
};
/**
* @brief The types of data that can be passed between Nodes
*/
enum DataType {
kNone,
kInt,
@@ -53,40 +67,155 @@ public:
kAny
};
/**
* @brief NodeParam Constructor
*/
NodeParam();
/**
* @brief The type of node paramter this is
*
* This must be set in subclasses, but most of the time you should probably subclass from NodeInput and NodeOutput
* anyway.
*/
virtual Type type() = 0;
/**
* @brief Name of this parameter to be shown to the user
*/
const QString& name();
void set_name(const QString& name);
/**
* @brief Node parent object
*
* Nodes and NodeParams use the QObject parent-child system. This function is a convenience function for
* static_cast<Node*>(QObject::parent())
*/
Node* parent();
/**
* @brief Return the row index of this parameter in the parent node (primarily used for UI drawing functions)
*/
int index();
/**
* @brief Return a list of edges (aka connections to other nodes)
*
* This list can't be modified directly. Use ConnectEdge() and DisconnectEdge() instead for that.
*/
const QVector<NodeEdgePtr>& edges();
static bool AreDataTypesCompatible(NodeParam* a, NodeParam* b);
/**
* @brief Determine whether two DataTypes are compatible and therefore whether two NodeParams can be connected
*
* Obviously a data type is compatible with itself, but sometimes fundamentally separate data types may still be
* allowed to connect (e.g. an integer output to a float input). This static function should be used to determine
* whether a data type is compatible with another.
*/
static bool AreDataTypesCompatible(const DataType& output_type, const DataType& input_type);
/**
* @brief Overload of AreDataTypesCompatible(const DataType& output_type, const DataType& input_type)
*
* Use this for a list of input data types (which NodeInput uses as it's possible for it to accept multiple types).
*/
static bool AreDataTypesCompatible(const DataType& output_type, const QList<DataType>& input_types);
/**
* @brief Overload of AreDataTypesCompatible(const DataType& output_type, const DataType& input_type)
*
* Convenience function for two NodeParams. Determines which is the input/output and determines whether their types
* are compatible.
*/
static bool AreDataTypesCompatible(NodeParam* a, NodeParam* b);
/**
* @brief Connect an output parameter to an input parameter
*
* This function makes no attempt to check whether the two NodeParams have compatible data types. This should be done
* beforehand or behavior is undefined.
*
* If the input already has an edge connected and can't accept multiple edges, that edge is disconnected before an
* attempt at a new connection is made. This function returns the new NodeEdge created by this connection.
*
* If the input *can* accept multiple edges but is already connected to this output, no new connection is made (since
* the connection already exists). In this situation, nullptr is returned.
*
* This function emits EdgeAdded().
*/
static NodeEdgePtr ConnectEdge(NodeOutput *output, NodeInput *input);
/**
* @brief Disconnect an edge
*
* This function emits EdgeRemoved(NodeEdgePtr edge).
*
* @param edge
*
* Edge to disconnect.
*/
static void DisconnectEdge(NodeEdgePtr edge);
/**
* @brief Disconnect an edge
*
* Sometimes this function is preferable if you don't know what the edge object is (or with undo commands where the
* edge object may change despite the connection being between the same parameters).
*
* This function emits EdgeRemoved(NodeEdgePtr edge).
*
* @param edge
*
* Edge to disconnect.
*/
static void DisconnectEdge(NodeOutput* output, NodeInput* input);
static NodeEdgePtr FreeSpaceForEdgeFromInput(NodeInput* input);
/**
* @brief If an input has an edge and can't take multiple, this function disconnects them and returns the edge object
*
* This is used just before a connection is about to be made. If an input is already connected to an output, but
* can't take multiple inputs, that connection will need to be removed before the new connection can be made.
* This function check if it's necessary to remove the edge from an input before connecting a new edge, and removes
* and returns it if so.
*
* If the input does NOT have anything connected, or it does but the input CAN accept multiple connections, nothing
* is disconnected and nullptr is returned.
*/
static NodeEdgePtr DisconnectForNewOutput(NodeInput* input);
/**
* @brief Get a human-readable translated name for a certain data type
*/
static QString GetDefaultDataTypeName(const DataType &type);
signals:
/**
* @brief Signal emitted when an edge is added to this parameter
*
* See ConnectEdge() for usage. Only one of the two parameters needs to emit this signal when a connection is made,
* because otherwise two of exactly the same signal will be emitted.
*/
void EdgeAdded(NodeEdgePtr edge);
/**
* @brief Signal emitted when an edge is removed from this parameter
*
* See DisconnectEdge() for usage. Only one of the two parameters needs to emit this signal when a connection is
* removed, because otherwise two of exactly the same signal will be emitted.
*/
void EdgeRemoved(NodeEdgePtr edge);
protected:
/**
* @brief Internal list of edges
*/
QVector<NodeEdgePtr> edges_;
private:
/**
* @brief Internal name string
*/
QString name_;
};
+3
View File
@@ -24,6 +24,9 @@
#include "node/node.h"
#include "rendererthread.h"
/**
* @brief A multithreaded OpenGL based renderer for node systems
*/
class RendererProcessor : public Node
{
Q_OBJECT
+1 -1
View File
@@ -15,7 +15,7 @@ void NodeEdgeAddCommand::redo()
return;
}
old_edge_ = NodeParam::FreeSpaceForEdgeFromInput(input_);
old_edge_ = NodeParam::DisconnectForNewOutput(input_);
NodeParam::ConnectEdge(output_, input_);