Commit f43d8512 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Major speedup in audio thumbs drawing on high zoom levels

Fixes #1082
Related to #439
parent 03e2ca45
......@@ -283,12 +283,9 @@ Item {
binId: controller.clipId
audioStream: controller.audioStreams[model.index]
isFirstChunk: false
showItem: audioThumb.visible
format: controller.audioThumbFormat
normalize: controller.audioThumbNormalize
scaleFactor: audioThumb.width / (root.duration - 1) / root.zoomFactor
drawInPoint: 0
drawOutPoint: audioThumb.width
waveInPoint: (root.duration - 1) * root.zoomStart * channels
waveOutPointWithUpdate: (root.duration - 1) * (root.zoomStart + root.zoomFactor) * channels
fillColor1: root.thumbColor1
......
......@@ -71,7 +71,7 @@ Rectangle {
property int slipOffset: boundValue(outPoint - maxDuration + 1, trimmingOffset, inPoint)
property int scrollStart: scrollView.contentX - (clipRoot.modelStart * timeline.scaleFactor)
property int mouseXPos: mouseArea.mouseX
width : clipDuration * timeScale
width : Math.round(clipDuration * timeScale)
opacity: dragProxyArea.drag.active && dragProxy.draggedItem == clipId ? 0.8 : 1.0
signal trimmingIn(var clip, real newDuration, bool shiftTrim, bool controlTrim)
......@@ -132,7 +132,7 @@ Rectangle {
}*/
onClipDurationChanged: {
width = clipDuration * timeScale
width = Math.round(clipDuration * timeScale)
if (parentTrack && parentTrack.isAudio && thumbsLoader.item) {
// Duration changed, we may need a different number of repeaters
thumbsLoader.item.reload(1)
......@@ -140,11 +140,11 @@ Rectangle {
}
onModelStartChanged: {
x = modelStart * timeScale;
x = Math.round(modelStart * timeScale);
}
onFakePositionChanged: {
x = fakePosition * timeScale;
x = Math.round(fakePosition * timeScale);
}
onFakeTidChanged: {
if (clipRoot.fakeTid > -1 && parentTrack) {
......@@ -168,17 +168,23 @@ Rectangle {
}
onTimeScaleChanged: {
x = modelStart * timeScale;
width = clipDuration * timeScale;
x = Math.round(modelStart * timeScale);
width = Math.round(clipDuration * timeScale);
updateLabelOffset()
if (!clipRoot.hideClipViews) {
if (effectRow.item && effectRow.item.kfrCanvas) {
effectRow.item.kfrCanvas.requestPaint()
}
}
if (isAudio && thumbsLoader.item) {
thumbsLoader.item.reload(1)
}
}
onScrollXChanged: {
updateLabelOffset()
if (isAudio && thumbsLoader.item) {
thumbsLoader.item.reload(1)
}
}
function updateLabelOffset()
......@@ -186,8 +192,8 @@ Rectangle {
labelRect.anchors.leftMargin = scrollX > modelStart * timeScale ? scrollX - modelStart * timeScale + (clipRoot.timeremap ? labelRect.height : 0) : clipRoot.timeremap ? labelRect.height : 0
}
border.color: (clipStatus === ClipStatus.StatusMissing || ClipStatus === ClipStatus.StatusWaiting || clipStatus === ClipStatus.StatusDeleting) ? "#ff0000" : selected ? root.selectionColor : grouped ? root.groupColor : borderColor
border.width: isGrabbed ? 8 : 2
/*border.color: (clipStatus === ClipStatus.StatusMissing || ClipStatus === ClipStatus.StatusWaiting || clipStatus === ClipStatus.StatusDeleting) ? "#ff0000" : selected ? root.selectionColor : grouped ? root.groupColor : borderColor
border.width: isGrabbed ? 8 : 2*/
function updateDrag() {
var itemPos = mapToItem(tracksContainerArea, 0, 0, clipRoot.width, clipRoot.height)
......@@ -346,21 +352,30 @@ Rectangle {
// Thumbs container
id: thumbsLoader
anchors.fill: parent
anchors.leftMargin: parentTrack.isAudio ? 0 : clipRoot.border.width + mixContainer.width
anchors.rightMargin: parentTrack.isAudio ? 0 : clipRoot.border.width
anchors.topMargin: clipRoot.border.width
anchors.bottomMargin: clipRoot.border.width
clip: true
anchors.leftMargin: parentTrack.isAudio ? 0 : itemBorder.border.width + mixContainer.width
anchors.rightMargin: parentTrack.isAudio ? 0 : itemBorder.border.width
anchors.topMargin: itemBorder.border.width
anchors.bottomMargin: itemBorder.border.width
//clip: true
asynchronous: true
visible: status == Loader.Ready
source: clipRoot.hideClipViews || clipRoot.itemType == 0 || clipRoot.itemType === ProducerType.Color ? "" : parentTrack.isAudio ? (timeline.showAudioThumbnails ? "ClipAudioThumbs.qml" : "") : timeline.showThumbnails ? "ClipThumbs.qml" : ""
}
Rectangle {
// Border rectangle
color: 'transparent'
id: itemBorder
anchors.fill: parent
border.color: (clipStatus === ClipStatus.StatusMissing || ClipStatus === ClipStatus.StatusWaiting || clipStatus === ClipStatus.StatusDeleting) ? "#ff0000" : selected ? root.selectionColor : grouped ? root.groupColor : borderColor
border.width: isGrabbed ? 8 : 2
}
Item {
// Clipping container
id: container
anchors.fill: parent
anchors.margins: clipRoot.border.width
anchors.margins: itemBorder.border.width
//clip: true
property bool showDetails: (!clipRoot.selected || !effectRow.visible) && container.height > 2.2 * labelRect.height
......@@ -445,7 +460,7 @@ Rectangle {
anchors.left = parent.left
parent.anchors.right = mixContainer.right
mixBackground.anchors.bottom = mixContainer.bottom
mixOut.color = clipRoot.border.color
mixOut.color = itemBorder.border.color
mixCutPos.anchors.right = mixCutPos.parent.right
root.trimInProgress = false;
}
......@@ -472,15 +487,15 @@ Rectangle {
}
onExited: {
if (!pressed) {
mixOut.color = clipRoot.border.color
mixOut.color = itemBorder.border.color
timeline.showToolTip()
}
}
Rectangle {
id: mixOut
width: clipRoot.border.width
width: itemBorder.border.width
height: mixContainer.height
color: clipRoot.border.color
color: itemBorder.border.color
Drag.active: trimInMixArea.drag.active
Drag.proposedAction: Qt.MoveAction
visible: trimInMixArea.pressed || (root.activeTool === ProjectTool.SelectTool && !mouseArea.drag.active && parent.enabled)
......@@ -501,8 +516,8 @@ Rectangle {
width: 1
height: container.height
x: clipRoot.speed < 0
? (clipRoot.maxDuration - clipRoot.inPoint) * timeScale + (Math.round(model.frame / clipRoot.speed)) * timeScale - clipRoot.border.width
: (Math.round(model.frame / clipRoot.speed) - clipRoot.inPoint) * timeScale - clipRoot.border.width;
? (clipRoot.maxDuration - clipRoot.inPoint) * timeScale + (Math.round(model.frame / clipRoot.speed)) * timeScale - itemBorder.border.width
: (Math.round(model.frame / clipRoot.speed) - clipRoot.inPoint) * timeScale - itemBorder.border.width;
color: model.color
}
Rectangle {
......@@ -548,7 +563,7 @@ Rectangle {
MouseArea {
// Left resize handle
id: trimInMouseArea
x: -clipRoot.border.width
x: -itemBorder.border.width
height: parent.height
width: root.baseUnit / 2
visible: root.activeTool === ProjectTool.SelectTool || (root.activeTool === ProjectTool.RippleTool && clipRoot.mixDuration <= 0 && !controller.hasClipEndMix(clipRoot.clipId))
......@@ -578,7 +593,7 @@ Rectangle {
}
onReleased: {
root.autoScrolling = timeline.autoScroll
x = -clipRoot.border.width
x = -itemBorder.border.width
if (sizeChanged) {
clipRoot.trimmedIn(clipRoot, shiftTrim, controlTrim)
sizeChanged = false
......@@ -600,7 +615,7 @@ Rectangle {
}
onPositionChanged: {
if (mouse.buttons === Qt.LeftButton) {
var currentFrame = Math.round((clipRoot.x + (x + clipRoot.border.width)) / timeScale)
var currentFrame = Math.round((clipRoot.x + (x + itemBorder.border.width)) / timeScale)
var currentClipPos = clipRoot.modelStart
var delta = currentFrame - currentClipPos
if (delta !== 0) {
......@@ -647,7 +662,7 @@ Rectangle {
Rectangle {
id: trimIn
anchors.left: parent.left
width: clipRoot.border.width
width: itemBorder.border.width
height: parent.height
color: 'lawngreen'
opacity: 0
......@@ -676,7 +691,7 @@ Rectangle {
// Right resize handle
id: trimOutMouseArea
anchors.right: parent.right
anchors.rightMargin: -clipRoot.border.width
anchors.rightMargin: -itemBorder.border.width
anchors.top: parent.top
height: parent.height
width: root.baseUnit / 2
......@@ -788,7 +803,7 @@ Rectangle {
Rectangle {
id: trimOut
anchors.right: parent.right
width: clipRoot.border.width
width: itemBorder.border.width
height: parent.height
color: 'red'
opacity: 0
......@@ -831,7 +846,7 @@ Rectangle {
// Clip name background
id: labelRect
color: clipRoot.selected ? 'darkred' : '#66000000'
width: label.width + (2 * clipRoot.border.width)
width: label.width + (2 * itemBorder.border.width)
height: label.height
visible: clipRoot.width > width / 2
anchors.left: parent.left
......@@ -844,7 +859,7 @@ Rectangle {
font: miniFont
anchors {
left: labelRect.left
leftMargin: clipRoot.border.width
leftMargin: itemBorder.border.width
}
color: 'white'
//style: Text.Outline
......@@ -1173,7 +1188,7 @@ Rectangle {
}
onPositionChanged: {
if (mouse.buttons === Qt.LeftButton) {
var delta = clipRoot.clipDuration - Math.floor((x + width / 2 - clipRoot.border.width)/ timeScale)
var delta = clipRoot.clipDuration - Math.floor((x + width / 2 - itemBorder.border.width)/ timeScale)
var duration = Math.max(0, delta)
duration = Math.min(duration, clipRoot.clipDuration)
if (lastDuration != duration) {
......@@ -1358,7 +1373,7 @@ Rectangle {
Rectangle {
id: currentRegionMoved
color: parent.color
x: slipBackground.x + slipControler.inPoint * timeScale + clipRoot.border.width
x: slipBackground.x + slipControler.inPoint * timeScale + itemBorder.border.width
anchors.bottom: parent.bottom
height: parent.height / 2
width: container.width
......
......@@ -4,7 +4,7 @@
*/
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Controls 2.11
import Kdenlive.Controls 1.0
import QtQml.Models 2.11
import com.enums 1.0
......@@ -12,7 +12,10 @@ import com.enums 1.0
Row {
id: waveform
opacity: clipState === ClipState.Disabled ? 0.2 : 1
property int maxWidth: 500 - (500 % timeline.scaleFactor) + 100 * timeline.scaleFactor
property int maxWidth: 2048
property int totalChunks: 0
property bool usesOffset: false
property int offset: -1
anchors.fill: parent
Timer {
......@@ -27,13 +30,12 @@ Row {
function reload(reset) {
if (reset === 0) {
waveform.offset = 0
waveform.totalChunks = 0
waveformRepeater.model = 0
}
waveTimer.start()
}
onMaxWidthChanged: {
waveTimer.start()
}
function processReload() {
// This is needed to make the model have the correct count.
......@@ -41,29 +43,49 @@ Row {
if (!waveform.visible || !timeline.showAudioThumbnails) {
return;
}
var chunks = Math.ceil(waveform.width / waveform.maxWidth)
var total = Math.ceil(waveform.width / waveform.maxWidth)
var chunks = total
var updatedOffset = 0
if (chunks > 10) {
// Having too many chunks causes major slowdowns. In this case, we use an offset and only allow up to 20 chunks
waveform.usesOffset = true
updatedOffset = Math.max(0, Math.floor(clipRoot.scrollStart / waveform.maxWidth - 2))
if (updatedOffset < waveform.offset || updatedOffset > (waveform.offset + 5) || total != waveform.totalChunks) {
waveformRepeater.model = 0
} else {
return
}
chunks = Math.min(10, total - updatedOffset)
} else {
waveform.usesOffset = false
updatedOffset = 0
}
waveform.offset = updatedOffset
waveform.totalChunks = total
if (waveformRepeater.model === undefined || chunks !== waveformRepeater.model) {
waveformRepeater.model = chunks
}
}
Item {
width: waveform.offset * waveform.maxWidth
height: parent.height
}
Repeater {
id: waveformRepeater
TimelineWaveform {
width: Math.min(waveform.width, waveform.maxWidth)
width: waveform.maxWidth < waveform.width ? (index + waveform.offset == waveform.totalChunks - 1 ? waveform.width % waveform.maxWidth : waveform.maxWidth) : Math.round(waveform.width)
height: waveform.height
ix: index
channels: clipRoot.audioChannels
isOpaque: true
binId: clipRoot.binId
audioStream: clipRoot.audioStream
isFirstChunk: index == 0
isFirstChunk: index + waveform.offset == 0
isOpaque: true
scaleFactor: timeline.scaleFactor
showItem: waveform.visible && (index * waveform.maxWidth < (clipRoot.scrollStart + scrollView.width)) && ((index * waveform.maxWidth + width) > clipRoot.scrollStart)
format: timeline.audioThumbFormat
normalize: timeline.audioThumbNormalize
drawInPoint: Math.max(0, clipRoot.scrollStart - (index * waveform.maxWidth))
drawOutPoint: (clipRoot.scrollStart + scrollView.width - (index * waveform.maxWidth))
waveInPoint: clipRoot.speed < 0 ? (Math.round((clipRoot.maxDuration - 1 - clipRoot.inPoint) * Math.abs(clipRoot.speed) - (index * waveform.maxWidth / timeline.scaleFactor) * Math.abs(clipRoot.speed)) * clipRoot.audioChannels) : (Math.round((clipRoot.inPoint + (index * waveform.maxWidth / timeline.scaleFactor)) * clipRoot.speed) * clipRoot.audioChannels)
waveInPoint: clipRoot.speed < 0 ? (Math.round((clipRoot.maxDuration - 1 - clipRoot.inPoint) * Math.abs(clipRoot.speed) - ((index + waveform.offset) * waveform.maxWidth / timeline.scaleFactor) * Math.abs(clipRoot.speed)) * clipRoot.audioChannels) : (Math.round((clipRoot.inPoint + ((index + waveform.offset) * waveform.maxWidth / timeline.scaleFactor)) * clipRoot.speed) * clipRoot.audioChannels)
waveOutPoint: clipRoot.speed < 0 ? (waveInPoint - Math.ceil(width / timeline.scaleFactor * Math.abs(clipRoot.speed)) * clipRoot.audioChannels) : (waveInPoint + Math.round(width / timeline.scaleFactor * clipRoot.speed) * clipRoot.audioChannels)
fillColor0: clipRoot.color
fillColor1: root.thumbColor1
......
......@@ -70,9 +70,8 @@ class TimelineWaveform : public QQuickPaintedItem
Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
Q_PROPERTY(int drawInPoint MEMBER m_drawInPoint NOTIFY propertyChanged)
Q_PROPERTY(int drawOutPoint MEMBER m_drawOutPoint NOTIFY propertyChanged)
Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged)
Q_PROPERTY(int ix MEMBER m_index)
Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged)
Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
......@@ -80,24 +79,21 @@ class TimelineWaveform : public QQuickPaintedItem
Q_PROPERTY(double scaleFactor MEMBER m_scale)
Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY normalizeChanged)
Q_PROPERTY(bool showItem READ showItem WRITE setShowItem NOTIFY showItemChanged)
Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
Q_PROPERTY(bool isOpaque MEMBER m_isOpaque)
Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint)
public:
TimelineWaveform(QQuickItem *parent = nullptr)
: QQuickPaintedItem(parent)
, m_isOpaque(false)
, m_opaquePaint(false)
{
setAntialiasing(false);
setOpaquePainting(m_isOpaque);
// setClip(true);
setOpaquePainting(m_opaquePaint);
setEnabled(false);
m_showItem = false;
m_precisionFactor = 1;
//setRenderTarget(QQuickPaintedItem::FramebufferObject);
//setMipmap(true);
setTextureSize(QSize(1, 1));
//setTextureSize(QSize(1, 1));
connect(this, &TimelineWaveform::levelsChanged, [&]() {
if (!m_binId.isEmpty()) {
if (m_audioLevels.isEmpty() && m_stream >= 0) {
......@@ -114,25 +110,10 @@ public:
});
connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
}
bool showItem() const
{
return m_showItem;
}
void setShowItem(bool show)
{
m_showItem = show;
if (show) {
setTextureSize(QSize(int(width()), int(height())));
update();
} else {
// Free memory
setTextureSize(QSize(1, 1));
}
}
void paint(QPainter *painter) override
{
if (!m_showItem || m_binId.isEmpty()) {
if (m_binId.isEmpty()) {
return;
}
if (m_audioLevels.isEmpty() && m_stream >= 0) {
......@@ -147,14 +128,11 @@ public:
return;
}
QRectF bgRect(0, 0, width(), height());
if (m_isOpaque) {
if (m_opaquePaint) {
painter->fillRect(bgRect, m_bgColor);
}
QPen pen = painter->pen();
pen.setColor(m_color);
painter->setBrush(m_color.darker(200));
pen.setCapStyle(Qt::FlatCap);
double increment = m_scale / m_channels; //qMax(1., 1. / qAbs(indicesPrPixel));
QPen pen(painter->pen());
double increment = qMax(1., m_scale / m_channels); //qMax(1., 1. / qAbs(indicesPrPixel));
qreal indicesPrPixel = m_channels / m_scale; //qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
int h = int(height());
double offset = 0;
......@@ -162,8 +140,14 @@ public:
if (increment > 1. && !pathDraw) {
pen.setWidth(int(ceil(increment)));
offset = pen.width() / 2.;
pen.setColor(m_color);
pen.setCapStyle(Qt::FlatCap);
} else if (pathDraw) {
pen.setWidthF(0);
pen.setWidth(0);
painter->setBrush(m_color);
pen.setColor(m_bgColor.darker(200));
} else {
pen.setColor(m_color);
}
painter->setPen(pen);
double scaleFactor = 255;
......@@ -177,13 +161,11 @@ public:
double level;
int j = 0;
QPainterPath path;
if (m_drawInPoint > 0) {
j = int(m_drawInPoint / increment);
}
if (pathDraw) {
path.moveTo(j - 1, height());
}
for (; i <= width() && i < m_drawOutPoint; j++) {
for (; i <= width(); j++) {
double level;
i = j * increment;
int idx = qCeil((startPos + i) * indicesPrPixel);
idx += idx % m_channels;
......@@ -196,7 +178,9 @@ public:
level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor);
}
if (pathDraw) {
path.lineTo(i, height() - level * height());
double val = height() - level * height();
path.lineTo(i, val);
path.lineTo(( j + 1) * increment - offset, val);
} else {
painter->drawLine(int(i), h, int(i), int(h - (h * level)));
}
......@@ -207,13 +191,13 @@ public:
}
} else {
double channelHeight = height() / m_channels;
QPen pen(painter->pen());
// Draw separate channels
scaleFactor = channelHeight / (2 * scaleFactor);
double i = 0;
double level;
bgRect.setHeight(channelHeight);
// Path for vector drawing
//qDebug()<<"==== DRAWING FROM: "<<m_drawInPoint<<" - "<<m_drawOutPoint<<", FIRST: "<<m_firstChunk;
for (int channel = 0; channel < m_channels; channel++) {
// y is channel median pos
double y = (channel * channelHeight) + channelHeight / 2;
......@@ -237,13 +221,7 @@ public:
painter->setOpacity(1);
i = 0;
int j = 0;
if (m_drawInPoint > 0) {
j = int(m_drawInPoint / increment);
}
if (pathDraw) {
path.moveTo(m_drawInPoint - 1, y);
}
for (; i <= width() && i < m_drawOutPoint; j++) {
for (; i <= width(); j++) {
i = j * increment;
int idx = int(ceil((startPos + i) * indicesPrPixel));
idx += idx % m_channels;
......@@ -276,30 +254,26 @@ signals:
void propertyChanged();
void normalizeChanged();
void inPointChanged();
void showItemChanged();
void audioChannelsChanged();
private:
QVector<uint8_t> m_audioLevels;
int m_inPoint;
int m_outPoint;
// Pixels outside the view, can be dropped
int m_drawInPoint;
int m_drawOutPoint;
QString m_binId;
QColor m_bgColor;
QColor m_color;
QColor m_color2;
QColor m_bgColor;
bool m_format;
bool m_normalize;
bool m_showItem;
int m_channels;
int m_precisionFactor;
int m_stream;
double m_scale;
double m_audioMax;
bool m_firstChunk;
bool m_isOpaque;
bool m_opaquePaint;
int m_index;
};
void registerTimelineItems()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment