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 Chris@1073: #include Chris@1073: #include Chris@1073: Chris@1073: class LayerGeometryProvider; Chris@1082: class VerticalBinLayer; Chris@1469: class RenderTimer; Chris@1071: class Dense3DModelPeakCache; 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 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 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 &binforx, Chris@1083: const std::vector &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 &binforx, Chris@1097: const std::vector &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@1212: int peakCacheIndex) const; // -1 => don't use cache Chris@1364: ColumnOp::Column getColumnRaw(int sx, int minbin, int nbins, Chris@1364: int peakCacheIndex) const; // -1 => don't use cache 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: