diff layer/Colour3DPlotRenderer.h @ 1216:dc2af6616c83

Merge from branch 3.0-integration
author Chris Cannam
date Fri, 13 Jan 2017 10:29:50 +0000
parents 34df6ff25472
children eaab8bab3522
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/Colour3DPlotRenderer.h	Fri Jan 13 10:29:50 2017 +0000
@@ -0,0 +1,316 @@
+/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL.
+    
+    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 COLOUR_3D_PLOT_RENDERER_H
+#define COLOUR_3D_PLOT_RENDERER_H
+
+#include "ColourScale.h"
+#include "ScrollableImageCache.h"
+#include "ScrollableMagRangeCache.h"
+
+#include "base/ColumnOp.h"
+#include "base/MagnitudeRange.h"
+
+#include <QRect>
+#include <QPainter>
+#include <QImage>
+
+class LayerGeometryProvider;
+class VerticalBinLayer;
+class DenseThreeDimensionalModel;
+class Dense3DModelPeakCache;
+class FFTModel;
+
+enum class BinDisplay {
+    AllBins,
+    PeakBins,
+    PeakFrequencies
+};
+
+enum class BinScale {
+    Linear,
+    Log
+};
+
+class Colour3DPlotRenderer
+{
+public:
+    struct Sources {
+        Sources() : verticalBinLayer(0), source(0), fft(0) { }
+        
+        // These must all outlive this class
+        const VerticalBinLayer *verticalBinLayer;  // always
+	const DenseThreeDimensionalModel *source;  // always
+	const FFTModel *fft;                       // optionally
+	std::vector<Dense3DModelPeakCache *> peakCaches; // zero or more
+    };        
+
+    struct Parameters {
+	Parameters() :
+	    colourScale(ColourScale::Parameters()),
+	    normalization(ColumnNormalization::None),
+	    binDisplay(BinDisplay::AllBins),
+            binScale(BinScale::Linear),
+	    alwaysOpaque(false),
+            interpolate(false),
+            invertVertical(false),
+            scaleFactor(1.0),
+            colourRotation(0) { }
+
+        /** A complete ColourScale object by value, used for colour
+         *  map conversion. Note that the final display gain setting is
+         *  also encapsulated here. */
+	ColourScale colourScale;
+
+        /** Type of column normalization. */
+	ColumnNormalization normalization;
+
+        /** Selection of bins to display. */
+	BinDisplay binDisplay;
+
+        /** Scale for vertical bin spacing (linear or logarithmic). */
+	BinScale binScale;
+
+        /** Whether cells should always be opaque. If false, then
+         *  large cells (when zoomed in a long way) will be rendered
+         *  translucent in order not to obscure anything in a layer
+         *  beneath. */
+	bool alwaysOpaque;
+
+        /** Whether to apply smoothing when rendering cells at more
+         *  than one pixel per cell.  !!! todo: decide about separating
+         *  out x-interpolate and y-interpolate as the spectrogram
+         *  actually does (or used to)
+         */
+	bool interpolate;
+
+        /** Whether to render the whole caboodle upside-down. */
+	bool invertVertical;
+
+        /** Initial scale factor (e.g. for FFT scaling). This factor
+         *  is applied to all values read from the underlying model
+         *  *before* magnitude ranges are calculated, in contrast to
+         *  the display gain found in the ColourScale parameter. */
+        double scaleFactor;
+
+        /** Colourmap rotation, in the range 0-255. */
+        int colourRotation;
+    };
+    
+    Colour3DPlotRenderer(Sources sources, Parameters parameters) :
+        m_sources(sources),
+	m_params(parameters)
+    { }
+
+    struct RenderResult {
+        /**
+         * The rect that was actually rendered. May be equal to the
+         * rect that was requested to render, or may be smaller if
+         * time ran out and the complete flag was not set.
+         */
+        QRect rendered;
+
+        /**
+         * The magnitude range of the data in the rendered area.
+         */
+        MagnitudeRange range;
+    };
+
+    /**
+     * Render the requested area using the given painter, obtaining
+     * geometry (e.g. start frame) from the given
+     * LayerGeometryProvider.
+     *
+     * The whole of the supplied rect will be rendered and the
+     * returned QRect will be equal to the supplied QRect. (See
+     * renderTimeConstrained for an alternative that may render only
+     * part of the rect in cases where obtaining source data is slow
+     * and retaining responsiveness is important.)
+     *
+     * Note that Colour3DPlotRenderer retains internal cache state
+     * related to the size and position of the supplied
+     * LayerGeometryProvider. Although it is valid to call render()
+     * successively on the same Colour3DPlotRenderer with different
+     * LayerGeometryProviders, it will be much faster to use a
+     * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
+     *
+     * If the model to render from is not ready, this will throw a
+     * std::logic_error exception. The model must be ready and the
+     * layer requesting the render must not be dormant in its view, so
+     * that the LayerGeometryProvider returns valid results; it is the
+     * caller's responsibility to ensure these.
+     */
+    RenderResult render(const LayerGeometryProvider *v,
+                        QPainter &paint, QRect rect);
+    
+    /**
+     * Render the requested area using the given painter, obtaining
+     * geometry (e.g. start frame) from the stored
+     * LayerGeometryProvider.
+     *
+     * As much of the rect will be rendered as can be managed given
+     * internal time constraints (using a RenderTimer object
+     * internally). The returned QRect (the rendered field in the
+     * RenderResult struct) will contain the area that was
+     * rendered. Note that we always render the full requested height,
+     * it's only width that is time-constrained.
+     *
+     * Note that Colour3DPlotRenderer retains internal cache state
+     * related to the size and position of the supplied
+     * LayerGeometryProvider. Although it is valid to call render()
+     * successively on the same Colour3DPlotRenderer with different
+     * LayerGeometryProviders, it will be much faster to use a
+     * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
+     *
+     * If the model to render from is not ready, this will throw a
+     * std::logic_error exception. The model must be ready and the
+     * layer requesting the render must not be dormant in its view, so
+     * that the LayerGeometryProvider returns valid results; it is the
+     * caller's responsibility to ensure these.
+     */
+    RenderResult renderTimeConstrained(const LayerGeometryProvider *v,
+                                       QPainter &paint, QRect rect);
+
+    /**
+     * Return the area of the largest rectangle within the entire area
+     * of the cache that is unavailable in the cache. This is only
+     * valid in relation to a preceding render() call which is
+     * presumed to have set the area, start frame, and zoom level for
+     * the cache. It could be used to establish a suitable region for
+     * a subsequent paint request (because if an area is not in the
+     * cache, it cannot have been rendered since the cache was
+     * cleared).
+     *
+     * Returns an empty QRect if the cache is entirely valid.
+     */
+    QRect getLargestUncachedRect(const LayerGeometryProvider *v);
+
+    /**
+     * Return true if the provider's geometry differs from the cache,
+     * or if we are not using a cache. i.e. if the cache will be
+     * regenerated for the next render, or the next render performed
+     * from scratch.
+     */
+    bool geometryChanged(const LayerGeometryProvider *v);
+    
+    /**
+     * Return true if the rendering will be opaque. This may be used
+     * by the calling layer to determine whether it can scroll
+     * directly without regard to any other layers beneath.
+     */
+    bool willRenderOpaque(const LayerGeometryProvider *v) {
+        return decideRenderType(v) != DirectTranslucent;
+    }
+    
+    /**
+     * Return the colour corresponding to the given value.
+     * \see ColourScale::getPixel
+     * \see ColourScale::getColour
+     */
+    QColor getColour(double value) const {
+        return m_params.colourScale.getColour(value, m_params.colourRotation);
+    }
+
+    /**
+     * Return the enclosing rectangle for the region of similar colour
+     * to the given point within the cache. Return an empty QRect if
+     * this is not possible. \see ImageRegionFinder
+     */
+    QRect findSimilarRegionExtents(QPoint point) const;
+    
+private:
+    Sources m_sources;
+    Parameters m_params;
+
+    // Draw buffer is the target of each partial repaint. It is always
+    // at view height (not model height) and is cleared and repainted
+    // on each fragment render. The only reason it's stored as a data
+    // member is to avoid reallocation.
+    QImage m_drawBuffer;
+
+    // A temporary store of magnitude ranges per-column, used when
+    // rendering to the draw buffer. This always has the same length
+    // as the width of the draw buffer, and the x coordinates of the
+    // two containers are equivalent.
+    std::vector<MagnitudeRange> m_magRanges;
+    
+    // The image cache is our persistent record of the visible
+    // area. It is always the same size as the view (i.e. the paint
+    // size reported by the LayerGeometryProvider) and is scrolled and
+    // partially repainted internally as appropriate. A render request
+    // is carried out by repainting to cache (via the draw buffer) any
+    // area that is being requested but is not valid in the cache, and
+    // then repainting from cache to the requested painter.
+    ScrollableImageCache m_cache;
+
+    // The mag range cache is our record of the column magnitude
+    // ranges for each of the columns in the cache. It always has the
+    // same start frame and width as the image cache, and the column
+    // indices match up across both. Our cache update mechanism
+    // guarantees that every valid column in the image cache has a
+    // valid range in the magnitude cache, but not necessarily vice
+    // versa (as the image cache is limited to contiguous ranges).
+    ScrollableMagRangeCache m_magCache;
+    
+    RenderResult render(const LayerGeometryProvider *v,
+                        QPainter &paint, QRect rect, bool timeConstrained);
+
+    MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v,
+                                           QPainter &paint, QRect rect);
+    
+    void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0,
+                                      int repaintWidth, bool rightToLeft,
+                                      bool timeConstrained);
+
+    void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0,
+                                    int repaintWidth);
+
+    int renderDrawBuffer(int w, int h,
+                         const std::vector<int> &binforx,
+                         const std::vector<double> &binfory,
+                         int peakCacheIndex, // -1 => don't use a peak cache
+                         bool rightToLeft,
+                         bool timeConstrained);
+
+    int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
+                                        int w, int h,
+                                        const std::vector<int> &binforx,
+                                        const std::vector<double> &binfory,
+                                        bool rightToLeft,
+                                        bool timeConstrained);
+    
+    void recreateDrawBuffer(int w, int h);
+    void clearDrawBuffer(int w, int h);
+
+    enum RenderType {
+        DrawBufferPixelResolution,
+        DrawBufferBinResolution,
+        DirectTranslucent
+    };
+
+    RenderType decideRenderType(const LayerGeometryProvider *) const;
+
+    QImage scaleDrawBufferImage(QImage source, int targetWidth, int targetHeight)
+        const;
+    
+    ColumnOp::Column getColumn(int sx, int minbin, int nbins,
+                               int peakCacheIndex) const; // -1 => don't use cache
+
+    void getPreferredPeakCache(const LayerGeometryProvider *,
+                               int &peakCacheIndex, int &binsPerPeak) const;
+};
+
+#endif
+