diff --git a/app/core.cpp b/app/core.cpp index 6a65fdfb8..1e8f650bf 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -219,8 +219,10 @@ void Core::CreateNewFolder() // FIXME: Test code #include "node/block/clip/clip.h" +#include "node/block/gap/gap.h" #include "node/input/media/media.h" #include "node/output/timeline/timeline.h" +#include "node/output/track/track.h" #include "node/output/viewer/viewer.h" #include "node/generator/solid/solid.h" #include "panel/panelmanager.h" @@ -271,16 +273,38 @@ void Core::CreateNewSequence() new_sequence); // FIXME: Test code + GapBlock* gb = new GapBlock(); + gb->set_length(1); + new_sequence->AddNode(gb); + + ClipBlock* cb = new ClipBlock(); + cb->set_length(2); + new_sequence->AddNode(cb); + + TrackOutput* to = new TrackOutput(); + new_sequence->AddNode(to); + + ClipBlock* cb2 = new ClipBlock(); + cb2->set_length(2); + new_sequence->AddNode(cb2); + + TrackOutput* to2 = new TrackOutput(); + new_sequence->AddNode(to2); + TimelineOutput* tb = new TimelineOutput(); new_sequence->AddNode(tb); ViewerOutput* vo = new ViewerOutput(); - vo->AttachViewer(olive::panel_focus_manager->MostRecentlyFocused()); new_sequence->AddNode(vo); - NodeParam::ConnectEdge(tb->texture_output(), vo->texture_input()); + Block::ConnectBlocks(gb, cb); + Block::ConnectBlocks(cb, to); + Block::ConnectBlocks(cb2, to2); + NodeParam::ConnectEdge(to->texture_output(), vo->texture_input()); + NodeParam::ConnectEdge(to->track_output(), tb->track_input()); + + vo->AttachViewer(olive::panel_focus_manager->MostRecentlyFocused()); tb->AttachTimeline(olive::panel_focus_manager->MostRecentlyFocused()); - olive::panel_focus_manager->MostRecentlyFocused()->SetGraph(new_sequence.get()); // End test code diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index c4f9db7f7..7ab867338 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(blend) add_subdirectory(block) add_subdirectory(generator) add_subdirectory(input) diff --git a/app/node/blend/CMakeLists.txt b/app/node/blend/CMakeLists.txt new file mode 100644 index 000000000..645e6b7a9 --- /dev/null +++ b/app/node/blend/CMakeLists.txt @@ -0,0 +1,24 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +add_subdirectory(alphaover) + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/blend/blend.h + node/blend/blend.cpp + PARENT_SCOPE +) diff --git a/app/node/blend/alphaover/CMakeLists.txt b/app/node/blend/alphaover/CMakeLists.txt new file mode 100644 index 000000000..e2af3e24d --- /dev/null +++ b/app/node/blend/alphaover/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/blend/alphaover/alphaover.h + node/blend/alphaover/alphaover.cpp + PARENT_SCOPE +) diff --git a/app/node/blend/alphaover/alphaover.cpp b/app/node/blend/alphaover/alphaover.cpp new file mode 100644 index 000000000..6013ceae7 --- /dev/null +++ b/app/node/blend/alphaover/alphaover.cpp @@ -0,0 +1,17 @@ +#include "alphaover.h" + +AlphaOverBlend::AlphaOverBlend() +{ + +} + +void AlphaOverBlend::Process(const rational &time) +{ + Q_UNUSED(time) + + // FIXME: Write Alpha Over Formula + + // Note that alpha will always be premultiplied by this point + + //GLuint base_tex = base_input_->get_value(time).value(); +} diff --git a/app/node/blend/alphaover/alphaover.h b/app/node/blend/alphaover/alphaover.h new file mode 100644 index 000000000..39d3d0a4f --- /dev/null +++ b/app/node/blend/alphaover/alphaover.h @@ -0,0 +1,15 @@ +#ifndef ALPHAOVER_H +#define ALPHAOVER_H + +#include "node/blend/blend.h" + +class AlphaOverBlend : public BlendNode +{ +public: + AlphaOverBlend(); + +public slots: + virtual void Process(const rational &time) override; +}; + +#endif // ALPHAOVER_H diff --git a/app/node/blend/blend.cpp b/app/node/blend/blend.cpp new file mode 100644 index 000000000..8a33d1a5c --- /dev/null +++ b/app/node/blend/blend.cpp @@ -0,0 +1,22 @@ +#include "blend.h" + +BlendNode::BlendNode() +{ + base_input_ = new NodeInput("base_in"); + base_input_->add_data_input(NodeParam::kTexture); + AddParameter(base_input_); + + blend_input_ = new NodeInput("blend_in"); + blend_input_->add_data_input(NodeParam::kTexture); + AddParameter(blend_input_); +} + +NodeInput *BlendNode::base_input() +{ + return base_input_; +} + +NodeInput *BlendNode::blend_input() +{ + return blend_input_; +} diff --git a/app/node/blend/blend.h b/app/node/blend/blend.h new file mode 100644 index 000000000..7f63c96c4 --- /dev/null +++ b/app/node/blend/blend.h @@ -0,0 +1,21 @@ +#ifndef BLEND_H +#define BLEND_H + +#include "node/node.h" + +class BlendNode : public Node +{ +public: + BlendNode(); + + NodeInput* base_input(); + + NodeInput* blend_input(); + +private: + NodeInput* base_input_; + + NodeInput* blend_input_; +}; + +#endif // BLEND_H diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 63706f918..17b1fae34 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -28,14 +28,14 @@ Block::Block() previous_input_->add_data_input(NodeParam::kBlock); AddParameter(previous_input_); - block_output_ = new NodeOutput("block_out"); - block_output_->set_data_type(NodeParam::kBlock); - AddParameter(block_output_); - next_input_ = new NodeInput("next_block"); next_input_->add_data_input(NodeParam::kBlock); AddParameter(next_input_); + block_output_ = new NodeOutput("block_out"); + block_output_->set_data_type(NodeParam::kBlock); + AddParameter(block_output_); + texture_output_ = new NodeOutput("tex_out"); texture_output_->set_data_type(NodeParam::kTexture); AddParameter(texture_output_); diff --git a/app/node/node.cpp b/app/node/node.cpp index 9ebd573d8..13b26a1b1 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -53,6 +53,11 @@ void Node::AddParameter(NodeParam *param) connect(param, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SIGNAL(EdgeRemoved(NodeEdgePtr))); } +void Node::RemoveParameter(NodeParam *param) +{ + delete param; +} + void Node::InvalidateCache(const rational &start_range, const rational &end_range) { QList params = parameters(); @@ -154,6 +159,25 @@ QList Node::GetExclusiveDependencies() return deps; } +bool Node::OutputsTo(Node *n) +{ + QList params = parameters(); + + foreach (NodeParam* param, params) { + if (param->type() == NodeParam::kOutput) { + QVector edges = param->edges(); + + foreach (NodeEdgePtr edge, edges) { + if (edge->input()->parent() == n) { + return true; + } + } + } + } + + return false; +} + QVariant Node::PtrToValue(void *ptr) { return reinterpret_cast(ptr); diff --git a/app/node/node.h b/app/node/node.h index bb479032a..53b02cb0a 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -121,6 +121,11 @@ public: */ QList GetExclusiveDependencies(); + /** + * @brief Returns whether this Node outputs data to the Node `n` in any way + */ + bool OutputsTo(Node* n); + /** * @brief Convert a pointer to a value that can be sent between NodeParams */ @@ -142,6 +147,13 @@ protected: */ void AddParameter(NodeParam* param); + /** + * @brief Deletes the parameter from this Node + * + * The NodeParam object is destroyed in the process. + */ + void RemoveParameter(NodeParam* param); + /** * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and * requires re-rendering diff --git a/app/node/output/CMakeLists.txt b/app/node/output/CMakeLists.txt index 0a09d0847..cac6ab041 100644 --- a/app/node/output/CMakeLists.txt +++ b/app/node/output/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see . add_subdirectory(timeline) +add_subdirectory(track) add_subdirectory(viewer) set(OLIVE_SOURCES diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index 41e9bf2d9..07777ddb9 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -26,19 +26,14 @@ #include "node/graph.h" TimelineOutput::TimelineOutput() : - current_block_(this), attached_timeline_(nullptr) { -} + track_input_ = new NodeInput("track_in"); + track_input_->add_data_input(NodeParam::kTrack); + AddParameter(track_input_); -Block::Type TimelineOutput::type() -{ - return kEnd; -} - -Block *TimelineOutput::copy() -{ - return new TimelineOutput(); + connect(this, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SLOT(TrackConnectionAdded(NodeEdgePtr))); + connect(this, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SLOT(TrackConnectionRemoved(NodeEdgePtr))); } QString TimelineOutput::Name() @@ -58,308 +53,110 @@ QString TimelineOutput::Category() QString TimelineOutput::Description() { - return tr("Node for communicating between a Timeline panel and the node graph. Also represents the end of a" - "Sequence."); + return tr("Node for communicating between a Timeline panel and the node graph."); } void TimelineOutput::AttachTimeline(TimelinePanel *timeline) { if (attached_timeline_ != nullptr) { - disconnect(attached_timeline_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); - disconnect(attached_timeline_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); + //TimelineView* view = attached_timeline_->view(); + //disconnect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); + //disconnect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); + + // Remove existing UI objects from TimelinePanel attached_timeline_->Clear(); } attached_timeline_ = timeline; if (attached_timeline_ != nullptr) { - attached_timeline_->Clear(); - // FIXME: TEST CODE ONLY attached_timeline_->SetTimebase(rational(1001, 30000)); // END TEST CODE - Block* previous_block = attached_block(); - while (previous_block != nullptr) { - - // FIXME: Dynamic cast is a dumb way of doing this - ClipBlock* clip_block = dynamic_cast(previous_block); - - if (clip_block != nullptr) { - attached_timeline_->AddClip(clip_block); - } - - previous_block = previous_block->previous(); + if (attached_track() != nullptr) { + // Defer to the track to make all the block UI items necessary + attached_track()->GenerateBlockWidgets(); } - TimelineView* view = attached_timeline_->view(); + //TimelineView* view = attached_timeline_->view(); - connect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); - connect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); + //connect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); + //connect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); } } -void TimelineOutput::set_length(const rational &) +NodeInput *TimelineOutput::track_input() { - // Prevent length changing on this Block -} - -void TimelineOutput::Refresh() -{ - QVector detect_attached_blocks; - - Block* previous = attached_block(); - while (previous != nullptr) { - detect_attached_blocks.prepend(previous); - - if (attached_timeline_ != nullptr && !block_cache_.contains(previous)) { - // FIXME: Remove the need to cast this - attached_timeline_->AddClip(static_cast(previous)); - } - - previous = previous->previous(); - } - - if (attached_timeline_ != nullptr) { - foreach (Block* b, block_cache_) { - if (!detect_attached_blocks.contains(b)) { - attached_timeline_->RemoveClip(static_cast(b)); - } - } - } - - block_cache_ = detect_attached_blocks; - - Block::Refresh(); + return track_input_; } void TimelineOutput::Process(const rational &time) { - // Run default node processing - Block::Process(time); + Q_UNUSED(time) +} - // This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence - if (time >= in()) { - texture_output()->set_value(0); - current_block_ = this; +TrackOutput *TimelineOutput::attached_track() +{ + return ValueToPtr(track_input_->get_value(0)); +} + +void TimelineOutput::TrackConnectionAdded(NodeEdgePtr edge) +{ + if (edge->input() != track_input()) { return; } - // If we're here, we need to find the current clip to display - // attached_block() is guaranteed to not be nullptr if we didn't return before - current_block_ = attached_block(); + TrackOutput* track = attached_track(); - // If the time requested is an earlier Block, traverse earlier until we find it - while (time < current_block_->in()) { - current_block_ = current_block_->previous(); + // Traverse through Tracks caching and connecting them + while (track != nullptr) { + track_cache_.append(track); + + connect(track, SIGNAL(BlockAdded(Block*)), this, SLOT(TrackAddedBlock(Block*))); + connect(track, SIGNAL(BlockRemoved(Block*)), this, SLOT(TrackRemovedBlock(Block*))); + + track->GenerateBlockWidgets(); + + track = track->next_track(); } - // If the time requested is in a later Block, traverse later - while (time >= current_block_->out()) { - current_block_ = current_block_->next(); + // FIXME: TEST CODE ONLY + if (attached_timeline_ != nullptr) { + attached_timeline_->SetTimebase(rational(1001, 30000)); } - - // At this point, we must have found the correct block so we use its texture output to produce the image - texture_output()->set_value(current_block_->texture_output()->get_value(time)); + // END TEST CODE } -void TimelineOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after) +void TimelineOutput::TrackConnectionRemoved(NodeEdgePtr edge) { - AddBlockToGraph(block); - - Block::DisconnectBlocks(before, after); - Block::ConnectBlocks(before, block); - Block::ConnectBlocks(block, after); -} - -void TimelineOutput::InsertBlockAfter(Block *block, Block *before) -{ - InsertBlockBetweenBlocks(block, before, before->next()); -} - -Block *TimelineOutput::attached_block() -{ - return ValueToPtr(previous_input()->get_value(0)); -} - -void TimelineOutput::PrependBlock(Block *block) -{ - AddBlockToGraph(block); - - if (block_cache_.isEmpty()) { - ConnectBlockInternal(block); - } else { - Block::ConnectBlocks(block, block_cache_.first()); - } -} - -void TimelineOutput::InsertBlockAtIndex(Block *block, int index) -{ - AddBlockToGraph(block); - - if (block_cache_.isEmpty()) { - - // If there are no blocks connected, the index doesn't matter. Just connect it. - ConnectBlockInternal(block); - - } else if (index == 0) { - - // If the index is 0, it goes at the very beginning - PrependBlock(block); - - } else if (index >= block_cache_.size()) { - - // Append Block at the end - AppendBlock(block); - - } else { - - // Insert Block just before the Block currently at that index so that it becomes the new Block at that index - InsertBlockBetweenBlocks(block, block_cache_.at(index - 1), block_cache_.at(index)); - - } -} - -void TimelineOutput::AppendBlock(Block *block) -{ - AddBlockToGraph(block); - - if (block_cache_.isEmpty()) { - ConnectBlockInternal(block); - } else { - InsertBlockBetweenBlocks(block, block_cache_.last(), this); - } -} - -void TimelineOutput::ConnectBlockInternal(Block *block) -{ - AddBlockToGraph(block); - - Block::ConnectBlocks(block, this); -} - -void TimelineOutput::AddBlockToGraph(Block *block) -{ - // Find the parent graph - NodeGraph* graph = static_cast(parent()); - graph->AddNodeWithDependencies(block); -} - -void TimelineOutput::PlaceBlock(Block *block, rational start) -{ - AddBlockToGraph(block); - - if (block->in() == start) { + if (edge->input() != track_input()) { return; } - // Place block at the beginning - if (start == 0) { - // FIXME: Remove existing - - PrependBlock(block); - return; + foreach (TrackOutput* track, track_cache_) { + disconnect(track, SIGNAL(BlockAdded(Block*)), this, SLOT(TrackAddedBlock(Block*))); + disconnect(track, SIGNAL(BlockRemoved(Block*)), this, SLOT(TrackRemovedBlock(Block*))); } - // Check if the placement location is past the end of the timeline - if (start >= in()) { - if (start > in()) { - // If so, insert a gap here - GapBlock* gap = new GapBlock(); - gap->set_length(start - in()); + track_cache_.clear(); - // Then append them - AppendBlock(gap); - } - - AppendBlock(block); - - return; - } - - // Check if the Block is placed at the in point of an existing Block, in which case a simple insert between will - // suffice - for (int i=1;iin() == start) { - Block* previous = block_cache_.at(i-1); - - // InsertBlockAtIndex() could work here, but this function is faster since we've already found the Blocks - InsertBlockBetweenBlocks(block, previous, comparison); - return; - } + if (attached_timeline_ != nullptr) { + attached_timeline_->Clear(); } } -void TimelineOutput::RemoveBlock(Block *block) +void TimelineOutput::TrackAddedBlock(Block *block) { - GapBlock* gap = new GapBlock(); - gap->set_length(block->length()); - - Block* previous = block->previous(); - Block* next = block->next(); - - // Remove block - RippleRemoveBlock(block); - - if (previous == nullptr) { - // Block must be at the beginning - PrependBlock(gap); - } else { - InsertBlockBetweenBlocks(gap, previous, next); + if (attached_timeline_ != nullptr) { + attached_timeline_->view()->AddBlock(block); } } -void TimelineOutput::RippleRemoveBlock(Block *block) +void TimelineOutput::TrackRemovedBlock(Block *block) { - Block* previous = block->previous(); - Block* next = block->next(); - - if (previous != nullptr) { - Block::DisconnectBlocks(previous, block); - } - - if (next != nullptr) { - Block::DisconnectBlocks(block, next); - } - - if (previous != nullptr && next != nullptr) { - Block::ConnectBlocks(previous, next); + if (attached_timeline_ != nullptr) { + attached_timeline_->view()->RemoveBlock(block); } } - -void TimelineOutput::SplitBlock(Block *block, rational time) -{ - if (time < block->in() || time >= block->out()) { - return; - } - - rational original_length = block->length(); - - block->set_length(time - block->in()); - - Block* copy = block->copy(); - copy->set_length(original_length - block->length()); - InsertBlockAfter(copy, block); -} - -void TimelineOutput::SpliceBlock(Block *inner, Block *outer, rational inner_in) -{ - Q_ASSERT(inner_in >= outer->in() && inner_in < outer->out()); - - // Cache original length - rational original_length = outer->length(); - - // Set outer clip to the clip that PRECEDES the inner clip - outer->set_length(inner_in - outer->in()); - - // Insert inner clip between BEFORE clip and its next clip - InsertBlockAfter(inner, outer); - - // Create the AFTER clip - Block* copy = outer->copy(); - copy->set_length(original_length - outer->length() - inner->length()); - InsertBlockAfter(copy, inner); -} diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index 89f14efe9..523a875f4 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -22,21 +22,18 @@ #define TIMELINEOUTPUT_H #include "node/block/block.h" +#include "node/output/track/track.h" #include "panel/timeline/timeline.h" /** * @brief Node that represents the end of the Timeline as well as a time traversal Node */ -class TimelineOutput : public Block +class TimelineOutput : public Node { Q_OBJECT public: TimelineOutput(); - virtual Type type() override; - - virtual Block* copy() override; - virtual QString Name() override; virtual QString id() override; virtual QString Category() override; @@ -44,101 +41,44 @@ public: void AttachTimeline(TimelinePanel* timeline); - virtual void set_length(const rational &length) override; - - virtual void Refresh() override; + NodeInput* track_input(); public slots: virtual void Process(const rational &time) override; private: - /** - * @brief Sets this Block as the only block in the Timeline (creating essentially a one clip sequence) - */ - void ConnectBlockInternal(Block* block); - - /** - * @brief Disconnects t - */ - void RemoveBlockInternal(); - - /** - * @brief Adds a Block to the parent graph so it can be connected to other Nodes - * - * Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs) - */ - void AddBlockToGraph(Block* block); - - Block* attached_block(); - - QVector block_cache_; - - Block* current_block_; + TrackOutput* attached_track(); TimelinePanel* attached_timeline_; + NodeInput* track_input_; + + /** + * @brief A cache of connected Tracks + */ + QVector track_cache_; + private slots: /** - * @brief Adds Block `block` at the very beginning of the Sequence before all other clips + * @brief Slot for when the track connection is added */ - void PrependBlock(Block* block); + void TrackConnectionAdded(NodeEdgePtr edge); /** - * @brief Inserts Block `block` at a specific index (0 is the start of the timeline) - * - * If the index == 0, this function does the same as PrependBlock(). If the index >= the current number of blocks, - * this function is the same as AppendBlock(). + * @brief Slot for when the track connection is removed */ - void InsertBlockAtIndex(Block* block, int index); + void TrackConnectionRemoved(NodeEdgePtr edge); /** - * @brief Inserts a Block between two other Blocks - * - * Disconnects `before` and `after`, and connects them to `block` with `block` in between. + * @brief Slot for when a connected Track has added a Block so we can update the UI */ - void InsertBlockBetweenBlocks(Block* block, Block* before, Block* after); + void TrackAddedBlock(Block* block); /** - * @brief Inserts Block after another Block - * - * Equivalent to calling InsertBlockBetweenBlocks(block, before, before->next()) + * @brief Slot for when a connected Track has added a Block so we can update the UI */ - void InsertBlockAfter(Block* block, Block* before); + void TrackRemovedBlock(Block* block); - /** - * @brief Adds Block `block` at the very end of the Sequence after all other clips - */ - void AppendBlock(Block* block); - - /** - * @brief Destructively places `block` at the in point `start` - * - * The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are - * either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of - * the Sequence, a GapBlock is inserted to compensate. - */ - void PlaceBlock(Block* block, rational start); - - /** - * @brief Removes a Block and places a Gap in its place - */ - void RemoveBlock(Block* block); - - /** - * @brief Removes a Block pushing all subsequent Blocks earlier to take up the space - */ - void RippleRemoveBlock(Block* block); - - /** - * @brief Splits `block` into two Blocks at the Sequence point `time` - */ - void SplitBlock(Block* block, rational time); - - /** - * @brief Inserts Block `inner` between Block `outer`, splitting and shortening it to fit without changing the overall - * length - */ - void SpliceBlock(Block* inner, Block* outer, rational inner_in); }; #endif // TIMELINEOUTPUT_H diff --git a/app/node/output/track/CMakeLists.txt b/app/node/output/track/CMakeLists.txt new file mode 100644 index 000000000..5f6cc9116 --- /dev/null +++ b/app/node/output/track/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/output/track/track.h + node/output/track/track.cpp + PARENT_SCOPE +) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp new file mode 100644 index 000000000..a12b44020 --- /dev/null +++ b/app/node/output/track/track.cpp @@ -0,0 +1,350 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "track.h" + +#include + +#include "node/block/gap/gap.h" +#include "node/graph.h" + +TrackOutput::TrackOutput() : + current_block_(this) +{ + track_input_ = new NodeInput("track_in"); + track_input_->add_data_input(NodeParam::kTrack); + AddParameter(track_input_); + + track_output_ = new NodeOutput("track_out"); + track_output_->set_data_type(NodeParam::kTrack); + AddParameter(track_output_); +} + +Block::Type TrackOutput::type() +{ + return kEnd; +} + +Block *TrackOutput::copy() +{ + return new TrackOutput(); +} + +QString TrackOutput::Name() +{ + return tr("Track"); +} + +QString TrackOutput::id() +{ + return "org.olivevideoeditor.Olive.track"; +} + +QString TrackOutput::Category() +{ + return tr("Output"); +} + +QString TrackOutput::Description() +{ + return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of " + "a Sequence."); +} + +void TrackOutput::set_length(const rational &) +{ + // Prevent length changing on this Block +} + +void TrackOutput::Refresh() +{ + QVector detect_attached_blocks; + + Block* previous = attached_block(); + while (previous != nullptr) { + detect_attached_blocks.prepend(previous); + + if (!block_cache_.contains(previous)) { + emit BlockAdded(previous); + } + + previous = previous->previous(); + } + + foreach (Block* b, block_cache_) { + if (!detect_attached_blocks.contains(b)) { + emit BlockRemoved(b); + } + } + + block_cache_ = detect_attached_blocks; + + Block::Refresh(); +} + +void TrackOutput::GenerateBlockWidgets() +{ + foreach (Block* block, block_cache_) { + emit BlockAdded(block); + } +} + +TrackOutput *TrackOutput::next_track() +{ + return ValueToPtr(track_input_->get_value(0)); +} + +NodeOutput* TrackOutput::track_output() +{ + return track_output_; +} + +void TrackOutput::Process(const rational &time) +{ + // Run default node processing + Block::Process(time); + + // Set track output correctly + track_output_->set_value(PtrToValue(this)); + + // This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence + if (time >= in()) { + texture_output()->set_value(0); + current_block_ = this; + return; + } + + // If we're here, we need to find the current clip to display + // attached_block() is guaranteed to not be nullptr if we didn't return before + current_block_ = attached_block(); + + // If the time requested is an earlier Block, traverse earlier until we find it + while (time < current_block_->in()) { + current_block_ = current_block_->previous(); + } + + // If the time requested is in a later Block, traverse later + while (time >= current_block_->out()) { + current_block_ = current_block_->next(); + } + + // At this point, we must have found the correct block so we use its texture output to produce the image + texture_output()->set_value(current_block_->texture_output()->get_value(time)); +} + +void TrackOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after) +{ + AddBlockToGraph(block); + + Block::DisconnectBlocks(before, after); + Block::ConnectBlocks(before, block); + Block::ConnectBlocks(block, after); +} + +void TrackOutput::InsertBlockAfter(Block *block, Block *before) +{ + InsertBlockBetweenBlocks(block, before, before->next()); +} + +Block *TrackOutput::attached_block() +{ + return ValueToPtr(previous_input()->get_value(0)); +} + +void TrackOutput::PrependBlock(Block *block) +{ + AddBlockToGraph(block); + + if (block_cache_.isEmpty()) { + ConnectBlockInternal(block); + } else { + Block::ConnectBlocks(block, block_cache_.first()); + } +} + +void TrackOutput::InsertBlockAtIndex(Block *block, int index) +{ + AddBlockToGraph(block); + + if (block_cache_.isEmpty()) { + + // If there are no blocks connected, the index doesn't matter. Just connect it. + ConnectBlockInternal(block); + + } else if (index == 0) { + + // If the index is 0, it goes at the very beginning + PrependBlock(block); + + } else if (index >= block_cache_.size()) { + + // Append Block at the end + AppendBlock(block); + + } else { + + // Insert Block just before the Block currently at that index so that it becomes the new Block at that index + InsertBlockBetweenBlocks(block, block_cache_.at(index - 1), block_cache_.at(index)); + + } +} + +void TrackOutput::AppendBlock(Block *block) +{ + AddBlockToGraph(block); + + if (block_cache_.isEmpty()) { + ConnectBlockInternal(block); + } else { + InsertBlockBetweenBlocks(block, block_cache_.last(), this); + } +} + +void TrackOutput::ConnectBlockInternal(Block *block) +{ + AddBlockToGraph(block); + + Block::ConnectBlocks(block, this); +} + +void TrackOutput::AddBlockToGraph(Block *block) +{ + // Find the parent graph + NodeGraph* graph = static_cast(parent()); + graph->AddNodeWithDependencies(block); +} + +void TrackOutput::PlaceBlock(Block *block, rational start) +{ + AddBlockToGraph(block); + + if (block->in() == start) { + return; + } + + // Place block at the beginning + if (start == 0) { + // FIXME: Remove existing + + PrependBlock(block); + return; + } + + // Check if the placement location is past the end of the timeline + if (start >= in()) { + if (start > in()) { + // If so, insert a gap here + GapBlock* gap = new GapBlock(); + gap->set_length(start - in()); + + // Then append them + AppendBlock(gap); + } + + AppendBlock(block); + + return; + } + + // Check if the Block is placed at the in point of an existing Block, in which case a simple insert between will + // suffice + for (int i=1;iin() == start) { + Block* previous = block_cache_.at(i-1); + + // InsertBlockAtIndex() could work here, but this function is faster since we've already found the Blocks + InsertBlockBetweenBlocks(block, previous, comparison); + return; + } + } +} + +void TrackOutput::RemoveBlock(Block *block) +{ + GapBlock* gap = new GapBlock(); + gap->set_length(block->length()); + + Block* previous = block->previous(); + Block* next = block->next(); + + // Remove block + RippleRemoveBlock(block); + + if (previous == nullptr) { + // Block must be at the beginning + PrependBlock(gap); + } else { + InsertBlockBetweenBlocks(gap, previous, next); + } +} + +void TrackOutput::RippleRemoveBlock(Block *block) +{ + Block* previous = block->previous(); + Block* next = block->next(); + + if (previous != nullptr) { + Block::DisconnectBlocks(previous, block); + } + + if (next != nullptr) { + Block::DisconnectBlocks(block, next); + } + + if (previous != nullptr && next != nullptr) { + Block::ConnectBlocks(previous, next); + } +} + +void TrackOutput::SplitBlock(Block *block, rational time) +{ + if (time < block->in() || time >= block->out()) { + return; + } + + rational original_length = block->length(); + + block->set_length(time - block->in()); + + Block* copy = block->copy(); + copy->set_length(original_length - block->length()); + InsertBlockAfter(copy, block); +} + +void TrackOutput::SpliceBlock(Block *inner, Block *outer, rational inner_in) +{ + Q_ASSERT(inner_in >= outer->in() && inner_in < outer->out()); + + // Cache original length + rational original_length = outer->length(); + + // Set outer clip to the clip that PRECEDES the inner clip + outer->set_length(inner_in - outer->in()); + + // Insert inner clip between BEFORE clip and its next clip + InsertBlockAfter(inner, outer); + + // Create the AFTER clip + Block* copy = outer->copy(); + copy->set_length(original_length - outer->length() - inner->length()); + InsertBlockAfter(copy, inner); +} diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h new file mode 100644 index 000000000..6638c4160 --- /dev/null +++ b/app/node/output/track/track.h @@ -0,0 +1,161 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TRACKOUTPUT_H +#define TRACKOUTPUT_H + +#include "node/block/block.h" +#include "panel/timeline/timeline.h" + +/** + * @brief A time traversal Node for sorting through one channel/track of Blocks + */ +class TrackOutput : public Block +{ + Q_OBJECT +public: + TrackOutput(); + + virtual Type type() override; + + virtual Block* copy() override; + + virtual QString Name() override; + virtual QString id() override; + virtual QString Category() override; + virtual QString Description() override; + + virtual void set_length(const rational &length) override; + + virtual void Refresh() override; + + void GenerateBlockWidgets(); + + TrackOutput* next_track(); + + NodeOutput *track_output(); + +signals: + /** + * @brief Signal emitted when a Block is added to this Track + */ + void BlockAdded(Block* block); + + /** + * @brief Signal emitted when a Block is removed from this Track + */ + void BlockRemoved(Block* block); + +public slots: + virtual void Process(const rational &time) override; + +private: + /** + * @brief Sets this Block as the only block in the Timeline (creating essentially a one clip sequence) + */ + void ConnectBlockInternal(Block* block); + + /** + * @brief Disconnects t + */ + void RemoveBlockInternal(); + + /** + * @brief Adds a Block to the parent graph so it can be connected to other Nodes + * + * Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs) + */ + void AddBlockToGraph(Block* block); + + Block* attached_block(); + + QVector block_cache_; + + Block* current_block_; + + NodeInput* track_input_; + + NodeOutput* track_output_; + +private slots: + /** + * @brief Adds Block `block` at the very beginning of the Sequence before all other clips + */ + void PrependBlock(Block* block); + + /** + * @brief Inserts Block `block` at a specific index (0 is the start of the timeline) + * + * If the index == 0, this function does the same as PrependBlock(). If the index >= the current number of blocks, + * this function is the same as AppendBlock(). + */ + void InsertBlockAtIndex(Block* block, int index); + + /** + * @brief Inserts a Block between two other Blocks + * + * Disconnects `before` and `after`, and connects them to `block` with `block` in between. + */ + void InsertBlockBetweenBlocks(Block* block, Block* before, Block* after); + + /** + * @brief Inserts Block after another Block + * + * Equivalent to calling InsertBlockBetweenBlocks(block, before, before->next()) + */ + void InsertBlockAfter(Block* block, Block* before); + + /** + * @brief Adds Block `block` at the very end of the Sequence after all other clips + */ + void AppendBlock(Block* block); + + /** + * @brief Destructively places `block` at the in point `start` + * + * The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are + * either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of + * the Sequence, a GapBlock is inserted to compensate. + */ + void PlaceBlock(Block* block, rational start); + + /** + * @brief Removes a Block and places a Gap in its place + */ + void RemoveBlock(Block* block); + + /** + * @brief Removes a Block pushing all subsequent Blocks earlier to take up the space + */ + void RippleRemoveBlock(Block* block); + + /** + * @brief Splits `block` into two Blocks at the Sequence point `time` + */ + void SplitBlock(Block* block, rational time); + + /** + * @brief Inserts Block `inner` between Block `outer`, splitting and shortening it to fit without changing the overall + * length + */ + void SpliceBlock(Block* inner, Block* outer, rational inner_in); +}; + +#endif // TRACKOUTPUT_H diff --git a/app/node/param.cpp b/app/node/param.cpp index 5e59619f7..da1d53b92 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -134,6 +134,18 @@ NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) } } + // Refuse to make a connection that is incompatible + if (!input->can_accept_type(output->data_type())) { + qWarning() << tr("Tried to make an invalid Node connection"); + return nullptr; + } + + // Ensure both nodes are in the same graph + if (output->parent()->parent() != input->parent()->parent()) { + qWarning() << tr("Tried to connect two nodes that aren't part of the same graph"); + return nullptr; + } + NodeEdgePtr edge = std::make_shared(output, input); output->edges_.append(edge); @@ -196,6 +208,7 @@ QString NodeParam::GetDefaultDataTypeName(const DataType& type) case kMatrix: return tr("Matrix"); case kBlock: return tr("Block"); case kFootage: return tr("Footage"); + case kTrack: return tr("Track"); case kAny: return tr("Any"); } diff --git a/app/node/param.h b/app/node/param.h index eaf718694..573c33f70 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -66,6 +66,7 @@ public: kMatrix, kBlock, kFootage, + kTrack, kAny }; diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index ee2cddc55..31401d7fa 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -25,7 +25,6 @@ RendererProcessor::RendererProcessor() : started_(false) { - } QString RendererProcessor::Name() @@ -45,6 +44,8 @@ QString RendererProcessor::Description() void RendererProcessor::Process(const rational &time) { + Q_UNUSED(time) + /* // Ensure we have started Start(); @@ -52,6 +53,8 @@ void RendererProcessor::Process(const rational &time) qWarning() << tr("An error occurred starting the Renderer node"); return; } + + */ } void RendererProcessor::Release() diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index 4cbbfab19..3d22fbb14 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -67,6 +67,15 @@ public: */ void SetParameters(const int& width, const int& height, const olive::PixelFormat& format); + /** + * @brief Return current instance of a RenderThread (or nullptr if there is none) + * + * This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if + * the cast fails (e.g. if this function is called from the main thread rather than a RendererThread). + */ + static RendererThread *CurrentThread(); + +private: /** * @brief Allocate and start the multithreaded backend */ @@ -77,15 +86,6 @@ public: */ void Stop(); - /** - * @brief Return current instance of a RenderThread (or nullptr if there is none) - * - * This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if - * the cast fails (e.g. if this function is called from the main thread rather than a RendererThread). - */ - static RendererThread *CurrentThread(); - -private: QVector threads_; bool started_; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 88f0c2468..a4e38375a 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -55,16 +55,6 @@ void TimelinePanel::Clear() view_->Clear(); } -void TimelinePanel::AddClip(ClipBlock *clip) -{ - view_->AddBlock(clip); -} - -void TimelinePanel::RemoveClip(ClipBlock *clip) -{ - view_->RemoveBlock(clip); -} - void TimelinePanel::SetTimebase(const rational &timebase) { ruler_->SetTimebase(timebase); diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index f1996e293..85b7672c6 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -33,10 +33,6 @@ public: void Clear(); - void AddClip(ClipBlock* clip); - - void RemoveClip(ClipBlock* clip); - void SetTimebase(const rational& timebase); TimelineView* view(); diff --git a/app/render/pixelservice.cpp b/app/render/pixelservice.cpp index f66be7f31..fc9815f83 100644 --- a/app/render/pixelservice.cpp +++ b/app/render/pixelservice.cpp @@ -161,7 +161,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm } break; } - case olive::PIX_FMT_RGBA16F: // 16-bit Integer -> 16-bit Integer + case olive::PIX_FMT_RGBA16F: // 16-bit Integer -> 16-bit Float { qfloat16* destination = reinterpret_cast(converted->data()); for (int i=0;i 16-bit Integer + case olive::PIX_FMT_RGBA32F: // 16-bit Integer -> 32-bit Float { float* destination = reinterpret_cast(converted->data()); for (int i=0;i