1 /*
2 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
3 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5
6 #include "klocalizedstring.h"
7 #include <QCheckBox>
8 #include <QComboBox>
9 #include <QDialogButtonBox>
10 #include <QHBoxLayout>
11 #include <QJsonArray>
12 #include <QJsonDocument>
13 #include <QJsonObject>
14 #include <QLabel>
15 #include <QPainter>
16 #include <QSpinBox>
17 #include <QVBoxLayout>
18 #include <utility>
19
20 #include "assets/keyframes/view/keyframeview.hpp"
21 #include "core.h"
22 #include "doc/kdenlivedoc.h"
23 #include "monitor/monitor.h"
24 #include "kdenlivesettings.h"
25 #include "keyframeimport.h"
26 #include "profiles/profilemodel.hpp"
27 #include "widgets/positionwidget.h"
28 #include <macros.hpp>
29
30 #include "mlt++/MltAnimation.h"
31 #include "mlt++/MltProperties.h"
32
33
KeyframeImport(const QString & animData,std::shared_ptr<AssetParameterModel> model,QList<QPersistentModelIndex> indexes,int parentIn,int parentDuration,QWidget * parent)34 KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr<AssetParameterModel> model, QList<QPersistentModelIndex> indexes, int parentIn, int parentDuration, QWidget *parent)
35 : QDialog(parent)
36 , m_model(std::move(model))
37 , m_indexes(indexes)
38 , m_supportsAnim(false)
39 , m_previewLabel(nullptr)
40 , m_sourceCombo(nullptr)
41 , m_targetCombo(nullptr)
42 , m_isReady(false)
43 {
44 setAttribute(Qt::WA_DeleteOnClose);
45 auto *lay = new QVBoxLayout(this);
46 auto *l1 = new QHBoxLayout;
47 QLabel *lab = new QLabel(i18n("Data to import:"), this);
48 l1->addWidget(lab);
49
50 m_dataCombo = new QComboBox(this);
51 l1->addWidget(m_dataCombo);
52 l1->addStretch(10);
53 lay->addLayout(l1);
54 int in = -1;
55 int out = -1;
56 // Set up data
57 auto json = QJsonDocument::fromJson(animData.toUtf8());
58 if (!json.isArray()) {
59 qDebug() << "Error : Json file should be an array";
60 // Try to build data from a single value
61 QJsonArray list;
62 QJsonObject currentParam;
63 currentParam.insert(QLatin1String("name"), QStringLiteral("data"));
64 currentParam.insert(QLatin1String("value"), animData);
65 bool ok;
66 QString firstFrame = animData.section(QLatin1Char('='), 0, 0);
67 in = firstFrame.toInt(&ok);
68 if (!ok) {
69 firstFrame.chop(1);
70 in = firstFrame.toInt(&ok);
71 }
72 QString first = animData.section(QLatin1Char('='), 1, 1);
73 if (!first.isEmpty()) {
74 int spaces = first.count(QLatin1Char(' '));
75 switch (spaces) {
76 case 0:
77 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::Animated)));
78 break;
79 default:
80 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
81 break;
82 }
83 //currentParam.insert(QLatin1String("min"), QJsonValue(min));
84 //currentParam.insert(QLatin1String("max"), QJsonValue(max));
85 list.push_back(currentParam);
86 json = QJsonDocument(list);
87 }
88 }
89 if (!json.isArray()) {
90 qDebug() << "Error : Json file should be an array";
91 return;
92 }
93 auto list = json.array();
94 int ix = 0;
95 for (const auto &entry : qAsConst(list)) {
96 if (!entry.isObject()) {
97 qDebug() << "Warning : Skipping invalid marker data";
98 continue;
99 }
100 auto entryObj = entry.toObject();
101 if (!entryObj.contains(QLatin1String("name"))) {
102 qDebug() << "Warning : Skipping invalid marker data (does not contain name)";
103 continue;
104 }
105 QString name = entryObj[QLatin1String("name")].toString();
106 QString displayName = entryObj[QLatin1String("DisplayName")].toString();
107 if (displayName.isEmpty()) {
108 displayName = name;
109 }
110 QString value = entryObj[QLatin1String("value")].toString();
111 int type = entryObj[QLatin1String("type")].toInt(0);
112 double min = entryObj[QLatin1String("min")].toDouble(0);
113 double max = entryObj[QLatin1String("max")].toDouble(0);
114 if (in == -1) {
115 in = entryObj[QLatin1String("in")].toInt(0);
116 }
117 if (out == -1) {
118 out = entryObj[QLatin1String("out")].toInt(0);
119 }
120 bool opacity = entryObj[QLatin1String("opacity")].toBool(true);
121 m_dataCombo->insertItem(ix, displayName);
122 m_dataCombo->setItemData(ix, value, Qt::UserRole);
123 m_dataCombo->setItemData(ix, type, Qt::UserRole + 1);
124 m_dataCombo->setItemData(ix, min, Qt::UserRole + 2);
125 m_dataCombo->setItemData(ix, max, Qt::UserRole + 3);
126 m_dataCombo->setItemData(ix, opacity, Qt::UserRole + 4);
127 ix++;
128 }
129 m_previewLabel = new QLabel(this);
130 m_previewLabel->setMinimumSize(100, 150);
131 m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
132 m_previewLabel->setScaledContents(true);
133
134 lay->addWidget(m_previewLabel);
135 // Zone in / out
136 in = qMax(0, in);
137 if (out <= 0) {
138 out = in + parentDuration;
139 }
140 m_inPoint = new PositionWidget(i18n("In"), in, 0, out, pCore->currentDoc()->timecode(), QString(), this);
141 connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
142 lay->addWidget(m_inPoint);
143 m_outPoint = new PositionWidget(i18n("Out"), out, in, out, pCore->currentDoc()->timecode(), QString(), this);
144 connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
145 lay->addWidget(m_outPoint);
146
147 // Output offset
148 int clipIn = parentIn;
149 m_offsetPoint = new PositionWidget(i18n("Time offset"), clipIn, 0, clipIn + parentDuration, pCore->currentDoc()->timecode(), "", this);
150 lay->addWidget(m_offsetPoint);
151
152 int count = 0;
153 // Check what kind of parameters are in our target
154 for (const QPersistentModelIndex &idx : indexes) {
155 auto type = m_model->data(idx, AssetParameterModel::TypeRole).value<ParamType>();
156 if (type == ParamType::KeyframeParam) {
157 m_simpleTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
158 QString name(m_model->data(idx, AssetParameterModel::NameRole).toString());
159 if(name.contains("Position X") || name.contains("Position Y") || name.contains("Size X") || name.contains("Size Y")) {
160 count++;
161 }
162 } else if (type == ParamType::Roto_spline) {
163 m_simpleTargets.insert(i18n("Rotoscoping shape"), idx);
164 } else if (type == ParamType::AnimatedRect) {
165 m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
166 }
167 }
168
169 l1 = new QHBoxLayout;
170 m_targetCombo = new QComboBox(this);
171 m_sourceCombo = new QComboBox(this);
172 /*ix = 0;
173 if (!m_geometryTargets.isEmpty()) {
174 m_sourceCombo->insertItem(ix, i18n("Geometry"));
175 m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole);
176 ix++;
177 m_sourceCombo->insertItem(ix, i18n("Position"));
178 m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole);
179 ix++;
180 }
181 if (!m_simpleTargets.isEmpty()) {
182 m_sourceCombo->insertItem(ix, i18n("X"));
183 m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole);
184 ix++;
185 m_sourceCombo->insertItem(ix, i18n("Y"));
186 m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole);
187 ix++;
188 m_sourceCombo->insertItem(ix, i18n("Width"));
189 m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole);
190 ix++;
191 m_sourceCombo->insertItem(ix, i18n("Height"));
192 m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole);
193 ix++;
194 }*/
195 connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateRange);
196 connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
197 m_alignSourceCombo = new QComboBox(this);
198 m_alignSourceCombo->addItem(i18n("Top left"), 0);
199 m_alignSourceCombo->addItem(i18n("Top center"), 1);
200 m_alignSourceCombo->addItem(i18n("Top right"), 2);
201 m_alignSourceCombo->addItem(i18n("Left center"), 3);
202 m_alignSourceCombo->addItem(i18n("Center"), 4);
203 m_alignSourceCombo->addItem(i18n("Right center"), 5);
204 m_alignSourceCombo->addItem(i18n("Bottom left"), 6);
205 m_alignSourceCombo->addItem(i18n("Bottom center"), 7);
206 m_alignSourceCombo->addItem(i18n("Bottom right"), 8);
207 m_alignTargetCombo = new QComboBox(this);
208 m_alignTargetCombo->addItem(i18n("Top left"), 0);
209 m_alignTargetCombo->addItem(i18n("Top center"), 1);
210 m_alignTargetCombo->addItem(i18n("Top right"), 2);
211 m_alignTargetCombo->addItem(i18n("Left center"), 3);
212 m_alignTargetCombo->addItem(i18n("Center"), 4);
213 m_alignTargetCombo->addItem(i18n("Right center"), 5);
214 m_alignTargetCombo->addItem(i18n("Bottom left"), 6);
215 m_alignTargetCombo->addItem(i18n("Bottom center"), 7);
216 m_alignTargetCombo->addItem(i18n("Bottom right"), 8);
217 connect(m_alignSourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
218 connect(m_alignTargetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
219 lab = new QLabel(i18n("Map "), this);
220 QLabel *lab2 = new QLabel(i18n(" to "), this);
221 l1->addWidget(lab);
222 l1->addWidget(m_sourceCombo);
223 l1->addWidget(m_alignSourceCombo);
224 l1->addWidget(lab2);
225 l1->addWidget(m_targetCombo);
226 l1->addWidget(m_alignTargetCombo);
227 ix = 0;
228 QMap<QString, QModelIndex>::const_iterator j = m_geometryTargets.constBegin();
229 while (j != m_geometryTargets.constEnd()) {
230 m_targetCombo->insertItem(ix, j.key());
231 m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
232 m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
233 ++j;
234 ix++;
235 }
236 if(count == 4) {
237 // add an option to map to a a fake rectangle (pretend position params are a mlt rect)
238 m_targetCombo->insertItem(ix, i18n("Rectangle"));
239 }
240 ix = 0;
241 j = m_simpleTargets.constBegin();
242 while (j != m_simpleTargets.constEnd()) {
243 m_targetCombo->insertItem(ix, j.key());
244 m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
245 m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
246 ++j;
247 ix++;
248 }
249 if (m_simpleTargets.count() + m_geometryTargets.count() > 1) {
250 // Target contains several animatable parameters, propose choice
251 }
252 lay->addLayout(l1);
253
254 m_offsetX.setRange(-pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->width());
255 m_offsetY.setRange(-pCore->getCurrentProfile()->height(), pCore->getCurrentProfile()->height());
256 connect(&m_offsetX, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
257 connect(&m_offsetY, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
258
259 // Destination range
260 l1 = new QHBoxLayout;
261 lab = new QLabel(i18n("Position offset"), this);
262 l1->addWidget(lab);
263 l1->addWidget(&m_offsetX);
264 l1->addWidget(&m_offsetY);
265 lay->addLayout(l1);
266
267
268 // Source range
269 m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this);
270 lay->addWidget(m_sourceRangeLabel);
271
272 // update range info
273 connect(m_targetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDestinationRange()));
274
275 // Destination range
276 l1 = new QHBoxLayout;
277 lab = new QLabel(i18n("Destination range"), this);
278
279 l1->addWidget(lab);
280 l1->addWidget(&m_destMin);
281 l1->addWidget(&m_destMax);
282 lay->addLayout(l1);
283
284 l1 = new QHBoxLayout;
285 m_limitRange = new QCheckBox(i18n("Actual range only"), this);
286 connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange);
287 connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
288 l1->addWidget(m_limitRange);
289 l1->addStretch(10);
290 lay->addLayout(l1);
291 l1 = new QHBoxLayout;
292 m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this);
293 m_limitKeyframes->setChecked(true);
294 m_limitNumber = new QSpinBox(this);
295 m_limitNumber->setMinimum(1);
296 m_limitNumber->setValue(20);
297 l1->addWidget(m_limitKeyframes);
298 l1->addWidget(m_limitNumber);
299 l1->addStretch(10);
300 lay->addLayout(l1);
301 connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled);
302 connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
303 connect(m_limitNumber, SIGNAL(valueChanged(int)), this, SLOT(updateDisplay()));
304 connect(m_dataCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDataDisplay()));
305 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
306 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
307 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
308 lay->addWidget(buttonBox);
309 m_isReady = true;
310 updateDestinationRange();
311 updateDataDisplay();
312 updateView();
313 }
314
315 KeyframeImport::~KeyframeImport() = default;
316
resizeEvent(QResizeEvent * ev)317 void KeyframeImport::resizeEvent(QResizeEvent *ev)
318 {
319 QWidget::resizeEvent(ev);
320 updateDisplay();
321 }
322
updateDataDisplay()323 void KeyframeImport::updateDataDisplay()
324 {
325 QString comboData = m_dataCombo->currentData().toString();
326 auto type = m_dataCombo->currentData(Qt::UserRole + 1).value<ParamType>();
327 auto values = m_dataCombo->currentData(Qt::UserRole).toString().split(QLatin1Char(';'));
328
329 // we do not need all the options if there is only one keyframe
330 bool onlyOne = values.length() == 1;
331 m_previewLabel->setVisible(!onlyOne);
332 m_limitKeyframes->setVisible(!onlyOne);
333 m_limitNumber->setVisible(!onlyOne);
334 m_inPoint->setVisible(!onlyOne);
335 m_outPoint->setVisible(!onlyOne);
336
337 m_maximas = KeyframeModel::getRanges(comboData, m_model);
338 m_sourceCombo->clear();
339 if (type == ParamType::KeyframeParam) {
340 // 1 dimensional param.
341 m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue);
342
343 // map rotation to rotation by default if possible
344 if (m_dataCombo->currentText() == QStringLiteral("rotation")) {
345 int idx = m_targetCombo->findText(i18n("Rotation"));
346 if (idx > -1) {
347 m_targetCombo->setCurrentIndex(idx);
348 }
349 }
350 updateRange();
351 return;
352 }
353 if (type == ParamType::Roto_spline) {
354 m_sourceCombo->addItem(i18n("Rotoscoping shape"), ImportRoles::RotoData);
355 return;
356 }
357 double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
358 double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
359 m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry);
360 m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position);
361 m_sourceCombo->addItem(i18n("Inverted Position"), ImportRoles::InvertedPosition);
362 m_sourceCombo->addItem(i18n("Offset Position"), ImportRoles::OffsetPosition);
363 m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly);
364 m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly);
365 if (wDist > 0) {
366 m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly);
367 }
368 if (hDist > 0) {
369 m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly);
370 }
371
372 // if available map to Rectangle by default
373 int idx = m_targetCombo->findText(i18n("Rectangle"));
374 if (idx > -1) {
375 m_targetCombo->setCurrentIndex(idx);
376 }
377 updateRange();
378 /*if (!m_inPoint->isValid()) {
379 m_inPoint->blockSignals(true);
380 m_outPoint->blockSignals(true);
381 // m_inPoint->setRange(0, m_keyframeView->duration);
382 m_inPoint->setPosition(0);
383 m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt());
384 m_inPoint->blockSignals(false);
385 m_outPoint->blockSignals(false);
386 }*/
387 }
388
updateRange()389 void KeyframeImport::updateRange()
390 {
391 int pos = m_sourceCombo->currentData().toInt();
392 m_alignSourceCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
393 m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
394 m_offsetX.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
395 m_offsetY.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
396 m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
397 m_limitRange->setEnabled(pos != ImportRoles::RotoData);
398 QString rangeText;
399 if (m_limitRange->isChecked()) {
400 switch (pos) {
401 case ImportRoles::SimpleValue:
402 case ImportRoles::XOnly:
403 rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y());
404 break;
405 case ImportRoles::YOnly:
406 rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y());
407 break;
408 case ImportRoles::WidthOnly:
409 rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y());
410 break;
411 case ImportRoles::HeightOnly:
412 rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y());
413 break;
414 default:
415 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());
416 break;
417 }
418 } else {
419 int profileWidth = pCore->getCurrentProfile()->width();
420 int profileHeight = pCore->getCurrentProfile()->height();
421 switch (pos) {
422 case ImportRoles::SimpleValue:
423 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
424 break;
425 case ImportRoles::XOnly:
426 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
427 break;
428 case ImportRoles::YOnly:
429 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
430 break;
431 case ImportRoles::WidthOnly:
432 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
433 break;
434 case ImportRoles::HeightOnly:
435 rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
436 break;
437 default:
438 rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()),
439 qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
440 break;
441 }
442 }
443 m_sourceRangeLabel->setText(rangeText);
444 updateDisplay();
445 }
446
updateDestinationRange()447 void KeyframeImport::updateDestinationRange()
448 {
449 if (m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
450 m_destMin.setEnabled(false);
451 m_destMax.setEnabled(false);
452 m_limitRange->setEnabled(false);
453 return;
454 }
455
456 if (m_simpleTargets.contains(m_targetCombo->currentText())) {
457 // 1 dimension target
458 m_destMin.setEnabled(true);
459 m_destMax.setEnabled(true);
460 m_limitRange->setEnabled(true);
461 double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble();
462 double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble();
463 m_destMin.setRange(min, max);
464 m_destMax.setRange(min, max);
465 m_destMin.setValue(min);
466 m_destMax.setValue(max);
467 } else {
468 int profileWidth = pCore->getCurrentProfile()->width();
469 m_destMin.setRange(-2 * profileWidth, 2 * profileWidth);
470 m_destMax.setRange(-2 * profileWidth, 2 * profileWidth);
471 m_destMin.setEnabled(false);
472 m_destMax.setEnabled(false);
473 m_limitRange->setEnabled(false);
474 updateView();
475 }
476 }
477
updateDisplay()478 void KeyframeImport::updateDisplay()
479 {
480 if (!m_isReady) {
481 // Not ready
482 return;
483 }
484 QPixmap pix(m_previewLabel->width(), m_previewLabel->height());
485 pix.fill(Qt::transparent);
486 QList<QPoint> maximas;
487 int selectedtarget = m_sourceCombo->currentData().toInt();
488 int profileWidth = pCore->getCurrentProfile()->width();
489 int profileHeight = pCore->getCurrentProfile()->height();
490 if (!m_maximas.isEmpty()) {
491 if (m_maximas.at(0).x() == m_maximas.at(0).y() || (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
492 maximas << QPoint();
493 } else {
494 if (m_limitRange->isChecked()) {
495 maximas << m_maximas.at(0);
496 } else {
497 QPoint p1;
498 if (selectedtarget == ImportRoles::SimpleValue) {
499 p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
500 } else {
501 p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
502 }
503 maximas << p1;
504 }
505 }
506 }
507 if (m_maximas.count() > 1) {
508 if (m_maximas.at(1).x() == m_maximas.at(1).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
509 maximas << QPoint();
510 } else {
511 if (m_limitRange->isChecked()) {
512 maximas << m_maximas.at(1);
513 } else {
514 QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
515 maximas << p2;
516 }
517 }
518 }
519 if (m_maximas.count() > 2) {
520 if (m_maximas.at(2).x() == m_maximas.at(2).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) {
521 maximas << QPoint();
522 } else {
523 if (m_limitRange->isChecked()) {
524 maximas << m_maximas.at(2);
525 } else {
526 QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
527 maximas << p3;
528 }
529 }
530 }
531 if (m_maximas.count() > 3) {
532 if (m_maximas.at(3).x() == m_maximas.at(3).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) {
533 maximas << QPoint();
534 } else {
535 if (m_limitRange->isChecked()) {
536 maximas << m_maximas.at(3);
537 } else {
538 QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
539 maximas << p4;
540 }
541 }
542 }
543 drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0,
544 palette().text().color());
545 m_previewLabel->setPixmap(pix);
546 }
547
selectedData() const548 QString KeyframeImport::selectedData() const
549 {
550 // return serialized keyframes
551 if (m_simpleTargets.contains(m_targetCombo->currentText())) {
552 // Exporting a 1 dimension animation
553 int ix = m_sourceCombo->currentData().toInt();
554 QPoint maximas;
555 if (m_limitRange->isChecked()) {
556 maximas = m_maximas.at(ix);
557 } else if (ix == ImportRoles::WidthOnly) {
558 // Width maximas
559 maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width()));
560 } else {
561 // Height maximas
562 maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height()));
563 }
564 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
565 if (m_dataCombo->currentText() == "spline") {
566 QJsonDocument doc = QJsonDocument::fromJson(m_dataCombo->currentData().toString().toLocal8Bit());
567 return QString(doc.toJson(QJsonDocument::Compact));
568 }
569 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
570 animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition());
571 int existingKeys = anim->key_count();
572 if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
573 // We need to limit keyframes, create new animation
574 int in = m_inPoint->getPosition();
575 int out = m_outPoint->getPosition();
576 std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
577 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
578 anim2->interpolate();
579
580 // Remove existing kfrs
581 int firstKeyframe = -1;
582 int lastKeyframe = -1;
583 if (anim2->is_key(0)) {
584 if (in == 0) {
585 firstKeyframe = 0;
586 }
587 anim2->remove(0);
588 }
589 int keyPos = anim2->next_key(0);
590 while (anim2->is_key(keyPos)) {
591 if (firstKeyframe == -1) {
592 firstKeyframe = keyPos;
593 }
594 if (keyPos < out) {
595 lastKeyframe = keyPos;
596 } else {
597 lastKeyframe = out;
598 }
599 anim2->remove(keyPos);
600 keyPos = anim2->next_key(keyPos);
601 }
602 anim2->interpolate();
603 int length = lastKeyframe;
604 double interval = double(length) / (m_limitNumber->value() - 1);
605 int pos = 0;
606 for (int i = 0; i < m_limitNumber->value(); i++) {
607 pos = firstKeyframe + in + i * interval;
608 pos = qMin(pos, length - 1);
609 double dval = animData->anim_get_double("key", pos);
610 animData2->anim_set("key", dval, pos);
611
612 }
613 anim2->interpolate();
614 return anim2->serialize_cut();
615 }
616 return anim->serialize_cut();
617 }
618 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
619 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
620 animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition());
621 int existingKeys = anim->key_count();
622 if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
623 // We need to limit keyframes, create new animation
624 int in = m_inPoint->getPosition();
625 int out = m_outPoint->getPosition();
626 std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
627 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
628 anim2->interpolate();
629
630 // Remove existing kfrs
631 int firstKeyframe = -1;
632 int lastKeyframe = -1;
633 if (anim2->is_key(0)) {
634 if (in == 0) {
635 firstKeyframe = 0;
636 }
637 anim2->remove(0);
638 }
639 int keyPos = anim2->next_key(0);
640 while (anim2->is_key(keyPos)) {
641 if (firstKeyframe == -1) {
642 firstKeyframe = keyPos;
643 }
644 if (keyPos < out) {
645 lastKeyframe = keyPos;
646 } else {
647 lastKeyframe = out;
648 }
649 anim2->remove(keyPos);
650 keyPos = anim2->next_key(keyPos);
651 }
652 anim2->interpolate();
653 int length = lastKeyframe - firstKeyframe;
654 double interval = double(length) / (m_limitNumber->value() - 1);
655 int pos = 0;
656 for (int i = 0; i < m_limitNumber->value(); i++) {
657 pos = firstKeyframe + i * interval;
658 pos = qMin(pos, lastKeyframe);
659 mlt_rect rect = animData->anim_get_rect("key", pos);
660 animData2->anim_set("key", rect, pos);
661 }
662 anim2->interpolate();
663 return anim2->serialize_cut();
664 }
665
666 return anim->serialize_cut();
667 }
668
selectedTarget() const669 QString KeyframeImport::selectedTarget() const
670 {
671 return m_targetCombo->currentData().toString();
672 }
673
drawKeyFrameChannels(QPixmap & pix,int in,int out,int limitKeyframes,const QColor & textColor)674 void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor)
675 {
676 qDebug()<<"============= DRAWING KFR CHANNS: "<<m_dataCombo->currentData().toString();
677 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
678 QRect br(0, 0, pix.width(), pix.height());
679 double frameFactor = double(out - in) / br.width();
680 int offset = 1;
681 if (limitKeyframes > 0) {
682 offset = int((out - in) / limitKeyframes / frameFactor);
683 }
684 double min = m_dataCombo->currentData(Qt::UserRole + 2).toDouble();
685 double max = m_dataCombo->currentData(Qt::UserRole + 3).toDouble();
686 double xDist;
687 if (max > min) {
688 xDist = max - min;
689 } else {
690 xDist = m_maximas.at(0).y() - m_maximas.at(0).x();
691 }
692 double yDist = m_maximas.at(1).y() - m_maximas.at(1).x();
693 double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
694 double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
695 double xOffset = m_maximas.at(0).x();
696 double yOffset = m_maximas.at(1).x();
697 double wOffset = m_maximas.at(2).x();
698 double hOffset = m_maximas.at(3).x();
699 QColor cX(255, 0, 0, 100);
700 QColor cY(0, 255, 0, 100);
701 QColor cW(0, 0, 255, 100);
702 QColor cH(255, 255, 0, 100);
703 // Draw curves labels
704 QPainter painter;
705 painter.begin(&pix);
706 QRectF txtRect = painter.boundingRect(br, QStringLiteral("t"));
707 txtRect.setX(2);
708 txtRect.setWidth(br.width() - 4);
709 txtRect.moveTop(br.height() - txtRect.height());
710 QRectF drawnText;
711 int maxHeight = int(br.height() - txtRect.height() - 2);
712 painter.setPen(textColor);
713 int rectSize = int(txtRect.height() / 2);
714 if (xDist > 0) {
715 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cX);
716 txtRect.setX(txtRect.x() + rectSize * 2);
717 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()),
718 &drawnText);
719 }
720 if (yDist > 0) {
721 if (drawnText.isValid()) {
722 txtRect.setX(drawnText.right() + rectSize);
723 }
724 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cY);
725 txtRect.setX(txtRect.x() + rectSize * 2);
726 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()),
727 &drawnText);
728 }
729 if (wDist > 0) {
730 if (drawnText.isValid()) {
731 txtRect.setX(drawnText.right() + rectSize);
732 }
733 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cW);
734 txtRect.setX(txtRect.x() + rectSize * 2);
735 painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText);
736 }
737 if (hDist > 0) {
738 if (drawnText.isValid()) {
739 txtRect.setX(drawnText.right() + rectSize);
740 }
741 painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cH);
742 txtRect.setX(txtRect.x() + rectSize * 2);
743 painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText);
744 }
745
746 // Draw curves
747 for (int i = 0; i < br.width(); i++) {
748 mlt_rect rect = animData->anim_get_rect("key", int(i * frameFactor) + in);
749 if (xDist > 0) {
750 painter.setPen(cX);
751 int val = int((rect.x - xOffset) * maxHeight / xDist);
752 qDebug() << "// DRAWINC CURVE : " << rect.x <<", POS: "<<(int(i * frameFactor) + in)<< ", RESULT: " << val;
753 painter.drawLine(i, maxHeight - val, i, maxHeight);
754 }
755 if (yDist > 0) {
756 painter.setPen(cY);
757 int val = int((rect.y - yOffset) * maxHeight / yDist);
758 painter.drawLine(i, maxHeight - val, i, maxHeight);
759 }
760 if (wDist > 0) {
761 painter.setPen(cW);
762 int val = int((rect.w - wOffset) * maxHeight / wDist);
763 qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val;
764 painter.drawLine(i, maxHeight - val, i, maxHeight);
765 }
766 if (hDist > 0) {
767 painter.setPen(cH);
768 int val = int((rect.h - hOffset) * maxHeight / hDist);
769 painter.drawLine(i, maxHeight - val, i, maxHeight);
770 }
771 }
772 if (offset > 1) {
773 // Overlay limited keyframes curve
774 cX.setAlpha(255);
775 cY.setAlpha(255);
776 cW.setAlpha(255);
777 cH.setAlpha(255);
778 mlt_rect rect1 = animData->anim_get_rect("key", in);
779 int prevPos = 0;
780 for (int i = offset; i < br.width(); i += offset) {
781 mlt_rect rect2 = animData->anim_get_rect("key", int(i * frameFactor) + in);
782 if (xDist > 0) {
783 painter.setPen(cX);
784 int val1 = int((rect1.x - xOffset) * maxHeight / xDist);
785 int val2 = int((rect2.x - xOffset) * maxHeight / xDist);
786 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
787 }
788 if (yDist > 0) {
789 painter.setPen(cY);
790 int val1 = int((rect1.y - yOffset) * maxHeight / yDist);
791 int val2 = int((rect2.y - yOffset) * maxHeight / yDist);
792 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
793 }
794 if (wDist > 0) {
795 painter.setPen(cW);
796 int val1 = int((rect1.w - wOffset) * maxHeight / wDist);
797 int val2 = int((rect2.w - wOffset) * maxHeight / wDist);
798 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
799 }
800 if (hDist > 0) {
801 painter.setPen(cH);
802 int val1 = int((rect1.h - hOffset) * maxHeight / hDist);
803 int val2 = int((rect2.h - hOffset) * maxHeight / hDist);
804 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
805 }
806 rect1 = rect2;
807 prevPos = i;
808 }
809 }
810 }
811
importSelectedData()812 void KeyframeImport::importSelectedData()
813 {
814 // Simple double value
815 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
816 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
817 std::shared_ptr<KeyframeModelList> kfrModel = m_model->getKeyframeModel();
818 Fun undo = []() { return true; };
819 Fun redo = []() { return true; };
820 // Geometry target
821 int sourceAlign = m_alignSourceCombo->currentData().toInt();
822 int targetAlign = m_alignTargetCombo->currentData().toInt();
823 QLocale locale; // Import from clipboard – OK to use locale here?
824 locale.setNumberOptions(QLocale::OmitGroupSeparator);
825 for (const auto &ix : qAsConst(m_indexes)) {
826 // update keyframes in other indexes
827 KeyframeModel *km = kfrModel->getKeyModel(ix);
828 qDebug()<<"== "<<ix<<" = "<<m_targetCombo->currentData().toModelIndex();
829
830 // wether we are mapping to a fake rectangle
831 bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
832 bool useOpacity = m_dataCombo->currentData(Qt::UserRole + 4).toBool();
833 if (ix == m_targetCombo->currentData().toModelIndex() || fakeRect) {
834 // Import our keyframes
835 int frame = 0;
836 KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles> (m_sourceCombo->currentData().toInt());
837 if (convertMode == ImportRoles::RotoData && m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
838 QJsonObject json = QJsonDocument::fromJson(selectedData().toLocal8Bit()).object();
839 for (int i = 0; i < json.count(); i++) {
840 int frame = json.keys().at(i).toInt();
841 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType::Linear, json.value(json.keys().at(i)), true, undo, redo);
842 }
843 continue;
844 }
845 mlt_keyframe_type type;
846 mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
847 for (int i = 0; i < anim->key_count(); i++) {
848 int error = anim->key_get(i, frame, type);
849 if (error) {
850 continue;
851 }
852 QVariant current = km->getInterpolatedValue(frame);
853 if (convertMode == ImportRoles::SimpleValue) {
854 double dval = animData->anim_get_double("key", frame);
855 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), dval, true, undo, redo);
856 continue;
857 }
858 QStringList kfrData = current.toString().split(QLatin1Char(' '));
859 // Safety check
860 if (fakeRect) {
861 while (kfrData.size() < 4) {
862 kfrData.append("0");
863 }
864 }
865 int size = kfrData.size();
866 switch (convertMode) {
867 case ImportRoles::FullGeometry:
868 case ImportRoles::HeightOnly:
869 case ImportRoles::WidthOnly:
870 if (size < 4) {
871 continue;
872 }
873 break;
874 case ImportRoles::Position:
875 case ImportRoles::InvertedPosition:
876 case ImportRoles::OffsetPosition:
877 case ImportRoles::YOnly:
878 if (size < 2) {
879 continue;
880 }
881 break;
882 default:
883 if (size == 0) {
884 continue;
885 }
886 break;
887 }
888 mlt_rect rect = animData->anim_get_rect("key", frame);
889 if (!useOpacity) {
890 rect.o = 1;
891 }
892 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
893 switch (sourceAlign) {
894 case 1:
895 // Align top center
896 rect.x += rect.w / 2;
897 break;
898 case 2:
899 // Align top right
900 rect.x += rect.w;
901 break;
902 case 3:
903 // Align left center
904 rect.y += rect.h / 2;
905 break;
906 case 4:
907 // Align center
908 rect.x += rect.w / 2;
909 rect.y += rect.h / 2;
910 break;
911 case 5:
912 // Align right center
913 rect.x += rect.w;
914 rect.y += rect.h / 2;
915 break;
916 case 6:
917 // Align bottom left
918 rect.y += rect.h;
919 break;
920 case 7:
921 // Align bottom center
922 rect.x += rect.w / 2;
923 rect.y += rect.h;
924 break;
925 case 8:
926 // Align bottom right
927 rect.x += rect.w;
928 rect.y += rect.h;
929 break;
930 default:
931 break;
932 }
933 switch (targetAlign) {
934 case 1:
935 // Align top center
936 rect.x -= kfrData[2].toInt() / 2;
937 break;
938 case 2:
939 // Align top right
940 rect.x -= kfrData[2].toInt();
941 break;
942 case 3:
943 // Align left center
944 rect.y -= kfrData[3].toInt() / 2;
945 break;
946 case 4:
947 // Align center
948 rect.x -= kfrData[2].toInt() / 2;
949 rect.y -= kfrData[3].toInt() / 2;
950 break;
951 case 5:
952 // Align right center
953 rect.x -= kfrData[2].toInt();
954 rect.y -= kfrData[3].toInt() / 2;
955 break;
956 case 6:
957 // Align bottom left
958 rect.y -= kfrData[3].toInt();
959 break;
960 case 7:
961 // Align bottom center
962 rect.x -= kfrData[2].toInt() / 2;
963 rect.y -= kfrData[3].toInt();
964 break;
965 case 8:
966 // Align bottom right
967 rect.x -= kfrData[2].toInt();
968 rect.y -= kfrData[3].toInt();
969 break;
970 default:
971 break;
972 }
973 }
974 rect.x += m_offsetX.value();
975 rect.y += m_offsetY.value();
976 switch (convertMode) {
977 case ImportRoles::RotoData:
978 break;
979 case ImportRoles::FullGeometry:
980 kfrData[0] = locale.toString(int(rect.x));
981 kfrData[1] = locale.toString(int(rect.y));
982 kfrData[2] = locale.toString(int(rect.w));
983 kfrData[3] = locale.toString(int(rect.h));
984 if (size > 4) {
985 kfrData[4] = QString::number(rect.o);
986 }
987 break;
988 case ImportRoles::Position:
989 kfrData[0] = locale.toString(int(rect.x));
990 kfrData[1] = locale.toString(int(rect.y));
991 break;
992 case ImportRoles::InvertedPosition:
993 kfrData[0] = locale.toString(int(-rect.x));
994 kfrData[1] = locale.toString(int(-rect.y));
995 break;
996 case ImportRoles::OffsetPosition:
997 kfrData[0] = locale.toString(int(firstRect.x - rect.x));
998 kfrData[1] = locale.toString(int(firstRect.y - rect.y));
999 break;
1000 case ImportRoles::SimpleValue:
1001 case ImportRoles::XOnly:
1002 kfrData[0] = locale.toString(int(rect.x));
1003 break;
1004 case ImportRoles::YOnly:
1005 kfrData[1] = locale.toString(int(rect.y));
1006 break;
1007 case ImportRoles::WidthOnly:
1008 kfrData[2] = locale.toString(int(rect.w));
1009 break;
1010 case ImportRoles::HeightOnly:
1011 kfrData[3] = locale.toString(int(rect.h));
1012 break;
1013 }
1014 // map the fake rectangle internaly to the right params
1015 QString name = ix.data(AssetParameterModel::NameRole).toString();
1016 QSize frameSize = pCore->getCurrentFrameSize();
1017 if (name.contains("Position X")
1018 && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly) ) {
1019 current = kfrData[0].toDouble() / frameSize.width();
1020 if (convertMode == ImportRoles::FullGeometry) {
1021 current = current.toDouble() + rect.w / frameSize.width() / 2;
1022 }
1023 } else if (name.contains("Position Y")
1024 && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1025 current = kfrData[1].toDouble() / frameSize.height();
1026 if (convertMode == ImportRoles::FullGeometry) {
1027 current = current.toDouble() + rect.h / frameSize.height() / 2;
1028 }
1029 } else if (name.contains("Size X")
1030 && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1031 current = kfrData[2].toDouble() / frameSize.width() / 2;
1032 } else if (name.contains("Size Y")
1033 && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1034 current = kfrData[3].toDouble() / frameSize.height() / 2;
1035 } else if(fakeRect){
1036 current = km->getInterpolatedValue(frame).toDouble();
1037 } else {
1038 current = kfrData.join(QLatin1Char(' '));
1039 }
1040 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, true, undo, redo);
1041 }
1042 } else {
1043 int frame = 0;
1044 mlt_keyframe_type type;
1045 for (int i = 0; i < anim->key_count(); i++) {
1046 int error = anim->key_get(i, frame, type);
1047 if (error) {
1048 continue;
1049 }
1050 //frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition());
1051 QVariant current = km->getInterpolatedValue(frame);
1052 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, true, undo, redo);
1053 }
1054 }
1055 }
1056 pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard"));
1057 }
1058
getImportType() const1059 int KeyframeImport::getImportType() const
1060 {
1061 if (m_simpleTargets.contains(m_targetCombo->currentText())) {
1062 return -1;
1063 }
1064 return m_sourceCombo->currentData().toInt();
1065 }
1066
updateView()1067 void KeyframeImport::updateView()
1068 {
1069 QPersistentModelIndex ix = m_targetCombo->currentData().toModelIndex();
1070 QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1071
1072 // Calculate updated keyframes
1073 std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
1074 std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
1075 // Geometry target
1076 int sourceAlign = m_alignSourceCombo->currentData().toInt();
1077 int targetAlign = m_alignTargetCombo->currentData().toInt();
1078 QLocale locale; // Import from clipboard – OK to use locale here?
1079 locale.setNumberOptions(QLocale::OmitGroupSeparator);
1080 // update keyframes in other indexes
1081 if (!m_originalParams.contains(ix)) {
1082 qDebug()<<"=== Original parameter not found";
1083 return;
1084 }
1085 QString kfrData = m_originalParams.value(ix);
1086 animData->set("original", kfrData.toUtf8().constData());
1087 std::shared_ptr<Mlt::Animation> animo(new Mlt::Animation(animData->get_animation("original")));
1088 animo->interpolate();
1089 // wether we are mapping to a fake rectangle
1090 bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
1091 // Import our keyframes
1092 int frame = 0;
1093 KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles> (m_sourceCombo->currentData().toInt());
1094 mlt_keyframe_type type;
1095 mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
1096 for (int i = 0; i < anim->key_count(); i++) {
1097 int error = anim->key_get(i, frame, type);
1098 if (error) {
1099 continue;
1100 }
1101 //QVariant current = km->getInterpolatedValue(frame);
1102 mlt_rect sourceRect = animData->anim_get_rect("original", frame);
1103 QStringList kfrData = {QString::number(sourceRect.x), QString::number(sourceRect.y), QString::number(sourceRect.w), QString::number(sourceRect.h), QString::number(sourceRect.o)};
1104 // Safety check
1105 if (fakeRect) {
1106 while (kfrData.size() < 4) {
1107 kfrData.append("0");
1108 }
1109 }
1110 int size = kfrData.size();
1111 switch (convertMode) {
1112 case ImportRoles::FullGeometry:
1113 case ImportRoles::HeightOnly:
1114 case ImportRoles::WidthOnly:
1115 if (size < 4) {
1116 continue;
1117 }
1118 break;
1119 case ImportRoles::Position:
1120 case ImportRoles::InvertedPosition:
1121 case ImportRoles::OffsetPosition:
1122 case ImportRoles::YOnly:
1123 if (size < 2) {
1124 continue;
1125 }
1126 break;
1127 default:
1128 if (size == 0) {
1129 continue;
1130 }
1131 break;
1132 }
1133 mlt_rect rect = animData->anim_get_rect("key", frame);
1134 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
1135 switch (sourceAlign) {
1136 case 1:
1137 // Align top center
1138 rect.x += rect.w / 2;
1139 break;
1140 case 2:
1141 // Align top right
1142 rect.x += rect.w;
1143 break;
1144 case 3:
1145 // Align left center
1146 rect.y += rect.h / 2;
1147 break;
1148 case 4:
1149 // Align center
1150 rect.x += rect.w / 2;
1151 rect.y += rect.h / 2;
1152 break;
1153 case 5:
1154 // Align right center
1155 rect.x += rect.w;
1156 rect.y += rect.h / 2;
1157 break;
1158 case 6:
1159 // Align bottom left
1160 rect.y += rect.h;
1161 break;
1162 case 7:
1163 // Align bottom center
1164 rect.x += rect.w / 2;
1165 rect.y += rect.h;
1166 break;
1167 case 8:
1168 // Align bottom right
1169 rect.x += rect.w;
1170 rect.y += rect.h;
1171 break;
1172 default:
1173 break;
1174 }
1175 switch (targetAlign) {
1176 case 1:
1177 // Align top center
1178 rect.x -= kfrData[2].toInt() / 2;
1179 break;
1180 case 2:
1181 // Align top right
1182 rect.x -= kfrData[2].toInt();
1183 break;
1184 case 3:
1185 // Align left center
1186 rect.y -= kfrData[3].toInt() / 2;
1187 break;
1188 case 4:
1189 // Align center
1190 rect.x -= kfrData[2].toInt() / 2;
1191 rect.y -= kfrData[3].toInt() / 2;
1192 break;
1193 case 5:
1194 // Align right center
1195 rect.x -= kfrData[2].toInt();
1196 rect.y -= kfrData[3].toInt() / 2;
1197 break;
1198 case 6:
1199 // Align bottom left
1200 rect.y -= kfrData[3].toInt();
1201 break;
1202 case 7:
1203 // Align bottom center
1204 rect.x -= kfrData[2].toInt() / 2;
1205 rect.y -= kfrData[3].toInt();
1206 break;
1207 case 8:
1208 // Align bottom right
1209 rect.x -= kfrData[2].toInt();
1210 rect.y -= kfrData[3].toInt();
1211 break;
1212 default:
1213 break;
1214 }
1215 }
1216 rect.x += m_offsetX.value();
1217 rect.y += m_offsetY.value();
1218 switch (convertMode) {
1219 case ImportRoles::RotoData:
1220 break;
1221 case ImportRoles::FullGeometry:
1222 kfrData[0] = locale.toString(int(rect.x));
1223 kfrData[1] = locale.toString(int(rect.y));
1224 kfrData[2] = locale.toString(int(rect.w));
1225 kfrData[3] = locale.toString(int(rect.h));
1226 kfrData[4] = QString::number(rect.o);
1227 break;
1228 case ImportRoles::Position:
1229 kfrData[0] = locale.toString(int(rect.x));
1230 kfrData[1] = locale.toString(int(rect.y));
1231 break;
1232 case ImportRoles::InvertedPosition:
1233 kfrData[0] = locale.toString(int(-rect.x));
1234 kfrData[1] = locale.toString(int(-rect.y));
1235 break;
1236 case ImportRoles::OffsetPosition:
1237 kfrData[0] = locale.toString(int(firstRect.x - rect.x));
1238 kfrData[1] = locale.toString(int(firstRect.y - rect.y));
1239 break;
1240 case ImportRoles::SimpleValue:
1241 case ImportRoles::XOnly:
1242 kfrData[0] = locale.toString(int(rect.x));
1243 break;
1244 case ImportRoles::YOnly:
1245 kfrData[1] = locale.toString(int(rect.y));
1246 break;
1247 case ImportRoles::WidthOnly:
1248 kfrData[2] = locale.toString(int(rect.w));
1249 break;
1250 case ImportRoles::HeightOnly:
1251 kfrData[3] = locale.toString(int(rect.h));
1252 break;
1253 }
1254 // map the fake rectangle internaly to the right params
1255 QString name = ix.data(AssetParameterModel::NameRole).toString();
1256 QSize frameSize = pCore->getCurrentFrameSize();
1257 QString current;
1258 if (name.contains("Position X")
1259 && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly) ) {
1260 current = kfrData[0].toDouble() / frameSize.width();
1261 if (convertMode == ImportRoles::FullGeometry) {
1262 current = current.toDouble() + rect.w / frameSize.width() / 2;
1263 }
1264 } else if (name.contains("Position Y")
1265 && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1266 current = kfrData[1].toDouble() / frameSize.height();
1267 if (convertMode == ImportRoles::FullGeometry) {
1268 current = current.toDouble() + rect.h / frameSize.height() / 2;
1269 }
1270 } else if (name.contains("Size X")
1271 && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1272 current = kfrData[2].toDouble() / frameSize.width() / 2;
1273 } else if (name.contains("Size Y")
1274 && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1275 current = kfrData[3].toDouble() / frameSize.height() / 2;
1276 } else if(fakeRect){
1277 current = QString::number(animData->anim_get_double("original", frame));
1278 } else {
1279 current = kfrData.join(QLatin1Char(' '));
1280 }
1281 animData->anim_set("key2", current.toUtf8().constData(), frame - m_inPoint->getPosition() + m_offsetPoint->getPosition());
1282 }
1283 std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData->get_animation("key2")));
1284 anim2->interpolate();
1285 m_model->getAsset()->set(paramName.toUtf8().constData(), anim2->serialize_cut());
1286 if (m_model->getOwnerId().first == ObjectType::BinClip) {
1287 pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1288 } else {
1289 pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1290 }
1291 emit updateQmlView();
1292 }
1293
reject()1294 void KeyframeImport::reject()
1295 {
1296 if (m_targetCombo == nullptr) {
1297 // no data to import, close
1298 QDialog::reject();
1299 return;
1300 }
1301 for (int i = 0; i < m_targetCombo->count(); i++) {
1302 QPersistentModelIndex ix = m_targetCombo->itemData(i).toModelIndex();
1303 if (m_originalParams.contains(ix)) {
1304 QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1305 m_model->getAsset()->set(paramName.toUtf8().constData(), m_originalParams.value(ix).toUtf8().constData());
1306 }
1307 }
1308 if (m_model->getOwnerId().first == ObjectType::BinClip) {
1309 pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1310 } else {
1311 pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1312 }
1313 emit updateQmlView();
1314 QDialog::reject();
1315 }
1316
accept()1317 void KeyframeImport::accept()
1318 {
1319 importSelectedData();
1320 QDialog::accept();
1321 }
1322