timelineitems.cpp 10.2 KB
Newer Older
1 2 3 4
/*
Based on Shotcut, Copyright (c) 2015-2016 Meltytech, LLC
Copyright (C) 2019  Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.
5

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
22

Nicolas Carion's avatar
format  
Nicolas Carion committed
23
#include "kdenlivesettings.h"
24 25
#include "core.h"
#include "bin/projectitemmodel.h"
26 27
#include <QPainter>
#include <QPainterPath>
Nicolas Carion's avatar
Nicolas Carion committed
28
#include <QQuickPaintedItem>
29
#include <QElapsedTimer>
Nicolas Carion's avatar
Nicolas Carion committed
30
#include <cmath>
31

32
const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
33

34 35
class TimelineTriangle : public QQuickPaintedItem
{
36 37
    Q_OBJECT
    Q_PROPERTY(QColor fillColor MEMBER m_color)
38
public:
39
    TimelineTriangle() { setAntialiasing(true); }
Nicolas Carion's avatar
Nicolas Carion committed
40
    void paint(QPainter *painter) override
41 42 43 44 45
    {
        QPainterPath path;
        path.moveTo(0, 0);
        path.lineTo(width(), 0);
        path.lineTo(0, height());
46 47 48
        painter->fillPath(path, m_color);
        painter->setPen(Qt::white);
        painter->drawLine(width(), 0, 0, height());
49
    }
Nicolas Carion's avatar
Nicolas Carion committed
50

51 52
private:
    QColor m_color;
53 54 55 56
};

class TimelinePlayhead : public QQuickPaintedItem
{
57 58 59 60
    Q_OBJECT
    Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged)

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

Nicolas Carion's avatar
Nicolas Carion committed
63
    void paint(QPainter *painter) override
64 65 66 67 68
    {
        QPainterPath path;
        path.moveTo(width(), 0);
        path.lineTo(width() / 2.0, height());
        path.lineTo(0, 0);
69
        painter->fillPath(path, m_color);
70
    }
71
signals:
72 73
    void colorChanged(const QColor &);

74 75
private:
    QColor m_color;
76 77
};

78 79 80
class TimelineWaveform : public QQuickPaintedItem
{
    Q_OBJECT
81 82
    Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
    Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
83
    Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
84 85
    Q_PROPERTY(int drawInPoint MEMBER m_drawInPoint NOTIFY propertyChanged)
    Q_PROPERTY(int drawOutPoint MEMBER m_drawOutPoint NOTIFY propertyChanged)
86
    Q_PROPERTY(int channels MEMBER m_channels NOTIFY audioChannelsChanged)
87
    Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged)
88
    Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
89
    Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
90
    Q_PROPERTY(int audioStream MEMBER m_stream)
91
    Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
92
    Q_PROPERTY(bool showItem READ showItem  WRITE setShowItem NOTIFY showItemChanged)
93
    Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
94 95 96 97

public:
    TimelineWaveform()
    {
98
        setAntialiasing(false);
99
        // setClip(true);
100
        setEnabled(false);
101
        m_showItem = false;
102
        m_precisionFactor = 1;
103 104 105
        //setRenderTarget(QQuickPaintedItem::FramebufferObject);
        //setMipmap(true);
        setTextureSize(QSize(1, 1));
106
        connect(this, &TimelineWaveform::levelsChanged, [&]() {
107 108 109 110 111 112 113
            if (!m_binId.isEmpty()) {
                if (m_audioLevels.isEmpty() && m_stream >= 0) {
                    update();
                } else {
                    // Clip changed, reset levels
                    m_audioLevels.clear();
                }
114
            }
115 116 117 118
        });
        connect(this, &TimelineWaveform::propertyChanged, [&]() {
            update();
        });
119
    }
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    bool showItem() const
    {
        return m_showItem;
    }
    void setShowItem(bool show)
    {
        m_showItem = show;
        if (show) {
            setTextureSize(QSize(width(), height()));
            update();
        } else {
            // Free memory
            setTextureSize(QSize(1, 1));
        }
    }
135

Nicolas Carion's avatar
Nicolas Carion committed
136
    void paint(QPainter *painter) override
137
    {
138
        if (!m_showItem || m_binId.isEmpty()) {
139 140
            return;
        }
141 142
        if (m_audioLevels.isEmpty() && m_stream >= 0) {
            m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
143 144 145 146
            if (m_audioLevels.isEmpty()) {
                return;
            }
        }
147
        qreal indicesPrPixel = qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
148 149
        QPen pen = painter->pen();
        pen.setColor(m_color);
150
        painter->setBrush(m_color);
151 152 153 154 155 156 157 158 159 160 161 162
        pen.setCapStyle(Qt::FlatCap);
        double increment = qMax(1., 1. / qAbs(indicesPrPixel));
        int h = height();
        double offset = 0;
        bool pathDraw = increment > 1.2;
        if (increment > 1. && !pathDraw) {
            pen.setWidth(ceil(increment));
            offset = pen.width() / 2.;
        } else if (pathDraw) {
            pen.setWidthF(0);
        }
        painter->setPen(pen);
163
        int startPos = m_inPoint / indicesPrPixel;
164 165
        if (!KdenliveSettings::displayallchannels()) {
            // Draw merged channels
166
            double i = 0;
167
            double level;
168
            int j = 0;
169
            QPainterPath path;
170 171 172 173 174 175 176 177
            if (m_drawInPoint > 0) {
                j = m_drawInPoint / increment;
            }
            if (pathDraw) {
                path.moveTo(j - 1, height());
            }
            for (; i <= width() && i < m_drawOutPoint; j++) {
                i = j * increment;
178
                int idx = ceil((startPos + i) * indicesPrPixel);
179 180
                idx += idx % m_channels;
                i -= offset;
181 182 183
                if (idx + m_channels >= m_audioLevels.length() || idx < 0) {
                    break;
                }
184
                level = m_audioLevels.at(idx) / 255.;
185 186 187 188 189 190 191
                for (int k = 1; k < m_channels; k++) {
                    level = qMax(level, m_audioLevels.at(idx + k) / 255.);
                }
                if (pathDraw) {
                    path.lineTo(i, height() - level * height());
                } else {
                    painter->drawLine(i, h, i, h - (h * level));
192
                }
193
            }
194 195 196 197
            if (pathDraw) {
                path.lineTo(i, height());
                painter->drawPath(path);
            }
198
        } else {
199
            double channelHeight = (double)height() / m_channels;
200
            // Draw separate channels
201
            double i = 0;
202
            double level;
203 204 205
            QRectF bgRect(0, 0, width(), channelHeight);
            // Path for vector drawing
            //qDebug()<<"==== DRAWING FROM: "<<m_drawInPoint<<" - "<<m_drawOutPoint<<", FIRST: "<<m_firstChunk;
206
            for (int channel = 0; channel < m_channels; channel++) {
207 208
                // y is channel median pos
                double y = (channel * channelHeight) + channelHeight / 2;
209
                QPainterPath path;
210
                path.moveTo(-1, y);
211 212
                if (channel % 2 == 0) {
                    // Add dark background on odd channels
213
                    painter->setOpacity(0.2);
214
                    bgRect.moveTo(0, channel * channelHeight);
215 216
                    painter->fillRect(bgRect, Qt::black);
                }
217
                // Draw channel median line
218 219
                pen.setColor(channel % 2 == 0 ? m_color : m_color2);
                painter->setBrush(channel % 2 == 0 ? m_color : m_color2);
220 221
                painter->setOpacity(0.5);
                pen.setWidthF(0);
222
                painter->setPen(pen);
223
                painter->drawLine(QLineF(0., y, width(), y));
224 225
                pen.setWidth(ceil(increment));
                painter->setPen(pathDraw ? Qt::NoPen : pen);
226
                painter->setOpacity(1);
227 228 229 230 231 232 233 234 235 236
                i = 0;
                int j = 0;
                if (m_drawInPoint > 0) {
                    j = m_drawInPoint / increment;
                }
                if (pathDraw) {
                    path.moveTo(m_drawInPoint - 1, y);
                }
                for (; i <= width() && i < m_drawOutPoint; j++) {
                    i = j * increment;
237
                    int idx = ceil((startPos + i) * indicesPrPixel);
238 239
                    idx += idx % m_channels;
                    i -= offset;
240 241
                    idx += channel;
                    if (idx >= m_audioLevels.length() || idx < 0) break;
242 243 244 245 246 247 248 249 250 251 252 253 254
                    if (pathDraw) {
                        level = m_audioLevels.at(idx) * channelHeight / 510.;
                        path.lineTo(i, y - level);
                    } else {
                        level = m_audioLevels.at(idx) * channelHeight / 510.; // divide height by 510 (2*255) to get height
                        painter->drawLine(i, y - level, i, y + level);
                    }
                }
                if (pathDraw) {
                    path.lineTo(i, y);
                    painter->drawPath(path);
                    QTransform tr(1, 0, 0, -1, 0, 2 * y);
                    painter->drawPath(tr.map(path));
255
                }
256
                if (m_firstChunk && m_channels > 1 && m_channels < 7) {
257
                    painter->drawText(2, y + channelHeight / 2, chanelNames[channel]);
258 259
                }
            }
260 261 262 263
        }
    }

signals:
264
    void levelsChanged();
265 266
    void propertyChanged();
    void inPointChanged();
267
    void showItemChanged();
268
    void audioChannelsChanged();
269 270

private:
271
    QVector<uint8_t> m_audioLevels;
272 273
    int m_inPoint;
    int m_outPoint;
274 275 276
    // Pixels outside the view, can be dropped
    int m_drawInPoint;
    int m_drawOutPoint;
277
    QString m_binId;
278
    QColor m_color;
279
    QColor m_color2;
280
    bool m_format;
281
    bool m_showItem;
282
    int m_channels;
283
    int m_precisionFactor;
284
    int m_stream;
285
    bool m_firstChunk;
286
};
287 288 289 290 291

void registerTimelineItems()
{
    qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle");
    qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead");
292
    qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform");
293
}
294 295

#include "timelineitems.moc"