Merge branch 'work/fakerectparam' into 'master'

Draft: Introduce new fakerect and animatedfakerect parameter types that handle...

See merge request multimedia/kdenlive!764
This commit is contained in:
Jean-Baptiste Mardelle
2025-12-05 12:54:21 +01:00
11 changed files with 434 additions and 56 deletions

View File

@@ -11,16 +11,11 @@
<parameter type="fixedcolor" name="av.color" default="black" alpha="1">
<name>Color</name>
</parameter>
<parameter type="animated" name="av.left" default="0" min="0" max="%width" factor="1">
<name>Left</name>
</parameter>
<parameter type="animated" name="av.right" default="0" min="0" max="%width" factor="1">
<name>Right</name>
</parameter>
<parameter type="animated" name="av.top" default="0" min="0" max="%height" factor="1">
<name>Top</name>
</parameter>
<parameter type="animated" name="av.bottom" default="0" min="0" max="%height" factor="1">
<name>Bottom</name>
<parameter type="animatedfakerect" name="kdenlive:fakerect" default="0 0 100% 100%" opacity="false">
<name>Rectangle</name>
<parammap position="0" src="av.left" max="%width" min="0" default="0" />
<parammap position="1" src="av.top" max="%height" min="0" default="0" />
<parammap position="2" src="av.right" max="%width" min="0" default="0" from="%width" fromborder="1"/>
<parammap position="3" src="av.bottom" max="%height" min="0" default="0" from="%height" fromborder="1"/>
</parameter>
</effect>

View File

@@ -4,17 +4,12 @@
<name>Rectangular Alpha mask</name>
<description>Creates an square alpha-channel mask</description>
<author>Richard Spindler</author>
<parameter type="animated" name="Left" max="1000" min="0" default="0" factor="1000">
<name>Left</name>
</parameter>
<parameter type="animated" name="Right" max="1000" min="0" default="0" factor="1000">
<name>Right</name>
</parameter>
<parameter type="animated" name="Top" max="1000" min="0" default="0" factor="1000">
<name>Top</name>
</parameter>
<parameter type="animated" name="Bottom" max="1000" min="0" default="0" factor="1000">
<name>Bottom</name>
<parameter type="fakerect" name="kdenlive:fakerect" default="0 0 100% 100%" opacity="false">
<name>Rectangle</name>
<parammap position="0" src="Left" max="%width" min="0" default="0" factor="%width"/>
<parammap position="1" src="Top" max="%height" min="0" default="0" factor="%height"/>
<parammap position="2" src="Right" max="%width" min="0" default="0" factor="%width" from="%width" fromborder="1"/>
<parammap position="3" src="Bottom" max="%height" min="0" default="0" factor="%height" from="%height" fromborder="1"/>
</parameter>
<parameter type="bool" name="Invert" default="1">
<name>Invert</name>

View File

@@ -359,7 +359,7 @@ bool KeyframeModel::moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal
// no change
return true;
}
if (m_paramType == ParamType::AnimatedRect) {
if (m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::AnimatedFakeRect) {
return updateKeyframe(pos, newVal);
}
// Calculate real value from normalized
@@ -381,7 +381,7 @@ bool KeyframeModel::moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal
qDebug() << "Move keyframe finished deletion:" << res;
qDebug() << getAnimProperty();
if (res) {
if (m_paramType == ParamType::AnimatedRect) {
if (m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::AnimatedFakeRect) {
if (!newVal.isValid()) {
newVal = oldValue;
}
@@ -672,7 +672,7 @@ QVariant KeyframeModel::data(const QModelIndex &index, int role) const
return false;
}
case NormalizedValueRole: {
if (m_paramType == ParamType::AnimatedRect) {
if (m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::AnimatedFakeRect) {
const QString &data = it->second.second.toString();
bool ok;
double converted = data.section(QLatin1Char(' '), -1).toDouble(&ok);
@@ -908,6 +908,7 @@ QString KeyframeModel::getAnimProperty() const
for (const auto &keyframe : m_keyframeList) {
switch (m_paramType) {
case ParamType::AnimatedRect:
case ParamType::AnimatedFakeRect:
case ParamType::Color:
mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
break;
@@ -983,7 +984,8 @@ void KeyframeModel::parseAnimProperty(const QString &prop, int in, int out)
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
case ParamType::AnimatedRect:
case ParamType::AnimatedFakeRect: {
const QString rect_str(mlt_prop.get("key"));
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
if (rect_str.contains(QLatin1Char('%'))) {
@@ -1041,6 +1043,8 @@ void KeyframeModel::resetAnimProperty(const QString &prop)
ptr->passProperties(mlt_prop);
if (m_paramType == ParamType::AnimatedRect) {
useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
} else if (m_paramType == ParamType::AnimatedFakeRect) {
useOpacity = false;
}
}
mlt_prop.set("key", prop.toUtf8().constData());
@@ -1061,7 +1065,8 @@ void KeyframeModel::resetAnimProperty(const QString &prop)
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
case ParamType::AnimatedRect:
case ParamType::AnimatedFakeRect: {
const QString rect_str(mlt_prop.get("key"));
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
if (rect_str.contains(QLatin1Char('%'))) {
@@ -1243,7 +1248,7 @@ QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
(void)mlt_prop.anim_get_double("key", 0, out);
return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps())));
}
if (!animData.isEmpty() && m_paramType == ParamType::AnimatedRect) {
if (!animData.isEmpty() && (m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::AnimatedFakeRect)) {
mlt_prop.set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_double("key", 0, out);
@@ -1487,7 +1492,8 @@ const QString KeyframeModel::getAnimationStringWithOffset(std::shared_ptr<AssetP
if (lastPos > duration) {
QVariant value;
switch (paramType) {
case ParamType::AnimatedRect: {
case ParamType::AnimatedRect:
case ParamType::AnimatedFakeRect: {
mlt_rect rect = mlt_prop.anim_get_rect("key", duration);
QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
if (useOpacity) {

View File

@@ -77,7 +77,7 @@ void KeyframeMonitorHelper::refreshParams(int pos)
std::shared_ptr<KeyframeModelList> keyframes = m_model->getKeyframeModel();
for (const auto &ix : std::as_const(m_indexes)) {
auto type = m_model->data(ix, AssetParameterModel::TypeRole).value<ParamType>();
if (type != ParamType::AnimatedRect) {
if (type != ParamType::AnimatedRect && type != ParamType::AnimatedFakeRect) {
continue;
}
@@ -121,7 +121,7 @@ void KeyframeMonitorHelper::slotUpdateFromMonitorData(const QVariantList &center
}
for (const auto &ix : std::as_const(m_indexes)) {
auto type = m_model->data(ix, AssetParameterModel::TypeRole).value<ParamType>();
if (type != ParamType::AnimatedRect) {
if (type != ParamType::AnimatedRect && type != ParamType::AnimatedFakeRect) {
continue;
}

View File

@@ -103,7 +103,7 @@ QPersistentModelIndex RotatedRectHelper::findAnimatedRectParameter() const
{
for (const auto &ix : std::as_const(m_indexes)) {
auto type = m_model->data(ix, AssetParameterModel::TypeRole).value<ParamType>();
if (type == ParamType::AnimatedRect) {
if (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect) {
return ix;
}
}

View File

@@ -141,7 +141,8 @@ AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset,
value.replace(posRegexp, "\\1.\\2");
break;
}
case ParamType::AnimatedRect: {
case ParamType::AnimatedRect:
case ParamType::AnimatedFakeRect: {
// Fix values like <position>=50 20 1920 1080 0,75
static const QRegularExpression animatedRegexp(R"((=\d+ \d+ \d+ \d+ \d+),(\d+))");
value.replace(animatedRegexp, "\\1.\\2");
@@ -172,6 +173,7 @@ AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset,
break;
case ParamType::Curve:
case ParamType::Geometry:
case ParamType::FakeRect:
case ParamType::Switch:
case ParamType::MultiSwitch:
case ParamType::Wipe:
@@ -184,6 +186,9 @@ AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset,
// Not sure if fine
converted = false;
break;
case ParamType::Unknown:
qDebug() << "//// WARNING UNKNOWN PARAM TYPE REQUESTED IN: " << m_assetId;
break;
}
if (converted) {
if (value != originalValue) {
@@ -320,8 +325,9 @@ void AssetParameterModel::internalSetParameter(const QString name, const QString
{
Q_ASSERT(m_asset->is_valid());
// TODO: this does not really belong here, but I don't see another way to do it so that undo works
ParamType type = ParamType::Unknown;
if (m_params.count(name) > 0) {
ParamType type = m_params.at(name).type;
type = m_params.at(name).type;
if (type == ParamType::Curve) {
QStringList vals = paramValue.split(QLatin1Char(';'), Qt::SkipEmptyParts);
int points = vals.size();
@@ -373,18 +379,96 @@ void AssetParameterModel::internalSetParameter(const QString name, const QString
m_fixedParams[name] = doubleValue;
}
} else {
qDebug() << "::::::::::SETTING EFFECT PARAM: " << name << " = " << paramValue;
m_asset->set(name.toLatin1().constData(), paramValue.toUtf8().constData());
if (m_fixedParams.count(name) == 0) {
m_params[name].value = paramValue;
qDebug() << ":::: SETTING PARAM: " << name << " = " << paramValue << "\nHHHHHHHHHHHHHHHHHH";
KeyframeModel *km = nullptr;
if (m_keyframes) {
// check if this parameter is keyframable
km = m_keyframes->getKeyModel(paramIndex);
}
if (km) {
// This is a fake query to force the animation to be parsed
(void)m_asset->anim_get_int(name.toLatin1().constData(), 0, -1);
KeyframeModel *km = m_keyframes->getKeyModel(paramIndex);
if (km) {
km->refresh();
} else {
qDebug() << "====ERROR KFMODEL NOT FOUND FOR: " << name << ", " << paramIndex;
if (type == ParamType::AnimatedFakeRect) {
QStringList xAnim;
QStringList yAnim;
QStringList wAnim;
QStringList hAnim;
mlt_profile profile = (mlt_profile)m_asset->get_data("_profile");
Mlt::Animation anim = m_asset->get_animation(name.toLatin1().constData());
QVariantMap mappedParams = data(paramIndex, FakeRectRole).toMap();
for (int i = 0; i < anim.key_count(); ++i) {
int frame;
mlt_keyframe_type type;
anim.key_get(i, frame, type);
mlt_rect rect = m_asset->anim_get_rect(name.toLatin1().constData(), frame);
if (paramValue.contains(QLatin1Char('%'))) {
rect.x *= profile->width;
rect.w *= profile->width;
rect.y *= profile->height;
rect.h *= profile->height;
}
// TODO DOPE: keyframe type
for (auto i = mappedParams.cbegin(), end = mappedParams.cend(); i != end; ++i) {
const AssetRectInfo paramInfo = i.value().value<AssetRectInfo>();
double val = paramInfo.getValue(rect);
switch (paramInfo.position) {
case 0:
xAnim << QStringLiteral("%1=%2").arg(frame).arg(val);
break;
case 1:
yAnim << QStringLiteral("%1=%2").arg(frame).arg(val);
break;
case 2:
wAnim << QStringLiteral("%1=%2").arg(frame).arg(val);
break;
case 3:
hAnim << QStringLiteral("%1=%2").arg(frame).arg(val);
break;
default:
qDebug() << "::: UNEXPECTED FAKE RECT INDEX: " << paramInfo.position;
}
}
}
for (auto i = mappedParams.cbegin(), end = mappedParams.cend(); i != end; ++i) {
const AssetRectInfo paramInfo = i.value().value<AssetRectInfo>();
switch (paramInfo.position) {
case 0:
m_asset->set(paramInfo.destName.toUtf8().constData(), xAnim.join(QLatin1Char(';')).toUtf8().constData());
break;
case 1:
m_asset->set(paramInfo.destName.toUtf8().constData(), yAnim.join(QLatin1Char(';')).toUtf8().constData());
break;
case 2:
m_asset->set(paramInfo.destName.toUtf8().constData(), wAnim.join(QLatin1Char(';')).toUtf8().constData());
break;
case 3:
m_asset->set(paramInfo.destName.toUtf8().constData(), hAnim.join(QLatin1Char(';')).toUtf8().constData());
break;
default:
qDebug() << "::: UNEXPECTED FAKE RECT INDEX: " << paramInfo.position;
}
}
}
km->refresh();
} else {
// Fixed value
if (type == ParamType::FakeRect) {
// Pass the values to the real MLT params
qDebug() << "/// PASSINF PARAMVALUE: " << paramValue;
QVariantMap mappedParams = data(paramIndex, FakeRectRole).toMap();
const QStringList splitValue = paramValue.split(QLatin1Char(' '));
for (auto i = mappedParams.cbegin(), end = mappedParams.cend(); i != end; ++i) {
const AssetRectInfo paramInfo = i.value().value<AssetRectInfo>();
double val = 0;
if (paramInfo.position >= splitValue.count()) {
continue;
}
val = paramInfo.getValue(splitValue);
m_asset->set(paramInfo.destName.toUtf8().constData(), val);
}
}
}
} else {
@@ -619,6 +703,21 @@ QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
return !element.hasAttribute(QStringLiteral("notintimeline"));
case AlphaRole:
return element.attribute(QStringLiteral("alpha")) == QLatin1String("1");
case FakeRectRole: {
QVariantMap mappedParams;
QDomNodeList children = element.elementsByTagName(QStringLiteral("parammap"));
for (int i = 0; i < children.count(); ++i) {
QDomElement currentParameter = children.item(i).toElement();
const QString position = currentParameter.attribute(QStringLiteral("position"));
AssetRectInfo paramInfo(currentParameter.attribute(QStringLiteral("src")), position.toInt(), currentParameter.attribute(QStringLiteral("default")),
currentParameter.attribute(QStringLiteral("min")), currentParameter.attribute(QStringLiteral("max")),
currentParameter.attribute(QStringLiteral("factor")),
currentParameter.attribute(QStringLiteral("fromborder")) == QLatin1String("1"),
currentParameter.attribute(QStringLiteral("from")));
mappedParams.insert(position, QVariant::fromValue(paramInfo));
}
return mappedParams;
}
case ValueRole: {
if (m_params.at(paramName).type == ParamType::MultiSwitch) {
// Multi params concatenate param names with a '\n' and param values with a space
@@ -752,6 +851,10 @@ ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
return ParamType::KeyframeParam;
} else if (type == QLatin1String("animatedrect") || type == QLatin1String("rect")) {
return ParamType::AnimatedRect;
} else if (type == QLatin1String("animatedfakerect")) {
return ParamType::AnimatedFakeRect;
} else if (type == QLatin1String("fakerect")) {
return ParamType::FakeRect;
} else if (type == QLatin1String("geometry")) {
return ParamType::Geometry;
} else if (type == QLatin1String("keyframe") || type == QLatin1String("animated")) {
@@ -792,8 +895,8 @@ ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
// static
bool AssetParameterModel::isAnimated(ParamType type)
{
return type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::ColorWheel || type == ParamType::Roto_spline ||
type == ParamType::Color;
return type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect || type == ParamType::ColorWheel ||
type == ParamType::Roto_spline || type == ParamType::Color;
}
// static
@@ -875,7 +978,7 @@ QVariant AssetParameterModel::parseAttribute(const QString &attribute, const QDo
if (frameSize.isEmpty()) {
frameSize = profileSize;
}
if (type == ParamType::AnimatedRect && content == "adjustcenter") {
if ((type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect || type == ParamType::FakeRect) && content == "adjustcenter") {
int contentHeight = profileSize.height();
int contentWidth = profileSize.width();
double sourceDar = frameSize.width() / frameSize.height();
@@ -925,7 +1028,8 @@ QVariant AssetParameterModel::parseAttribute(const QString &attribute, const QDo
.replace(QLatin1String("%fittedContentHeight"), QString::number(frameSize.height() * fitScale))
.replace(QLatin1String("%out"), QString::number(out))
.replace(QLatin1String("%fade"), QString::number(frame_duration));
if ((type == ParamType::AnimatedRect || type == ParamType::Geometry) && attribute == QLatin1String("default")) {
if ((type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect || type == ParamType::FakeRect || type == ParamType::Geometry) &&
attribute == QLatin1String("default")) {
if (content.contains(QLatin1Char('%'))) {
// This is a generic default like: "25% 0% 50% 100%". Parse values
QStringList numbers = content.split(QLatin1Char(' '));
@@ -1129,7 +1233,7 @@ QJsonDocument AssetParameterModel::toJson(QVector<int> selection, bool includeFi
QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
ParamType type = data(ix, AssetParameterModel::TypeRole).value<ParamType>();
if (percentageExport && type == ParamType::AnimatedRect) {
if (percentageExport && (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect || type == ParamType::FakeRect)) {
// Convert values to percents
const QString percentVal = animationToPercentage(fixed.second.toString());
if (percentVal.isEmpty()) {
@@ -1190,7 +1294,7 @@ QJsonDocument AssetParameterModel::toJson(QVector<int> selection, bool includeFi
} else {
QString resultValue = param.second.value.toString();
ParamType type = data(ix, AssetParameterModel::TypeRole).value<ParamType>();
if (percentageExport && type == ParamType::AnimatedRect) {
if (percentageExport && (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect || type == ParamType::FakeRect)) {
// Convert values to percents
const QString percentVal = animationToPercentage(resultValue);
if (!percentVal.isEmpty()) {

View File

@@ -5,6 +5,7 @@
#pragma once
#include "core.h"
#include "definitions.h"
#include "klocalizedstring.h"
#include <QAbstractListModel>
@@ -28,7 +29,9 @@ enum class ParamType {
Switch,
EffectButtons,
MultiSwitch,
AnimatedRect, // Animated rects have X, Y, width, height, and opacity (in [0,1])
AnimatedRect, // Animated rects have X, Y, width, height, and opacity (in [0,1])
AnimatedFakeRect, // Contains 4 parameters that make a rect, animated
FakeRect, // Contains 4 parameters that make a rect
Geometry,
KeyframeParam,
Color,
@@ -44,10 +47,138 @@ enum class ParamType {
Fontfamily,
Filterjob,
Readonly,
Hidden
Hidden,
Unknown
};
Q_DECLARE_METATYPE(ParamType)
struct AssetRectInfo
{
QString destName;
int position{0};
double defaultValue;
double minimum;
double maximum;
double factor{1};
double from{0};
bool fromBorder;
explicit AssetRectInfo(const QString &name, int pos, const QString &value, const QString &min, const QString &max, const QString &fac = QString(),
bool border = false, const QString &fr = QString())
: destName(name)
, position(pos)
, fromBorder(border)
{
if (value == QLatin1String("%width")) {
defaultValue = pCore->getCurrentFrameSize().width();
} else if (value == QLatin1String("%height")) {
defaultValue = pCore->getCurrentFrameSize().height();
} else {
defaultValue = value.toDouble();
}
if (min == QLatin1String("%width")) {
minimum = pCore->getCurrentFrameSize().width();
} else if (min == QLatin1String("%height")) {
minimum = pCore->getCurrentFrameSize().height();
} else {
minimum = min.toDouble();
}
if (max == QLatin1String("%width")) {
maximum = pCore->getCurrentFrameSize().width();
} else if (max == QLatin1String("%height")) {
maximum = pCore->getCurrentFrameSize().height();
} else {
maximum = max.toDouble();
}
if (fac == QLatin1String("%width")) {
factor = pCore->getCurrentFrameSize().width();
} else if (fac == QLatin1String("%height")) {
factor = pCore->getCurrentFrameSize().height();
} else {
if (fac.isEmpty()) {
factor = 1.0;
} else {
factor = fac.toDouble();
}
}
if (fr == QLatin1String("%width")) {
from = pCore->getCurrentFrameSize().width();
} else if (fr == QLatin1String("%height")) {
from = pCore->getCurrentFrameSize().height();
} else {
from = fr.toDouble();
}
}
explicit AssetRectInfo() {}
double getValue(double val) const
{
if (from != 0) {
val = from - val;
}
if (factor != 1.) {
val /= factor;
}
return val;
}
double getValue(const QStringList &vals) const
{
double val = vals.at(position).toDouble();
if (fromBorder) {
if (position == 2) {
// Width
val += vals.at(0).toDouble();
} else if (position == 3) {
// Width
val += vals.at(1).toDouble();
}
}
if (from != 0) {
val = from - val;
}
if (factor != 1.) {
val /= factor;
}
return val;
}
double getValue(const mlt_rect rect) const
{
double val = 0.;
switch (position) {
case 0:
val = rect.x;
break;
case 1:
val = rect.y;
break;
case 2:
val = rect.w;
break;
case 3:
val = rect.h;
break;
default:
break;
}
if (fromBorder) {
if (position == 2) {
// Width
val += rect.x;
} else if (position == 3) {
// Width
val += rect.y;
}
}
if (from != 0) {
val = from - val;
}
if (factor != 1.) {
val /= factor;
}
return val;
}
};
/** @class AssetParameterModel
@brief This class is the model for a list of parameters.
The behaviour of a transition or an effect is typically controlled by several parameters. This class exposes this parameters as a list that can be rendered
@@ -116,6 +247,8 @@ public:
ParentPositionRole,
ParentDurationRole,
HideKeyframesFirstRole,
// Obtain the real (distinct) parameters for a fake rect (x, y, w, h)
FakeRectRole,
List1Role,
List2Role,
Enum1Role,

View File

@@ -289,13 +289,17 @@ int AssetParameterView::contentHeight() const
MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
{
MonitorSceneType requestedType = MonitorSceneDefault;
if (m_mainKeyframeWidget) {
return m_mainKeyframeWidget->requiredScene();
requestedType = m_mainKeyframeWidget->requiredScene();
}
if (requestedType != MonitorSceneDefault) {
return requestedType;
}
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
auto type = m_model->data(index, AssetParameterModel::TypeRole).value<ParamType>();
if (type == ParamType::Geometry) {
if (type == ParamType::Geometry || type == ParamType::FakeRect) {
return MonitorSceneGeometry;
}
}

View File

@@ -102,6 +102,7 @@ std::pair<AbstractParamWidget *, KeyframeContainer *> AbstractParamWidget::const
widget = new BoolParamWidget(model, index, parent);
break;
case ParamType::Geometry:
case ParamType::FakeRect:
widget = new GeometryEditWidget(model, index, frameSize, parent, layout);
break;
case ParamType::Position:

View File

@@ -273,7 +273,13 @@ KeyframeContainer::KeyframeContainer(std::shared_ptr<AssetParameterModel> model,
QList<QPersistentModelIndex> rectParams;
for (const auto &w : m_parameters) {
auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>();
if (type == ParamType::AnimatedRect) {
if (type == ParamType::AnimatedFakeRect) {
paramList.insert(w.first, i18n("Height"));
paramList.insert(w.first, i18n("Width"));
paramList.insert(w.first, i18n("Y position"));
paramList.insert(w.first, i18n("X position"));
rectParams << w.first;
} else if (type == ParamType::AnimatedRect) {
if (m_model->data(w.first, AssetParameterModel::OpacityRole).toBool()) {
paramList.insert(w.first, i18n("Opacity"));
}
@@ -437,7 +443,7 @@ void KeyframeContainer::slotRefreshParams()
auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value<ParamType>();
if (type == ParamType::KeyframeParam) {
(static_cast<DoubleWidget *>(w.second))->setValue(m_keyframes->getInterpolatedValue(pos, w.first).toDouble());
} else if (type == ParamType::AnimatedRect) {
} else if (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect) {
const QString val = m_keyframes->getInterpolatedValue(pos, w.first).toString();
const QStringList vals = val.split(QLatin1Char(' '));
QRect rect;
@@ -596,7 +602,7 @@ void KeyframeContainer::initNeededSceneAndHelper()
m_neededScene = MonitorSceneType::MonitorSceneRoto;
m_monitorHelper = new RotoHelper(pCore->getMonitor(m_model->monitorId), m_model, m_parent);
break;
} else if (type == ParamType::AnimatedRect) {
} else if (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect) {
m_neededScene = MonitorSceneType::MonitorSceneGeometry;
m_monitorHelper = new KeyframeMonitorHelper(pCore->getMonitor(m_model->monitorId), m_model, m_neededScene, m_parent);
break;
@@ -625,7 +631,7 @@ void KeyframeContainer::addParameter(const QPersistentModelIndex &index)
qDebug() << "::::::PARAM ADDED:" << name << static_cast<int>(type) << comment << suffix;
// create KeyframeCurveEditor(s) which controls the current parameter
if (type == ParamType::AnimatedRect) {
if (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect) {
QVector<QString> tabname = QVector<QString>() << i18n("X position") << i18n("Y position") << i18n("Width") << i18n("Height");
if (m_model->data(index, AssetParameterModel::OpacityRole).toBool()) {
tabname.append(i18n("Opacity"));
@@ -641,7 +647,7 @@ void KeyframeContainer::addParameter(const QPersistentModelIndex &index)
QLabel *labelWidget = nullptr;
QWidget *paramWidget = nullptr;
QString paramName = m_model->data(index, AssetParameterModel::NameRole).toString();
if (type == ParamType::AnimatedRect) {
if (type == ParamType::AnimatedRect || type == ParamType::AnimatedFakeRect) {
int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt();
QPair<int, int> range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt());
const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString();

View File

@@ -10,6 +10,47 @@
#include "effectstackmodel.hpp"
#include <utility>
static QMap<mlt_keyframe_type, QString> typeMap = {
// Map keyframe type to any single character except numeric values.
{mlt_keyframe_discrete, "|"},
{mlt_keyframe_discrete, "!"},
{mlt_keyframe_linear, ""},
{mlt_keyframe_smooth, "~"},
{mlt_keyframe_smooth_loose, "~"},
{mlt_keyframe_smooth_natural, "$"},
{mlt_keyframe_smooth_tight, "-"},
{mlt_keyframe_sinusoidal_in, "a"},
{mlt_keyframe_sinusoidal_out, "b"},
{mlt_keyframe_sinusoidal_in_out, "c"},
{mlt_keyframe_quadratic_in, "d"},
{mlt_keyframe_quadratic_out, "e"},
{mlt_keyframe_quadratic_in_out, "f"},
{mlt_keyframe_cubic_in, "g"},
{mlt_keyframe_cubic_out, "h"},
{mlt_keyframe_cubic_in_out, "i"},
{mlt_keyframe_quartic_in, "j"},
{mlt_keyframe_quartic_out, "k"},
{mlt_keyframe_quartic_in_out, "l"},
{mlt_keyframe_quintic_in, "m"},
{mlt_keyframe_quintic_out, "n"},
{mlt_keyframe_quintic_in_out, "o"},
{mlt_keyframe_exponential_in, "p"},
{mlt_keyframe_exponential_out, "q"},
{mlt_keyframe_exponential_in_out, "r"},
{mlt_keyframe_circular_in, "s"},
{mlt_keyframe_circular_out, "t"},
{mlt_keyframe_circular_in_out, "u"},
{mlt_keyframe_back_in, "v"},
{mlt_keyframe_back_out, "w"},
{mlt_keyframe_back_in_out, "x"},
{mlt_keyframe_elastic_in, "y"},
{mlt_keyframe_elastic_out, "z"},
{mlt_keyframe_elastic_in_out, "A"},
{mlt_keyframe_bounce_in, "B"},
{mlt_keyframe_bounce_out, "C"},
{mlt_keyframe_bounce_in_out, "D"},
};
EffectItemModel::EffectItemModel(const QList<QVariant> &effectData, std::unique_ptr<Mlt::Properties> effect, const QDomElement &xml, const QString &effectId,
const std::shared_ptr<AbstractTreeModel> &stack, bool isEnabled, QString originalDecimalPoint)
: AbstractEffectItem(EffectItemType::Effect, effectData, stack, false, isEnabled)
@@ -80,7 +121,100 @@ std::shared_ptr<EffectItemModel> EffectItemModel::construct(std::unique_ptr<Mlt:
continue;
}
QString paramValue = effect->get(paramName.toUtf8().constData());
qDebug() << effectId << ": Setting parameter " << paramName << " to " << paramValue;
if (paramValue.isEmpty() && paramType == QLatin1String("animatedfakerect")) {
// Check if we have existing values for the fake rect individual components
QDomNodeList children = currentParameter.elementsByTagName(QLatin1String("parammap"));
qDebug() << ":::: FOUND FAKE RECT PARAM WITH EMPTY VALUES: " << paramName;
QMap<int, QRectF> rectValues;
QMap<int, mlt_keyframe_type> keyframeMap;
QMap<QString, std::pair<bool, int>> paramsFrom;
// Sort parameters
QMap<int, QString> sortedParams;
for (int c = 0; c < children.count(); ++c) {
QDomElement currentParameter = children.item(c).toElement();
int position = currentParameter.attribute(QStringLiteral("position")).toInt();
const QString childName = currentParameter.attribute(QStringLiteral("src"));
bool fromBorder = currentParameter.attribute(QStringLiteral("fromborder")).toInt() == 1;
int from = 0;
if (currentParameter.hasAttribute(QStringLiteral("from"))) {
const QString fromString = currentParameter.attribute(QStringLiteral("from"));
if (fromString == QLatin1String("%width")) {
from = pCore->getCurrentFrameSize().width();
} else if (fromString == QLatin1String("%height")) {
from = pCore->getCurrentFrameSize().height();
}
}
paramsFrom.insert(childName, {fromBorder, from});
sortedParams.insert(position, childName);
}
for (auto param = sortedParams.cbegin(), end = sortedParams.cend(); param != end; ++param) {
if (!effect->property_exists(param.value().toUtf8().constData())) {
// Missing parameter, use default values
break;
}
// Force animation parsing
(void)effect->anim_get_double(param.value().toUtf8().constData(), 0);
Mlt::Animation anim = effect->get_animation(param.value().toUtf8().constData());
qDebug() << "Found" << anim.key_count() << " Keyframes in " << param.value();
int frame;
if (param.key() == 0) {
// First param, parse keyframes
for (int j = 0; j < anim.key_count(); ++j) {
mlt_keyframe_type type;
anim.key_get(j, frame, type);
keyframeMap.insert(frame, type);
}
if (keyframeMap.isEmpty()) {
// No keyframes found, use default value
break;
}
for (auto &k : keyframeMap.keys()) {
QRectF rect(effect->anim_get_double(param.value().toUtf8().constData(), k), 0, 0, 0);
rectValues.insert(k, rect);
}
} else {
for (auto &k : keyframeMap.keys()) {
QRectF rect = rectValues.value(k);
bool fromBorder = paramsFrom.value(param.value()).first;
int from = paramsFrom.value(param.value()).second;
double value = effect->anim_get_double(param.value().toUtf8().constData(), k);
switch (param.key()) {
case 1:
rect.setY(value);
break;
case 2:
if (fromBorder) {
value += rect.x();
}
if (from > 0) {
value = from - value;
}
rect.setWidth(value);
break;
case 3:
if (fromBorder) {
value += rect.y();
}
if (from > 0) {
value = from - value;
}
rect.setHeight(value);
break;
default:
qDebug() << ":::::: UNEXPECTED FAKERECT POSITION: " << param.key();
break;
}
rectValues.insert(k, rect);
}
}
}
for (auto vals = rectValues.cbegin(), end = rectValues.cend(); vals != end; ++vals) {
paramValue.append(QString::number(vals.key()));
paramValue.append(typeMap.value(keyframeMap.value(vals.key())));
paramValue.append(QString("%1 %2 %3 %4;").arg(vals.value().x()).arg(vals.value().y()).arg(vals.value().width()).arg(vals.value().height()));
}
}
currentParameter.setAttribute(QStringLiteral("value"), paramValue);
}