1 /*
2 Based on Shotcut, SPDX-FileCopyrightText: 2015-2016 Meltytech LLC
3 SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org>
4 This file is part of Kdenlive. See www.kdenlive.org.
5
6 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7 */
8
9 #include "kdenlivesettings.h"
10 #include "core.h"
11 #include "bin/projectitemmodel.h"
12 #include <QPainter>
13 #include <QPainterPath>
14 #include <QQuickPaintedItem>
15 #include <QElapsedTimer>
16 #include <QtMath>
17 #include <cmath>
18 #include "kdenlivesettings.h"
19
20 const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
21
22 class TimelineTriangle : public QQuickPaintedItem
23 {
24 Q_OBJECT
25 Q_PROPERTY(QColor fillColor MEMBER m_color)
26 public:
TimelineTriangle()27 TimelineTriangle() { setAntialiasing(true); }
paint(QPainter * painter)28 void paint(QPainter *painter) override
29 {
30 QPainterPath path;
31 path.moveTo(0, 0);
32 path.lineTo(width(), 0);
33 path.lineTo(0, height());
34 painter->fillPath(path, m_color);
35 painter->setPen(Qt::white);
36 painter->drawLine(int(width()), 0, 0, int(height()));
37 }
38
39 private:
40 QColor m_color;
41 };
42
43 class TimelinePlayhead : public QQuickPaintedItem
44 {
45 Q_OBJECT
46 Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged)
47
48 public:
TimelinePlayhead()49 TimelinePlayhead() { connect(this, SIGNAL(colorChanged(QColor)), this, SLOT(update())); }
50
paint(QPainter * painter)51 void paint(QPainter *painter) override
52 {
53 QPainterPath path;
54 path.moveTo(width(), 0);
55 path.lineTo(width() / 2.0, height());
56 path.lineTo(0, 0);
57 painter->fillPath(path, m_color);
58 }
59 signals:
60 void colorChanged(const QColor &);
61
62 private:
63 QColor m_color;
64 };
65
66 class TimelineWaveform : public QQuickPaintedItem
67 {
68 Q_OBJECT
69 Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged)
70 Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
71 Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
72 Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
73 Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged)
74 Q_PROPERTY(int ix MEMBER m_index)
75 Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged)
76 Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
77 Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
78 Q_PROPERTY(int audioStream MEMBER m_stream)
79 Q_PROPERTY(double scaleFactor MEMBER m_scale)
80 Q_PROPERTY(double speed MEMBER m_speed)
81 Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
82 Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY normalizeChanged)
83 Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
84 Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint)
85
86 public:
TimelineWaveform(QQuickItem * parent=nullptr)87 TimelineWaveform(QQuickItem *parent = nullptr)
88 : QQuickPaintedItem(parent)
89 , m_speed(1.)
90 , m_opaquePaint(false)
91 {
92 setAntialiasing(false);
93 setOpaquePainting(m_opaquePaint);
94 setEnabled(false);
95 m_precisionFactor = 1;
96 //setRenderTarget(QQuickPaintedItem::FramebufferObject);
97 //setMipmap(true);
98 //setTextureSize(QSize(1, 1));
99 connect(this, &TimelineWaveform::levelsChanged, [&]() {
100 if (!m_binId.isEmpty()) {
101 if (m_audioLevels.isEmpty() && m_stream >= 0) {
102 update();
103 } else {
104 // Clip changed, reset levels
105 m_audioLevels.clear();
106 }
107 }
108 });
109 connect(this, &TimelineWaveform::normalizeChanged, [&]() {
110 m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
111 update();
112 });
113 connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
114 }
115
paint(QPainter * painter)116 void paint(QPainter *painter) override
117 {
118 if (m_binId.isEmpty()) {
119 return;
120 }
121 if (m_audioLevels.isEmpty() && m_stream >= 0) {
122 m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
123 if (m_audioLevels.isEmpty()) {
124 return;
125 }
126 m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
127 }
128
129 if (m_outPoint == m_inPoint) {
130 return;
131 }
132 QRectF bgRect(0, 0, width(), height());
133 if (m_opaquePaint) {
134 painter->fillRect(bgRect, m_bgColor);
135 }
136 QPen pen(painter->pen());
137 double increment = qMax(1., m_scale / m_channels); //qMax(1., 1. / qAbs(indicesPrPixel));
138 qreal indicesPrPixel = m_channels / m_scale * qAbs(m_speed); //qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
139 int h = int(height());
140 double offset = 0;
141 bool pathDraw = increment > 1.2;
142 if (increment > 1. && !pathDraw) {
143 pen.setWidth(int(ceil(increment)));
144 offset = pen.width() / 2.;
145 pen.setColor(m_color);
146 pen.setCapStyle(Qt::FlatCap);
147 } else if (pathDraw) {
148 pen.setWidth(0);
149 painter->setBrush(m_color);
150 pen.setColor(m_bgColor.darker(200));
151 } else {
152 pen.setColor(m_color);
153 }
154 painter->setPen(pen);
155 double scaleFactor = 255;
156 if (m_audioMax > 1) {
157 scaleFactor = m_audioMax;
158 }
159 int startPos = int(m_inPoint / indicesPrPixel);
160 if (!KdenliveSettings::displayallchannels()) {
161 // Draw merged channels
162 double i = 0;
163 double level;
164 int j = 0;
165 QPainterPath path;
166 if (pathDraw) {
167 path.moveTo(j - 1, height());
168 }
169 for (; i <= width(); j++) {
170 double level;
171 i = j * increment;
172 int idx = qCeil((startPos + i) * indicesPrPixel);
173 idx += idx % m_channels;
174 i -= offset;
175 if (idx + m_channels >= m_audioLevels.length() || idx < 0) {
176 break;
177 }
178 level = m_audioLevels.at(idx) / scaleFactor;
179 for (int k = 1; k < m_channels; k++) {
180 level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor);
181 }
182 if (pathDraw) {
183 double val = height() - level * height();
184 path.lineTo(i, val);
185 path.lineTo(( j + 1) * increment - offset, val);
186 } else {
187 painter->drawLine(int(i), h, int(i), int(h - (h * level)));
188 }
189 }
190 if (pathDraw) {
191 path.lineTo(i, height());
192 painter->drawPath(path);
193 }
194 } else {
195 double channelHeight = height() / m_channels;
196 QPen pen(painter->pen());
197 // Draw separate channels
198 scaleFactor = channelHeight / (2 * scaleFactor);
199 double i = 0;
200 double level;
201 bgRect.setHeight(channelHeight);
202 // Path for vector drawing
203 for (int channel = 0; channel < m_channels; channel++) {
204 // y is channel median pos
205 double y = (channel * channelHeight) + channelHeight / 2;
206 QPainterPath path;
207 path.moveTo(-1, y);
208 if (channel % 2 == 0) {
209 // Add dark background on odd channels
210 painter->setOpacity(0.2);
211 bgRect.moveTo(0, channel * channelHeight);
212 painter->fillRect(bgRect, Qt::black);
213 }
214 // Draw channel median line
215 pen.setColor(channel % 2 == 0 ? m_color : m_color2);
216 painter->setBrush(channel % 2 == 0 ? m_color : m_color2);
217 painter->setOpacity(0.5);
218 pen.setWidthF(0);
219 painter->setPen(pen);
220 painter->drawLine(QLineF(0., y, width(), y));
221 pen.setWidth(int(ceil(increment)));
222 painter->setPen(pathDraw ? Qt::NoPen : pen);
223 painter->setOpacity(1);
224 i = 0;
225 int j = 0;
226 for (; i <= width(); j++) {
227 i = j * increment;
228 int idx = int(ceil((startPos + i) * indicesPrPixel));
229 idx += idx % m_channels;
230 i -= offset;
231 idx += channel;
232 if (idx >= m_audioLevels.length() || idx < 0) break;
233 if (pathDraw) {
234 level = m_audioLevels.at(idx) * scaleFactor;
235 path.lineTo(i, y - level);
236 } else {
237 level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height
238 painter->drawLine(int(i), int(y - level), int(i), int(y + level));
239 }
240 }
241 if (pathDraw) {
242 path.lineTo(i, y);
243 painter->drawPath(path);
244 QTransform tr(1, 0, 0, -1, 0, 2 * y);
245 painter->drawPath(tr.map(path));
246 }
247 if (m_firstChunk && m_channels > 1 && m_channels < 7) {
248 painter->drawText(2, int(y + channelHeight / 2), chanelNames[channel]);
249 }
250 }
251 }
252 }
253
254 signals:
255 void levelsChanged();
256 void propertyChanged();
257 void normalizeChanged();
258 void inPointChanged();
259 void audioChannelsChanged();
260
261 private:
262 QVector<uint8_t> m_audioLevels;
263 int m_inPoint;
264 int m_outPoint;
265 QString m_binId;
266 QColor m_bgColor;
267 QColor m_color;
268 QColor m_color2;
269 bool m_format;
270 bool m_normalize;
271 int m_channels;
272 int m_precisionFactor;
273 int m_stream;
274 double m_scale;
275 double m_speed;
276 double m_audioMax;
277 bool m_firstChunk;
278 bool m_opaquePaint;
279 int m_index;
280 };
281
registerTimelineItems()282 void registerTimelineItems()
283 {
284 qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle");
285 qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead");
286 qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform");
287 }
288
289 #include "timelineitems.moc"
290