/*** Olive - Non-Linear Video Editor Copyright (C) 2020 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 "input.h" #include "common/bezier.h" #include "common/lerp.h" #include "common/xmlutils.h" #include "node.h" #include "project/item/footage/stream.h" #define super NodeConnectable namespace olive { NodeInput::NodeInput(Node* parent, const QString& id, NodeValue::Type type, const QVector &default_value) { Init(parent, id, type, default_value); } NodeInput::NodeInput(Node *parent, const QString &id, NodeValue::Type type, const QVariant &default_value) { Init(parent, id, type, NodeValue::split_normal_value_into_track_values(type, default_value)); } NodeInput::NodeInput(Node* parent, const QString &id, NodeValue::Type type) { Init(parent, id, type, QVector()); } NodeInput::~NodeInput() { // Disconnect everything DisconnectAll(); // Delete instances delete primary_; qDeleteAll(subinputs_); } QString NodeInput::name() const { if (name_.isEmpty()) { return tr("Input"); } return name_; } void NodeInput::DisconnectAll() { std::map copied_edges = edges(); for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) { DisconnectEdge(it->second, this, it->first); } } void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) { while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("primary")) { // Load primary immediate LoadImmediate(reader, -1, xml_node_data, cancelled); } else if (reader->name() == QStringLiteral("subelements")) { // Load subelements XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("count")) { ArrayResize(attr.value().toInt()); } } int element_counter = 0; while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("element")) { LoadImmediate(reader, element_counter, xml_node_data, cancelled); element_counter++; } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("connections")) { // Load connections while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("connection")) { int ele = -1; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("element")) { ele = attr.value().toInt(); } } xml_node_data.desired_connections.append({this, ele, reader->readElementText().toULongLong()}); } else { reader->skipCurrentElement(); } } } else { reader->skipCurrentElement(); } } } void NodeInput::Save(QXmlStreamWriter *writer) const { writer->writeAttribute(QStringLiteral("id"), id()); writer->writeStartElement(QStringLiteral("primary")); SaveImmediate(writer, -1); writer->writeEndElement(); // primary writer->writeStartElement(QStringLiteral("subelements")); writer->writeAttribute(QStringLiteral("count"), QString::number(array_size_)); for (int i=0; iwriteStartElement(QStringLiteral("element")); SaveImmediate(writer, i); writer->writeEndElement(); // element } writer->writeEndElement(); // subelements writer->writeStartElement(QStringLiteral("connections")); for (auto it=input_connections().cbegin(); it!=input_connections().cend(); it++) { writer->writeStartElement(QStringLiteral("connection")); writer->writeAttribute(QStringLiteral("element"), QString::number(it->first)); writer->writeCharacters(QString::number(reinterpret_cast(it->second))); writer->writeEndElement(); // connection } writer->writeEndElement(); // connections } Node *NodeInput::parent() const { return static_cast(QObject::parent()); } bool NodeInput::event(QEvent *e) { if (e->type() == QEvent::DynamicPropertyChange) { QByteArray key = static_cast(e)->propertyName(); emit PropertyChanged(key, property(key)); return true; } return QObject::event(e); } void NodeInput::childEvent(QChildEvent *event) { super::childEvent(event); NodeKeyframe* key = dynamic_cast(event->child()); if (key) { if (event->type() == QEvent::ChildAdded) { GetImmediate(key->element())->insert_keyframe(key); connect(key, &NodeKeyframe::TimeChanged, this, &NodeInput::KeyframeTimeChanged); connect(key, &NodeKeyframe::ValueChanged, this, &NodeInput::KeyframeValueChanged); connect(key, &NodeKeyframe::TypeChanged, this, &NodeInput::KeyframeTypeChanged); connect(key, &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); connect(key, &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); emit KeyframeAdded(key); emit ValueChanged(get_range_affected_by_keyframe(key), key->element()); } else if (event->type() == QEvent::ChildRemoved) { TimeRange time_affected = get_range_affected_by_keyframe(key); disconnect(key, &NodeKeyframe::TimeChanged, this, &NodeInput::KeyframeTimeChanged); disconnect(key, &NodeKeyframe::ValueChanged, this, &NodeInput::KeyframeValueChanged); disconnect(key, &NodeKeyframe::TypeChanged, this, &NodeInput::KeyframeTypeChanged); disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &NodeInput::KeyframeBezierInChanged); disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &NodeInput::KeyframeBezierOutChanged); GetImmediate(key->element())->remove_keyframe(key); emit KeyframeRemoved(key); emit ValueChanged(time_affected, key->element()); } } } void NodeInput::Init(Node* parent, const QString &id, NodeValue::Type type, const QVector& default_val) { setParent(parent); id_ = id; keyframable_ = true; connectable_ = true; default_value_ = default_val; array_size_ = 0; data_type_ = type; primary_ = CreateImmediate(); } void NodeInput::LoadImmediate(QXmlStreamReader *reader, int element, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) { if (reader->name() == QStringLiteral("standard")) { // Load standard value int val_index = 0; while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("value")) { QString value_text = reader->readElementText(); QVariant value_on_track; if (!value_text.isEmpty()) { value_on_track = StringToValue(value_text, xml_node_data.footage_connections, element); } SetStandardValueOnTrack(value_on_track, val_index, element); val_index++; } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("keyframing")) { SetIsKeyframing(reader->readElementText().toInt(), element); } else if (reader->name() == QStringLiteral("keyframes")) { int track = 0; while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("track")) { while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { return; } if (reader->name() == QStringLiteral("key")) { rational key_time; NodeKeyframe::Type key_type; QVariant key_value; QPointF key_in_handle; QPointF key_out_handle; XMLAttributeLoop(reader, attr) { if (cancelled && *cancelled) { return; } if (attr.name() == QStringLiteral("time")) { key_time = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("type")) { key_type = static_cast(attr.value().toInt()); } else if (attr.name() == QStringLiteral("inhandlex")) { key_in_handle.setX(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("inhandley")) { key_in_handle.setY(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("outhandlex")) { key_out_handle.setX(attr.value().toDouble()); } else if (attr.name() == QStringLiteral("outhandley")) { key_out_handle.setY(attr.value().toDouble()); } } key_value = StringToValue(reader->readElementText(), xml_node_data.footage_connections, element); NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, this); key->set_bezier_control_in(key_in_handle); key->set_bezier_control_out(key_out_handle); } else { reader->skipCurrentElement(); } } track++; } else { reader->skipCurrentElement(); } } } else if (reader->name() == QStringLiteral("csinput")) { setProperty("col_input", reader->readElementText()); } else if (reader->name() == QStringLiteral("csdisplay")) { setProperty("col_display", reader->readElementText()); } else if (reader->name() == QStringLiteral("csview")) { setProperty("col_view", reader->readElementText()); } else if (reader->name() == QStringLiteral("cslook")) { setProperty("col_look", reader->readElementText()); } else { reader->skipCurrentElement(); } } void NodeInput::SaveImmediate(QXmlStreamWriter* writer, int element) const { writer->writeTextElement(QStringLiteral("keyframing"), QString::number(IsKeyframing(element))); // Write standard value writer->writeStartElement("standard"); foreach (const QVariant& v, GetSplitStandardValue(element)) { writer->writeTextElement("value", ValueToString(v)); } writer->writeEndElement(); // standard // Write keyframes writer->writeStartElement("keyframes"); foreach (const NodeKeyframeTrack& track, GetKeyframeTracks(element)) { writer->writeStartElement("track"); foreach (NodeKeyframe* key, track) { writer->writeStartElement("key"); writer->writeAttribute("time", key->time().toString()); writer->writeAttribute("type", QString::number(key->type())); writer->writeAttribute("inhandlex", QString::number(key->bezier_control_in().x())); writer->writeAttribute("inhandley", QString::number(key->bezier_control_in().y())); writer->writeAttribute("outhandlex", QString::number(key->bezier_control_out().x())); writer->writeAttribute("outhandley", QString::number(key->bezier_control_out().y())); writer->writeCharacters(ValueToString(key->value())); writer->writeEndElement(); // key } writer->writeEndElement(); // track } writer->writeEndElement(); // keyframes if (data_type_ == NodeValue::kColor) { // Save color management information writer->writeTextElement(QStringLiteral("csinput"), property("col_input").toString()); writer->writeTextElement(QStringLiteral("csdisplay"), property("col_display").toString()); writer->writeTextElement(QStringLiteral("csview"), property("col_view").toString()); writer->writeTextElement(QStringLiteral("cslook"), property("col_look").toString()); } } void NodeInput::ChangeArraySizeInternal(int size) { array_size_ = size; emit ArraySizeChanged(array_size_); emit ValueChanged(TimeRange(RATIONAL_MIN, RATIONAL_MAX), -1); } NodeInputImmediate *NodeInput::CreateImmediate() { return new NodeInputImmediate(data_type_, default_value_); } void NodeInput::GetDependencies(QVector &list, bool traverse, bool exclusive_only) const { for (auto it=input_connections().cbegin(); it!=input_connections().cend(); it++) { Node* connected = it->second; if (connected->edges().size() == 1 || !exclusive_only) { if (!list.contains(connected)) { list.append(connected); if (traverse) { foreach (NodeInput* i, connected->parameters()) { i->GetDependencies(list, traverse, exclusive_only); } } } } } } QVariant NodeInput::GetDefaultValueForTrack(int track) const { if (default_value_.isEmpty()) { return QVariant(); } return default_value_.at(track); } QVector NodeInput::GetDependencies(bool traverse, bool exclusive_only) const { QVector list; GetDependencies(list, traverse, exclusive_only); return list; } QVector NodeInput::GetExclusiveDependencies() const { return GetDependencies(true, true); } QVector NodeInput::GetImmediateDependencies() const { return GetDependencies(false, false); } void NodeInput::ArrayInsert(int index) { // Add new input subinputs_.insert(index, CreateImmediate()); ChangeArraySizeInternal(array_size_ + 1); // Move connections down std::map copied_edges = edges(); for (auto it=copied_edges.crbegin(); it!=copied_edges.crend(); it++) { if (it->first >= index) { // Disconnect this and reconnect it one element down DisconnectEdge(it->second, this, it->first); ConnectEdge(it->second, this, it->first + 1); } } } void NodeInput::ArrayRemove(int index) { // Remove input delete subinputs_.takeAt(index); ChangeArraySizeInternal(array_size_ - 1); // Move connections up std::map copied_edges = edges(); for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) { if (it->first >= index) { // Disconnect this and reconnect it one element up if it's not the element being removed DisconnectEdge(it->second, this, it->first); if (it->first > index) { ConnectEdge(it->second, this, it->first - 1); } } } } void NodeInput::ArrayPrepend() { ArrayInsert(0); } void NodeInput::ArrayResize(int size) { if (array_size_ != size) { // Update array size ChangeArraySizeInternal(size); if (array_size_ > size) { // Decreasing in size, disconnect any extraneous edges for (int i=size; i NodeInput::GetSplitValuesAtTime(const rational &time, int element) const { QVector vals; int nb_tracks = GetNumberOfKeyframeTracks(); for (int i=0;itime() >= time) { // This time precedes any keyframe, so we just return the first value return key_track.first()->value(); } if (key_track.last()->time() <= time) { // This time is after any keyframes so we return the last value return key_track.last()->value(); } // If we're here, the time must be somewhere in between the keyframes for (int i=0;itime() == time || !NodeValue::type_can_be_interpolated(data_type_) || (before->time() < time && before->type() == NodeKeyframe::kHold)) { // Time == keyframe time, so value is precise return before->value(); } else if (after->time() == time) { // Time == keyframe time, so value is precise return after->value(); } else if (before->time() < time && after->time() > time) { // We must interpolate between these keyframes if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Perform a cubic bezier with two control points double t = Bezier::CubicXtoT(time.toDouble(), before->time().toDouble(), before->time().toDouble() + before->bezier_control_out().x(), after->time().toDouble() + after->bezier_control_in().x(), after->time().toDouble()); double y = Bezier::CubicTtoY(before->value().toDouble(), before->value().toDouble() + before->bezier_control_out().y(), after->value().toDouble() + after->bezier_control_in().y(), after->value().toDouble(), t); return y; } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Perform a quadratic bezier with only one control point QPointF control_point; double control_point_time; double control_point_value; if (before->type() == NodeKeyframe::kBezier) { control_point = before->bezier_control_out(); control_point_time = before->time().toDouble() + control_point.x(); control_point_value = before->value().toDouble() + control_point.y(); } else { control_point = after->bezier_control_in(); control_point_time = after->time().toDouble() + control_point.x(); control_point_value = after->value().toDouble() + control_point.y(); } // Generate T from time values - used to determine bezier progress double t = Bezier::QuadraticXtoT(time.toDouble(), before->time().toDouble(), control_point_time, after->time().toDouble()); // Generate value using T double y = Bezier::QuadraticTtoY(before->value().toDouble(), control_point_value, after->value().toDouble(), t); return y; } else { // To have arrived here, the keyframes must both be linear qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble()); return lerp(before->value().toDouble(), after->value().toDouble(), period_progress); } } } } return GetStandardValueOnTrack(track, element); } bool NodeInput::IsKeyframable() const { return keyframable_; } void NodeInput::SetKeyframable(bool k) { keyframable_ = k; } void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections, bool traverse_arrays) { Q_ASSERT(source->id() == dest->id()); CopyValuesOfElement(source, dest, -1); // Copy array size dest->ArrayResize(source->ArraySize()); // If enabled, copy arrays too if (traverse_arrays) { for (int i=0; iArraySize(); i++) { CopyValuesOfElement(source, dest, i); } } // Copy connections if (include_connections) { if (traverse_arrays) { // Copy all connections for (auto it=source->input_connections().cbegin(); it!=source->input_connections().cend(); it++) { ConnectEdge(it->second, dest, it->first); } } else { // Just copy the primary connection (at -1) if (source->IsConnected()) { ConnectEdge(source->GetConnectedNode(), dest); } } } } void NodeInput::CopyValuesOfElement(NodeInput *src, NodeInput *dst, int element) { // Copy standard value dst->SetSplitStandardValue(src->GetSplitStandardValue(element), element); // Copy keyframes foreach (const NodeKeyframeTrack& track, src->GetImmediate(element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { key->copy(dst); } } // Copy keyframing state if (src->IsKeyframable()) { dst->SetIsKeyframing(src->IsKeyframing(element), element); } // If this is the root of an array, copy the array size if (element == -1) { dst->ArrayResize(src->ArraySize()); } } QStringList NodeInput::get_combobox_strings() const { return property("combo_str").toStringList(); } void NodeInput::set_combobox_strings(const QStringList &strings) { setProperty("combo_str", strings); } void NodeInput::KeyframeBezierInChanged() { NodeKeyframe* key = static_cast(sender()); const NodeKeyframeTrack& track = GetTrackFromKeyframe(key); int keyframe_index = track.indexOf(key); rational start = RATIONAL_MIN; rational end = key->time(); if (keyframe_index > 0) { start = track.at(keyframe_index - 1)->time(); } emit ValueChanged(TimeRange(start, end), key->element()); } void NodeInput::KeyframeBezierOutChanged() { NodeKeyframe* key = static_cast(sender()); const NodeKeyframeTrack& track = GetTrackFromKeyframe(key); int keyframe_index = track.indexOf(key); rational start = key->time(); rational end = RATIONAL_MAX; if (keyframe_index < track.size() - 1) { end = track.at(keyframe_index + 1)->time(); } emit ValueChanged(TimeRange(start, end), key->element()); } void NodeInput::KeyframeTimeChanged() { NodeKeyframe* key = static_cast(sender()); NodeInputImmediate* immediate = GetImmediate(key->element()); TimeRange original_range = get_range_affected_by_keyframe(key); TimeRangeList invalidate_range; invalidate_range.insert(original_range); if (!(original_range.in() < key->time() && original_range.out() > key->time())) { // This keyframe needs resorting, store it and remove it from the list immediate->remove_keyframe(key); // Automatically insertion sort immediate->insert_keyframe(key); // Invalidate new area that the keyframe has been moved to invalidate_range.insert(get_range_affected_by_keyframe(key)); } // Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it // was resorted in the if block above) foreach (const TimeRange& r, invalidate_range) { emit ValueChanged(r, key->element()); } } void NodeInput::KeyframeValueChanged() { NodeKeyframe* key = static_cast(sender()); emit ValueChanged(get_range_affected_by_keyframe(key), key->element()); } void NodeInput::KeyframeTypeChanged() { NodeKeyframe* key = static_cast(sender()); const NodeKeyframeTrack& track = GetTrackFromKeyframe(key); if (track.size() == 1) { // If there are no other frames, the interpolation won't do anything return; } // Invalidate entire range emit ValueChanged(get_range_around_index(track.indexOf(key), key->track(), key->element()), key->element()); } TimeRange NodeInput::get_range_affected_by_keyframe(NodeKeyframe *key) const { const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key); int keyframe_index = key_track.indexOf(key); TimeRange range = get_range_around_index(keyframe_index, key->track(), key->element()); // If a previous key exists and it's a hold, we don't need to invalidate those frames if (key_track.size() > 1 && keyframe_index > 0 && key_track.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) { range.set_in(key->time()); } return range; } TimeRange NodeInput::get_range_around_index(int index, int track, int element) const { rational range_begin = RATIONAL_MIN; rational range_end = RATIONAL_MAX; const NodeKeyframeTrack& key_track = GetImmediate(element)->keyframe_tracks().at(track); if (key_track.size() > 1) { if (index > 0) { // If this is not the first key, we'll need to limit it to the key just before range_begin = key_track.at(index - 1)->time(); } if (index < key_track.size() - 1) { // If this is not the last key, we'll need to limit it to the key just after range_end = key_track.at(index + 1)->time(); } } return TimeRange(range_begin, range_end); } QString NodeInput::ValueToString(const QVariant &value) const { return NodeValue::ValueToString(data_type_, value, true); } QVariant NodeInput::StringToValue(const QString &string, QList& footage_connections, int element) { if (data_type_ == NodeValue::kFootage) { footage_connections.append({this, element, string.toULongLong()}); } return NodeValue::StringToValue(data_type_, string, true); } }