Reintroduce rotoscoping

parent 5740e651
......@@ -3,7 +3,7 @@
<name>Rotoscoping</name>
<description>Keyframable vector based rotoscoping</description>
<author>Till Theato</author>
<parameter type="roto-spline" name="spline" default="" />
<parameter type="list" name="mode" default="alpha" paramlist="alpha;luma;rgb">
<paramlistdisplay>Alpha,Luma,RGB</paramlistdisplay>
<name>Mode</name>
......@@ -29,6 +29,4 @@
<parameter type="constant" name="feather_passes" max="20" min="1" default="1">
<name>Feathering passes</name>
</parameter>
<parameter type="roto-spline" name="spline" default="" />
</effect>
......@@ -5,6 +5,8 @@ set(kdenlive_SRCS
assets/assetlist/model/assetfilter.cpp
assets/assetlist/model/assettreemodel.cpp
assets/assetpanel.cpp
assets/keyframes/model/rotoscoping/bpoint.cpp
assets/keyframes/model/rotoscoping/rotowidget.cpp
assets/keyframes/model/keyframemodel.cpp
assets/keyframes/model/keyframemodellist.cpp
assets/keyframes/view/keyframeview.cpp
......
......@@ -20,11 +20,14 @@
***************************************************************************/
#include "keyframemodel.hpp"
#include "rotoscoping/bpoint.h"
#include "rotoscoping/rotowidget.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "macros.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <mlt++/Mlt.h>
KeyframeModel::KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent)
......@@ -612,6 +615,21 @@ QString KeyframeModel::getAnimProperty() const
return prop;
}
QString KeyframeModel::getRotoProperty() const
{
QJsonDocument doc;
if (auto ptr = m_model.lock()) {
int in = ptr->data(m_index, AssetParameterModel::InRole).toInt();
int out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
QMap<QString, QVariant> map;
for (const auto keyframe : m_keyframeList) {
map.insert(QString::number(in + keyframe.first.frames(pCore->getCurrentFps())).rightJustified(log10((double)out) + 1, '0'), keyframe.second.second);
}
doc = QJsonDocument::fromVariant(QVariant(map));
}
return doc.toJson();
}
mlt_keyframe_type convertToMltType(KeyframeType type)
{
switch (type) {
......@@ -688,6 +706,25 @@ void KeyframeModel::parseAnimProperty(const QString &prop)
*/
}
void KeyframeModel::parseRotoProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(prop.toLatin1(), &jsonError);
QVariant data = doc.toVariant();
if (data.canConvert(QVariant::Map)) {
QList<int> keyframes;
QMap<QString, QVariant> map = data.toMap();
QMap<QString, QVariant>::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
++i;
}
}
}
QVariant KeyframeModel::getInterpolatedValue(int p) const
{
auto pos = GenTime(p, pCore->getCurrentFps());
......@@ -696,10 +733,12 @@ QVariant KeyframeModel::getInterpolatedValue(int p) const
QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
{
int p = pos.frames(pCore->getCurrentFps());
if (m_keyframeList.count(pos) > 0) {
return m_keyframeList.at(pos).second;
}
if (m_keyframeList.size() == 0) {
return QVariant();
}
auto next = m_keyframeList.upper_bound(pos);
if (next == m_keyframeList.cbegin()) {
return (m_keyframeList.cbegin())->second.second;
......@@ -713,6 +752,7 @@ QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
// We now have surrounding keyframes, we use mlt to compute the value
Mlt::Properties prop;
QLocale locale;
int p = pos.frames(pCore->getCurrentFps());
if (m_paramType == ParamType::KeyframeParam) {
prop.anim_set("keyframe", prev->second.second.toDouble(), prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(prev->second.first));
......@@ -752,6 +792,28 @@ QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
rect = prop.anim_get_rect("keyframe", p);
const QString res = QStringLiteral("%1 %2 %3 %4 %5").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h).arg(rect.o);
return QVariant(res);
} else if (m_paramType == ParamType::Roto_spline) {
// interpolate
QSize frame = pCore->getCurrentFrameSize();
QList<BPoint> p1 = RotoWidget::getPoints(prev->second.second, frame);
qreal relPos = (p - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps())) + 1);
QList<BPoint> p2 = RotoWidget::getPoints(next->second.second, frame);
int count = qMin(p1.count(), p2.count());
QList<QVariant> vlist;
for (int i = 0; i < count; ++i) {
BPoint bp;
QList<QVariant> pl;
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];
}
pl << QVariant(QList<QVariant>() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
}
vlist << QVariant(pl);
}
return vlist;
}
return QVariant();
}
......@@ -765,6 +827,9 @@ void KeyframeModel::sendModification()
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
data = getAnimProperty();
ptr->setParameter(name, data);
} else if (m_paramType == ParamType::Roto_spline) {
data = getRotoProperty();
ptr->setParameter(name, data);
} else {
Q_ASSERT(false); // Not implemented, TODO
}
......@@ -785,6 +850,8 @@ void KeyframeModel::refresh()
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
qDebug() << "parsing keyframe" << data;
parseAnimProperty(data);
} else if (m_paramType == ParamType::Roto_spline) {
parseRotoProperty(data);
} else {
// first, try to convert to double
bool ok = false;
......
......@@ -173,9 +173,11 @@ protected:
|= represents a discrete keyframe, = a linear one and ~= a Catmull-Rom spline
*/
QString getAnimProperty() const;
QString getRotoProperty() const;
/* @brief this function does the opposite: given a MLT representation of an animation, build the corresponding model */
void parseAnimProperty(const QString &prop);
void parseRotoProperty(const QString &prop);
private:
std::weak_ptr<AssetParameterModel> m_model;
......
......@@ -166,6 +166,12 @@ bool KeyframeModelList::singleKeyframe() const
return m_parameters.begin()->second->singleKeyframe();
}
bool KeyframeModelList::isEmpty() const
{
READ_LOCK();
return (m_parameters.size() == 0 || m_parameters.begin()->second->rowCount() == 0);
}
Keyframe KeyframeModelList::getNextKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
......
......@@ -90,6 +90,9 @@ public:
/* @brief Returns true if we only have 1 keyframe
*/
bool singleKeyframe() const;
/* @brief Returns true if we only have no keyframe
*/
bool isEmpty() const;
/* @brief Returns the keyframe located after given position.
If there is a keyframe at given position it is ignored.
......
/***************************************************************************
* Copyright (C) 2011 by Till Theato (root@ttill.de) *
* This file is part of Kdenlive (www.kdenlive.org). *
* *
* Kdenlive 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 2 of the License, or *
* (at your option) any later version. *
* *
* Kdenlive 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 Kdenlive. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "bpoint.h"
#include <QLineF>
BPoint::BPoint()
: h1(-1, -1)
, p(-1, -1)
, h2(-1, -1)
, handlesLinked(true)
{
}
BPoint::BPoint(const QPointF &handle1, const QPointF &point, const QPointF &handle2)
: h1(handle1)
, p(point)
, h2(handle2)
{
autoSetLinked();
}
QPointF &BPoint::operator[](int i)
{
return i == 0 ? h1 : (i == 1 ? p : h2);
}
const QPointF &BPoint::operator[](int i) const
{
return i == 0 ? h1 : (i == 1 ? p : h2);
}
bool BPoint::operator==(const BPoint &point) const
{
return point.h1 == h1 && point.p == p && point.h2 == h2;
}
void BPoint::setP(const QPointF &point, bool updateHandles)
{
QPointF offset = point - p;
p = point;
if (updateHandles) {
h1 += offset;
h2 += offset;
}
}
void BPoint::setH1(const QPointF &handle1)
{
h1 = handle1;
if (handlesLinked) {
qreal angle = QLineF(h1, p).angle();
QLineF l = QLineF(p, h2);
l.setAngle(angle);
h2 = l.p2();
}
}
void BPoint::setH2(const QPointF &handle2)
{
h2 = handle2;
if (handlesLinked) {
qreal angle = QLineF(h2, p).angle();
QLineF l = QLineF(p, h1);
l.setAngle(angle);
h1 = l.p2();
}
}
void BPoint::autoSetLinked()
{
// sometimes the angle is returned as 360°
// due to rounding problems the angle is sometimes not quite 0
qreal angle = QLineF(h1, p).angleTo(QLineF(p, h2));
handlesLinked = angle < 1e-3 || qRound(angle) == 360;
}
void BPoint::setHandlesLinked(bool linked)
{
handlesLinked = linked;
if (linked) {
// we force recomputing one of the handles
setH1(h1);
}
}
/***************************************************************************
* Copyright (C) 2011 by Till Theato (root@ttill.de) *
* This file is part of Kdenlive (www.kdenlive.org). *
* *
* Kdenlive 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 2 of the License, or *
* (at your option) any later version. *
* *
* Kdenlive 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 Kdenlive. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifndef BPOINT_H
#define BPOINT_H
#include <QPointF>
/**
* @brief Represents a point in a cubic Bézier spline.
*/
class BPoint
{
public:
enum class PointType { H1 = 0, P = 1, H2 = 2 };
/** @brief Sets the point to -1, -1 to mark it as unusable (until point + handles have proper values) */
BPoint();
/** @brief Sets up according to the params. Linking detecting is done using autoSetLinked(). */
BPoint(const QPointF &handle1, const QPointF &point, const QPointF &handle2);
bool operator==(const BPoint &point) const;
/** @brief Returns h1 if i = 0, p if i = 1, h2 if i = 2. */
QPointF &operator[](int i);
/** @brief Returns h1 if i = 0, p if i = 1, h2 if i = 2. */
const QPointF &operator[](int i) const;
/** @brief Sets p to @param point.
* @param updateHandles (default = true) Whether to make sure the handles keep their position relative to p. */
void setP(const QPointF &point, bool updateHandles = true);
/** @brief Sets h1 to @param handle1.
*
* If handlesLinked is true h2 is updated. */
void setH1(const QPointF &handle1);
/** @brief Sets h2 to @param handle2.
* If handlesLinked is true h1 is updated. */
void setH2(const QPointF &handle2);
/** @brief Sets handlesLinked to true if the handles are in a linked state (line through h1, p, h2) otherwise to false. */
void autoSetLinked();
/** @brief Toggles the link of the handles to @param linked*/
void setHandlesLinked(bool linked);
/** handle 1 */
QPointF h1;
/** point */
QPointF p;
/** handle 2 */
QPointF h2;
/** handles are linked to achieve a natural locking spline => PH1 = -r*PH2 ; a line can be drawn through h1, p, h2 */
bool handlesLinked;
};
#endif
/*
Copyright (C) 2018 Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.
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 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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 <http://www.gnu.org/licenses/>.
*/
#include "rotowidget.hpp"
#include "gentime.h"
#include "monitor/monitor.h"
#include <QSize>
RotoWidget::RotoWidget(Monitor *monitor, QPersistentModelIndex index, QWidget *parent)
: QWidget(parent)
, m_monitor(monitor)
, m_index(index)
, m_active(false)
{
}
bool RotoWidget::connectMonitor(bool activate)
{
if (activate == m_active) {
return false;
}
m_active = activate;
if (activate) {
connect(m_monitor, &Monitor::effectPointsChanged, this, &RotoWidget::slotUpdateRotoMonitor, Qt::UniqueConnection);
} else {
disconnect(m_monitor, &Monitor::effectPointsChanged, this, &RotoWidget::slotUpdateRotoMonitor);
}
return m_active;
}
void RotoWidget::slotUpdateRotoMonitor(const QVariantList &v)
{
emit updateRotoKeyframe(m_index, v);
}
QVariant RotoWidget::getSpline(QVariant value, const QSize frame)
{
QList<BPoint> bPoints;
const QVariantList points = value.toList();
for (int i = 0; i < points.size() / 3; i++) {
BPoint b(points.at(3 * i).toPointF(), points.at(3 * i + 1).toPointF(), points.at(3 * i + 2).toPointF());
bPoints << b;
}
QList<QVariant> vlist;
foreach (const BPoint &point, bPoints) {
QList<QVariant> pl;
for (int i = 0; i < 3; ++i) {
pl << QVariant(QList<QVariant>() << QVariant(point[i].x() / frame.width()) << QVariant(point[i].y() / frame.height()));
}
vlist << QVariant(pl);
}
return vlist;
}
QList<BPoint> RotoWidget::getPoints(QVariant value, const QSize frame)
{
QList<BPoint> points;
QList<QVariant> data = value.toList();
// skip tracking flag
if (data.count() && data.at(0).canConvert(QVariant::String)) {
data.removeFirst();
}
foreach (const QVariant &bpoint, data) {
QList<QVariant> l = bpoint.toList();
BPoint p;
for (int i = 0; i < 3; ++i) {
p[i] = QPointF(l.at(i).toList().at(0).toDouble() * frame.width(), l.at(i).toList().at(1).toDouble() * frame.height());
}
points << p;
}
return points;
}
/*
Copyright (C) 2018 Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.
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 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef ROTOHELPER_H
#define ROTOHELPER_H
#include "bpoint.h"
#include <QVariant>
#include <QWidget>
#include <QPersistentModelIndex>
class Monitor;
class RotoWidget : public QWidget
{
Q_OBJECT
public:
/* @brief Construct a keyframe list bound to the given effect
@param init_value is the value taken by the param at time 0.
@param model is the asset this parameter belong to
@param index is the index of this parameter in its model
*/
explicit RotoWidget(Monitor *monitor, QPersistentModelIndex index, QWidget *parent = nullptr);
/** @brief Send signals to the monitor to update the qml overlay.
@param returns : true if the monitor's connection was changed to active.
*/
bool connectMonitor(bool activate);
/** @brief Returns a spline defined as string, based on its control points and frame size
@param value : the control points
@param frame: the frame size
*/
static QVariant getSpline(QVariant value, const QSize frame);
/** @brief Returns a list of spline control points, based on its string definition and frame size
@param value : the spline's string definition
@param frame: the frame size
*/
static QList<BPoint> getPoints(QVariant value, const QSize frame);
private:
Monitor *m_monitor;
QPersistentModelIndex m_index;
bool m_active;
private slots:
void slotUpdateRotoMonitor(const QVariantList &v);
signals:
void updateRotoKeyframe(QPersistentModelIndex, const QVariantList&);
};
#endif
......@@ -127,7 +127,7 @@ void AssetParameterModel::prepareKeyframes()
if (m_keyframes) return;
int ix = 0;
for (const auto &name : m_rows) {
if (m_params[name].type == ParamType::KeyframeParam || m_params[name].type == ParamType::AnimatedRect) {
if (m_params[name].type == ParamType::KeyframeParam || m_params[name].type == ParamType::AnimatedRect || m_params[name].type == ParamType::Roto_spline) {
addKeyframeParam(index(ix, 0));
}
ix++;
......@@ -280,6 +280,8 @@ QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
return pCore->getItemIn(m_ownerId);
case ParentDurationRole:
return pCore->getItemDuration(m_ownerId);
case ParentPositionRole:
return pCore->getItemPosition(m_ownerId);
case MinRole:
return parseAttribute(m_ownerId, QStringLiteral("min"), element);
case MaxRole:
......
......@@ -93,6 +93,7 @@ public:
InRole,
OutRole,
ParentInRole,
ParentPositionRole,
ParentDurationRole
};
......
......@@ -187,6 +187,9 @@ int AssetParameterView::contentHeight() const
MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
{
if (m_mainKeyframeWidget) {
if (m_model->getAssetId() == QLatin1String("frei0r.c0rners")) {
return MonitorSceneCorners;
}
return MonitorSceneGeometry;
}
for (int i = 0; i < m_model->rowCount(); ++i) {
......@@ -196,6 +199,9 @@ MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
return MonitorSceneGeometry;
}
}
if (m_model->getAssetId() == QLatin1String("rotoscoping")) {
return MonitorSceneRoto;
}
return MonitorSceneDefault;
}
......
......@@ -82,6 +82,7 @@ AbstractParamWidget *AbstractParamWidget::construct(const std::shared_ptr<AssetP
break;
case ParamType::KeyframeParam:
case ParamType::AnimatedRect:
case ParamType::Roto_spline:
widget = new KeyframeWidget(model, index, parent);
break;
case ParamType::Geometry:
......@@ -115,7 +116,6 @@ AbstractParamWidget *AbstractParamWidget::construct(const std::shared_ptr<AssetP
case ParamType::Addedgeometry:
case ParamType::Curve:
case ParamType::Bezier_spline:
case ParamType::Roto_spline:
case ParamType::Keywords:
case ParamType::Fontfamily:
case ParamType::Filterjob:
......
......@@ -19,6 +19,7 @@
#include "keyframewidget.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "assets/keyframes/model/rotoscoping/rotowidget.hpp"
#include "assets/keyframes/view/keyframeview.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
......@@ -36,9 +37,8 @@
KeyframeWidget::KeyframeWidget(std::shared_ptr<AssetParameterModel> model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(model, index, parent)
, m_keyframes(model->getKeyframeModel())
{
m_keyframes = model->getKeyframeModel();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_lay = new QVBoxLayout(this);
......@@ -107,6 +107,10 @@ KeyframeWidget::KeyframeWidget(std::shared_ptr<AssetParameterModel> model, QMode
connect(m_buttonPrevious, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToPrev);