X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=src%2Frotoscoping%2Frotowidget.cpp;h=15ca31e618ab43b6e1839d3cf59694caa4e9a981;hb=c8a0bb427a030b1dab68182426e58c3c651543df;hp=2d9e0adcfff06731145e7da92d1375181e5ea540;hpb=bbc9a66eae6d7b06727543a2cb53f5a7efc8289b;p=kdenlive diff --git a/src/rotoscoping/rotowidget.cpp b/src/rotoscoping/rotowidget.cpp index 2d9e0adc..15ca31e6 100644 --- a/src/rotoscoping/rotowidget.cpp +++ b/src/rotoscoping/rotowidget.cpp @@ -23,19 +23,28 @@ #include "monitoreditwidget.h" #include "onmonitoritems/rotoscoping/bpointitem.h" #include "onmonitoritems/rotoscoping/splineitem.h" +#include "simplekeyframes/simplekeyframewidget.h" +#include "kdenlivesettings.h" + +#include #include #include +#include + -RotoWidget::RotoWidget(QString data, Monitor *monitor, int in, int out, QWidget* parent) : +RotoWidget::RotoWidget(QString data, Monitor *monitor, int in, int out, Timecode t, QWidget* parent) : QWidget(parent), m_monitor(monitor), m_showScene(true), m_in(in), - m_out(out), - m_pos(0) + m_out(out) { + QVBoxLayout *l = new QVBoxLayout(this); + m_keyframeWidget = new SimpleKeyframeWidget(t, m_out - m_in, this); + l->addWidget(m_keyframeWidget); + MonitorEditWidget *edit = monitor->getEffectEdit(); edit->showVisibilityButton(true); m_scene = edit->getScene(); @@ -47,27 +56,51 @@ RotoWidget::RotoWidget(QString data, Monitor *monitor, int in, int out, QWidget* // :( } - int width = m_monitor->render->frameRenderWidth(); - int height = m_monitor->render->renderHeight(); - QList points; - foreach (const QVariant &bpoint, m_data.toList()) { - QList l = bpoint.toList(); - BPoint p; - p.h1 = QPointF(l.at(0).toList().at(0).toDouble() * width, l.at(0).toList().at(1).toDouble() * height); - p.p = QPointF(l.at(1).toList().at(0).toDouble() * width, l.at(2).toList().at(1).toDouble() * height); - p.h2 = QPointF(l.at(2).toList().at(0).toDouble() * width, l.at(2).toList().at(1).toDouble() * height); - points << p; + + if (m_data.canConvert(QVariant::Map)) { + /* + * pass keyframe data to keyframe timeline + */ + QList keyframes; + QMap map = m_data.toMap(); + QMap ::const_iterator i = map.constBegin(); + while (i != map.constEnd()) { + keyframes.append(i.key().toInt() - m_in); + ++i; + } + m_keyframeWidget->setKeyframes(keyframes); + + for (int j = 0; j < keyframes.count(); ++j) { + // key might already be justified + if (map.contains(QString::number(keyframes.at(j) + m_in))) { + QVariant value = map.take(QString::number(keyframes.at(j) + m_in)); + map[QString::number(keyframes.at(j) + m_in).rightJustified(log10((double)m_out) + 1, '0')] = value; + } + } + m_data = QVariant(map); + } else { + // static (only one keyframe) + m_keyframeWidget->setKeyframes(QList () << 0); } - m_item = new SplineItem(points, NULL, m_scene); + m_item = new SplineItem(QList (), NULL, m_scene); - connect(m_item, SIGNAL(changed()), this, SLOT(slotUpdateData())); + connect(m_item, SIGNAL(changed(bool)), this, SLOT(slotUpdateData(bool))); connect(edit, SIGNAL(showEdit(bool)), this, SLOT(slotShowScene(bool))); connect(m_monitor, SIGNAL(renderPosition(int)), this, SLOT(slotCheckMonitorPosition(int))); + connect(m_keyframeWidget, SIGNAL(positionChanged(int)), this, SLOT(slotPositionChanged(int))); + connect(m_keyframeWidget, SIGNAL(keyframeAdded(int)), this, SLOT(slotAddKeyframe(int))); + connect(m_keyframeWidget, SIGNAL(keyframeRemoved(int)), this, SLOT(slotRemoveKeyframe(int))); + connect(m_keyframeWidget, SIGNAL(keyframeMoved(int,int)), this, SLOT(slotMoveKeyframe(int,int))); + connect(m_scene, SIGNAL(addKeyframe()), this, SLOT(slotAddKeyframe())); + + slotPositionChanged(0, false); } RotoWidget::~RotoWidget() { + delete m_keyframeWidget; + m_scene->removeItem(m_item); delete m_item; @@ -87,7 +120,9 @@ void RotoWidget::slotCheckMonitorPosition(int renderPos) void RotoWidget::slotSyncPosition(int relTimelinePos) { - Q_UNUSED(relTimelinePos); + relTimelinePos = qBound(0, relTimelinePos, m_out); + m_keyframeWidget->slotSetPosition(relTimelinePos, false); + slotPositionChanged(relTimelinePos, false); } void RotoWidget::slotShowScene(bool show) @@ -99,11 +134,16 @@ void RotoWidget::slotShowScene(bool show) slotCheckMonitorPosition(m_monitor->render->seekFramePosition()); } -void RotoWidget::slotUpdateData() +void RotoWidget::slotUpdateData(int pos, bool editing) { + Q_UNUSED(editing) + int width = m_monitor->render->frameRenderWidth(); int height = m_monitor->render->renderHeight(); + /* + * use the position of the on-monitor points to create a storable list + */ QList spline = m_item->getPoints(); QList vlist; foreach (const BPoint &point, spline) { @@ -112,15 +152,266 @@ void RotoWidget::slotUpdateData() pl << QVariant(QList () << QVariant(point[i].x() / width) << QVariant(point[i].y() / height)); vlist << QVariant(pl); } - m_data = QVariant(vlist); + + if (m_data.canConvert(QVariant::Map)) { + QMap map = m_data.toMap(); + // replace or insert at position + // we have to fill with 0s to maintain the correct order + map[QString::number((pos < 0 ? m_keyframeWidget->getPosition() : pos) + m_in).rightJustified(log10((double)m_out) + 1, '0')] = QVariant(vlist); + m_data = QVariant(map); + } else { + m_data = QVariant(vlist); + } emit valueChanged(); } +void RotoWidget::slotUpdateData(bool editing) +{ + slotUpdateData(-1, editing); +} + QString RotoWidget::getSpline() { QJson::Serializer serializer; return QString(serializer.serialize(m_data)); } +void RotoWidget::slotPositionChanged(int pos, bool seek) +{ + // do not update while the spline is being edited (points are being dragged) + if (m_item->editing()) + return; + + m_keyframeWidget->slotSetPosition(pos, false); + + pos += m_in; + + QList p; + + if (m_data.canConvert(QVariant::Map)) { + QMap map = m_data.toMap(); + QMap ::const_iterator i = map.constBegin(); + int keyframe1, keyframe2; + keyframe1 = keyframe2 = i.key().toInt(); + // find keyframes next to pos + while (i.key().toInt() < pos && ++i != map.constEnd()) { + keyframe1 = keyframe2; + keyframe2 = i.key().toInt(); + } + + if (keyframe1 != keyframe2 && pos < keyframe2) { + /* + * in between two keyframes + * -> interpolate + */ + QList p1 = getPoints(keyframe1); + QList p2 = getPoints(keyframe2); + qreal relPos = (pos - keyframe1) / (qreal)(keyframe2 - keyframe1 + 1); + + // additionaly points are ignored (same behavior as MLT filter) + int count = qMin(p1.count(), p2.count()); + for (int i = 0; i < count; ++i) { + BPoint bp; + for (int j = 0; j < 3; ++j) { + if (p1.at(i)[j] != p2.at(i)[j]) + bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos); + else + bp[j] = p1.at(i)[j]; + } + p.append(bp); + } + + m_item->setPoints(p); + m_item->setEnabled(false); + m_scene->setEnabled(false); + } else { + p = getPoints(keyframe2); + // only update if necessary to preserve the current point selection + if (p != m_item->getPoints()) + m_item->setPoints(p); + m_item->setEnabled(pos == keyframe2); + m_scene->setEnabled(pos == keyframe2); + } + } else { + p = getPoints(-1); + // only update if necessary to preserve the current point selection + if (p != m_item->getPoints()) + m_item->setPoints(p); + m_item->setEnabled(true); + m_scene->setEnabled(true); + } + + if (seek) + emit seekToPos(pos - m_in); +} + +QList RotoWidget::getPoints(int keyframe) +{ + int width = m_monitor->render->frameRenderWidth(); + int height = m_monitor->render->renderHeight(); + QList points; + QList data; + if (keyframe >= 0) + data = m_data.toMap()[QString::number(keyframe).rightJustified(log10((double)m_out) + 1, '0')].toList(); + else + data = m_data.toList(); + foreach (const QVariant &bpoint, data) { + QList l = bpoint.toList(); + BPoint p; + for (int i = 0; i < 3; ++i) + p[i] = QPointF(l.at(i).toList().at(0).toDouble() * width, l.at(i).toList().at(1).toDouble() * height); + points << p; + } + return points; +} + +void RotoWidget::slotAddKeyframe(int pos) +{ + if (!m_data.canConvert(QVariant::Map)) { + QVariant data = m_data; + QMap map; + map[QString::number(m_in).rightJustified(log10((double)m_out) + 1, '0')] = data; + m_data = QVariant(map); + } + + if (pos < 0) + m_keyframeWidget->addKeyframe(); + + slotUpdateData(pos); + m_item->setEnabled(true); + m_scene->setEnabled(true); +} + +void RotoWidget::slotRemoveKeyframe(int pos) +{ + if (pos < 0) + pos = m_keyframeWidget->getPosition(); + + if (!m_data.canConvert(QVariant::Map) || m_data.toMap().count() < 2) + return; + + QMap map = m_data.toMap(); + map.remove(QString::number(pos + m_in).rightJustified(log10((double)m_out) + 1, '0')); + m_data = QVariant(map); + + if (m_data.toMap().count() == 1) { + // only one keyframe -> switch from map to list again + m_data = m_data.toMap().begin().value(); + } + + slotPositionChanged(m_keyframeWidget->getPosition(), false); + emit valueChanged(); +} + +void RotoWidget::slotMoveKeyframe(int oldPos, int newPos) +{ + if (m_data.canConvert(QVariant::Map)) { + QMap map = m_data.toMap(); + map[QString::number(newPos + m_in).rightJustified(log10((double)m_out) + 1, '0')] = map.take(QString::number(oldPos + m_in).rightJustified(log10((double)m_out) + 1, '0')); + m_data = QVariant(map); + } + + slotPositionChanged(m_keyframeWidget->getPosition(), false); + emit valueChanged(); +} + +void RotoWidget::updateTimecodeFormat() +{ + m_keyframeWidget->updateTimecodeFormat(); +} + + + +static QVariant interpolate(int position, int in, int out, QVariant *splineIn, QVariant *splineOut) +{ + qreal relPos = (position - in) / (qreal)(out - in + 1); + QList keyframe1 = splineIn->toList(); + QList keyframe2 = splineOut->toList(); + QList keyframe; + int max = qMin(keyframe1.count(), keyframe2.count()); + for (int i = 0; i < max; ++i) { + QList p1 = keyframe1.at(i).toList(); + QList p2 = keyframe2.at(i).toList(); + QList p; + for (int j = 0; j < 3; ++j) { + QPointF middle = QLineF(QPointF(p1.at(j).toList().at(0).toDouble(), p1.at(j).toList().at(1).toDouble()), + QPointF(p2.at(j).toList().at(0).toDouble(), p2.at(j).toList().at(1).toDouble())).pointAt(relPos); + p.append(QVariant(QList() << QVariant(middle.x()) << QVariant(middle.y()))); + } + keyframe.append(QVariant(p)); + } + return QVariant(keyframe); +} + +bool adjustRotoDuration(QString* data, int in, int out) +{ + QJson::Parser parser; + bool ok; + QVariant splines = parser.parse(data->toUtf8(), &ok); + if (!ok) { + *data = QString(); + return true; + } + + if (!splines.canConvert(QVariant::Map)) + return false; + + QMap map = splines.toMap(); + QMap::iterator i = map.end(); + int lastPos = -1; + QVariant last = QVariant(); + + /* + * Take care of resize from start + */ + bool startFound = false; + while (i-- != map.begin()) { + if (i.key().toInt() < in) { + if (!startFound) { + startFound = true; + if (lastPos < 0) + map[QString::number(in).rightJustified(log10((double)out) + 1, '0')] = i.value(); + else + map[QString::number(in).rightJustified(log10((double)out) + 1, '0')] = interpolate(in, i.key().toInt(), lastPos, &i.value(), &last); + } + } + lastPos = i.key().toInt(); + last = i.value(); + if (startFound) + i = map.erase(i); + } + + /* + * Take care of resize from end + */ + i = map.begin(); + lastPos = -1; + bool endFound = false; + while (i != map.end()) { + if (i.key().toInt() > out) { + if (!endFound) { + endFound = true; + if (lastPos < 0) + map[QString::number(out)] = i.value(); + else + map[QString::number(out)] = interpolate(out, lastPos, i.key().toInt(), &last, &i.value()); + } + } + lastPos = i.key().toInt(); + last = i.value(); + if (endFound) + i = map.erase(i); + else + ++i; + } + + QJson::Serializer serializer; + *data = QString(serializer.serialize(QVariant(map))); + + if (startFound || endFound) + return true; + return false; +} + #include "rotowidget.moc"