# HG changeset patch # User Chris Cannam # Date 1454080081 0 # Node ID 0be17aafa9351dda8b30679fe1de7f23005c9391 # Parent fdfd84b022df308804e32f735e45db7340ee5af2 Start refactoring out the spectrogram image cache diff -r fdfd84b022df -r 0be17aafa935 layer/ScrollableImageCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.h Fri Jan 29 15:08:01 2016 +0000 @@ -0,0 +1,262 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + 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) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SCROLLABLE_IMAGE_CACHE_H +#define SCROLLABLE_IMAGE_CACHE_H + +#include "base/BaseTypes.h" + +#include "view/LayerGeometryProvider.h" + +#include +#include +#include + +/** + * A cached image for a view that scrolls horizontally, primarily the + * spectrogram. The cache object holds an image, reports the size of + * the image (likely the same as the underlying view, but it's the + * caller's responsibility to set the size appropriately), can scroll + * the image, and can report and update which contiguous horizontal + * range of the image is valid. + * + * The only way to *update* the valid area in a cache is to draw to it + * using the drawImage call. + */ +class ScrollableImageCache +{ +public: + ScrollableImageCache(const LayerGeometryProvider *v = 0) : + m_v(v), + m_left(0), + m_width(0), + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_width = 0; + } + + bool isValid() const { + return m_width > 0; + } + + bool spans(int left, int right) const { + return (getValidLeft() <= left && + getValidRight() >= right); + } + + QSize getSize() const { + return m_image.size(); + } + + void resize(QSize newSize) { + m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); + invalidate(); + } + + int getValidLeft() const { + return m_left; + } + + int getValidWidth() const { + return m_width; + } + + int getValidRight() const { + return m_left + m_width; + } + + QRect getValidArea() const { + return QRect(m_left, 0, m_width, m_image.height()); + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + void setZoomLevel(int zoom) { + m_zoomLevel = zoom; + invalidate(); + } + + const QImage &getImage() const { + return m_image; + } + + void scrollTo(sv_frame_t newStartFrame) { + + if (!m_v) throw std::logic_error("ScrollableImageCache: not associated with a LayerGeometryProvider"); + + int dx = (m_v->getXForFrame(m_startFrame) - + m_v->getXForFrame(newStartFrame)); + + m_startFrame = newStartFrame; + + if (!isValid()) { + return; + } + + int w = m_image.width(); + + if (dx == 0) { + // haven't moved + return; + } + + if (dx <= -w || dx >= w) { + // scrolled entirely off + invalidate(); + return; + } + + // dx is in range, cache is scrollable + + int dxp = dx; + if (dxp < 0) dxp = -dxp; + + int copylen = (w - dxp) * int(sizeof(QRgb)); + for (int y = 0; y < m_image.height(); ++y) { + QRgb *line = (QRgb *)m_image.scanLine(y); + if (dx < 0) { + memmove(line, line + dxp, copylen); + } else { + memmove(line + dxp, line, copylen); + } + } + + // update valid area + + int px = m_left; + int pw = m_width; + + px += dx; + + if (dx < 0) { + // we scrolled left + if (px < 0) { + pw += px; + px = 0; + if (pw < 0) { + pw = 0; + } + } + } else { + // we scrolled right + if (px + pw > w) { + pw = w - px; + if (pw < 0) { + pw = 0; + } + } + } + + m_left = px; + m_width = pw; + } + + void resizeToTouchValidArea(int &left, int &width, + bool &isLeftOfValidArea) const { + if (left < m_left) { + isLeftOfValidArea = true; + if (left + width < m_left + m_width) { + width = m_left - left; + } + } else { + isLeftOfValidArea = false; + width = left + width - (m_left + m_width); + left = m_left + m_width; + if (width < 0) width = 0; + } + } + + void drawImage(int left, + int width, + QImage image, + int imageLeft, + int imageWidth) { + + if (image.height() != m_image.height()) { + throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage"); + } + if (left < 0 || left + width > m_image.width()) { + throw std::logic_error("Drawing area out of bounds in ScrollableImageCache::drawImage"); + } + + QPainter painter(&m_image); + painter.drawImage(QRect(left, 0, width, m_image.height()), + image, + QRect(imageLeft, 0, imageWidth, image.height())); + painter.end(); + + if (!isValid()) { + m_left = left; + m_width = width; + return; + } + + if (left < m_left) { + if (left + width > m_left + m_width) { + // new image completely contains the old valid area -- + // use the new area as is + m_left = left; + m_width = width; + } else if (left + width < m_left) { + // new image completely off left of old valid area -- + // we can't extend the valid area because the bit in + // between is not valid, so must use the new area only + m_left = left; + m_width = width; + } else { + // new image overlaps old valid area on left side -- + // use new left edge, and extend width to existing + // right edge + m_width = (m_left + m_width) - left; + m_left = left; + } + } else { + if (left > m_left + m_width) { + // new image completely off right of old valid area -- + // we can't extend the valid area because the bit in + // between is not valid, so must use the new area only + m_left = left; + m_width = width; + } else if (left + width > m_left + m_width) { + // new image overlaps old valid area on right side -- + // use existing left edge, and extend width to new + // right edge + m_width = (left + width) - m_left; + // (m_left unchanged) + } else { + // new image completely contained within old valid + // area -- leave the old area unchanged + } + } + } + +private: + const LayerGeometryProvider *m_v; + QImage m_image; + int m_left; // of valid region + int m_width; // of valid region + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r fdfd84b022df -r 0be17aafa935 layer/SpectrogramLayer.cpp --- a/layer/SpectrogramLayer.cpp Wed Jan 27 11:10:48 2016 +0000 +++ b/layer/SpectrogramLayer.cpp Fri Jan 29 15:08:01 2016 +0000 @@ -584,74 +584,7 @@ { for (ViewImageCache::iterator i = m_imageCaches.begin(); i != m_imageCaches.end(); ++i) { - i->second.validArea = QRect(); - } -} - -void -SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) -{ - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - - //!!! when are views removed from the map? on setLayerDormant? - const LayerGeometryProvider *v = i->first; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::invalidateImageCaches(" - << startFrame << ", " << endFrame << "): view range is " - << v->getStartFrame() << ", " << v->getEndFrame() - << endl; - - cerr << "Valid area was: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif - - if (int(startFrame) > v->getStartFrame()) { - if (startFrame >= v->getEndFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified start frame is off right of view" << endl; -#endif - return; - } - int x = v->getXForFrame(startFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "clipping from 0 to " << x-1 << endl; -#endif - if (x > 1) { - i->second.validArea &= - QRect(0, 0, x-1, v->getPaintHeight()); - } else { - i->second.validArea = QRect(); - } - } else { - if (int(endFrame) < v->getStartFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified end frame is off left of view" << endl; -#endif - return; - } - int x = v->getXForFrame(endFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "clipping from " << x+1 << " to " << v->getPaintWidth() - << endl; -#endif - if (x < v->getPaintWidth()) { - i->second.validArea &= - QRect(x+1, 0, v->getPaintWidth()-(x+1), v->getPaintHeight()); - } else { - i->second.validArea = QRect(); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area is now: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif + i->second.invalidate(); } } @@ -988,11 +921,11 @@ invalidateImageCaches(); - m_imageCaches.erase(view); - - if (m_fftModels.find(view) != m_fftModels.end()) { - - if (m_sliceableModel == m_fftModels[view]) { + m_imageCaches.erase(view->getId()); + + if (m_fftModels.find(view->getId()) != m_fftModels.end()) { + + if (m_sliceableModel == m_fftModels[view->getId()]) { bool replaced = false; for (ViewFFTMap::iterator i = m_fftModels.begin(); i != m_fftModels.end(); ++i) { @@ -1005,11 +938,11 @@ if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); } - delete m_fftModels[view]; - m_fftModels.erase(view); - - delete m_peakCaches[view]; - m_peakCaches.erase(view); + delete m_fftModels[view->getId()]; + m_fftModels.erase(view->getId()); + + delete m_peakCaches[view->getId()]; + m_peakCaches.erase(view->getId()); } } else { @@ -1036,7 +969,13 @@ cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; #endif - invalidateImageCaches(from, to); + // We used to call invalidateMagnitudes(from, to) to invalidate + // only those caches whose views contained some of the (from, to) + // range. That's the right thing to do; it has been lost in + // pulling out the image cache code, but it might not matter very + // much, since the underlying models for spectrogram layers don't + // change very often. Let's see. + invalidateImageCaches(); invalidateMagnitudes(); } @@ -1102,8 +1041,8 @@ double max = 1.0; if (m_normalization == NormalizeVisibleArea) { - min = m_viewMags[v].getMin(); - max = m_viewMags[v].getMax(); + min = m_viewMags[v->getId()].getMin(); + max = m_viewMags[v->getId()].getMax(); } else if (m_normalization != NormalizeColumns) { if (m_colourScale == LinearColourScale //|| // m_colourScale == MeterColourScale) { @@ -1510,30 +1449,30 @@ const View *view = v->getView(); - if (m_fftModels.find(view) != m_fftModels.end()) { - if (m_fftModels[view] == 0) { + if (m_fftModels.find(view->getId()) != m_fftModels.end()) { + if (m_fftModels[view->getId()] == 0) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; #endif return 0; } - if (m_fftModels[view]->getHeight() != fftSize / 2 + 1) { + if (m_fftModels[view->getId()]->getHeight() != fftSize / 2 + 1) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; + cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view->getId()]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; #endif - delete m_fftModels[view]; - m_fftModels.erase(view); - delete m_peakCaches[view]; - m_peakCaches.erase(view); + delete m_fftModels[view->getId()]; + m_fftModels.erase(view->getId()); + delete m_peakCaches[view->getId()]; + m_peakCaches.erase(view->getId()); } else { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view]->getHeight() << endl; + cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view->getId()]->getHeight() << endl; #endif - return m_fftModels[view]; + return m_fftModels[view->getId()]; } } - if (m_fftModels.find(view) == m_fftModels.end()) { + if (m_fftModels.find(view->getId()) == m_fftModels.end()) { FFTModel *model = new FFTModel(m_model, m_channel, @@ -1548,7 +1487,7 @@ tr("Failed to create the FFT model for this spectrogram.\n" "There may be insufficient memory or disc space to continue.")); delete model; - m_fftModels[view] = 0; + m_fftModels[view->getId()] = 0; return 0; } @@ -1560,22 +1499,22 @@ m_sliceableModel = model; } - m_fftModels[view] = model; + m_fftModels[view->getId()] = model; } - return m_fftModels[view]; + return m_fftModels[view->getId()]; } Dense3DModelPeakCache * SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const { const View *view = v->getView(); - if (!m_peakCaches[view]) { + if (!m_peakCaches[view->getId()]) { FFTModel *f = getFFTModel(v); if (!f) return 0; - m_peakCaches[view] = new Dense3DModelPeakCache(f, 8); + m_peakCaches[view->getId()] = new Dense3DModelPeakCache(f, 8); } - return m_peakCaches[view]; + return m_peakCaches[view->getId()]; } const Model * @@ -1656,8 +1595,8 @@ #endif if (!mag.isSet()) return false; - if (mag == m_viewMags[v]) return false; - m_viewMags[v] = mag; + if (mag == m_viewMags[v->getId()]) return false; + m_viewMags[v->getId()] = mag; return true; } @@ -1667,12 +1606,18 @@ m_synchronous = synchronous; } +ScrollableImageCache & +SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const +{ + if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) { + m_imageCaches[view->getId()] = ScrollableImageCache(view); + } + return m_imageCaches.at(view->getId()); +} + void SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { - // What a lovely, old-fashioned function this is. - // It's practically FORTRAN 77 in its clarity and linearity. - Profiler profiler("SpectrogramLayer::paint", false); #ifdef DEBUG_SPECTROGRAM_REPAINT @@ -1701,230 +1646,118 @@ int fftSize = getFFTSize(v); const View *view = v->getView(); - - ImageCache &cache = m_imageCaches[view]; + ScrollableImageCache &cache = getImageCacheReference(view); #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.validArea.x() << "," << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; + cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.getValidLeft() << " width " << cache.getValidWidth() << ", height " << cache.getSize().height() << endl; + if (rect.x() + rect.width() + 1 < cache.getValidLeft() || + rect.x() > cache.getValidRight()) { + cerr << "SpectrogramLayer: NOTE: requested rect is not contiguous with cache valid area" << endl; + } #endif int zoomLevel = v->getZoomLevel(); - int x0 = 0; - int x1 = v->getPaintWidth(); - - bool recreateWholeImageCache = true; - - x0 = rect.left(); - x1 = rect.right() + 1; + int x0 = v->getXForViewX(rect.x()); + int x1 = v->getXForViewX(rect.x() + rect.width()); + if (x0 < 0) x0 = 0; + if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth(); if (updateViewMagnitudes(v)) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; + cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl; #endif if (m_normalization == NormalizeVisibleArea) { - cache.validArea = QRect(); + cache.invalidate(); } } + + if (cache.getZoomLevel() != zoomLevel || + cache.getSize() != v->getPaintSize()) { + cache.resize(v->getPaintSize()); + } - if (cache.validArea.width() > 0) { - - int cw = cache.image.width(); - int ch = cache.image.height(); + if (cache.isValid()) { - if (int(cache.zoomLevel) == zoomLevel && - cw == v->getPaintWidth() && - ch == v->getPaintHeight()) { - - // cache size and zoom level exactly match the view - - if (v->getXForFrame(cache.startFrame) == - v->getXForFrame(startFrame) && - cache.validArea.x() <= x0 && - cache.validArea.x() + cache.validArea.width() >= x1) { - - // and cache begins at the right frame, so use it whole + if (v->getXForFrame(cache.getStartFrame()) == + v->getXForFrame(startFrame) && + cache.getValidLeft() <= x0 && + cache.getValidRight() >= x1) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache good" << endl; + cerr << "SpectrogramLayer: image cache hit!" << endl; #endif - paint.drawImage(rect, cache.image, rect); - - illuminateLocalFeatures(v, paint); - return; - - } else { - - // cache doesn't begin at the right frame or doesn't - // contain the complete view, but might be scrollable - // or partially usable + paint.drawImage(rect, cache.getImage(), rect); + + illuminateLocalFeatures(v, paint); + return; + + } else { + + // cache doesn't begin at the right frame or doesn't + // contain the complete view, but might be scrollable or + // partially usable #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache partially OK" << endl; + cerr << "SpectrogramLayer: scrolling the image cache if applicable" << endl; #endif - recreateWholeImageCache = false; - - int dx = v->getXForFrame(cache.startFrame) - - v->getXForFrame(startFrame); - + cache.scrollTo(startFrame); + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl; + cerr << "SpectrogramLayer: cache valid now from " + << cache.getValidLeft() << " width " << cache.getValidWidth() + << endl; #endif - - if (dx != 0 && - dx > -cw && - dx < cw) { - - // cache is scrollable, scroll it - - int dxp = dx; - if (dxp < 0) dxp = -dxp; - size_t copy = (cw - dxp) * sizeof(QRgb); - for (int y = 0; y < ch; ++y) { - QRgb *line = (QRgb *)cache.image.scanLine(y); - if (dx < 0) { - memmove(line, line + dxp, copy); - } else { - memmove(line + dxp, line, copy); - } - } - - // and calculate its new valid area - - int px = cache.validArea.x(); - int pw = cache.validArea.width(); - - // so px and pw will be updated to the new x and - // width of the valid area of cache; - - // x0 and x1 will be the left and right extents of - // the area needing repainted - - px += dx; - - if (dx < 0) { - // we scrolled left - if (px < 0) { - pw += px; - px = 0; - if (pw < 0) { - pw = 0; - } - } - x0 = px + pw; - x1 = cw; - } else { - // we scrolled right - if (px + pw > cw) { - pw = cw - px; - if (pw < 0) { - pw = 0; - } - } - x0 = 0; - x1 = px; - } - - cache.validArea = - QRect(px, cache.validArea.y(), - pw, cache.validArea.height()); - - if (cache.validArea.width() == 0) { - recreateWholeImageCache = true; - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: valid area now " - << px << "," << cache.validArea.y() - << " " << pw << "x" << cache.validArea.height() - << endl; -#endif - - } else if (dx != 0) { - - // we've moved too far from the cached area for it - // to be of use - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: dx == " << dx << ": scrolled too far for cache to be useful" << endl; -#endif - - cache.validArea = QRect(); - recreateWholeImageCache = true; - - } else { - - // dx == 0, we haven't scrolled but the cache is - // only partly valid - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: haven't scrolled, but cache is not complete" << endl; -#endif - if (cache.validArea.x() == 0) { - x0 = cache.validArea.width(); - } else { - x1 = cache.validArea.x(); - } - } - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache useless" << endl; - if (int(cache.zoomLevel) != zoomLevel) { - cerr << "(cache zoomLevel " << cache.zoomLevel - << " != " << zoomLevel << ")" << endl; - } - if (cw != v->getPaintWidth()) { - cerr << "(cache width " << cw - << " != " << v->getPaintWidth(); - } - if (ch != v->getPaintHeight()) { - cerr << "(cache height " << ch - << " != " << v->getPaintHeight(); - } -#endif - cache.validArea = QRect(); - } + } } - if (recreateWholeImageCache) { + bool rightToLeft = false; + + if (!cache.isValid()) { if (!m_synchronous) { // When rendering the whole thing, start from somewhere near // the middle so that the region of interest appears first - x0 = int(v->getPaintWidth() * 0.4); - } else { - x0 = 0; + if (x0 == 0 && x1 == v->getPaintWidth()) { + x0 = int(x1 * 0.4); + } } - x1 = v->getPaintWidth(); + } else { + // When rendering only a part of the cache, we need to make + // sure that the part we're rendering is adjacent to (or + // overlapping) a valid area of cache, if we have one. The + // alternative is to ditch the valid area of cache and render + // only the requested area, but that's risky because this can + // happen when just waving the pointer over a small part of + // the view -- if we lose the partly-built cache every time + // the user does that, we'll never finish building it. + int left = x0; + int width = x1 - x0; + bool isLeftOfValidArea = false; + cache.resizeToTouchValidArea(left, width, isLeftOfValidArea); + x0 = left; + x1 = x0 + width; + + // That call also told us whether we should be painting + // sub-regions of our target region in right-to-left order in + // order to ensure contiguity + rightToLeft = isLeftOfValidArea; } - + // We always paint the full height when refreshing the cache. // Smaller heights can be used when painting direct from cache // (further up in this function), but we want to ensure the cache // is coherent without having to worry about vertical matching of // required and valid areas as well as horizontal. - int h = v->getPaintHeight(); int repaintWidth = x1 - x0; - // If we are painting a section to the left of a valid area of - // cache, then we must paint it "backwards", right-to-left. This - // is because painting may be interrupted by a timeout, leaving us - // with a partially painted area, and we have no way to record - // that the union of the existing cached area and this new - // partially painted bit is a valid cache area unless they are - // adjacent (because our valid extent is a single x,width range). - - bool rightToLeft = (x0 == 0 && x1 < v->getPaintWidth()); - #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1 << ", repaintWidth " << repaintWidth << ", h " << h - << ", rightToLeft " << rightToLeft - << ", recreateWholeImageCache " << recreateWholeImageCache << endl; + << ", rightToLeft " << rightToLeft << endl; #endif sv_samplerate_t sr = m_model->getSampleRate(); @@ -1975,7 +1808,7 @@ bool logarithmic = (m_frequencyScale == LogFrequencyScale); - MagnitudeRange overallMag = m_viewMags[v]; + MagnitudeRange overallMag = m_viewMags[v->getId()]; bool overallMagChanged = false; #ifdef DEBUG_SPECTROGRAM_REPAINT @@ -2118,9 +1951,9 @@ } if (overallMagChanged) { - m_viewMags[v] = overallMag; + m_viewMags[v->getId()] = overallMag; #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl; + cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl; #endif } @@ -2128,14 +1961,6 @@ Profiler profiler2("SpectrogramLayer::paint: draw image"); - if (recreateWholeImageCache) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Recreating image cache: width = " << v->getPaintWidth() - << ", height = " << h << endl; -#endif - cache.image = QImage(v->getPaintWidth(), h, QImage::Format_ARGB32_Premultiplied); - } - if (repaintWidth > 0) { #ifdef DEBUG_SPECTROGRAM_REPAINT @@ -2145,8 +1970,6 @@ << x0 << "," << 0 << endl; #endif - QPainter cachePainter(&cache.image); - if (bufferBinResolution) { int scaledLeft = v->getXForFrame(leftBoundaryFrame); int scaledRight = v->getXForFrame(rightBoundaryFrame); @@ -2170,62 +1993,29 @@ cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; #endif - cachePainter.drawImage - (QRect(scaledLeftCrop, 0, - scaledRightCrop - scaledLeftCrop, h), + + cache.drawImage + (scaledLeftCrop, + scaledRightCrop - scaledLeftCrop, scaled, - QRect(scaledLeftCrop - scaledLeft, 0, - scaledRightCrop - scaledLeftCrop, h)); + scaledLeftCrop - scaledLeft, + scaledRightCrop - scaledLeftCrop); } else { - cachePainter.drawImage(QRect(x0, 0, repaintWidth, h), - m_drawBuffer, - QRect(0, 0, repaintWidth, h)); + cache.drawImage(x0, repaintWidth, + m_drawBuffer, + 0, repaintWidth); } - - cachePainter.end(); } - // update cache valid area based on painted area - - int left = x0; - int wid = x1 - x0; - - if (failedToRepaint > 0) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Reduced painted extent from " - << left << "," << wid; + cerr << "SpectrogramLayer: Cache valid area now from " << cache.getValidLeft() + << " width " << cache.getValidWidth() << ", height " + << cache.getSize().height() << endl; #endif - if (rightToLeft) { - left += failedToRepaint; - } - wid -= failedToRepaint; - - if (wid < 0) wid = 0; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << " to " << left << "," << wid << endl; -#endif - } - - if (cache.validArea.width() > 0) { - left = min(left, cache.validArea.x()); - wid = cache.validArea.width() + wid; - } - - cache.validArea = QRect(left, 0, wid, h); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Cache valid area becomes " << cache.validArea.x() - << ", " << cache.validArea.y() << ", " - << cache.validArea.width() << "x" - << cache.validArea.height() << " (size = " - << cache.image.width() << "x" << cache.image.height() << ")" - << endl; -#endif - - QRect pr = rect & cache.validArea; + + QRect pr = rect & cache.getValidArea(); #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height() @@ -2233,46 +2023,41 @@ << " to window" << endl; #endif - paint.drawImage(pr.x(), pr.y(), cache.image, + paint.drawImage(pr.x(), pr.y(), cache.getImage(), pr.x(), pr.y(), pr.width(), pr.height()); - cache.startFrame = startFrame; - cache.zoomLevel = zoomLevel; - if (!m_synchronous) { if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) { - if (cache.validArea.x() > 0) { + if (cache.getValidLeft() > 0) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::paint() updating left (0, " - << cache.validArea.x() << ")" << endl; + << cache.getValidLeft() << ")" << endl; #endif - v->getView()->update(0, 0, cache.validArea.x(), h); + v->updatePaintRect(QRect(0, 0, cache.getValidLeft(), h)); } - if (cache.validArea.x() + cache.validArea.width() < - cache.image.width()) { + if (cache.getValidRight() < + cache.getSize().width()) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::paint() updating right (" - << cache.validArea.x() + cache.validArea.width() - << ", " - << cache.image.width() - (cache.validArea.x() + - cache.validArea.width()) - << ")" << endl; + << cache.getValidRight() + << ", " + << cache.getSize().width() - cache.getValidRight() + << ")" << endl; #endif - v->getView()->update - (cache.validArea.x() + cache.validArea.width(), - 0, - cache.image.width() - (cache.validArea.x() + - cache.validArea.width()), - h); + v->updatePaintRect + (QRect(cache.getValidRight(), + 0, + cache.getSize().width() - cache.getValidRight(), + h)); } } else { // overallMagChanged cerr << "\noverallMagChanged - updating all\n" << endl; - cache.validArea = QRect(); - v->getView()->update(); + cache.invalidate(); + v->updatePaintRect(v->getPaintRect()); } } @@ -2766,9 +2551,9 @@ { const View *view = v->getView(); - if (m_fftModels.find(view) == m_fftModels.end()) return 100; - - int completion = m_fftModels[view]->getCompletion(); + if (m_fftModels.find(view->getId()) == m_fftModels.end()) return 100; + + int completion = m_fftModels[view->getId()]->getCompletion(); #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; #endif @@ -2779,8 +2564,8 @@ SpectrogramLayer::getError(LayerGeometryProvider *v) const { const View *view = v->getView(); - if (m_fftModels.find(view) == m_fftModels.end()) return ""; - return m_fftModels[view]->getError(); + if (m_fftModels.find(view->getId()) == m_fftModels.end()) return ""; + return m_fftModels[view->getId()]->getError(); } bool @@ -2876,12 +2661,12 @@ SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) { const View *view = v->getView(); - ImageCache &cache = m_imageCaches[view]; - - cerr << "cache width: " << cache.image.width() << ", height: " - << cache.image.height() << endl; - - QImage image = cache.image; + ScrollableImageCache &cache = getImageCacheReference(view); + + cerr << "cache width: " << cache.getSize().width() << ", height: " + << cache.getSize().height() << endl; + + QImage image = cache.getImage(); ImageRegionFinder finder; QRect rect = finder.findRegionExtents(&image, e->pos()); @@ -3196,8 +2981,8 @@ paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); QString top, bottom; - double min = m_viewMags[v].getMin(); - double max = m_viewMags[v].getMax(); + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); double dBmin = AudioLevel::multiplier_to_dB(min); double dBmax = AudioLevel::multiplier_to_dB(max); diff -r fdfd84b022df -r 0be17aafa935 layer/SpectrogramLayer.h --- a/layer/SpectrogramLayer.h Wed Jan 27 11:10:48 2016 +0000 +++ b/layer/SpectrogramLayer.h Fri Jan 29 15:08:01 2016 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _SPECTROGRAM_LAYER_H_ -#define _SPECTROGRAM_LAYER_H_ +#ifndef SPECTROGRAM_LAYER_H +#define SPECTROGRAM_LAYER_H #include "SliceableLayer.h" #include "base/Window.h" @@ -25,6 +25,8 @@ #include "data/model/DenseTimeValueModel.h" #include "data/model/FFTModel.h" +#include "ScrollableImageCache.h" + #include #include #include @@ -294,22 +296,10 @@ Palette m_palette; - /** - * ImageCache covers the area of the view, at view resolution. - * Not all of it is necessarily valid at once (it is refreshed - * in parts when scrolling, for example). - */ - struct ImageCache - { - QImage image; - QRect validArea; - sv_frame_t startFrame; - int zoomLevel; - }; - typedef std::map ViewImageCache; + typedef std::map ViewImageCache; // key is view id void invalidateImageCaches(); - void invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame); mutable ViewImageCache m_imageCaches; + ScrollableImageCache &getImageCacheReference(const LayerGeometryProvider *) const; /** * When painting, we draw directly onto the draw buffer and then @@ -363,8 +353,8 @@ Dense3DModelPeakCache *getPeakCache(const LayerGeometryProvider *v) const; void invalidateFFTModels(); - typedef std::map ViewFFTMap; - typedef std::map PeakCacheMap; + typedef std::map ViewFFTMap; // key is view id + typedef std::map PeakCacheMap; // key is view id mutable ViewFFTMap m_fftModels; mutable PeakCacheMap m_peakCaches; mutable Model *m_sliceableModel; @@ -411,7 +401,7 @@ float m_max; }; - typedef std::map ViewMagMap; + typedef std::map ViewMagMap; // key is view id mutable ViewMagMap m_viewMags; mutable std::vector m_columnMags; void invalidateMagnitudes(); diff -r fdfd84b022df -r 0be17aafa935 svgui.pro --- a/svgui.pro Wed Jan 27 11:10:48 2016 +0000 +++ b/svgui.pro Fri Jan 29 15:08:01 2016 +0000 @@ -52,6 +52,7 @@ layer/PaintAssistant.h \ layer/PianoScale.h \ layer/RegionLayer.h \ + layer/ScrollableImageCache.h \ layer/SingleColourLayer.h \ layer/SliceableLayer.h \ layer/SliceLayer.h \ diff -r fdfd84b022df -r 0be17aafa935 view/LayerGeometryProvider.h --- a/view/LayerGeometryProvider.h Wed Jan 27 11:10:48 2016 +0000 +++ b/view/LayerGeometryProvider.h Fri Jan 29 15:08:01 2016 +0000 @@ -17,6 +17,9 @@ #include "base/BaseTypes.h" +#include +#include + class ViewManager; class View; class Layer; @@ -24,6 +27,20 @@ class LayerGeometryProvider { public: + LayerGeometryProvider() { + static QMutex idMutex; + static int nextId = 1; + QMutexLocker locker(&idMutex); + m_id = nextId; + nextId++; + } + + /** + * Retrieve the id of this object. Each LayerGeometryProvider has + * a separate id. + */ + int getId() const { return m_id; } + /** * Retrieve the first visible sample frame on the widget. * This is a calculated value based on the centre-frame, widget @@ -61,6 +78,18 @@ virtual sv_frame_t getModelsEndFrame() const = 0; /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. + */ + virtual int getXForViewX(int viewx) const = 0; + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. + */ + virtual int getViewXForX(int x) const = 0; + + /** * Return the pixel y-coordinate corresponding to a given * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -123,8 +152,13 @@ virtual void drawMeasurementRect(QPainter &p, const Layer *, QRect rect, bool focus) const = 0; + virtual void updatePaintRect(QRect r) = 0; + virtual View *getView() = 0; virtual const View *getView() const = 0; + +private: + int m_id; }; #endif diff -r fdfd84b022df -r 0be17aafa935 view/View.h --- a/view/View.h Wed Jan 27 11:10:48 2016 +0000 +++ b/view/View.h Fri Jan 29 15:08:01 2016 +0000 @@ -62,7 +62,7 @@ * be managed elsewhere (e.g. by the Document). */ virtual ~View(); - + /** * Retrieve the first visible sample frame on the widget. * This is a calculated value based on the centre-frame, widget @@ -108,6 +108,20 @@ sv_frame_t getFrameForX(int x) const; /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getXForViewX(int viewx) const { return viewx; } + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getViewXForX(int x) const { return x; } + + /** * Return the pixel y-coordinate corresponding to a given * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -333,6 +347,8 @@ sv_frame_t alignToReference(sv_frame_t) const; sv_frame_t getAlignedPlaybackFrame() const; + void updatePaintRect(QRect r) { update(r); } + View *getView() { return this; } const View *getView() const { return this; } @@ -384,6 +400,9 @@ protected: View(QWidget *, bool showProgress); + + int m_id; + virtual void paintEvent(QPaintEvent *e); virtual void drawSelections(QPainter &); virtual bool shouldLabelSelections() const { return true; } diff -r fdfd84b022df -r 0be17aafa935 view/ViewProxy.h --- a/view/ViewProxy.h Wed Jan 27 11:10:48 2016 +0000 +++ b/view/ViewProxy.h Fri Jan 29 15:08:01 2016 +0000 @@ -42,6 +42,12 @@ sv_frame_t f1 = m_view->getFrameForX((x / m_scaleFactor) + 1); return f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor; } + virtual int getXForViewX(int viewx) const { + return viewx * m_scaleFactor; + } + virtual int getViewXForX(int x) const { + return x / m_scaleFactor; + } virtual sv_frame_t getModelsStartFrame() const { return m_view->getModelsStartFrame(); } @@ -129,6 +135,13 @@ m_view->drawMeasurementRect(p, layer, rect, focus); } + virtual void updatePaintRect(QRect r) { + m_view->update(r.x() / m_scaleFactor, + r.y() / m_scaleFactor, + r.width() / m_scaleFactor, + r.height() / m_scaleFactor); + } + virtual View *getView() { return m_view; } virtual const View *getView() const { return m_view; }