File indexing completed on 2024-05-12 04:52:53
0001 /* 0002 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "klocalizedstring.h" 0007 #include <QCheckBox> 0008 #include <QComboBox> 0009 #include <QDialogButtonBox> 0010 #include <QHBoxLayout> 0011 #include <QJsonArray> 0012 #include <QJsonDocument> 0013 #include <QJsonObject> 0014 #include <QPainter> 0015 #include <QSpinBox> 0016 #include <QVBoxLayout> 0017 #include <utility> 0018 0019 #include "assets/keyframes/view/keyframeview.hpp" 0020 #include "core.h" 0021 #include "doc/kdenlivedoc.h" 0022 #include "kdenlivesettings.h" 0023 #include "keyframeimport.h" 0024 #include "monitor/monitor.h" 0025 #include "profiles/profilemodel.hpp" 0026 #include "widgets/positionwidget.h" 0027 #include <macros.hpp> 0028 0029 #include "mlt++/MltAnimation.h" 0030 #include "mlt++/MltProperties.h" 0031 0032 KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr<AssetParameterModel> model, const QList<QPersistentModelIndex> &indexes, int parentIn, 0033 int parentDuration, QWidget *parent) 0034 : QDialog(parent) 0035 , m_model(std::move(model)) 0036 , m_indexes(indexes) 0037 , m_supportsAnim(false) 0038 , m_previewLabel(nullptr) 0039 , m_sourceCombo(nullptr) 0040 , m_targetCombo(nullptr) 0041 , m_isReady(false) 0042 { 0043 setAttribute(Qt::WA_DeleteOnClose); 0044 auto *lay = new QVBoxLayout(this); 0045 auto *l1 = new QHBoxLayout; 0046 QLabel *lab = new QLabel(i18n("Data to import:"), this); 0047 l1->addWidget(lab); 0048 0049 m_dataCombo = new QComboBox(this); 0050 l1->addWidget(m_dataCombo); 0051 l1->addStretch(10); 0052 lay->addLayout(l1); 0053 int in = -1; 0054 int out = -1; 0055 std::pair<int, int> sourceInOut = {-1, -1}; 0056 // Set up data 0057 auto json = QJsonDocument::fromJson(animData.toUtf8()); 0058 if (!json.isArray()) { 0059 qDebug() << "Error : Json file should be an array"; 0060 // Try to build data from a single value 0061 QJsonArray list; 0062 QJsonObject currentParam; 0063 currentParam.insert(QLatin1String("name"), QStringLiteral("data")); 0064 currentParam.insert(QLatin1String("value"), animData); 0065 bool ok; 0066 QString firstFrame = animData.section(QLatin1Char('='), 0, 0); 0067 in = firstFrame.toInt(&ok); 0068 if (!ok) { 0069 firstFrame.chop(1); 0070 in = firstFrame.toInt(&ok); 0071 } 0072 if (ok && sourceInOut.first == -1) { 0073 sourceInOut.first = in; 0074 } 0075 QString lastFrame = animData.section(QLatin1Char(';'), -1); 0076 lastFrame = lastFrame.section(QLatin1Char('='), 0, 0); 0077 int lastPos = lastFrame.toInt(&ok); 0078 if (!ok) { 0079 lastFrame.chop(1); 0080 lastPos = lastFrame.toInt(&ok); 0081 } 0082 if (ok && sourceInOut.second == -1) { 0083 sourceInOut.second = lastPos; 0084 } 0085 QString first = animData.section(QLatin1Char('='), 1, 1); 0086 if (!first.isEmpty()) { 0087 int spaces = first.count(QLatin1Char(' ')); 0088 switch (spaces) { 0089 case 0: 0090 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::Hidden))); 0091 break; 0092 default: 0093 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect))); 0094 break; 0095 } 0096 // currentParam.insert(QLatin1String("min"), QJsonValue(min)); 0097 // currentParam.insert(QLatin1String("max"), QJsonValue(max)); 0098 list.push_back(currentParam); 0099 json = QJsonDocument(list); 0100 } 0101 } 0102 if (!json.isArray()) { 0103 qDebug() << "Error : Json file should be an array"; 0104 return; 0105 } 0106 auto list = json.array(); 0107 int ix = 0; 0108 for (const auto &entry : qAsConst(list)) { 0109 if (!entry.isObject()) { 0110 qDebug() << "Warning : Skipping invalid marker data"; 0111 continue; 0112 } 0113 auto entryObj = entry.toObject(); 0114 if (!entryObj.contains(QLatin1String("name"))) { 0115 qDebug() << "Warning : Skipping invalid marker data (does not contain name)"; 0116 continue; 0117 } 0118 QString name = entryObj[QLatin1String("name")].toString(); 0119 QString displayName = entryObj[QLatin1String("DisplayName")].toString(); 0120 if (displayName.isEmpty()) { 0121 displayName = name; 0122 } 0123 QString value = entryObj[QLatin1String("value")].toString(); 0124 int type = entryObj[QLatin1String("type")].toInt(0); 0125 double min = entryObj[QLatin1String("min")].toDouble(0); 0126 double max = entryObj[QLatin1String("max")].toDouble(0); 0127 if (sourceInOut.first == -1) { 0128 sourceInOut.first = entryObj[QLatin1String("in")].toInt(0); 0129 } 0130 if (sourceInOut.second == -1) { 0131 sourceInOut.second = entryObj[QLatin1String("out")].toInt(0); 0132 } 0133 if (in == -1) { 0134 in = entryObj[QLatin1String("in")].toInt(0); 0135 } 0136 if (out == -1) { 0137 out = entryObj[QLatin1String("out")].toInt(0); 0138 } 0139 bool opacity = entryObj[QLatin1String("opacity")].toBool(true); 0140 m_dataCombo->insertItem(ix, displayName); 0141 m_dataCombo->setItemData(ix, value, ValueRole); 0142 m_dataCombo->setItemData(ix, type, TypeRole); 0143 m_dataCombo->setItemData(ix, min, MinRole); 0144 m_dataCombo->setItemData(ix, max, MaxRole); 0145 m_dataCombo->setItemData(ix, opacity, OpacityRole); 0146 ix++; 0147 } 0148 // If we have several available parameters, put the geometry first 0149 if (m_dataCombo->count() > 1) { 0150 for (int indx = 0; indx < m_dataCombo->count(); indx++) { 0151 auto type = m_dataCombo->itemData(indx, TypeRole).value<ParamType>(); 0152 if (type == ParamType::AnimatedRect) { 0153 m_dataCombo->setCurrentIndex(indx); 0154 break; 0155 } 0156 } 0157 } 0158 m_previewLabel = new QLabel(this); 0159 m_previewLabel->setMinimumSize(100, 150); 0160 m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0161 m_previewLabel->setScaledContents(true); 0162 0163 lay->addWidget(m_previewLabel); 0164 // Zone in / out 0165 in = qMax(0, in); 0166 out = qMin(sourceInOut.first + parentDuration - 1, sourceInOut.second); 0167 m_inPoint = new PositionWidget(i18n("In"), in, 0, out, QString(), this); 0168 connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay); 0169 lay->addWidget(m_inPoint); 0170 m_outPoint = new PositionWidget(i18n("Duration"), out, in, out, QString(), this); 0171 connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay); 0172 lay->addWidget(m_outPoint); 0173 0174 // Output offset 0175 int clipIn = parentIn; 0176 m_offsetPoint = new PositionWidget(i18n("Time offset:"), clipIn, 0, clipIn + parentDuration, "", this); 0177 lay->addWidget(m_offsetPoint); 0178 0179 int count = 0; 0180 // Check what kind of parameters are in our target 0181 for (const QPersistentModelIndex &idx : indexes) { 0182 auto type = m_model->data(idx, AssetParameterModel::TypeRole).value<ParamType>(); 0183 if (type == ParamType::KeyframeParam) { 0184 m_simpleTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx); 0185 QString name(m_model->data(idx, AssetParameterModel::NameRole).toString()); 0186 if (name.contains("Position X") || name.contains("Position Y") || name.contains("Size X") || name.contains("Size Y")) { 0187 count++; 0188 } 0189 } else if (type == ParamType::Roto_spline) { 0190 m_simpleTargets.insert(i18n("Rotoscoping shape"), idx); 0191 } else if (type == ParamType::AnimatedRect) { 0192 m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx); 0193 } 0194 } 0195 0196 l1 = new QHBoxLayout; 0197 m_targetCombo = new QComboBox(this); 0198 m_sourceCombo = new QComboBox(this); 0199 /*ix = 0; 0200 if (!m_geometryTargets.isEmpty()) { 0201 m_sourceCombo->insertItem(ix, i18n("Geometry")); 0202 m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole); 0203 ix++; 0204 m_sourceCombo->insertItem(ix, i18n("Position")); 0205 m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole); 0206 ix++; 0207 } 0208 if (!m_simpleTargets.isEmpty()) { 0209 m_sourceCombo->insertItem(ix, i18n("X")); 0210 m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole); 0211 ix++; 0212 m_sourceCombo->insertItem(ix, i18n("Y")); 0213 m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole); 0214 ix++; 0215 m_sourceCombo->insertItem(ix, i18n("Width")); 0216 m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole); 0217 ix++; 0218 m_sourceCombo->insertItem(ix, i18n("Height")); 0219 m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole); 0220 ix++; 0221 }*/ 0222 connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateRange); 0223 connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView); 0224 m_alignSourceCombo = new QComboBox(this); 0225 m_alignSourceCombo->addItem(i18n("Top left"), 0); 0226 m_alignSourceCombo->addItem(i18n("Top center"), 1); 0227 m_alignSourceCombo->addItem(i18n("Top right"), 2); 0228 m_alignSourceCombo->addItem(i18n("Left center"), 3); 0229 m_alignSourceCombo->addItem(i18n("Center"), 4); 0230 m_alignSourceCombo->addItem(i18n("Right center"), 5); 0231 m_alignSourceCombo->addItem(i18n("Bottom left"), 6); 0232 m_alignSourceCombo->addItem(i18n("Bottom center"), 7); 0233 m_alignSourceCombo->addItem(i18n("Bottom right"), 8); 0234 m_alignTargetCombo = new QComboBox(this); 0235 m_alignTargetCombo->addItem(i18n("Top left"), 0); 0236 m_alignTargetCombo->addItem(i18n("Top center"), 1); 0237 m_alignTargetCombo->addItem(i18n("Top right"), 2); 0238 m_alignTargetCombo->addItem(i18n("Left center"), 3); 0239 m_alignTargetCombo->addItem(i18n("Center"), 4); 0240 m_alignTargetCombo->addItem(i18n("Right center"), 5); 0241 m_alignTargetCombo->addItem(i18n("Bottom left"), 6); 0242 m_alignTargetCombo->addItem(i18n("Bottom center"), 7); 0243 m_alignTargetCombo->addItem(i18n("Bottom right"), 8); 0244 connect(m_alignSourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView); 0245 connect(m_alignTargetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView); 0246 lab = new QLabel(i18n("Map "), this); 0247 QLabel *lab2 = new QLabel(i18n(" to "), this); 0248 l1->addWidget(lab); 0249 l1->addWidget(m_sourceCombo); 0250 l1->addWidget(m_alignSourceCombo); 0251 l1->addWidget(lab2); 0252 l1->addWidget(m_targetCombo); 0253 l1->addWidget(m_alignTargetCombo); 0254 ix = 0; 0255 QMap<QString, QModelIndex>::const_iterator j = m_geometryTargets.constBegin(); 0256 while (j != m_geometryTargets.constEnd()) { 0257 m_targetCombo->insertItem(ix, j.key()); 0258 m_targetCombo->setItemData(ix, j.value(), Qt::UserRole); 0259 m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString()); 0260 ++j; 0261 ix++; 0262 } 0263 if (count == 4) { 0264 // add an option to map to a a fake rectangle (pretend position params are a mlt rect) 0265 m_targetCombo->insertItem(ix, i18n("Rectangle")); 0266 } 0267 ix = 0; 0268 j = m_simpleTargets.constBegin(); 0269 while (j != m_simpleTargets.constEnd()) { 0270 m_targetCombo->insertItem(ix, j.key()); 0271 m_targetCombo->setItemData(ix, j.value(), Qt::UserRole); 0272 m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString()); 0273 ++j; 0274 ix++; 0275 } 0276 if (m_simpleTargets.count() + m_geometryTargets.count() > 1) { 0277 // Target contains several animatable parameters, propose choice 0278 } 0279 lay->addLayout(l1); 0280 0281 m_offsetX.setRange(-pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->width()); 0282 m_offsetY.setRange(-pCore->getCurrentProfile()->height(), pCore->getCurrentProfile()->height()); 0283 connect(&m_offsetX, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView); 0284 connect(&m_offsetY, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView); 0285 0286 // Destination range 0287 l1 = new QHBoxLayout; 0288 lab = new QLabel(i18n("Position offset:"), this); 0289 l1->addWidget(lab); 0290 l1->addWidget(&m_offsetX); 0291 l1->addWidget(&m_offsetY); 0292 lay->addLayout(l1); 0293 0294 // Source range 0295 m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this); 0296 lay->addWidget(m_sourceRangeLabel); 0297 0298 // update range info 0299 0300 connect(m_targetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateDestinationRange); 0301 0302 // Destination range 0303 l1 = new QHBoxLayout; 0304 lab = new QLabel(i18n("Destination range:"), this); 0305 0306 l1->addWidget(lab); 0307 l1->addWidget(&m_destMin); 0308 l1->addWidget(&m_destMax); 0309 lay->addLayout(l1); 0310 0311 l1 = new QHBoxLayout; 0312 m_limitRange = new QCheckBox(i18n("Actual range only"), this); 0313 connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange); 0314 connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay); 0315 l1->addWidget(m_limitRange); 0316 l1->addStretch(10); 0317 lay->addLayout(l1); 0318 l1 = new QHBoxLayout; 0319 m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this); 0320 m_limitKeyframes->setChecked(true); 0321 m_limitNumber = new QSpinBox(this); 0322 m_limitNumber->setMinimum(1); 0323 m_limitNumber->setValue(20); 0324 l1->addWidget(m_limitKeyframes); 0325 l1->addWidget(m_limitNumber); 0326 l1->addStretch(10); 0327 lay->addLayout(l1); 0328 connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled); 0329 connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay); 0330 connect(m_limitNumber, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateDisplay); 0331 connect(m_dataCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateDataDisplay); 0332 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0333 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 0334 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0335 lay->addWidget(buttonBox); 0336 m_isReady = true; 0337 updateDestinationRange(); 0338 updateDataDisplay(); 0339 updateView(); 0340 } 0341 0342 KeyframeImport::~KeyframeImport() = default; 0343 0344 void KeyframeImport::resizeEvent(QResizeEvent *ev) 0345 { 0346 QWidget::resizeEvent(ev); 0347 updateDisplay(); 0348 } 0349 0350 void KeyframeImport::updateDataDisplay() 0351 { 0352 QString comboData = m_dataCombo->currentData().toString(); 0353 auto type = m_dataCombo->currentData(TypeRole).value<ParamType>(); 0354 auto values = m_dataCombo->currentData(ValueRole).toString().split(QLatin1Char(';')); 0355 0356 // we do not need all the options if there is only one keyframe 0357 bool onlyOne = values.length() == 1; 0358 m_previewLabel->setVisible(!onlyOne); 0359 m_limitKeyframes->setVisible(!onlyOne); 0360 m_limitNumber->setVisible(!onlyOne); 0361 m_inPoint->setVisible(!onlyOne); 0362 m_outPoint->setVisible(!onlyOne); 0363 0364 m_maximas = KeyframeModel::getRanges(comboData, m_model); 0365 m_sourceCombo->clear(); 0366 if (type == ParamType::KeyframeParam) { 0367 // 1 dimensional param. 0368 m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue); 0369 0370 // map rotation to rotation by default if possible 0371 if (m_dataCombo->currentText() == QStringLiteral("rotation")) { 0372 int idx = m_targetCombo->findText(i18n("Rotation")); 0373 if (idx > -1) { 0374 m_targetCombo->setCurrentIndex(idx); 0375 } 0376 } 0377 updateRange(); 0378 return; 0379 } 0380 if (type == ParamType::Roto_spline) { 0381 m_sourceCombo->addItem(i18n("Rotoscoping shape"), ImportRoles::RotoData); 0382 return; 0383 } 0384 double wDist = m_maximas.at(2).y() - m_maximas.at(2).x(); 0385 double hDist = m_maximas.at(3).y() - m_maximas.at(3).x(); 0386 m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry); 0387 m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position); 0388 m_sourceCombo->addItem(i18n("Inverted Position"), ImportRoles::InvertedPosition); 0389 m_sourceCombo->addItem(i18n("Offset Position"), ImportRoles::OffsetPosition); 0390 m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly); 0391 m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly); 0392 if (wDist > 0) { 0393 m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly); 0394 } 0395 if (hDist > 0) { 0396 m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly); 0397 } 0398 0399 // if available map to Rectangle by default 0400 int idx = m_targetCombo->findText(i18n("Rectangle")); 0401 if (idx > -1) { 0402 m_targetCombo->setCurrentIndex(idx); 0403 } 0404 updateRange(); 0405 /*if (!m_inPoint->isValid()) { 0406 m_inPoint->blockSignals(true); 0407 m_outPoint->blockSignals(true); 0408 // m_inPoint->setRange(0, m_keyframeView->duration); 0409 m_inPoint->setPosition(0); 0410 m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt()); 0411 m_inPoint->blockSignals(false); 0412 m_outPoint->blockSignals(false); 0413 }*/ 0414 } 0415 0416 void KeyframeImport::updateRange() 0417 { 0418 int pos = m_sourceCombo->currentData().toInt(); 0419 m_alignSourceCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition); 0420 m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition); 0421 m_offsetX.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData); 0422 m_offsetY.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData); 0423 m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition); 0424 m_limitRange->setEnabled(pos != ImportRoles::RotoData); 0425 QString rangeText; 0426 if (m_limitRange->isChecked()) { 0427 switch (pos) { 0428 case ImportRoles::SimpleValue: 0429 case ImportRoles::XOnly: 0430 rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y()); 0431 break; 0432 case ImportRoles::YOnly: 0433 rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y()); 0434 break; 0435 case ImportRoles::WidthOnly: 0436 rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y()); 0437 break; 0438 case ImportRoles::HeightOnly: 0439 rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y()); 0440 break; 0441 default: 0442 rangeText = i18n("Source range: (%1-%2), (%3-%4)", m_maximas.at(0).x(), m_maximas.at(0).y(), m_maximas.at(1).x(), m_maximas.at(1).y()); 0443 break; 0444 } 0445 } else { 0446 int profileWidth = pCore->getCurrentProfile()->width(); 0447 int profileHeight = pCore->getCurrentProfile()->height(); 0448 switch (pos) { 0449 case ImportRoles::SimpleValue: 0450 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y()); 0451 break; 0452 case ImportRoles::XOnly: 0453 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y())); 0454 break; 0455 case ImportRoles::YOnly: 0456 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y())); 0457 break; 0458 case ImportRoles::WidthOnly: 0459 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y())); 0460 break; 0461 case ImportRoles::HeightOnly: 0462 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y())); 0463 break; 0464 default: 0465 rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()), 0466 qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y())); 0467 break; 0468 } 0469 } 0470 m_sourceRangeLabel->setText(rangeText); 0471 updateDisplay(); 0472 } 0473 0474 void KeyframeImport::updateDestinationRange() 0475 { 0476 if (m_targetCombo->currentText() == i18n("Rotoscoping shape")) { 0477 m_destMin.setEnabled(false); 0478 m_destMax.setEnabled(false); 0479 m_limitRange->setEnabled(false); 0480 return; 0481 } 0482 0483 if (m_simpleTargets.contains(m_targetCombo->currentText())) { 0484 // 1 dimension target 0485 m_destMin.setEnabled(true); 0486 m_destMax.setEnabled(true); 0487 m_limitRange->setEnabled(true); 0488 double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble(); 0489 double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble(); 0490 m_destMin.setRange(min, max); 0491 m_destMax.setRange(min, max); 0492 m_destMin.setValue(min); 0493 m_destMax.setValue(max); 0494 } else { 0495 int profileWidth = pCore->getCurrentProfile()->width(); 0496 m_destMin.setRange(-2 * profileWidth, 2 * profileWidth); 0497 m_destMax.setRange(-2 * profileWidth, 2 * profileWidth); 0498 m_destMin.setEnabled(false); 0499 m_destMax.setEnabled(false); 0500 m_limitRange->setEnabled(false); 0501 updateView(); 0502 } 0503 } 0504 0505 void KeyframeImport::updateDisplay() 0506 { 0507 if (!m_isReady) { 0508 // Not ready 0509 return; 0510 } 0511 QPixmap pix(m_previewLabel->width(), m_previewLabel->height()); 0512 pix.fill(Qt::transparent); 0513 QList<QPoint> maximas; 0514 int selectedtarget = m_sourceCombo->currentData().toInt(); 0515 int profileWidth = pCore->getCurrentProfile()->width(); 0516 int profileHeight = pCore->getCurrentProfile()->height(); 0517 if (!m_maximas.isEmpty()) { 0518 if (m_maximas.at(0).x() == m_maximas.at(0).y() || 0519 (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) { 0520 maximas << QPoint(); 0521 } else { 0522 if (m_limitRange->isChecked()) { 0523 maximas << m_maximas.at(0); 0524 } else { 0525 QPoint p1; 0526 if (selectedtarget == ImportRoles::SimpleValue) { 0527 p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y()); 0528 } else { 0529 p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y())); 0530 } 0531 maximas << p1; 0532 } 0533 } 0534 } 0535 if (m_maximas.count() > 1) { 0536 if (m_maximas.at(1).x() == m_maximas.at(1).y() || 0537 (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) { 0538 maximas << QPoint(); 0539 } else { 0540 if (m_limitRange->isChecked()) { 0541 maximas << m_maximas.at(1); 0542 } else { 0543 QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y())); 0544 maximas << p2; 0545 } 0546 } 0547 } 0548 if (m_maximas.count() > 2) { 0549 if (m_maximas.at(2).x() == m_maximas.at(2).y() || 0550 (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) { 0551 maximas << QPoint(); 0552 } else { 0553 if (m_limitRange->isChecked()) { 0554 maximas << m_maximas.at(2); 0555 } else { 0556 QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y())); 0557 maximas << p3; 0558 } 0559 } 0560 } 0561 if (m_maximas.count() > 3) { 0562 if (m_maximas.at(3).x() == m_maximas.at(3).y() || 0563 (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) { 0564 maximas << QPoint(); 0565 } else { 0566 if (m_limitRange->isChecked()) { 0567 maximas << m_maximas.at(3); 0568 } else { 0569 QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y())); 0570 maximas << p4; 0571 } 0572 } 0573 } 0574 drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0, 0575 palette().text().color()); 0576 m_previewLabel->setPixmap(pix); 0577 } 0578 0579 QString KeyframeImport::selectedData() const 0580 { 0581 // return serialized keyframes 0582 if (m_simpleTargets.contains(m_targetCombo->currentText())) { 0583 // Exporting a 1 dimension animation 0584 int ix = m_sourceCombo->currentData().toInt(); 0585 QPoint maximas; 0586 if (m_limitRange->isChecked()) { 0587 maximas = m_maximas.at(ix); 0588 } else if (ix == ImportRoles::WidthOnly) { 0589 // Width maximas 0590 maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width())); 0591 } else { 0592 // Height maximas 0593 maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height())); 0594 } 0595 if (m_dataCombo->currentData(TypeRole).value<ParamType>() == ParamType::Roto_spline) { 0596 QJsonDocument doc = QJsonDocument::fromJson(m_dataCombo->currentData().toString().toUtf8()); 0597 return QString(doc.toJson(QJsonDocument::Compact)); 0598 } 0599 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString()); 0600 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key"))); 0601 animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition()); 0602 int existingKeys = anim->key_count(); 0603 int out = m_outPoint->getPosition(); 0604 if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) { 0605 // We need to limit keyframes, create new animation 0606 int in = m_inPoint->getPosition(); 0607 std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString()); 0608 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key"))); 0609 anim2->interpolate(); 0610 0611 // Remove existing kfrs 0612 int firstKeyframe = -1; 0613 int lastKeyframe = -1; 0614 if (anim2->is_key(0)) { 0615 if (in == 0) { 0616 firstKeyframe = 0; 0617 } 0618 anim2->remove(0); 0619 } 0620 int keyPos = anim2->next_key(0); 0621 while (anim2->is_key(keyPos)) { 0622 if (firstKeyframe == -1) { 0623 firstKeyframe = keyPos; 0624 } 0625 if (keyPos < out) { 0626 lastKeyframe = keyPos; 0627 } else { 0628 lastKeyframe = out; 0629 } 0630 anim2->remove(keyPos); 0631 keyPos = anim2->next_key(keyPos); 0632 } 0633 anim2->interpolate(); 0634 int length = lastKeyframe; 0635 double interval = double(length) / (m_limitNumber->value() - 1); 0636 for (int i = 0; i < m_limitNumber->value(); i++) { 0637 int pos = firstKeyframe + in + i * interval; 0638 if (pos > out) { 0639 break; 0640 } 0641 pos = qMin(pos, length - 1); 0642 double dval = animData->anim_get_double("key", pos); 0643 animData2->anim_set("key", dval, pos); 0644 } 0645 anim2->interpolate(); 0646 return anim2->serialize_cut(); 0647 } else { 0648 // Ensure we have a keyframe at destination end 0649 if (out < anim->length() && !anim->is_key(out)) { 0650 double dval = animData->anim_get_double("key", out); 0651 animData->anim_set("key", dval, out); 0652 } 0653 } 0654 return anim->serialize_cut(); 0655 } 0656 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString()); 0657 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key"))); 0658 animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition()); 0659 // Ensure we have a keyframe at destination end 0660 int out = m_outPoint->getPosition(); 0661 int existingKeys = anim->key_count(); 0662 if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) { 0663 // We need to limit keyframes, create new animation 0664 int in = m_inPoint->getPosition(); 0665 std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString()); 0666 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key"))); 0667 anim2->interpolate(); 0668 0669 // Remove existing kfrs 0670 int firstKeyframe = -1; 0671 int lastKeyframe = -1; 0672 if (anim2->is_key(0)) { 0673 if (in == 0) { 0674 firstKeyframe = 0; 0675 } 0676 anim2->remove(0); 0677 } 0678 int keyPos = anim2->next_key(0); 0679 while (anim2->is_key(keyPos)) { 0680 if (firstKeyframe == -1) { 0681 firstKeyframe = keyPos; 0682 } 0683 if (keyPos < out) { 0684 lastKeyframe = keyPos; 0685 } else { 0686 lastKeyframe = out; 0687 } 0688 anim2->remove(keyPos); 0689 keyPos = anim2->next_key(keyPos); 0690 } 0691 anim2->interpolate(); 0692 int length = lastKeyframe - firstKeyframe; 0693 double interval = double(length) / (m_limitNumber->value() - 1); 0694 for (int i = 0; i < m_limitNumber->value(); i++) { 0695 int pos = firstKeyframe + i * interval; 0696 pos = qMin(pos, lastKeyframe); 0697 if (pos > out) { 0698 break; 0699 } 0700 mlt_rect rect = animData->anim_get_rect("key", pos); 0701 animData2->anim_set("key", rect, pos); 0702 } 0703 anim2->interpolate(); 0704 return anim2->serialize_cut(); 0705 } else { 0706 if (out < anim->length() && !anim->is_key(out)) { 0707 mlt_rect rect = animData->anim_get_rect("key", out); 0708 animData->anim_set("key", rect, out); 0709 } 0710 } 0711 return anim->serialize_cut(); 0712 } 0713 0714 QString KeyframeImport::selectedTarget() const 0715 { 0716 return m_targetCombo->currentData().toString(); 0717 } 0718 0719 void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor) 0720 { 0721 // qDebug() << "============= DRAWING KFR CHANNS: " << m_dataCombo->currentData().toString(); 0722 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString()); 0723 QRect br(0, 0, pix.width(), pix.height()); 0724 double frameFactor = double(out - in) / br.width(); 0725 int offset = 1; 0726 if (limitKeyframes > 0) { 0727 offset = int((out - in) / limitKeyframes / frameFactor); 0728 } 0729 double min = m_dataCombo->currentData(MinRole).toDouble(); 0730 double max = m_dataCombo->currentData(MaxRole).toDouble(); 0731 double xDist; 0732 if (max > min) { 0733 xDist = max - min; 0734 } else { 0735 xDist = m_maximas.at(0).y() - m_maximas.at(0).x(); 0736 } 0737 double yDist = m_maximas.at(1).y() - m_maximas.at(1).x(); 0738 double wDist = m_maximas.at(2).y() - m_maximas.at(2).x(); 0739 double hDist = m_maximas.at(3).y() - m_maximas.at(3).x(); 0740 double xOffset = m_maximas.at(0).x(); 0741 double yOffset = m_maximas.at(1).x(); 0742 double wOffset = m_maximas.at(2).x(); 0743 double hOffset = m_maximas.at(3).x(); 0744 QColor cX(255, 0, 0, 100); 0745 QColor cY(0, 255, 0, 100); 0746 QColor cW(0, 0, 255, 100); 0747 QColor cH(255, 255, 0, 100); 0748 // Draw curves labels 0749 QPainter painter; 0750 painter.begin(&pix); 0751 QRectF txtRect = painter.boundingRect(br, QStringLiteral("t")); 0752 txtRect.setX(2); 0753 txtRect.setWidth(br.width() - 4); 0754 txtRect.moveTop(br.height() - txtRect.height()); 0755 QRectF drawnText; 0756 int maxHeight = int(br.height() - txtRect.height() - 2); 0757 painter.setPen(textColor); 0758 int rectSize = int(txtRect.height() / 2); 0759 if (xDist > 0) { 0760 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cX); 0761 txtRect.setX(txtRect.x() + rectSize * 2); 0762 painter.drawText(txtRect, 0, i18nc("X as in x coordinate", "X") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(0).x()).arg(m_maximas.at(0).y()), 0763 &drawnText); 0764 } 0765 if (yDist > 0) { 0766 if (drawnText.isValid()) { 0767 txtRect.setX(drawnText.right() + rectSize); 0768 } 0769 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cY); 0770 txtRect.setX(txtRect.x() + rectSize * 2); 0771 painter.drawText(txtRect, 0, i18nc("Y as in y coordinate", "Y") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(1).x()).arg(m_maximas.at(1).y()), 0772 &drawnText); 0773 } 0774 if (wDist > 0) { 0775 if (drawnText.isValid()) { 0776 txtRect.setX(drawnText.right() + rectSize); 0777 } 0778 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cW); 0779 txtRect.setX(txtRect.x() + rectSize * 2); 0780 painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText); 0781 } 0782 if (hDist > 0) { 0783 if (drawnText.isValid()) { 0784 txtRect.setX(drawnText.right() + rectSize); 0785 } 0786 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cH); 0787 txtRect.setX(txtRect.x() + rectSize * 2); 0788 painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText); 0789 } 0790 0791 // Draw curves 0792 for (int i = 0; i < br.width(); i++) { 0793 mlt_rect rect = animData->anim_get_rect("key", int(i * frameFactor) + in); 0794 if (xDist > 0) { 0795 painter.setPen(cX); 0796 int val = int((rect.x - xOffset) * maxHeight / xDist); 0797 // qDebug() << "// DRAWINC CURVE : " << rect.x << ", POS: " << (int(i * frameFactor) + in) << ", RESULT: " << val; 0798 painter.drawLine(i, maxHeight - val, i, maxHeight); 0799 } 0800 if (yDist > 0) { 0801 painter.setPen(cY); 0802 int val = int((rect.y - yOffset) * maxHeight / yDist); 0803 painter.drawLine(i, maxHeight - val, i, maxHeight); 0804 } 0805 if (wDist > 0) { 0806 painter.setPen(cW); 0807 int val = int((rect.w - wOffset) * maxHeight / wDist); 0808 qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val; 0809 painter.drawLine(i, maxHeight - val, i, maxHeight); 0810 } 0811 if (hDist > 0) { 0812 painter.setPen(cH); 0813 int val = int((rect.h - hOffset) * maxHeight / hDist); 0814 painter.drawLine(i, maxHeight - val, i, maxHeight); 0815 } 0816 } 0817 if (offset > 1) { 0818 // Overlay limited keyframes curve 0819 cX.setAlpha(255); 0820 cY.setAlpha(255); 0821 cW.setAlpha(255); 0822 cH.setAlpha(255); 0823 mlt_rect rect1 = animData->anim_get_rect("key", in); 0824 int prevPos = 0; 0825 for (int i = offset; i < br.width(); i += offset) { 0826 mlt_rect rect2 = animData->anim_get_rect("key", int(i * frameFactor) + in); 0827 if (xDist > 0) { 0828 painter.setPen(cX); 0829 int val1 = int((rect1.x - xOffset) * maxHeight / xDist); 0830 int val2 = int((rect2.x - xOffset) * maxHeight / xDist); 0831 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2); 0832 } 0833 if (yDist > 0) { 0834 painter.setPen(cY); 0835 int val1 = int((rect1.y - yOffset) * maxHeight / yDist); 0836 int val2 = int((rect2.y - yOffset) * maxHeight / yDist); 0837 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2); 0838 } 0839 if (wDist > 0) { 0840 painter.setPen(cW); 0841 int val1 = int((rect1.w - wOffset) * maxHeight / wDist); 0842 int val2 = int((rect2.w - wOffset) * maxHeight / wDist); 0843 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2); 0844 } 0845 if (hDist > 0) { 0846 painter.setPen(cH); 0847 int val1 = int((rect1.h - hOffset) * maxHeight / hDist); 0848 int val2 = int((rect2.h - hOffset) * maxHeight / hDist); 0849 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2); 0850 } 0851 rect1 = rect2; 0852 prevPos = i; 0853 } 0854 } 0855 } 0856 0857 void KeyframeImport::importSelectedData() 0858 { 0859 // Simple double value 0860 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData()); 0861 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key"))); 0862 std::shared_ptr<KeyframeModelList> kfrModel = m_model->getKeyframeModel(); 0863 Fun undo = []() { return true; }; 0864 Fun redo = []() { return true; }; 0865 // Geometry target 0866 int sourceAlign = m_alignSourceCombo->currentData().toInt(); 0867 int targetAlign = m_alignTargetCombo->currentData().toInt(); 0868 QLocale locale; // Import from clipboard – OK to use locale here? 0869 locale.setNumberOptions(QLocale::OmitGroupSeparator); 0870 // wether we are mapping to a fake rectangle 0871 bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle"); 0872 bool useOpacity = m_dataCombo->currentData(OpacityRole).toBool(); 0873 for (const auto &ix : qAsConst(m_indexes)) { 0874 // update keyframes in other indexes 0875 KeyframeModel *km = kfrModel->getKeyModel(ix); 0876 qDebug() << "== " << ix << " = " << m_targetCombo->currentData().toModelIndex(); 0877 if (ix == m_targetCombo->currentData().toModelIndex() || fakeRect) { 0878 // Import our keyframes 0879 KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles>(m_sourceCombo->currentData().toInt()); 0880 if (convertMode == ImportRoles::RotoData && m_targetCombo->currentText() == i18n("Rotoscoping shape")) { 0881 QJsonObject json = QJsonDocument::fromJson(selectedData().toUtf8()).object(); 0882 for (int i = 0; i < json.count(); i++) { 0883 int frame = json.keys().at(i).toInt(); 0884 if (frame > m_outPoint->getPosition()) { 0885 break; 0886 } 0887 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType::Linear, 0888 json.value(json.keys().at(i)), true, undo, redo); 0889 } 0890 continue; 0891 } 0892 mlt_keyframe_type type; 0893 mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0)); 0894 for (int i = 0; i < anim->key_count(); i++) { 0895 int frame = 0; 0896 int error = anim->key_get(i, frame, type); 0897 if (frame > m_outPoint->getPosition()) { 0898 break; 0899 } 0900 if (error) { 0901 continue; 0902 } 0903 QVariant current = km->getInterpolatedValue(frame); 0904 if (convertMode == ImportRoles::SimpleValue) { 0905 double dval = animData->anim_get_double("key", frame); 0906 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), dval, 0907 true, undo, redo); 0908 continue; 0909 } 0910 QStringList kfrData = current.toString().split(QLatin1Char(' ')); 0911 // Safety check 0912 if (fakeRect) { 0913 while (kfrData.size() < 4) { 0914 kfrData.append("0"); 0915 } 0916 } 0917 int size = kfrData.size(); 0918 switch (convertMode) { 0919 case ImportRoles::FullGeometry: 0920 case ImportRoles::HeightOnly: 0921 case ImportRoles::WidthOnly: 0922 if (size < 4) { 0923 continue; 0924 } 0925 break; 0926 case ImportRoles::Position: 0927 case ImportRoles::InvertedPosition: 0928 case ImportRoles::OffsetPosition: 0929 case ImportRoles::YOnly: 0930 if (size < 2) { 0931 continue; 0932 } 0933 break; 0934 default: 0935 if (size == 0) { 0936 continue; 0937 } 0938 break; 0939 } 0940 mlt_rect rect = animData->anim_get_rect("key", frame); 0941 if (!useOpacity) { 0942 rect.o = 1; 0943 } 0944 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) { 0945 switch (sourceAlign) { 0946 case 1: 0947 // Align top center 0948 rect.x += rect.w / 2; 0949 break; 0950 case 2: 0951 // Align top right 0952 rect.x += rect.w; 0953 break; 0954 case 3: 0955 // Align left center 0956 rect.y += rect.h / 2; 0957 break; 0958 case 4: 0959 // Align center 0960 rect.x += rect.w / 2; 0961 rect.y += rect.h / 2; 0962 break; 0963 case 5: 0964 // Align right center 0965 rect.x += rect.w; 0966 rect.y += rect.h / 2; 0967 break; 0968 case 6: 0969 // Align bottom left 0970 rect.y += rect.h; 0971 break; 0972 case 7: 0973 // Align bottom center 0974 rect.x += rect.w / 2; 0975 rect.y += rect.h; 0976 break; 0977 case 8: 0978 // Align bottom right 0979 rect.x += rect.w; 0980 rect.y += rect.h; 0981 break; 0982 default: 0983 break; 0984 } 0985 switch (targetAlign) { 0986 case 1: 0987 // Align top center 0988 rect.x -= kfrData[2].toInt() / 2; 0989 break; 0990 case 2: 0991 // Align top right 0992 rect.x -= kfrData[2].toInt(); 0993 break; 0994 case 3: 0995 // Align left center 0996 rect.y -= kfrData[3].toInt() / 2; 0997 break; 0998 case 4: 0999 // Align center 1000 rect.x -= kfrData[2].toInt() / 2; 1001 rect.y -= kfrData[3].toInt() / 2; 1002 break; 1003 case 5: 1004 // Align right center 1005 rect.x -= kfrData[2].toInt(); 1006 rect.y -= kfrData[3].toInt() / 2; 1007 break; 1008 case 6: 1009 // Align bottom left 1010 rect.y -= kfrData[3].toInt(); 1011 break; 1012 case 7: 1013 // Align bottom center 1014 rect.x -= kfrData[2].toInt() / 2; 1015 rect.y -= kfrData[3].toInt(); 1016 break; 1017 case 8: 1018 // Align bottom right 1019 rect.x -= kfrData[2].toInt(); 1020 rect.y -= kfrData[3].toInt(); 1021 break; 1022 default: 1023 break; 1024 } 1025 } 1026 rect.x += m_offsetX.value(); 1027 rect.y += m_offsetY.value(); 1028 switch (convertMode) { 1029 case ImportRoles::RotoData: 1030 break; 1031 case ImportRoles::FullGeometry: 1032 kfrData[0] = locale.toString(int(rect.x)); 1033 kfrData[1] = locale.toString(int(rect.y)); 1034 kfrData[2] = locale.toString(int(rect.w)); 1035 kfrData[3] = locale.toString(int(rect.h)); 1036 if (size > 4) { 1037 kfrData[4] = QString::number(rect.o); 1038 } 1039 break; 1040 case ImportRoles::Position: 1041 kfrData[0] = locale.toString(int(rect.x)); 1042 kfrData[1] = locale.toString(int(rect.y)); 1043 break; 1044 case ImportRoles::InvertedPosition: 1045 kfrData[0] = locale.toString(int(-rect.x)); 1046 kfrData[1] = locale.toString(int(-rect.y)); 1047 break; 1048 case ImportRoles::OffsetPosition: 1049 kfrData[0] = locale.toString(int(firstRect.x - rect.x)); 1050 kfrData[1] = locale.toString(int(firstRect.y - rect.y)); 1051 break; 1052 case ImportRoles::SimpleValue: 1053 case ImportRoles::XOnly: 1054 kfrData[0] = locale.toString(int(rect.x)); 1055 break; 1056 case ImportRoles::YOnly: 1057 kfrData[1] = locale.toString(int(rect.y)); 1058 break; 1059 case ImportRoles::WidthOnly: 1060 kfrData[2] = locale.toString(int(rect.w)); 1061 break; 1062 case ImportRoles::HeightOnly: 1063 kfrData[3] = locale.toString(int(rect.h)); 1064 break; 1065 } 1066 // map the fake rectangle internaly to the right params 1067 QString name = ix.data(AssetParameterModel::NameRole).toString(); 1068 QSize frameSize = pCore->getCurrentFrameSize(); 1069 if (name.contains("Position X") && 1070 !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly)) { 1071 current = kfrData[0].toDouble() / frameSize.width(); 1072 if (convertMode == ImportRoles::FullGeometry) { 1073 current = current.toDouble() + rect.w / frameSize.width() / 2; 1074 } 1075 } else if (name.contains("Position Y") && 1076 !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) { 1077 current = kfrData[1].toDouble() / frameSize.height(); 1078 if (convertMode == ImportRoles::FullGeometry) { 1079 current = current.toDouble() + rect.h / frameSize.height() / 2; 1080 } 1081 } else if (name.contains("Size X") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || 1082 convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) { 1083 current = kfrData[2].toDouble() / frameSize.width() / 2; 1084 } else if (name.contains("Size Y") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || 1085 convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) { 1086 current = kfrData[3].toDouble() / frameSize.height() / 2; 1087 } else if (fakeRect) { 1088 current = km->getInterpolatedValue(frame).toDouble(); 1089 } else { 1090 current = kfrData.join(QLatin1Char(' ')); 1091 } 1092 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, 1093 true, undo, redo); 1094 } 1095 } else { 1096 int frame = 0; 1097 mlt_keyframe_type type; 1098 for (int i = 0; i < anim->key_count(); i++) { 1099 int error = anim->key_get(i, frame, type); 1100 if (frame > m_outPoint->getPosition()) { 1101 break; 1102 } 1103 if (error) { 1104 continue; 1105 } 1106 // frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition()); 1107 QVariant current = km->getInterpolatedValue(frame); 1108 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, 1109 true, undo, redo); 1110 } 1111 } 1112 } 1113 pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard")); 1114 } 1115 1116 int KeyframeImport::getImportType() const 1117 { 1118 if (m_simpleTargets.contains(m_targetCombo->currentText())) { 1119 return -1; 1120 } 1121 return m_sourceCombo->currentData().toInt(); 1122 } 1123 1124 void KeyframeImport::updateView() 1125 { 1126 QPersistentModelIndex ix = m_targetCombo->currentData().toModelIndex(); 1127 QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString(); 1128 1129 // Calculate updated keyframes 1130 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData()); 1131 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key"))); 1132 // Geometry target 1133 int sourceAlign = m_alignSourceCombo->currentData().toInt(); 1134 int targetAlign = m_alignTargetCombo->currentData().toInt(); 1135 QLocale locale; // Import from clipboard – OK to use locale here? 1136 locale.setNumberOptions(QLocale::OmitGroupSeparator); 1137 // update keyframes in other indexes 1138 if (!m_originalParams.contains(ix)) { 1139 qDebug() << "=== Original parameter not found"; 1140 return; 1141 } 1142 animData->set("original", m_originalParams.value(ix).toUtf8().constData()); 1143 std::shared_ptr<Mlt::Animation> animo(new Mlt::Animation(animData->get_animation("original"))); 1144 animo->interpolate(); 1145 // wether we are mapping to a fake rectangle 1146 bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle"); 1147 // Import our keyframes 1148 int frame = 0; 1149 KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles>(m_sourceCombo->currentData().toInt()); 1150 mlt_keyframe_type type; 1151 mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0)); 1152 bool useOpacity = m_dataCombo->currentData(OpacityRole).toBool(); 1153 for (int i = 0; i < anim->key_count(); i++) { 1154 int error = anim->key_get(i, frame, type); 1155 if (error) { 1156 continue; 1157 } 1158 mlt_rect sourceRect = animData->anim_get_rect("original", frame); 1159 if (!useOpacity) { 1160 sourceRect.o = 1; 1161 } 1162 QStringList kfrData = {QString::number(sourceRect.x), QString::number(sourceRect.y), QString::number(sourceRect.w), QString::number(sourceRect.h), 1163 QString::number(sourceRect.o)}; 1164 // Safety check 1165 if (fakeRect) { 1166 while (kfrData.size() < 4) { 1167 kfrData.append("0"); 1168 } 1169 } 1170 int size = kfrData.size(); 1171 switch (convertMode) { 1172 case ImportRoles::FullGeometry: 1173 case ImportRoles::HeightOnly: 1174 case ImportRoles::WidthOnly: 1175 if (size < 4) { 1176 continue; 1177 } 1178 break; 1179 case ImportRoles::Position: 1180 case ImportRoles::InvertedPosition: 1181 case ImportRoles::OffsetPosition: 1182 case ImportRoles::YOnly: 1183 if (size < 2) { 1184 continue; 1185 } 1186 break; 1187 default: 1188 if (size == 0) { 1189 continue; 1190 } 1191 break; 1192 } 1193 mlt_rect rect = animData->anim_get_rect("key", frame); 1194 mlt_keyframe_type kfrType = anim->keyframe_type(frame); 1195 if (!useOpacity) { 1196 rect.o = 1; 1197 } 1198 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) { 1199 switch (sourceAlign) { 1200 case 1: 1201 // Align top center 1202 rect.x += rect.w / 2; 1203 break; 1204 case 2: 1205 // Align top right 1206 rect.x += rect.w; 1207 break; 1208 case 3: 1209 // Align left center 1210 rect.y += rect.h / 2; 1211 break; 1212 case 4: 1213 // Align center 1214 rect.x += rect.w / 2; 1215 rect.y += rect.h / 2; 1216 break; 1217 case 5: 1218 // Align right center 1219 rect.x += rect.w; 1220 rect.y += rect.h / 2; 1221 break; 1222 case 6: 1223 // Align bottom left 1224 rect.y += rect.h; 1225 break; 1226 case 7: 1227 // Align bottom center 1228 rect.x += rect.w / 2; 1229 rect.y += rect.h; 1230 break; 1231 case 8: 1232 // Align bottom right 1233 rect.x += rect.w; 1234 rect.y += rect.h; 1235 break; 1236 default: 1237 break; 1238 } 1239 switch (targetAlign) { 1240 case 1: 1241 // Align top center 1242 rect.x -= kfrData[2].toInt() / 2; 1243 break; 1244 case 2: 1245 // Align top right 1246 rect.x -= kfrData[2].toInt(); 1247 break; 1248 case 3: 1249 // Align left center 1250 rect.y -= kfrData[3].toInt() / 2; 1251 break; 1252 case 4: 1253 // Align center 1254 rect.x -= kfrData[2].toInt() / 2; 1255 rect.y -= kfrData[3].toInt() / 2; 1256 break; 1257 case 5: 1258 // Align right center 1259 rect.x -= kfrData[2].toInt(); 1260 rect.y -= kfrData[3].toInt() / 2; 1261 break; 1262 case 6: 1263 // Align bottom left 1264 rect.y -= kfrData[3].toInt(); 1265 break; 1266 case 7: 1267 // Align bottom center 1268 rect.x -= kfrData[2].toInt() / 2; 1269 rect.y -= kfrData[3].toInt(); 1270 break; 1271 case 8: 1272 // Align bottom right 1273 rect.x -= kfrData[2].toInt(); 1274 rect.y -= kfrData[3].toInt(); 1275 break; 1276 default: 1277 break; 1278 } 1279 } 1280 rect.x += m_offsetX.value(); 1281 rect.y += m_offsetY.value(); 1282 switch (convertMode) { 1283 case ImportRoles::RotoData: 1284 break; 1285 case ImportRoles::FullGeometry: 1286 kfrData[0] = locale.toString(int(rect.x)); 1287 kfrData[1] = locale.toString(int(rect.y)); 1288 kfrData[2] = locale.toString(int(rect.w)); 1289 kfrData[3] = locale.toString(int(rect.h)); 1290 kfrData[4] = QString::number(rect.o); 1291 break; 1292 case ImportRoles::Position: 1293 kfrData[0] = locale.toString(int(rect.x)); 1294 kfrData[1] = locale.toString(int(rect.y)); 1295 break; 1296 case ImportRoles::InvertedPosition: 1297 kfrData[0] = locale.toString(int(-rect.x)); 1298 kfrData[1] = locale.toString(int(-rect.y)); 1299 break; 1300 case ImportRoles::OffsetPosition: 1301 kfrData[0] = locale.toString(int(firstRect.x - rect.x)); 1302 kfrData[1] = locale.toString(int(firstRect.y - rect.y)); 1303 break; 1304 case ImportRoles::SimpleValue: 1305 case ImportRoles::XOnly: 1306 kfrData[0] = locale.toString(int(rect.x)); 1307 break; 1308 case ImportRoles::YOnly: 1309 kfrData[1] = locale.toString(int(rect.y)); 1310 break; 1311 case ImportRoles::WidthOnly: 1312 kfrData[2] = locale.toString(int(rect.w)); 1313 break; 1314 case ImportRoles::HeightOnly: 1315 kfrData[3] = locale.toString(int(rect.h)); 1316 break; 1317 } 1318 // map the fake rectangle internaly to the right params 1319 QString name = ix.data(AssetParameterModel::NameRole).toString(); 1320 QSize frameSize = pCore->getCurrentFrameSize(); 1321 bool doubleParameter = false; 1322 double val; 1323 if (name.contains("Position X") && 1324 !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly)) { 1325 val = kfrData[0].toDouble() / frameSize.width(); 1326 if (convertMode == ImportRoles::FullGeometry) { 1327 val += rect.w / frameSize.width() / 2; 1328 } 1329 doubleParameter = true; 1330 } else if (name.contains("Position Y") && 1331 !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) { 1332 val = kfrData[1].toDouble() / frameSize.height(); 1333 if (convertMode == ImportRoles::FullGeometry) { 1334 val += rect.h / frameSize.height() / 2; 1335 } 1336 doubleParameter = true; 1337 } else if (name.contains("Size X") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || 1338 convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) { 1339 val = kfrData[2].toDouble() / frameSize.width() / 2; 1340 doubleParameter = true; 1341 } else if (name.contains("Size Y") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || 1342 convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) { 1343 val = kfrData[3].toDouble() / frameSize.height() / 2; 1344 doubleParameter = true; 1345 } else if (fakeRect) { 1346 val = animData->anim_get_double("original", frame); 1347 doubleParameter = true; 1348 } else { 1349 mlt_rect resultRect; 1350 resultRect.x = kfrData.at(0).toInt(); 1351 resultRect.y = kfrData.at(1).toInt(); 1352 resultRect.w = kfrData.at(2).toInt(); 1353 resultRect.h = kfrData.at(3).toInt(); 1354 resultRect.o = kfrData.at(4).toDouble(); 1355 animData->anim_set("key2", resultRect, frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), 1356 m_outPoint->getPosition() - m_inPoint->getPosition(), kfrType); 1357 } 1358 if (doubleParameter) { 1359 animData->anim_set("key2", val, frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), 1360 m_outPoint->getPosition() - m_inPoint->getPosition(), kfrType); 1361 } 1362 } 1363 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData->get_animation("key2"))); 1364 anim2->interpolate(); 1365 m_model->getAsset()->set(paramName.toUtf8().constData(), anim2->serialize_cut()); 1366 if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) { 1367 pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor(); 1368 } else { 1369 pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor(); 1370 } 1371 Q_EMIT updateQmlView(); 1372 } 1373 1374 void KeyframeImport::reject() 1375 { 1376 if (m_targetCombo == nullptr) { 1377 // no data to import, close 1378 QDialog::reject(); 1379 return; 1380 } 1381 for (int i = 0; i < m_targetCombo->count(); i++) { 1382 QPersistentModelIndex ix = m_targetCombo->itemData(i).toModelIndex(); 1383 if (m_originalParams.contains(ix)) { 1384 QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString(); 1385 m_model->getAsset()->set(paramName.toUtf8().constData(), m_originalParams.value(ix).toUtf8().constData()); 1386 } 1387 } 1388 if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) { 1389 pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor(); 1390 } else { 1391 pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor(); 1392 } 1393 Q_EMIT updateQmlView(); 1394 QDialog::reject(); 1395 } 1396 1397 void KeyframeImport::accept() 1398 { 1399 importSelectedData(); 1400 QDialog::accept(); 1401 }