X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Frotoscoping%2Frotowidget.cpp;h=d5832f6a26b302570f1845f27f555cb5f47b6c7b;hb=64834d517d243b2a7672080453324bf8872ed60d;hp=937116951d7c714ca40994198a0d51bb49bbea22;hpb=1358c7644913c58cfca2b97c3dff9a2d165b7894;p=kdenlive diff --git a/src/rotoscoping/rotowidget.cpp b/src/rotoscoping/rotowidget.cpp index 93711695..d5832f6a 100644 --- a/src/rotoscoping/rotowidget.cpp +++ b/src/rotoscoping/rotowidget.cpp @@ -20,12 +20,14 @@ #include "monitor.h" #include "renderer.h" #include "monitorscene.h" -#include "monitoreditwidget.h" +#include "widgets/monitoreditwidget.h" #include "onmonitoritems/rotoscoping/bpointitem.h" #include "onmonitoritems/rotoscoping/splineitem.h" #include "simplekeyframes/simplekeyframewidget.h" #include "kdenlivesettings.h" +#include + #include #include @@ -33,68 +35,50 @@ #include +/** @brief Listener for "tracking-finished" event in MLT rotoscoping filter. */ +void tracking_finished(mlt_service *owner, RotoWidget *self, char *data) +{ + Q_UNUSED(owner) + + if (self) + self->setSpline(QString(data)); +} -RotoWidget::RotoWidget(QString data, Monitor *monitor, int in, int out, Timecode t, QWidget* parent) : +RotoWidget::RotoWidget(const QString &data, Monitor *monitor, const ItemInfo &info, const Timecode &t, QWidget* parent) : QWidget(parent), m_monitor(monitor), m_showScene(true), - m_in(in), - m_out(out) + m_in(info.cropStart.frames(KdenliveSettings::project_fps())), + m_out((info.cropStart + info.cropDuration).frames(KdenliveSettings::project_fps()) - 1), + m_filter(NULL) { QVBoxLayout *l = new QVBoxLayout(this); - m_keyframeWidget = new SimpleKeyframeWidget(t, m_out - m_in - 1, 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(); - - QJson::Parser parser; - bool ok; - m_data = parser.parse(data.toUtf8(), &ok); - if (!ok) { - // :( - } - - - if (m_data.canConvert(QVariant::Map)) { - 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(qRound(log10((double)m_out)), '0')] = value; - } - } - m_data = QVariant(map); - } else { - m_keyframeWidget->setKeyframes(QList () << 0); - } + m_scene->cleanup(); m_item = new SplineItem(QList (), NULL, m_scene); 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); + setSpline(data, false); + setupTrackingListen(info); + m_scene->centerView(); } RotoWidget::~RotoWidget() { + if (m_filter) + mlt_events_disconnect(m_filter->get_properties(), this); + delete m_keyframeWidget; m_scene->removeItem(m_item); @@ -102,18 +86,11 @@ RotoWidget::~RotoWidget() if (m_monitor) { MonitorEditWidget *edit = m_monitor->getEffectEdit(); - edit->showVisibilityButton(false); edit->removeCustomControls(); - m_monitor->slotEffectScene(false); + m_monitor->slotShowEffectScene(false); } } -void RotoWidget::slotCheckMonitorPosition(int renderPos) -{ - if (m_showScene) - emit checkMonitorPosition(renderPos); -} - void RotoWidget::slotSyncPosition(int relTimelinePos) { relTimelinePos = qBound(0, relTimelinePos, m_out); @@ -121,15 +98,6 @@ void RotoWidget::slotSyncPosition(int relTimelinePos) slotPositionChanged(relTimelinePos, false); } -void RotoWidget::slotShowScene(bool show) -{ - m_showScene = show; - if (!m_showScene) - m_monitor->slotEffectScene(false); - else - slotCheckMonitorPosition(m_monitor->render->seekFramePosition()); -} - void RotoWidget::slotUpdateData(int pos, bool editing) { Q_UNUSED(editing) @@ -137,6 +105,9 @@ void RotoWidget::slotUpdateData(int pos, bool 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) { @@ -148,10 +119,17 @@ void RotoWidget::slotUpdateData(int pos, bool editing) if (m_data.canConvert(QVariant::Map)) { QMap map = m_data.toMap(); - map[QString::number((pos < 0 ? m_keyframeWidget->getPosition() : pos) + m_in).rightJustified(qRound(log10((double)m_out)), '0')] = QVariant(vlist); + // 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 { + // timeline update is only required if the first keyframe did not exist yet + bool update = m_data.isNull(); m_data = QVariant(vlist); + if (update) { + keyframeTimelineFullUpdate(); + } } emit valueChanged(); @@ -170,6 +148,7 @@ QString RotoWidget::getSpline() 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; @@ -184,16 +163,22 @@ void RotoWidget::slotPositionChanged(int pos, bool seek) 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; @@ -237,15 +222,19 @@ QList RotoWidget::getPoints(int keyframe) QList points; QList data; if (keyframe >= 0) - data = m_data.toMap()[QString::number(keyframe).rightJustified(qRound(log10((double)m_out)), '0')].toList(); + data = m_data.toMap()[QString::number(keyframe).rightJustified(log10((double)m_out) + 1, '0')].toList(); else data = m_data.toList(); + + // skip tracking flag + if (data.count() && data.at(0).canConvert(QVariant::String)) + data.removeFirst(); + foreach (const QVariant &bpoint, data) { 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(1).toList().at(1).toDouble() * height); - p.h2 = QPointF(l.at(2).toList().at(0).toDouble() * width, l.at(2).toList().at(1).toDouble() * height); + 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; @@ -256,7 +245,7 @@ void RotoWidget::slotAddKeyframe(int pos) if (!m_data.canConvert(QVariant::Map)) { QVariant data = m_data; QMap map; - map[QString::number(m_in).rightJustified(qRound(log10((double)m_out)), '0')] = data; + map[QString::number(m_in).rightJustified(log10((double)m_out) + 1, '0')] = data; m_data = QVariant(map); } @@ -277,11 +266,13 @@ void RotoWidget::slotRemoveKeyframe(int pos) return; QMap map = m_data.toMap(); - map.remove(QString::number(pos + m_in).rightJustified(qRound(log10((double)m_out)), '0')); + map.remove(QString::number(pos + m_in).rightJustified(log10((double)m_out) + 1, '0')); m_data = QVariant(map); - if (m_data.toMap().count() == 1) + 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(); @@ -291,7 +282,7 @@ 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(qRound(log10((double)m_out)), '0')] = map.take(QString::number(oldPos + m_in).rightJustified(qRound(log10((double)m_out)), '0')); + 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); } @@ -304,4 +295,181 @@ void RotoWidget::updateTimecodeFormat() m_keyframeWidget->updateTimecodeFormat(); } +void RotoWidget::keyframeTimelineFullUpdate() +{ + if (m_data.canConvert(QVariant::Map)) { + 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) + // make sure the first keyframe was already created + if (m_data.isValid()) { + m_keyframeWidget->setKeyframes(QList () << 0); + } + } +} + +void RotoWidget::setupTrackingListen(const ItemInfo &info) +{ + if (info.startPos < GenTime()) { + // TODO: track effects + return; + } + + Mlt::Service service(m_monitor->render->getProducer()->parent().get_service()); + Mlt::Tractor tractor(service); + Mlt::Producer trackProducer(tractor.track(tractor.count() - info.track - 1)); + Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); + + Mlt::Producer *clip = trackPlaylist.get_clip_at((int)info.startPos.frames(KdenliveSettings::project_fps())); + if (!clip) { + return; + } + + int i = 0; + Mlt::Filter *filter = clip->filter(0); + while (filter) { + if (strcmp(filter->get("kdenlive_id"), "rotoscoping") == 0) { + m_filter = filter; + filter->listen("tracking-finished", this, (mlt_listener)tracking_finished); + break; + } + filter = clip->filter(++i); + } + + delete clip; +} + +void RotoWidget::setSpline(const QString &spline, bool notify) +{ + QJson::Parser parser; + bool ok; + m_data = parser.parse(spline.simplified().toUtf8(), &ok); + if (!ok) { + // :( + } + keyframeTimelineFullUpdate(); + slotPositionChanged(m_keyframeWidget->getPosition(), false); + if (notify) + emit valueChanged(); +} + + +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; + if (keyframe1.count() && keyframe1.at(0).canConvert(QVariant::String)) + keyframe1.removeFirst(); + if (keyframe2.count() && keyframe2.at(0).canConvert(QVariant::String)) + keyframe2.removeFirst(); + 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 newMap; + 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 (!startFound && i.key().toInt() < in) { + startFound = true; + if (lastPos < 0) + newMap[QString::number(in).rightJustified(log10((double)out) + 1, '0')] = i.value(); + else + newMap[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 (!endFound && i.key().toInt() > out) { + endFound = true; + if (lastPos < 0) + newMap[QString::number(out)] = i.value(); + else + newMap[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; + } + + /* + * Update key lengths to prevent sorting issues + */ + i = map.begin(); + while (i != map.end()) { + newMap[i.key().rightJustified(log10((double)out) + 1, '0', true)] = i.value(); + ++i; + } + + QJson::Serializer serializer; + *data = QString(serializer.serialize(QVariant(newMap))); + + if (startFound || endFound) + return true; + return false; +} + #include "rotowidget.moc"