Chris@1071: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@1071: 
Chris@1071: /*
Chris@1071:     Sonic Visualiser
Chris@1071:     An audio file viewer and annotation editor.
Chris@1071:     Centre for Digital Music, Queen Mary, University of London.
Chris@1071:     This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@1071:     
Chris@1071:     This program is free software; you can redistribute it and/or
Chris@1071:     modify it under the terms of the GNU General Public License as
Chris@1071:     published by the Free Software Foundation; either version 2 of the
Chris@1071:     License, or (at your option) any later version.  See the file
Chris@1071:     COPYING included with this distribution for more information.
Chris@1071: */
Chris@1071: 
Chris@1071: #ifndef COLOUR_3D_PLOT_RENDERER_H
Chris@1071: #define COLOUR_3D_PLOT_RENDERER_H
Chris@1071: 
Chris@1071: #include "ColourScale.h"
Chris@1073: #include "ScrollableImageCache.h"
Chris@1119: #include "ScrollableMagRangeCache.h"
Chris@1071: 
Chris@1071: #include "base/ColumnOp.h"
Chris@1073: #include "base/MagnitudeRange.h"
Chris@1071: 
Chris@1469: #include "data/model/Model.h"
Chris@1469: 
Chris@1073: #include <QRect>
Chris@1073: #include <QPainter>
Chris@1073: #include <QImage>
Chris@1073: 
Chris@1073: class LayerGeometryProvider;
Chris@1082: class VerticalBinLayer;
Chris@1469: class RenderTimer;
Chris@1071: class Dense3DModelPeakCache;
Chris@1502: class DenseThreeDimensionalModel;
Chris@1071: 
Chris@1103: enum class BinDisplay {
Chris@1103:     AllBins,
Chris@1103:     PeakBins,
Chris@1103:     PeakFrequencies
Chris@1103: };
Chris@1103: 
Chris@1103: enum class BinScale {
Chris@1103:     Linear,
Chris@1103:     Log
Chris@1103: };
Chris@1103: 
Chris@1071: class Colour3DPlotRenderer
Chris@1071: {
Chris@1071: public:
Chris@1073:     struct Sources {
Chris@1469:         Sources() : verticalBinLayer(0) { }
Chris@1073:         
Chris@1073:         // These must all outlive this class
Chris@1469:         const VerticalBinLayer *verticalBinLayer; // always
Chris@1469:         ModelId source; // always; a DenseThreeDimensionalModel
Chris@1473:         ModelId fft; // optionally; an FFTModel; used for phase/peak-freq modes
Chris@1473:         std::vector<ModelId> peakCaches; // zero or more
Chris@1073:     };        
Chris@1073: 
Chris@1071:     struct Parameters {
Chris@1266:         Parameters() :
Chris@1266:             colourScale(ColourScale::Parameters()),
Chris@1266:             normalization(ColumnNormalization::None),
Chris@1266:             binDisplay(BinDisplay::AllBins),
Chris@1103:             binScale(BinScale::Linear),
Chris@1266:             alwaysOpaque(false),
Chris@1125:             interpolate(false),
Chris@1112:             invertVertical(false),
Chris@1364:             showDerivative(false),
Chris@1125:             scaleFactor(1.0),
Chris@1112:             colourRotation(0) { }
Chris@1071: 
Chris@1125:         /** A complete ColourScale object by value, used for colour
Chris@1125:          *  map conversion. Note that the final display gain setting is
Chris@1125:          *  also encapsulated here. */
Chris@1266:         ColourScale colourScale;
Chris@1125: 
Chris@1125:         /** Type of column normalization. */
Chris@1266:         ColumnNormalization normalization;
Chris@1125: 
Chris@1125:         /** Selection of bins to display. */
Chris@1266:         BinDisplay binDisplay;
Chris@1125: 
Chris@1125:         /** Scale for vertical bin spacing (linear or logarithmic). */
Chris@1266:         BinScale binScale;
Chris@1125: 
Chris@1125:         /** Whether cells should always be opaque. If false, then
Chris@1125:          *  large cells (when zoomed in a long way) will be rendered
Chris@1125:          *  translucent in order not to obscure anything in a layer
Chris@1125:          *  beneath. */
Chris@1266:         bool alwaysOpaque;
Chris@1125: 
Chris@1125:         /** Whether to apply smoothing when rendering cells at more
Chris@1125:          *  than one pixel per cell.  !!! todo: decide about separating
Chris@1125:          *  out x-interpolate and y-interpolate as the spectrogram
Chris@1125:          *  actually does (or used to)
Chris@1125:          */
Chris@1266:         bool interpolate;
Chris@1125: 
Chris@1125:         /** Whether to render the whole caboodle upside-down. */
Chris@1266:         bool invertVertical;
Chris@1125: 
Chris@1364:         /** Whether to show the frame-to-frame difference instead of
Chris@1364:          *  the actual value */
Chris@1364:         bool showDerivative;
Chris@1364: 
Chris@1125:         /** Initial scale factor (e.g. for FFT scaling). This factor
Chris@1125:          *  is applied to all values read from the underlying model
Chris@1125:          *  *before* magnitude ranges are calculated, in contrast to
Chris@1125:          *  the display gain found in the ColourScale parameter. */
Chris@1125:         double scaleFactor;
Chris@1125: 
Chris@1125:         /** Colourmap rotation, in the range 0-255. */
Chris@1112:         int colourRotation;
Chris@1071:     };
Chris@1073:     
Chris@1073:     Colour3DPlotRenderer(Sources sources, Parameters parameters) :
Chris@1073:         m_sources(sources),
Chris@1266:         m_params(parameters),
Chris@1221:         m_secondsPerXPixel(0.0),
Chris@1221:         m_secondsPerXPixelValid(false)
Chris@1071:     { }
Chris@1071: 
Chris@1073:     struct RenderResult {
Chris@1073:         /**
Chris@1073:          * The rect that was actually rendered. May be equal to the
Chris@1073:          * rect that was requested to render, or may be smaller if
Chris@1073:          * time ran out and the complete flag was not set.
Chris@1073:          */
Chris@1073:         QRect rendered;
Chris@1073: 
Chris@1073:         /**
Chris@1248:          * The magnitude range of the data in the rendered area, after
Chris@1248:          * initial scaling (parameters.scaleFactor) and normalisation,
Chris@1248:          * for use in displaying colour scale etc. (Note that the
Chris@1248:          * magnitude range *before* normalisation would not be very
Chris@1248:          * meaningful for this purpose, as the scale would need to be
Chris@1248:          * different for every column if column or hybrid
Chris@1248:          * normalisation was in use.)
Chris@1073:          */
Chris@1073:         MagnitudeRange range;
Chris@1073:     };
Chris@1073: 
Chris@1073:     /**
Chris@1073:      * Render the requested area using the given painter, obtaining
Chris@1090:      * geometry (e.g. start frame) from the given
Chris@1073:      * LayerGeometryProvider.
Chris@1073:      *
Chris@1090:      * The whole of the supplied rect will be rendered and the
Chris@1090:      * returned QRect will be equal to the supplied QRect. (See
Chris@1090:      * renderTimeConstrained for an alternative that may render only
Chris@1090:      * part of the rect in cases where obtaining source data is slow
Chris@1090:      * and retaining responsiveness is important.)
Chris@1090:      *
Chris@1090:      * Note that Colour3DPlotRenderer retains internal cache state
Chris@1090:      * related to the size and position of the supplied
Chris@1090:      * LayerGeometryProvider. Although it is valid to call render()
Chris@1090:      * successively on the same Colour3DPlotRenderer with different
Chris@1090:      * LayerGeometryProviders, it will be much faster to use a
Chris@1090:      * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
Chris@1075:      *
Chris@1075:      * If the model to render from is not ready, this will throw a
Chris@1075:      * std::logic_error exception. The model must be ready and the
Chris@1075:      * layer requesting the render must not be dormant in its view, so
Chris@1075:      * that the LayerGeometryProvider returns valid results; it is the
Chris@1075:      * caller's responsibility to ensure these.
Chris@1073:      */
Chris@1125:     RenderResult render(const LayerGeometryProvider *v,
Chris@1125:                         QPainter &paint, QRect rect);
Chris@1076:     
Chris@1076:     /**
Chris@1076:      * Render the requested area using the given painter, obtaining
Chris@1076:      * geometry (e.g. start frame) from the stored
Chris@1076:      * LayerGeometryProvider.
Chris@1076:      *
Chris@1076:      * As much of the rect will be rendered as can be managed given
Chris@1076:      * internal time constraints (using a RenderTimer object
Chris@1076:      * internally). The returned QRect (the rendered field in the
Chris@1076:      * RenderResult struct) will contain the area that was
Chris@1076:      * rendered. Note that we always render the full requested height,
Chris@1076:      * it's only width that is time-constrained.
Chris@1076:      *
Chris@1090:      * Note that Colour3DPlotRenderer retains internal cache state
Chris@1090:      * related to the size and position of the supplied
Chris@1090:      * LayerGeometryProvider. Although it is valid to call render()
Chris@1090:      * successively on the same Colour3DPlotRenderer with different
Chris@1090:      * LayerGeometryProviders, it will be much faster to use a
Chris@1090:      * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
Chris@1090:      *
Chris@1076:      * If the model to render from is not ready, this will throw a
Chris@1076:      * std::logic_error exception. The model must be ready and the
Chris@1076:      * layer requesting the render must not be dormant in its view, so
Chris@1076:      * that the LayerGeometryProvider returns valid results; it is the
Chris@1076:      * caller's responsibility to ensure these.
Chris@1076:      */
Chris@1113:     RenderResult renderTimeConstrained(const LayerGeometryProvider *v,
Chris@1090:                                        QPainter &paint, QRect rect);
Chris@1096: 
Chris@1096:     /**
Chris@1096:      * Return the area of the largest rectangle within the entire area
Chris@1096:      * of the cache that is unavailable in the cache. This is only
Chris@1096:      * valid in relation to a preceding render() call which is
Chris@1096:      * presumed to have set the area, start frame, and zoom level for
Chris@1096:      * the cache. It could be used to establish a suitable region for
Chris@1096:      * a subsequent paint request (because if an area is not in the
Chris@1096:      * cache, it cannot have been rendered since the cache was
Chris@1096:      * cleared).
Chris@1096:      *
Chris@1096:      * Returns an empty QRect if the cache is entirely valid.
Chris@1096:      */
Chris@1121:     QRect getLargestUncachedRect(const LayerGeometryProvider *v);
Chris@1113: 
Chris@1113:     /**
Chris@1122:      * Return true if the provider's geometry differs from the cache,
Chris@1122:      * or if we are not using a cache. i.e. if the cache will be
Chris@1122:      * regenerated for the next render, or the next render performed
Chris@1122:      * from scratch.
Chris@1122:      */
Chris@1122:     bool geometryChanged(const LayerGeometryProvider *v);
Chris@1122:     
Chris@1122:     /**
Chris@1113:      * Return true if the rendering will be opaque. This may be used
Chris@1113:      * by the calling layer to determine whether it can scroll
Chris@1113:      * directly without regard to any other layers beneath.
Chris@1113:      */
Chris@1113:     bool willRenderOpaque(const LayerGeometryProvider *v) {
Chris@1113:         return decideRenderType(v) != DirectTranslucent;
Chris@1113:     }
Chris@1073:     
Chris@1125:     /**
Chris@1125:      * Return the colour corresponding to the given value.
Chris@1125:      * \see ColourScale::getPixel
Chris@1125:      * \see ColourScale::getColour
Chris@1125:      */
Chris@1125:     QColor getColour(double value) const {
Chris@1125:         return m_params.colourScale.getColour(value, m_params.colourRotation);
Chris@1125:     }
Chris@1139: 
Chris@1139:     /**
Chris@1139:      * Return the enclosing rectangle for the region of similar colour
Chris@1139:      * to the given point within the cache. Return an empty QRect if
Chris@1139:      * this is not possible. \see ImageRegionFinder
Chris@1139:      */
Chris@1139:     QRect findSimilarRegionExtents(QPoint point) const;
Chris@1125:     
Chris@1071: private:
Chris@1073:     Sources m_sources;
Chris@1071:     Parameters m_params;
Chris@1072: 
Chris@1073:     // Draw buffer is the target of each partial repaint. It is always
Chris@1073:     // at view height (not model height) and is cleared and repainted
Chris@1073:     // on each fragment render. The only reason it's stored as a data
Chris@1073:     // member is to avoid reallocation.
Chris@1073:     QImage m_drawBuffer;
Chris@1072: 
Chris@1121:     // A temporary store of magnitude ranges per-column, used when
Chris@1121:     // rendering to the draw buffer. This always has the same length
Chris@1121:     // as the width of the draw buffer, and the x coordinates of the
Chris@1121:     // two containers are equivalent.
Chris@1121:     std::vector<MagnitudeRange> m_magRanges;
Chris@1121:     
Chris@1119:     // The image cache is our persistent record of the visible
Chris@1119:     // area. It is always the same size as the view (i.e. the paint
Chris@1119:     // size reported by the LayerGeometryProvider) and is scrolled and
Chris@1119:     // partially repainted internally as appropriate. A render request
Chris@1119:     // is carried out by repainting to cache (via the draw buffer) any
Chris@1073:     // area that is being requested but is not valid in the cache, and
Chris@1073:     // then repainting from cache to the requested painter.
Chris@1073:     ScrollableImageCache m_cache;
Chris@1073: 
Chris@1119:     // The mag range cache is our record of the column magnitude
Chris@1119:     // ranges for each of the columns in the cache. It always has the
Chris@1119:     // same start frame and width as the image cache, and the column
Chris@1119:     // indices match up across both. Our cache update mechanism
Chris@1119:     // guarantees that every valid column in the image cache has a
Chris@1119:     // valid range in the magnitude cache, but not necessarily vice
Chris@1119:     // versa (as the image cache is limited to contiguous ranges).
Chris@1119:     ScrollableMagRangeCache m_magCache;
Chris@1221: 
Chris@1221:     double m_secondsPerXPixel;
Chris@1221:     bool m_secondsPerXPixelValid;
Chris@1119:     
Chris@1113:     RenderResult render(const LayerGeometryProvider *v,
Chris@1090:                         QPainter &paint, QRect rect, bool timeConstrained);
Chris@1109: 
Chris@1121:     MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1121:                                            QPainter &paint, QRect rect);
Chris@1109:     
Chris@1113:     void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0,
Chris@1094:                                       int repaintWidth, bool rightToLeft,
Chris@1094:                                       bool timeConstrained);
Chris@1109: 
Chris@1113:     void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0,
Chris@1094:                                     int repaintWidth);
Chris@1097: 
Chris@1083:     int renderDrawBuffer(int w, int h,
Chris@1083:                          const std::vector<int> &binforx,
Chris@1083:                          const std::vector<double> &binfory,
Chris@1212:                          int peakCacheIndex, // -1 => don't use a peak cache
Chris@1083:                          bool rightToLeft,
Chris@1083:                          bool timeConstrained);
Chris@1097: 
Chris@1113:     int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097:                                         int w, int h,
Chris@1097:                                         const std::vector<int> &binforx,
Chris@1097:                                         const std::vector<double> &binfory,
Chris@1097:                                         bool rightToLeft,
Chris@1097:                                         bool timeConstrained);
Chris@1097:     
Chris@1095:     void recreateDrawBuffer(int w, int h);
Chris@1079:     void clearDrawBuffer(int w, int h);
Chris@1109: 
Chris@1109:     enum RenderType {
Chris@1109:         DrawBufferPixelResolution,
Chris@1109:         DrawBufferBinResolution,
Chris@1109:         DirectTranslucent
Chris@1109:     };
Chris@1109: 
Chris@1113:     RenderType decideRenderType(const LayerGeometryProvider *) const;
Chris@1138: 
Chris@1167:     QImage scaleDrawBufferImage(QImage source, int targetWidth, int targetHeight)
Chris@1167:         const;
Chris@1167:     
Chris@1161:     ColumnOp::Column getColumn(int sx, int minbin, int nbins,
Chris@1502:                                std::shared_ptr<DenseThreeDimensionalModel> source) const;
Chris@1364:     ColumnOp::Column getColumnRaw(int sx, int minbin, int nbins,
Chris@1502:                                   std::shared_ptr<DenseThreeDimensionalModel> source) const;
Chris@1213: 
Chris@1213:     void getPreferredPeakCache(const LayerGeometryProvider *,
Chris@1213:                                int &peakCacheIndex, int &binsPerPeak) const;
Chris@1221: 
Chris@1221:     void updateTimings(const RenderTimer &timer, int xPixelCount);
Chris@1071: };
Chris@1071: 
Chris@1071: #endif
Chris@1071: