diff --git a/app/common/clamp.h b/app/common/clamp.h index b87fe1657..b5e891bd9 100644 --- a/app/common/clamp.h +++ b/app/common/clamp.h @@ -2,6 +2,16 @@ #define CLAMP_H template +/** + * @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; diff --git a/app/common/lerp.h b/app/common/lerp.h index f84ecbcf9..4d98db0a1 100644 --- a/app/common/lerp.h +++ b/app/common/lerp.h @@ -2,6 +2,9 @@ #define LERP_H template +/** + * @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); } diff --git a/app/decoder/ffmpeg/ffmpegdecoder.h b/app/decoder/ffmpeg/ffmpegdecoder.h index bc750dce8..46b3fc9cc 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.h +++ b/app/decoder/ffmpeg/ffmpegdecoder.h @@ -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: diff --git a/app/decoder/frame.h b/app/decoder/frame.h index 1fde58258..6992ebbac 100644 --- a/app/decoder/frame.h +++ b/app/decoder/frame.h @@ -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. */ diff --git a/app/node/edge.h b/app/node/edge.h index a6b747135..1bbe8dc20 100644 --- a/app/node/edge.h +++ b/app/node/edge.h @@ -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: diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 2a785ce9c..f284d7790 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -17,6 +17,11 @@ QString SolidGenerator::Name() return tr("Solid"); } +QString SolidGenerator::id() +{ + return "org.olivevideoeditor.Olive.solidgenerator"; +} + QString SolidGenerator::Category() { return tr("Generator"); diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 6c6f7bf62..fd378d1ff 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -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; diff --git a/app/node/graph.h b/app/node/graph.h index e6215e86a..0d5064829 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -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 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: diff --git a/app/node/input.h b/app/node/input.h index 952e3ed11..2c8f116c0 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -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 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 (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& inputs(); private: + /** + * @brief Internal list of accepted data types + * + * Use can_accept_type() to check if a type is in this list + */ QList 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 keyframes_; + /** + * @brief Internal keyframing enabled setting + */ bool keyframing_; + /** + * @brief Internal multiple inputs accepted setting + */ bool can_accept_multiple_inputs_; }; diff --git a/app/node/input/image/image.cpp b/app/node/input/image/image.cpp index 9c8efb7f2..e82f30cc4 100644 --- a/app/node/input/image/image.cpp +++ b/app/node/input/image/image.cpp @@ -13,6 +13,11 @@ QString ImageInput::Name() return tr("Image"); } +QString ImageInput::id() +{ + return "org.olivevideoeditor.Olive.imageinput"; +} + QString ImageInput::Category() { return tr("Input"); diff --git a/app/node/input/image/image.h b/app/node/input/image/image.h index e64a541c8..b07e1734d 100644 --- a/app/node/input/image/image.h +++ b/app/node/input/image/image.h @@ -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; diff --git a/app/node/keyframe.h b/app/node/keyframe.h index e482bccfd..59dbe6be2 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -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); diff --git a/app/node/node.h b/app/node/node.h index e776255d5..766683786 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -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); }; diff --git a/app/node/output.h b/app/node/output.h index 8e2014ed8..a03574fd5 100644 --- a/app/node/output.h +++ b/app/node/output.h @@ -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: diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 224ea6537..3b5b75299 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -15,6 +15,11 @@ QString ViewerOutput::Name() return tr("Viewer"); } +QString ViewerOutput::id() +{ + return "org.olivevideoeditor.Olive.vieweroutput"; +} + QString ViewerOutput::Category() { return tr("Output"); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index f07dd4b6e..d219fa8ac 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -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; diff --git a/app/node/param.cpp b/app/node/param.cpp index 553b978ad..4f5939325 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -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()) { diff --git a/app/node/param.h b/app/node/param.h index 7ae269af8..7d83aef01 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -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(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& 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& 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 edges_; private: - + /** + * @brief Internal name string + */ QString name_; }; diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index 4ccd050ff..6285c22cf 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -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 diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 9836bc97a..4e61e422a 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -15,7 +15,7 @@ void NodeEdgeAddCommand::redo() return; } - old_edge_ = NodeParam::FreeSpaceForEdgeFromInput(input_); + old_edge_ = NodeParam::DisconnectForNewOutput(input_); NodeParam::ConnectEdge(output_, input_);