timelineitems.cpp 10.2 KB
Newer Older
1
/*
Camille Moulin's avatar
Camille Moulin committed
2
3
Based on Shotcut, SPDX-FileCopyrightText: 2015-2016 Meltytech LLC
SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org>
4
This file is part of Kdenlive. See www.kdenlive.org.
5

Camille Moulin's avatar
Camille Moulin committed
6
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7
*/
8

Nicolas Carion's avatar
format    
Nicolas Carion committed
9
#include "kdenlivesettings.h"
10
11
#include "core.h"
#include "bin/projectitemmodel.h"
12
13
#include <QPainter>
#include <QPainterPath>
Nicolas Carion's avatar
Nicolas Carion committed
14
#include <QQuickPaintedItem>
15
#include <QElapsedTimer>
16
#include <QtMath>
Nicolas Carion's avatar
Nicolas Carion committed
17
#include <cmath>
18
#include "kdenlivesettings.h"
19

20
const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
21

22
23
class TimelineTriangle : public QQuickPaintedItem
{
24
25
    Q_OBJECT
    Q_PROPERTY(QColor fillColor MEMBER m_color)
26
public:
27
    TimelineTriangle() { setAntialiasing(true); }
Nicolas Carion's avatar
Nicolas Carion committed
28
    void paint(QPainter *painter) override
29
30
31
32
33
    {
        QPainterPath path;
        path.moveTo(0, 0);
        path.lineTo(width(), 0);
        path.lineTo(0, height());
34
35
        painter->fillPath(path, m_color);
        painter->setPen(Qt::white);
Vincent Pinon's avatar
Vincent Pinon committed
36
        painter->drawLine(int(width()), 0, 0, int(height()));
37
    }
Nicolas Carion's avatar
Nicolas Carion committed
38

39
40
private:
    QColor m_color;
41
42
43
44
};

class TimelinePlayhead : public QQuickPaintedItem
{
45
46
47
48
    Q_OBJECT
    Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged)

public:
49
    TimelinePlayhead() { connect(this, SIGNAL(colorChanged(QColor)), this, SLOT(update())); }
50

Nicolas Carion's avatar
Nicolas Carion committed
51
    void paint(QPainter *painter) override
52
53
54
55
56
    {
        QPainterPath path;
        path.moveTo(width(), 0);
        path.lineTo(width() / 2.0, height());
        path.lineTo(0, 0);
57
        painter->fillPath(path, m_color);
58
    }
59
signals:
60
61
    void colorChanged(const QColor &);

62
63
private:
    QColor m_color;
64
65
};

66
67
68
class TimelineWaveform : public QQuickPaintedItem
{
    Q_OBJECT
69
    Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged)
70
71
    Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
    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:
87
88
    TimelineWaveform(QQuickItem *parent = nullptr)
        : 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
97
        //setRenderTarget(QQuickPaintedItem::FramebufferObject);
        //setMipmap(true);
98
        //setTextureSize(QSize(1, 1));
99
        connect(this, &TimelineWaveform::levelsChanged, [&]() {
100
101
102
103
104
105
106
            if (!m_binId.isEmpty()) {
                if (m_audioLevels.isEmpty() && m_stream >= 0) {
                    update();
                } else {
                    // Clip changed, reset levels
                    m_audioLevels.clear();
                }
107
            }
108
        });
109
        connect(this, &TimelineWaveform::normalizeChanged, [&]() {
110
            m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
111
112
            update();
        });
113
        connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
114
115
    }

Nicolas Carion's avatar
Nicolas Carion committed
116
    void paint(QPainter *painter) override
117
    {
118
        if (m_binId.isEmpty()) {
119
120
            return;
        }
121
122
        if (m_audioLevels.isEmpty() && m_stream >= 0) {
            m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
123
124
125
            if (m_audioLevels.isEmpty()) {
                return;
            }
126
            m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
127
        }
128

129
130
131
        if (m_outPoint == m_inPoint) {
            return;
        }
132
        QRectF bgRect(0, 0, width(), height());
133
        if (m_opaquePaint) {
134
135
            painter->fillRect(bgRect, m_bgColor);
        }
136
137
        QPen pen(painter->pen());
        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;
Vincent Pinon's avatar
Vincent Pinon committed
139
        int h = int(height());
140
141
142
        double offset = 0;
        bool pathDraw = increment > 1.2;
        if (increment > 1. && !pathDraw) {
Vincent Pinon's avatar
Vincent Pinon committed
143
            pen.setWidth(int(ceil(increment)));
144
            offset = pen.width() / 2.;
145
146
            pen.setColor(m_color);
            pen.setCapStyle(Qt::FlatCap);
147
        } else if (pathDraw) {
148
149
150
151
152
            pen.setWidth(0);
            painter->setBrush(m_color);
            pen.setColor(m_bgColor.darker(200));
        } else {
            pen.setColor(m_color);
153
154
        }
        painter->setPen(pen);
155
        double scaleFactor = 255;
156
        if (m_audioMax > 1) {
157
            scaleFactor = m_audioMax;
158
        }
Vincent Pinon's avatar
Vincent Pinon committed
159
        int startPos = int(m_inPoint / indicesPrPixel);
160
161
        if (!KdenliveSettings::displayallchannels()) {
            // Draw merged channels
162
            double i = 0;
163
            double level;
164
            int j = 0;
165
            QPainterPath path;
166
167
168
            if (pathDraw) {
                path.moveTo(j - 1, height());
            }
169
170
            for (; i <= width(); j++) {
                double level;
171
                i = j * increment;
172
                int idx = qCeil((startPos + i) * indicesPrPixel);
173
174
                idx += idx % m_channels;
                i -= offset;
175
176
177
                if (idx + m_channels >= m_audioLevels.length() || idx < 0) {
                    break;
                }
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
184
185
                    double val = height() - level * height();
                    path.lineTo(i, val);
                    path.lineTo(( j + 1) * increment - offset, val);
186
                } else {
Vincent Pinon's avatar
Vincent Pinon committed
187
                    painter->drawLine(int(i), h, int(i), int(h - (h * level)));
188
                }
189
            }
190
191
192
193
            if (pathDraw) {
                path.lineTo(i, height());
                painter->drawPath(path);
            }
194
        } else {
Vincent Pinon's avatar
Vincent Pinon committed
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
205
                // y is channel median pos
                double y = (channel * channelHeight) + channelHeight / 2;
206
                QPainterPath path;
207
                path.moveTo(-1, y);
208
209
                if (channel % 2 == 0) {
                    // Add dark background on odd channels
210
                    painter->setOpacity(0.2);
211
                    bgRect.moveTo(0, channel * channelHeight);
212
213
                    painter->fillRect(bgRect, Qt::black);
                }
214
                // Draw channel median line
215
216
                pen.setColor(channel % 2 == 0 ? m_color : m_color2);
                painter->setBrush(channel % 2 == 0 ? m_color : m_color2);
217
218
                painter->setOpacity(0.5);
                pen.setWidthF(0);
219
                painter->setPen(pen);
220
                painter->drawLine(QLineF(0., y, width(), y));
Vincent Pinon's avatar
Vincent Pinon committed
221
                pen.setWidth(int(ceil(increment)));
222
                painter->setPen(pathDraw ? Qt::NoPen : pen);
223
                painter->setOpacity(1);
224
225
                i = 0;
                int j = 0;
226
                for (; i <= width(); j++) {
227
                    i = j * increment;
Vincent Pinon's avatar
Vincent Pinon committed
228
                    int idx = int(ceil((startPos + i) * indicesPrPixel));
229
230
                    idx += idx % m_channels;
                    i -= offset;
231
232
                    idx += channel;
                    if (idx >= m_audioLevels.length() || idx < 0) break;
233
                    if (pathDraw) {
234
                        level = m_audioLevels.at(idx) * scaleFactor;
235
236
                        path.lineTo(i, y - level);
                    } else {
237
                        level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height
Vincent Pinon's avatar
Vincent Pinon committed
238
                        painter->drawLine(int(i), int(y - level), int(i), int(y + level));
239
240
241
242
243
244
245
                    }
                }
                if (pathDraw) {
                    path.lineTo(i, y);
                    painter->drawPath(path);
                    QTransform tr(1, 0, 0, -1, 0, 2 * y);
                    painter->drawPath(tr.map(path));
246
                }
247
                if (m_firstChunk && m_channels > 1 && m_channels < 7) {
Vincent Pinon's avatar
Vincent Pinon committed
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
264
    int m_inPoint;
    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
279
    bool m_opaquePaint;
    int m_index;
280
};
281
282
283
284
285

void registerTimelineItems()
{
    qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle");
    qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead");
286
    qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform");
287
}
288
289

#include "timelineitems.moc"