File indexing completed on 2024-05-19 04:55:07
0001 /* 0002 SPDX-FileCopyrightText: 2015-2016 Meltytech LLC 0003 SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org> 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "bin/projectitemmodel.h" 0009 #include "capture/mediacapture.h" 0010 #include "core.h" 0011 #include "kdenlivesettings.h" 0012 #include <QElapsedTimer> 0013 #include <QPainter> 0014 #include <QPainterPath> 0015 #include <QQuickPaintedItem> 0016 #include <QtMath> 0017 #include <cmath> 0018 0019 class TimelineTriangle : public QQuickPaintedItem 0020 { 0021 Q_OBJECT 0022 Q_PROPERTY(QColor fillColor MEMBER m_color) 0023 public: 0024 TimelineTriangle(QQuickItem *parent = nullptr) 0025 : QQuickPaintedItem(parent) 0026 { 0027 setAntialiasing(true); 0028 } 0029 void paint(QPainter *painter) override 0030 { 0031 QPainterPath path; 0032 path.moveTo(0, 0); 0033 path.lineTo(width(), 0); 0034 path.lineTo(0, height()); 0035 painter->fillPath(path, m_color); 0036 painter->setPen(Qt::white); 0037 painter->drawLine(int(width()), 0, 0, int(height())); 0038 } 0039 0040 private: 0041 QColor m_color; 0042 }; 0043 0044 class TimelinePlayhead : public QQuickPaintedItem 0045 { 0046 Q_OBJECT 0047 Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged) 0048 0049 public: 0050 TimelinePlayhead(QQuickItem *parent = nullptr) 0051 : QQuickPaintedItem(parent) 0052 { 0053 connect(this, &TimelinePlayhead::colorChanged, this, [&](const QColor &) { update(); }); 0054 } 0055 0056 void paint(QPainter *painter) override 0057 { 0058 QPainterPath path; 0059 path.moveTo(width(), 0); 0060 path.lineTo(width() / 2.0, height()); 0061 path.lineTo(0, 0); 0062 painter->fillPath(path, m_color); 0063 } 0064 Q_SIGNALS: 0065 void colorChanged(const QColor &); 0066 0067 private: 0068 QColor m_color; 0069 }; 0070 0071 class TimelineWaveform : public QQuickPaintedItem 0072 { 0073 Q_OBJECT 0074 Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged) 0075 Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged) 0076 Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged) 0077 Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged) 0078 Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged) 0079 Q_PROPERTY(int ix MEMBER m_index) 0080 Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged) 0081 Q_PROPERTY(int waveOutPoint MEMBER m_outPoint) 0082 Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged) 0083 Q_PROPERTY(int audioStream MEMBER m_stream) 0084 Q_PROPERTY(double scaleFactor MEMBER m_scale) 0085 Q_PROPERTY(double speed MEMBER m_speed) 0086 Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged) 0087 Q_PROPERTY(bool enforceRepaint MEMBER m_repaint NOTIFY propertyChanged) 0088 Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY normalizeChanged) 0089 Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk) 0090 Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint) 0091 0092 public: 0093 TimelineWaveform(QQuickItem *parent = nullptr) 0094 : QQuickPaintedItem(parent) 0095 , m_repaint(false) 0096 , m_speed(1.) 0097 , m_opaquePaint(false) 0098 { 0099 setAntialiasing(false); 0100 setOpaquePainting(m_opaquePaint); 0101 setEnabled(false); 0102 m_precisionFactor = 1; 0103 // setRenderTarget(QQuickPaintedItem::FramebufferObject); 0104 // setMipmap(true); 0105 // setTextureSize(QSize(1, 1)); 0106 connect(this, &TimelineWaveform::levelsChanged, [&]() { 0107 if (!m_binId.isEmpty()) { 0108 if (m_audioLevels.isEmpty() && m_stream >= 0) { 0109 update(); 0110 } else { 0111 // Clip changed, reset levels 0112 m_audioLevels.clear(); 0113 } 0114 } 0115 }); 0116 connect(this, &TimelineWaveform::normalizeChanged, [&]() { 0117 m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0; 0118 update(); 0119 }); 0120 connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update)); 0121 } 0122 0123 void paint(QPainter *painter) override 0124 { 0125 if (m_binId.isEmpty()) { 0126 return; 0127 } 0128 if (m_audioLevels.isEmpty() && m_stream >= 0) { 0129 m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream); 0130 if (m_audioLevels.isEmpty()) { 0131 return; 0132 } 0133 m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0; 0134 } 0135 0136 if (m_outPoint == m_inPoint) { 0137 return; 0138 } 0139 QRectF bgRect(0, 0, width(), height()); 0140 if (m_opaquePaint) { 0141 painter->fillRect(bgRect, m_bgColor); 0142 } 0143 QPen pen(painter->pen()); 0144 double increment = qMax(1., m_scale / m_channels); // qMax(1., 1. / qAbs(indicesPrPixel)); 0145 qreal indicesPrPixel = m_channels / m_scale * qAbs(m_speed); // qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor; 0146 int h = int(height()); 0147 double offset = 0; 0148 bool pathDraw = increment > 1.2; 0149 if (increment > 1. && !pathDraw) { 0150 pen.setWidth(int(ceil(increment))); 0151 offset = pen.width() / 2.; 0152 pen.setColor(m_color); 0153 pen.setCapStyle(Qt::FlatCap); 0154 } else if (pathDraw) { 0155 pen.setWidth(0); 0156 painter->setBrush(m_color); 0157 pen.setColor(m_bgColor.darker(200)); 0158 } else { 0159 pen.setColor(m_color); 0160 } 0161 painter->setPen(pen); 0162 double scaleFactor = 255; 0163 if (m_audioMax > 1) { 0164 scaleFactor = m_audioMax; 0165 } 0166 bool reverse = m_speed < 0; 0167 int maxLength = m_audioLevels.length(); 0168 if (reverse) { 0169 m_inPoint = qMin(m_inPoint, maxLength - m_channels); 0170 } 0171 int startPos = int(m_inPoint / indicesPrPixel); 0172 if (!KdenliveSettings::displayallchannels()) { 0173 // Draw merged channels 0174 double i = 0; 0175 int j = 0; 0176 int idx = 0; 0177 QPainterPath path; 0178 if (pathDraw) { 0179 path.moveTo(j - 1, height()); 0180 } 0181 for (; i <= width(); j++) { 0182 double level; 0183 i = j * increment; 0184 if (reverse) { 0185 idx = qCeil((startPos - i) * indicesPrPixel); 0186 idx -= idx % m_channels; 0187 } else { 0188 idx = qCeil((startPos + i) * indicesPrPixel); 0189 idx += idx % m_channels; 0190 } 0191 i -= offset; 0192 if (idx + m_channels >= maxLength || idx < 0) { 0193 break; 0194 } 0195 level = m_audioLevels.at(idx) / scaleFactor; 0196 for (int k = 1; k < m_channels; k++) { 0197 level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor); 0198 } 0199 if (pathDraw) { 0200 double val = height() - level * height(); 0201 path.lineTo(i, val); 0202 path.lineTo((j + 1) * increment - offset, val); 0203 } else { 0204 painter->drawLine(int(i), h, int(i), int(h - (h * level))); 0205 } 0206 } 0207 if (pathDraw) { 0208 path.lineTo(i, height()); 0209 painter->drawPath(path); 0210 } 0211 } else { 0212 double channelHeight = height() / m_channels; 0213 QPen pen(painter->pen()); 0214 // Draw separate channels 0215 scaleFactor = channelHeight / (2 * scaleFactor); 0216 bgRect.setHeight(channelHeight); 0217 // Path for vector drawing 0218 for (int channel = 0; channel < m_channels; channel++) { 0219 double level; 0220 // y is channel median pos 0221 double y = (channel * channelHeight) + channelHeight / 2; 0222 QPainterPath path; 0223 path.moveTo(-1, y); 0224 if (channel % 2 == 0) { 0225 // Add dark background on odd channels 0226 painter->setOpacity(0.2); 0227 bgRect.moveTo(0, channel * channelHeight); 0228 painter->fillRect(bgRect, Qt::black); 0229 } 0230 // Draw channel median line 0231 pen.setColor(channel % 2 == 0 ? m_color : m_color2); 0232 painter->setBrush(channel % 2 == 0 ? m_color : m_color2); 0233 painter->setOpacity(0.5); 0234 pen.setWidthF(0); 0235 painter->setPen(pen); 0236 painter->drawLine(QLineF(0., y, width(), y)); 0237 pen.setWidth(int(ceil(increment))); 0238 painter->setPen(pathDraw ? Qt::NoPen : pen); 0239 painter->setOpacity(1); 0240 double i = 0; 0241 int j = 0; 0242 int idx = 0; 0243 for (; i <= width(); j++) { 0244 i = j * increment; 0245 if (reverse) { 0246 idx = qCeil((startPos - i) * indicesPrPixel); 0247 idx -= idx % m_channels; 0248 } else { 0249 idx = qCeil((startPos + i) * indicesPrPixel); 0250 idx += idx % m_channels; 0251 } 0252 i -= offset; 0253 idx += channel; 0254 if (idx >= maxLength || idx < 0) break; 0255 if (pathDraw) { 0256 level = m_audioLevels.at(idx) * scaleFactor; 0257 path.lineTo(i, y - level); 0258 } else { 0259 level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height 0260 painter->drawLine(int(i), int(y - level), int(i), int(y + level)); 0261 } 0262 } 0263 if (pathDraw) { 0264 path.lineTo(i, y); 0265 painter->drawPath(path); 0266 QTransform tr(1, 0, 0, -1, 0, 2 * y); 0267 painter->drawPath(tr.map(path)); 0268 } 0269 if (m_firstChunk && m_channels > 1 && m_channels < 7) { 0270 const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"}; 0271 painter->drawText(2, int(y + channelHeight / 2), chanelNames[channel]); 0272 } 0273 } 0274 } 0275 } 0276 0277 Q_SIGNALS: 0278 void levelsChanged(); 0279 void propertyChanged(); 0280 void normalizeChanged(); 0281 void inPointChanged(); 0282 void audioChannelsChanged(); 0283 0284 private: 0285 QVector<uint8_t> m_audioLevels; 0286 int m_inPoint; 0287 int m_outPoint; 0288 QString m_binId; 0289 QColor m_bgColor; 0290 QColor m_color; 0291 QColor m_color2; 0292 bool m_format; 0293 bool m_repaint; 0294 bool m_normalize; 0295 int m_channels; 0296 int m_precisionFactor; 0297 int m_stream; 0298 double m_scale; 0299 double m_speed; 0300 double m_audioMax; 0301 bool m_firstChunk; 0302 bool m_opaquePaint; 0303 int m_index; 0304 }; 0305 0306 class TimelineRecWaveform : public QQuickPaintedItem 0307 { 0308 Q_OBJECT 0309 Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged) 0310 Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged) 0311 Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged) 0312 Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged) 0313 Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged) 0314 Q_PROPERTY(int ix MEMBER m_index) 0315 Q_PROPERTY(int waveOutPoint MEMBER m_outPoint) 0316 Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged) 0317 Q_PROPERTY(double scaleFactor MEMBER m_scale) 0318 Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged) 0319 Q_PROPERTY(bool enforceRepaint MEMBER m_repaint NOTIFY propertyChanged) 0320 Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk) 0321 Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint) 0322 0323 public: 0324 TimelineRecWaveform(QQuickItem *parent = nullptr) 0325 : QQuickPaintedItem(parent) 0326 , m_repaint(false) 0327 , m_opaquePaint(false) 0328 { 0329 setAntialiasing(false); 0330 setOpaquePainting(m_opaquePaint); 0331 setEnabled(false); 0332 m_precisionFactor = 1; 0333 // setRenderTarget(QQuickPaintedItem::FramebufferObject); 0334 // setMipmap(true); 0335 // setTextureSize(QSize(1, 1)); 0336 connect(this, &TimelineRecWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update)); 0337 } 0338 0339 void paint(QPainter *painter) override 0340 { 0341 const QVector<double> &audioLevels = pCore->getAudioDevice()->recLevels(); 0342 if (audioLevels.isEmpty()) { 0343 return; 0344 } 0345 0346 if (m_outPoint == m_inPoint) { 0347 return; 0348 } 0349 QRectF bgRect(0, 0, width(), height()); 0350 if (m_opaquePaint) { 0351 painter->fillRect(bgRect, m_bgColor); 0352 } 0353 QPen pen(painter->pen()); 0354 int maxLength = audioLevels.length(); 0355 double increment = 1 / m_scale; 0356 qreal indicesPrPixel = m_channels / m_scale; // qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor; 0357 int h = int(height()); 0358 double offset = 0; 0359 bool pathDraw = increment > 1.2; 0360 if (increment > 1. && !pathDraw) { 0361 pen.setWidth(int(ceil(increment))); 0362 offset = pen.width() / 2.; 0363 pen.setColor(m_color); 0364 pen.setCapStyle(Qt::FlatCap); 0365 } else if (pathDraw) { 0366 pen.setWidth(0); 0367 painter->setBrush(m_color); 0368 pen.setColor(m_bgColor.darker(200)); 0369 } else { 0370 pen.setColor(m_color); 0371 } 0372 painter->setPen(pen); 0373 int startPos = int(m_inPoint / indicesPrPixel); 0374 // Draw merged channels 0375 double i = 0; 0376 int j = 0; 0377 int idx = 0; 0378 QPainterPath path; 0379 if (pathDraw) { 0380 path.moveTo(j - 1, height()); 0381 } 0382 for (; i <= width(); j++) { 0383 double level; 0384 i = j * increment; 0385 idx = qCeil((startPos + i) * indicesPrPixel); 0386 idx += idx % m_channels; 0387 i -= offset; 0388 if (idx + m_channels >= maxLength || idx < 0) { 0389 break; 0390 } 0391 level = audioLevels.at(idx); 0392 if (pathDraw) { 0393 double val = height() - level * height(); 0394 path.lineTo(i, val); 0395 path.lineTo((j + 1) * increment - offset, val); 0396 } else { 0397 painter->drawLine(int(i), h, int(i), int(h - (h * level))); 0398 } 0399 } 0400 if (pathDraw) { 0401 path.lineTo(i, height()); 0402 painter->drawPath(path); 0403 } 0404 } 0405 0406 Q_SIGNALS: 0407 void levelsChanged(); 0408 void propertyChanged(); 0409 void inPointChanged(); 0410 void audioChannelsChanged(); 0411 0412 private: 0413 int m_inPoint; 0414 int m_outPoint; 0415 QColor m_bgColor; 0416 QColor m_color; 0417 QColor m_color2; 0418 bool m_format; 0419 bool m_repaint; 0420 int m_channels; 0421 int m_precisionFactor; 0422 double m_scale; 0423 bool m_firstChunk; 0424 bool m_opaquePaint; 0425 int m_index; 0426 }; 0427 0428 void registerTimelineItems() 0429 { 0430 qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle"); 0431 qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead"); 0432 qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform"); 0433 qmlRegisterType<TimelineRecWaveform>("Kdenlive.Controls", 1, 0, "TimelineRecWaveform"); 0434 } 0435 0436 #include "timelineitems.moc"