Mercurial > hg > svgui
changeset 1367:f5566f7271fe waverevision
Rework waveform renderer to use smooth paths, aiming to get near-pixel-identical results when zoomed out far enough for a single path not to be relevant
author | Chris Cannam |
---|---|
date | Wed, 31 Oct 2018 15:06:32 +0000 |
parents | c2a3ac0a6688 |
children | 38f35c2e03c4 a476c2a015e8 |
files | layer/ColourDatabase.cpp layer/WaveformLayer.cpp layer/WaveformLayer.h |
diffstat | 3 files changed, 153 insertions(+), 157 deletions(-) [+] |
line wrap: on
line diff
--- a/layer/ColourDatabase.cpp Tue Oct 30 16:23:03 2018 +0000 +++ b/layer/ColourDatabase.cpp Wed Oct 31 15:06:32 2018 +0000 @@ -85,6 +85,26 @@ return -1; } +QColor +ColourDatabase::getContrastingColour(int c) const +{ + QColor col = getColour(c); + if (col.red() > col.blue()) { + if (col.green() > col.blue()) { + return Qt::blue; + } else { + return Qt::yellow; + } + } else { + if (col.green() > col.blue()) { + return Qt::yellow; + } else { + return Qt::red; + } + } + return Qt::red; +} + bool ColourDatabase::useDarkBackground(int c) const {
--- a/layer/WaveformLayer.cpp Tue Oct 30 16:23:03 2018 +0000 +++ b/layer/WaveformLayer.cpp Wed Oct 31 15:06:32 2018 +0000 @@ -45,7 +45,6 @@ m_gain(1.0f), m_autoNormalize(false), m_showMeans(true), - m_greyscale(true), m_channelMode(SeparateChannels), m_channel(-1), m_scale(LinearScale), @@ -274,15 +273,6 @@ } void -WaveformLayer::setUseGreyscale(bool useGreyscale) -{ - if (m_greyscale == useGreyscale) return; - m_greyscale = useGreyscale; - m_cacheValid = false; - emit layerParametersChanged(); -} - -void WaveformLayer::setChannelMode(ChannelMode channelMode) { if (m_channelMode == channelMode) return; @@ -356,14 +346,14 @@ return true; } -int +double WaveformLayer::dBscale(double sample, int m) const { if (sample < 0.0) return dBscale(-sample, m); double dB = AudioLevel::multiplier_to_dB(sample); if (dB < -50.0) return 0; if (dB > 0.0) return m; - return int(((dB + 50.0) * m) / 50.0 + 0.1); + return ((dB + 50.0) * m) / 50.0; } int @@ -547,7 +537,7 @@ paint = &viewPainter; } - paint->setRenderHint(QPainter::Antialiasing, false); + paint->setRenderHint(QPainter::Antialiasing, true); if (m_middleLineHeight != 0.5) { paint->save(); @@ -764,9 +754,8 @@ if (channels == 0) return; QColor baseColour = getBaseQColor(); - vector<QColor> greys = getPartialShades(v); - QColor midColour = baseColour; + if (midColour == Qt::black) { midColour = Qt::gray; } else if (v->hasLightBackground()) { @@ -775,9 +764,6 @@ midColour = midColour.light(50); } - int prevRangeBottom = -1, prevRangeTop = -1; - QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour; - double gain = m_effectiveGains[ch]; int m = (h / channels) / 2; @@ -795,7 +781,8 @@ my = m + (((ch - minChannel) * h) / channels); } - paint->setPen(greys[1]); + // Horizontal axis along middle + paint->setPen(QPen(midColour, 0)); paint->drawLine(x0, my, x1, my); paintChannelScaleGuides(v, paint, rect, ch); @@ -808,6 +795,13 @@ (void)frame1; // not actually used #endif + QPainterPath waveformPath; + QPainterPath meanPath; + QPainterPath clipPath; + vector<QPointF> individualSamplePoints; + + bool firstPoint = true; + for (int x = x0; x <= x1; ++x) { sv_frame_t f0, f1; @@ -863,7 +857,7 @@ continue; } - int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; + double rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; if (mergingChannels && ranges.size() > 1) { @@ -895,65 +889,61 @@ } } - int greyLevels = 1; - if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; - switch (m_scale) { case LinearScale: - rangeBottom = int(double(m * greyLevels) * range.min() * gain); - rangeTop = int(double(m * greyLevels) * range.max() * gain); - meanBottom = int(double(-m) * range.absmean() * gain); - meanTop = int(double(m) * range.absmean() * gain); + rangeBottom = range.min() * gain * m; + rangeTop = range.max() * gain * m; + meanBottom = range.absmean() * gain * (-m); + meanTop = range.absmean() * gain * m; break; case dBScale: if (!mergingChannels) { - int db0 = dBscale(range.min() * gain, m); - int db1 = dBscale(range.max() * gain, m); - rangeTop = std::max(db0, db1); - meanTop = std::min(db0, db1); + double db0 = dBscale(range.min() * gain, m); + double db1 = dBscale(range.max() * gain, m); + rangeTop = std::max(db0, db1); + meanTop = std::min(db0, db1); if (mixingChannels) rangeBottom = meanTop; else rangeBottom = dBscale(range.absmean() * gain, m); - meanBottom = rangeBottom; + meanBottom = rangeBottom; } else { - rangeBottom = -dBscale(range.min() * gain, m * greyLevels); - rangeTop = dBscale(range.max() * gain, m * greyLevels); - meanBottom = -dBscale(range.absmean() * gain, m); - meanTop = dBscale(range.absmean() * gain, m); + rangeBottom = -dBscale(range.min() * gain, m); + rangeTop = dBscale(range.max() * gain, m); + meanBottom = -dBscale(range.absmean() * gain, m); + meanTop = dBscale(range.absmean() * gain, m); } break; case MeterScale: if (!mergingChannels) { - int r0 = abs(AudioLevel::multiplier_to_preview(range.min() * gain, m)); - int r1 = abs(AudioLevel::multiplier_to_preview(range.max() * gain, m)); - rangeTop = std::max(r0, r1); - meanTop = std::min(r0, r1); + double r0 = fabs(AudioLevel::multiplier_to_preview + (range.min() * gain, m)); + double r1 = fabs(AudioLevel::multiplier_to_preview + (range.max() * gain, m)); + rangeTop = std::max(r0, r1); + meanTop = std::min(r0, r1); if (mixingChannels) rangeBottom = meanTop; - else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); + else rangeBottom = AudioLevel::multiplier_to_preview + (range.absmean() * gain, m); meanBottom = rangeBottom; } else { - rangeBottom = -AudioLevel::multiplier_to_preview(range.min() * gain, m * greyLevels); - rangeTop = AudioLevel::multiplier_to_preview(range.max() * gain, m * greyLevels); - meanBottom = -AudioLevel::multiplier_to_preview(range.absmean() * gain, m); - meanTop = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); + rangeBottom = -AudioLevel::multiplier_to_preview + (range.min() * gain, m); + rangeTop = AudioLevel::multiplier_to_preview + (range.max() * gain, m); + meanBottom = -AudioLevel::multiplier_to_preview + (range.absmean() * gain, m); + meanTop = AudioLevel::multiplier_to_preview + (range.absmean() * gain, m); } break; } - rangeBottom = my * greyLevels - rangeBottom; - rangeTop = my * greyLevels - rangeTop; - meanBottom = my - meanBottom; - meanTop = my - meanTop; - - int topFill = (rangeTop % greyLevels); - if (topFill > 0) topFill = greyLevels - topFill; - - int bottomFill = (rangeBottom % greyLevels); - - rangeTop = rangeTop / greyLevels; - rangeBottom = rangeBottom / greyLevels; + rangeBottom = my - rangeBottom; + rangeTop = my - rangeTop; + meanBottom = my - meanBottom; + meanTop = my - meanTop; bool clipped = false; @@ -962,99 +952,102 @@ if (rangeBottom < my - m) { rangeBottom = my - m; } if (rangeBottom > my + m) { rangeBottom = my + m; } - if (range.max() <= -1.0 || - range.max() >= 1.0) clipped = true; + if (range.max() <= -1.0 || range.max() >= 1.0) { + clipped = true; + } - if (meanBottom > rangeBottom) meanBottom = rangeBottom; - if (meanTop < rangeTop) meanTop = rangeTop; + bool drawMean = m_showMeans; - bool drawMean = m_showMeans; - if (meanTop == rangeTop) { - if (meanTop < meanBottom) ++meanTop; - else drawMean = false; + meanTop = meanTop - 0.5; + meanBottom = meanBottom + 0.5; + + if (meanTop <= rangeTop + 1.0) { + meanTop = rangeTop + 1.0; } - if (meanBottom == rangeBottom && m_scale == LinearScale) { - if (meanBottom > meanTop) --meanBottom; - else drawMean = false; + if (meanBottom >= rangeBottom - 1.0 && m_scale == LinearScale) { + meanBottom = rangeBottom - 1.0; } - - if (showIndividualSample) { - paint->setPen(baseColour); - paint->drawRect(x-1, rangeTop-1, 2, 2); - if (rangeTop != rangeBottom) { // e.g. for "butterfly" merging mode - paint->drawRect(x-1, rangeBottom-1, 2, 2); - } - } - - if (x != x0 && prevRangeBottom != -1) { - if (prevRangeBottom > rangeBottom + 1 && - prevRangeTop > rangeBottom + 1) { -// paint->setPen(midColour); - paint->setPen(baseColour); - paint->drawLine(x-1, prevRangeTop, x, rangeBottom + 1); - paint->setPen(prevRangeTopColour); - paint->drawPoint(x-1, prevRangeTop); - } else if (prevRangeBottom < rangeTop - 1 && - prevRangeTop < rangeTop - 1) { -// paint->setPen(midColour); - paint->setPen(baseColour); - paint->drawLine(x-1, prevRangeBottom, x, rangeTop - 1); - paint->setPen(prevRangeBottomColour); - paint->drawPoint(x-1, prevRangeBottom); - } - } - - if (m_model->isReady()) { - if (clipped /*!!! || - range.min() * gain <= -1.0 || - range.max() * gain >= 1.0 */) { - paint->setPen(Qt::red); //!!! getContrastingColour - } else { - paint->setPen(baseColour); - } - } else { - paint->setPen(midColour); + if (meanTop > meanBottom - 1.0) { + drawMean = false; } #ifdef DEBUG_WAVEFORM_PAINT_BY_PIXEL SVCERR << "range " << rangeBottom << " -> " << rangeTop << ", means " << meanBottom << " -> " << meanTop << ", raw range " << range.min() << " -> " << range.max() << endl; #endif - if (rangeTop == rangeBottom) { - paint->drawPoint(x, rangeTop); - } else { - paint->drawLine(x, rangeBottom, x, rangeTop); - } - - prevRangeTopColour = baseColour; - prevRangeBottomColour = baseColour; - - if (m_greyscale && (m_scale == LinearScale) && m_model->isReady()) { - if (!clipped) { - if (rangeTop < rangeBottom) { - if (topFill > 0 && - (!drawMean || (rangeTop < meanTop - 1))) { - paint->setPen(greys[topFill - 1]); - paint->drawPoint(x, rangeTop); - prevRangeTopColour = greys[topFill - 1]; - } - if (bottomFill > 0 && - (!drawMean || (rangeBottom > meanBottom + 1))) { - paint->setPen(greys[bottomFill - 1]); - paint->drawPoint(x, rangeBottom); - prevRangeBottomColour = greys[bottomFill - 1]; - } - } + double rangeMiddle = (rangeTop + rangeBottom) / 2.0; + bool trivialRange = (fabs(rangeTop - rangeBottom) < 1.0); + double px = x + 0.5; + + if (showIndividualSample) { + individualSamplePoints.push_back(QPointF(px, rangeTop)); + if (!trivialRange) { + // common e.g. in "butterfly" merging mode + individualSamplePoints.push_back(QPointF(px, rangeBottom)); } } + if (firstPoint) { + waveformPath = QPainterPath(QPointF(px, rangeMiddle)); + firstPoint = false; + } else { + waveformPath.lineTo(QPointF(px, rangeMiddle)); + } + + if (!trivialRange) { + waveformPath.lineTo(QPointF(px, rangeTop)); + waveformPath.lineTo(QPointF(px, rangeBottom)); + waveformPath.lineTo(QPointF(px, rangeMiddle)); + } + if (drawMean) { - paint->setPen(midColour); - paint->drawLine(x, meanBottom, x, meanTop); + meanPath.moveTo(QPointF(px, meanBottom)); + meanPath.lineTo(QPointF(px, meanTop)); } - - prevRangeBottom = rangeBottom; - prevRangeTop = rangeTop; + + if (clipped) { + if (trivialRange) { + clipPath.moveTo(QPointF(px, rangeMiddle)); + clipPath.lineTo(QPointF(px+1, rangeMiddle)); + } else { + clipPath.moveTo(QPointF(px, rangeBottom)); + clipPath.lineTo(QPointF(px, rangeTop)); + } + } + } + + double penWidth = 1.0; + + if (m_model->isReady()) { + paint->setPen(QPen(baseColour, penWidth)); + } else { + paint->setPen(QPen(midColour, penWidth)); + } + paint->drawPath(waveformPath); + + if (!clipPath.isEmpty()) { + paint->save(); + paint->setPen(QPen(ColourDatabase::getInstance()-> + getContrastingColour(m_colour), penWidth)); + paint->drawPath(clipPath); + paint->restore(); + } + + if (!meanPath.isEmpty()) { + paint->save(); + paint->setPen(QPen(midColour, penWidth)); + paint->drawPath(meanPath); + paint->restore(); + } + + if (!individualSamplePoints.empty()) { + paint->save(); + paint->setPen(QPen(baseColour, penWidth)); + double sz = ViewManager::scalePixelSize(2.0); + for (QPointF p: individualSamplePoints) { + paint->drawRect(QRectF(p.x() - sz/2, p.y() - sz/2, sz, sz)); + } + paint->restore(); } } @@ -1239,7 +1232,7 @@ break; case dBScale: - vy = dBscale(value, m); + vy = int(dBscale(value, m)); break; } @@ -1511,7 +1504,8 @@ "autoNormalize=\"%9\"") .arg(m_gain) .arg(m_showMeans) - .arg(m_greyscale) + .arg(true) // Option removed, but effectively always on, so + // retained in the session file for compatibility .arg(m_channelMode) .arg(m_channel) .arg(m_scale) @@ -1536,10 +1530,6 @@ attributes.value("showMeans") == "true"); setShowMeans(showMeans); - bool greyscale = (attributes.value("greyscale") == "1" || - attributes.value("greyscale") == "true"); - setUseGreyscale(greyscale); - ChannelMode channelMode = (ChannelMode) attributes.value("channelMode").toInt(&ok); if (ok) setChannelMode(channelMode); @@ -1555,7 +1545,7 @@ bool aggressive = (attributes.value("aggressive") == "1" || attributes.value("aggressive") == "true"); - setUseGreyscale(aggressive); + setAggressiveCacheing(aggressive); bool autoNormalize = (attributes.value("autoNormalize") == "1" || attributes.value("autoNormalize") == "true");
--- a/layer/WaveformLayer.h Tue Oct 30 16:23:03 2018 +0000 +++ b/layer/WaveformLayer.h Wed Oct 31 15:06:32 2018 +0000 @@ -87,19 +87,6 @@ void setShowMeans(bool); bool getShowMeans() const { return m_showMeans; } - /** - * Set whether to use shades of grey (or of the base colour) to - * provide additional perceived vertical resolution (i.e. using - * half-filled pixels to represent levels that only just meet the - * pixel unit boundary). This provides a small improvement in - * waveform quality at a small cost in rendering speed. - * - * The default is to use greyscale. - */ - void setUseGreyscale(bool); - bool getUseGreyscale() const { return m_greyscale; } - - enum ChannelMode { SeparateChannels, MixChannels, MergeChannels }; /** @@ -205,7 +192,7 @@ virtual bool canExistWithoutModel() const { return true; } protected: - int dBscale(double sample, int m) const; + double dBscale(double sample, int m) const; const RangeSummarisableTimeValueModel *m_model; // I do not own this @@ -247,7 +234,6 @@ float m_gain; bool m_autoNormalize; bool m_showMeans; - bool m_greyscale; ChannelMode m_channelMode; int m_channel; Scale m_scale;