From df68f0f59c160e3b33d6aca6cddd5ecceafbeb1a Mon Sep 17 00:00:00 2001 From: Till Theato Date: Tue, 5 Apr 2011 16:22:08 +0000 Subject: [PATCH] Fixes and cleanups to effect handling upon clip resize: - Update roto keyframes - Some fixes on updating the fades svn path=/trunk/kdenlive/; revision=5535 --- src/clipitem.cpp | 286 +++++++++++++++++++++------------ src/clipitem.h | 9 +- src/customtrackview.cpp | 24 +-- src/rotoscoping/rotowidget.cpp | 102 +++++++++++- src/rotoscoping/rotowidget.h | 3 + 5 files changed, 292 insertions(+), 132 deletions(-) diff --git a/src/clipitem.cpp b/src/clipitem.cpp index 02a4cc44..c225c747 100644 --- a/src/clipitem.cpp +++ b/src/clipitem.cpp @@ -27,6 +27,9 @@ #include "kdenlivesettings.h" #include "kthumb.h" #include "profilesdialog.h" +#ifdef QJSON +#include "rotoscoping/rotowidget.h" +#endif #include #include @@ -1189,54 +1192,6 @@ void ClipItem::resizeEnd(int posx) } } - -bool ClipItem::checkEffectsKeyframesPos(const int previous, const int current, bool fromStart) -{ - bool effModified = false; - for (int i = 0; i < m_effectList.count(); i++) { - QDomElement effect = m_effectList.at(i); - QDomNodeList params = effect.elementsByTagName("parameter"); - for (int j = 0; j < params.count(); j++) { - bool modified = false; - QDomElement e = params.item(j).toElement(); - if (!e.isNull() && (e.attribute("type") == "keyframe" || e.attribute("type") == "simplekeyframe")) { - // parse keyframes and adjust values - const QStringList keyframes = e.attribute("keyframes").split(';', QString::SkipEmptyParts); - QMap kfr; - int pos; - double val; - foreach(const QString &str, keyframes) { - pos = str.section(':', 0, 0).toInt(); - val = str.section(':', 1, 1).toDouble(); - if (pos == previous) { - // first or last keyframe - kfr[current] = val; - modified = true; - } else { - if ((fromStart && pos >= current) || (!fromStart && pos <= current)) { - // only keyframes in range - kfr[pos] = val; - modified = true; - } - } - } - if (modified) { - effModified = true; - QString newkfr; - QMap::const_iterator k = kfr.constBegin(); - while (k != kfr.constEnd()) { - newkfr.append(QString::number(k.key()) + ':' + QString::number(k.value()) + ';'); - ++k; - } - e.setAttribute("keyframes", newkfr); - } - } - } - } - if (effModified && m_selectedEffect >= 0) setSelectedEffect(m_selectedEffect); - return effModified; -} - //virtual QVariant ClipItem::itemChange(GraphicsItemChange change, const QVariant &value) { @@ -1776,60 +1731,7 @@ QList ClipItem::updatePanZoom(int width, int height, int cut) continue; if (e.attribute("type") == "geometry" && !e.hasAttribute("fixed")) { effectPositions << i; - - int in = cropStart().frames(fps()); - int out = in + cropDuration().frames(fps()); - int dur = out - in - 1; - - effect.setAttribute("in", in); - effect.setAttribute("out", out); - - Mlt::Geometry geometry(e.attribute("value").toUtf8().data(), dur, width, height); - Mlt::GeometryItem item; - bool endFrameAdded = false; - if (cut == 0) { - while (!geometry.next_key(&item, dur)) { - if (!endFrameAdded) { - // add keyframe at the end with interpolated value - - // but only once ;) - endFrameAdded = true; - - Mlt::GeometryItem endItem; - Mlt::GeometryItem interp; - geometry.fetch(&interp, dur - 1); - endItem.frame(dur - 1); - endItem.x(interp.x()); - endItem.y(interp.y()); - endItem.w(interp.w()); - endItem.h(interp.h()); - endItem.mix(interp.mix()); - geometry.insert(&endItem); - } - geometry.remove(item.frame()); - } - } else { - Mlt::Geometry origGeometry(e.attribute("value").toUtf8().data(), dur, width, height); - // remove keyframes before cut point - while (!geometry.prev_key(&item, cut - 1) && item.frame() < cut) - geometry.remove(item.frame()); - - // add a keyframe at new pos 0 - origGeometry.fetch(&item, cut); - item.frame(0); - geometry.insert(&item); - - // move exisiting keyframes by -cut - while (!origGeometry.next_key(&item, cut)) { - geometry.remove(item.frame()); - origGeometry.remove(item.frame()); - item.frame(item.frame() - cut); - geometry.insert(&item); - } - - } - - e.setAttribute("value", geometry.serialise()); + updateGeometryKeyframes(effect, j, width, height, cut); } } } @@ -1847,4 +1749,184 @@ Mlt::Producer *ClipItem::getProducer(int track, bool trackSpecific) return m_clip->producer(trackSpecific ? track : -1); } +QMap ClipItem::adjustEffectsToDuration(int width, int height, int previous, int current, bool fromStart) +{ + QMap effects; + for (int i = 0; i < m_effectList.count(); i++) { + QDomElement effect = m_effectList.at(i); + + if (effect.attribute("id").startsWith("fade")) { + QString id = effect.attribute("id"); + int in = EffectsList::parameter(effect, "in").toInt(); + int out = EffectsList::parameter(effect, "out").toInt(); + int clipEnd = (cropStart() + cropDuration()).frames(m_fps); + if (id == "fade_from_black" || id == "fadein") { + if (in != cropStart().frames(m_fps)) { + effects[i] = effect.cloneNode().toElement(); + int diff = in - cropStart().frames(m_fps); + in -= diff; + out -= diff; + EffectsList::setParameter(effect, "in", QString::number(in)); + EffectsList::setParameter(effect, "out", QString::number(out)); + } + if (out > clipEnd) { + if (!effects.contains(i)) + effects[i] = effect.cloneNode().toElement(); + EffectsList::setParameter(effect, "out", QString::number(clipEnd)); + } + if (effects.contains(i)) + setFadeIn(out - in); + } else { + if (out != clipEnd) { + effects[i] = effect.cloneNode().toElement(); + int diff = out - clipEnd; + in -= diff; + out -= diff; + EffectsList::setParameter(effect, "in", QString::number(in)); + EffectsList::setParameter(effect, "out", QString::number(out)); + } + if (in < cropStart().frames(m_fps)) { + if (!effects.contains(i)) + effects[i] = effect.cloneNode().toElement(); + EffectsList::setParameter(effect, "in", QString::number(cropStart().frames(m_fps))); + } + if (effects.contains(i)) + setFadeOut(out - in); + } + continue; + } else if (fromStart && effect.attribute("id") == "freeze") { + effects[i] = effect.cloneNode().toElement(); + int diff = previous - current; + int frame = EffectsList::parameter(effect, "frame").toInt(); + EffectsList::setParameter(effect, "frame", QString::number(frame - diff)); + continue; + } + + QDomNodeList params = effect.elementsByTagName("parameter"); + for (int j = 0; j < params.count(); j++) { + QDomElement param = params.item(j).toElement(); + + QString type = param.attribute("type"); + if (type == "geometry" && !param.hasAttribute("fixed")) { + if (!effects.contains(i)) + effects[i] = effect.cloneNode().toElement(); + updateGeometryKeyframes(effect, j, width, height, 0); + } else if (type == "simplekeyframe" || type == "keyframe") { + if (!effects.contains(i)) + effects[i] = effect.cloneNode().toElement(); + updateNormalKeyframes(param, previous, current, fromStart); +#ifdef QJSON + } else if (type == "roto-spline") { + if (!effects.contains(i)) + effects[i] = effect.cloneNode().toElement(); + QString value = param.attribute("value"); + if (adjustRotoDuration(&value, cropStart().frames(m_fps), (cropStart() + cropDuration()).frames(m_fps) - 1)) + param.setAttribute("value", value); +#endif + } + } + } + return effects; +} + +bool ClipItem::updateNormalKeyframes(QDomElement parameter, const int previous, const int current, bool fromStart) +{ + // TODO: add interpolated keyframes at clip in/out point + + bool modified = false; + + // parse keyframes and adjust values + const QStringList keyframes = parameter.attribute("keyframes").split(';', QString::SkipEmptyParts); + QMap kfr; + int pos; + double val; + foreach(const QString &str, keyframes) { + pos = str.section(':', 0, 0).toInt(); + val = str.section(':', 1, 1).toDouble(); + if (pos == previous) { + // first or last keyframe + kfr[current] = val; + modified = true; + } else { + if ((fromStart && pos >= current) || (!fromStart && pos <= current)) { + // only keyframes in range + kfr[pos] = val; + modified = true; + } + } + } + + if (modified) { + QString newkfr; + QMap::const_iterator k = kfr.constBegin(); + while (k != kfr.constEnd()) { + newkfr.append(QString::number(k.key()) + ':' + QString::number(k.value()) + ';'); + ++k; + } + parameter.setAttribute("keyframes", newkfr); + } + + return modified; +} + +void ClipItem::updateGeometryKeyframes(QDomElement effect, int paramIndex, int width, int height, int cut) +{ + // TODO: properly update when clip resized from start + + QDomElement param = effect.elementsByTagName("parameter").item(paramIndex).toElement(); + + int in = cropStart().frames(fps()); + int out = in + cropDuration().frames(fps()); + int dur = out - in - 1; + + effect.setAttribute("in", in); + effect.setAttribute("out", out); + + Mlt::Geometry geometry(param.attribute("value").toUtf8().data(), dur, width, height); + Mlt::GeometryItem item; + bool endFrameAdded = false; + if (cut == 0) { + while (!geometry.next_key(&item, dur)) { + if (!endFrameAdded) { + // add keyframe at the end with interpolated value + + // but only once ;) + endFrameAdded = true; + + Mlt::GeometryItem endItem; + Mlt::GeometryItem interp; + geometry.fetch(&interp, dur - 1); + endItem.frame(dur - 1); + endItem.x(interp.x()); + endItem.y(interp.y()); + endItem.w(interp.w()); + endItem.h(interp.h()); + endItem.mix(interp.mix()); + geometry.insert(&endItem); + } + geometry.remove(item.frame()); + } + } else { + Mlt::Geometry origGeometry(param.attribute("value").toUtf8().data(), dur, width, height); + // remove keyframes before cut point + while (!geometry.prev_key(&item, cut - 1) && item.frame() < cut) + geometry.remove(item.frame()); + + // add a keyframe at new pos 0 + origGeometry.fetch(&item, cut); + item.frame(0); + geometry.insert(&item); + + // move exisiting keyframes by -cut + while (!origGeometry.next_key(&item, cut)) { + geometry.remove(item.frame()); + origGeometry.remove(item.frame()); + item.frame(item.frame() - cut); + geometry.insert(&item); + } + } + + param.setAttribute("value", geometry.serialise()); +} + #include "clipitem.moc" diff --git a/src/clipitem.h b/src/clipitem.h index 8a3de4f2..77708af7 100644 --- a/src/clipitem.h +++ b/src/clipitem.h @@ -156,12 +156,6 @@ public: bool isVideoOnly() const; bool isAudioOnly() const; - /** @brief Adjusts the keyframe values to fit a clip resizement. - * @param previous Old crop value - * @param current New crop value - * @param fromStart true = crop from start, false = crop from end - * @return true if anything was modified */ - bool checkEffectsKeyframesPos(const int previous, const int current, bool fromStart); void insertKeyframe(QDomElement effect, int pos, int val); void movedKeyframe(QDomElement effect, int oldpos, int newpos, double value); void updateKeyframes(QDomElement effect); @@ -173,6 +167,9 @@ public: * * Can be used for all effects using mlt_geometry with keyframes, but at the moment Pan & Zoom is the only one. */ QList updatePanZoom(int width, int height, int cut = 0); + void updateGeometryKeyframes(QDomElement effect, int paramIndex, int width, int height, int cut = 0); + bool updateNormalKeyframes(QDomElement parameter, const int previous, const int current, bool fromStart); + QMap adjustEffectsToDuration(int width, int height, int previous, int current, bool fromStart); /** Returns the necessary (audio, video, general) producer. * @param track Track of the requested producer diff --git a/src/customtrackview.cpp b/src/customtrackview.cpp index 6c139a29..a177cd71 100644 --- a/src/customtrackview.cpp +++ b/src/customtrackview.cpp @@ -6663,30 +6663,14 @@ void CustomTrackView::slotRefreshThumbs(const QString &id, bool resetThumbs) void CustomTrackView::adjustEffects(ClipItem* item, ItemInfo oldInfo, bool fromStart, QUndoCommand* command) { - bool update = false; - QMap effects; - for (int i = 0; i < item->effectsCount(); ++i) { - QDomElement effect = item->getEffectAt(i); - bool nonStdKeyframeUpdate = effect.attribute("id").startsWith("fade") || effect.attribute("id") == "freeze" || EffectsList::hasGeometryKeyFrames(effect); - if (nonStdKeyframeUpdate) - update = true; - if (nonStdKeyframeUpdate || EffectsList::hasKeyFrames(effect) || EffectsList::hasSimpleKeyFrames(effect)) - effects.insert(i, effect.cloneNode().toElement()); - } - - if(effects.isEmpty()) - return; - if (fromStart) - update |= item->checkEffectsKeyframesPos(oldInfo.cropStart.frames(m_document->fps()), item->cropStart().frames(m_document->fps()), true); + effects = item->adjustEffectsToDuration(0, 0, oldInfo.cropStart.frames(m_document->fps()), item->cropStart().frames(m_document->fps()), true); else - update |= item->checkEffectsKeyframesPos((oldInfo.cropStart + oldInfo.endPos - oldInfo.startPos).frames(m_document->fps()) - 1, (item->cropStart() + item->cropDuration()).frames(m_document->fps()) - 1, false); - - update |= item->updatePanZoom(m_document->width(), m_document->height(), 0).count() > 0; - updatePositionEffects(item, oldInfo, false); + effects = item->adjustEffectsToDuration(0, 0, (oldInfo.cropStart + oldInfo.cropDuration).frames(m_document->fps()) - 1, + (item->cropStart() + item->cropDuration()).frames(m_document->fps()) - 1, false); - if (update) { + if (effects.count()) { QMap::const_iterator i = effects.constBegin(); while (i != effects.constEnd()) { new EditEffectCommand(this, m_document->tracksCount() - item->track(), item->startPos(), i.value(), item->effectAt(i.key()), i.key(), false, command); diff --git a/src/rotoscoping/rotowidget.cpp b/src/rotoscoping/rotowidget.cpp index cf8544f0..57268f6b 100644 --- a/src/rotoscoping/rotowidget.cpp +++ b/src/rotoscoping/rotowidget.cpp @@ -42,7 +42,7 @@ RotoWidget::RotoWidget(QString data, Monitor *monitor, int in, int out, Timecode m_out(out) { 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(); @@ -259,9 +259,8 @@ QList RotoWidget::getPoints(int keyframe) 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; @@ -322,4 +321,99 @@ 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, bool cut) +{ + Q_UNUSED(cut) + + 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; + + /* + * 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" diff --git a/src/rotoscoping/rotowidget.h b/src/rotoscoping/rotowidget.h index 93dddbec..a343a38d 100644 --- a/src/rotoscoping/rotowidget.h +++ b/src/rotoscoping/rotowidget.h @@ -29,6 +29,9 @@ class MonitorScene; class SplineItem; class SimpleKeyframeWidget; +/** @brief Adjusts keyframes after resizing a clip. */ +bool adjustRotoDuration(QString *data, int in, int out, bool cut = false); + class RotoWidget : public QWidget { Q_OBJECT -- 2.39.5