annotate layer/Colour3DPlotRenderer.h @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
parents 62aad7969f8b
children
rev   line source
Chris@1071 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1071 2
Chris@1071 3 /*
Chris@1071 4 Sonic Visualiser
Chris@1071 5 An audio file viewer and annotation editor.
Chris@1071 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1071 7 This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@1071 8
Chris@1071 9 This program is free software; you can redistribute it and/or
Chris@1071 10 modify it under the terms of the GNU General Public License as
Chris@1071 11 published by the Free Software Foundation; either version 2 of the
Chris@1071 12 License, or (at your option) any later version. See the file
Chris@1071 13 COPYING included with this distribution for more information.
Chris@1071 14 */
Chris@1071 15
Chris@1071 16 #ifndef COLOUR_3D_PLOT_RENDERER_H
Chris@1071 17 #define COLOUR_3D_PLOT_RENDERER_H
Chris@1071 18
Chris@1071 19 #include "ColourScale.h"
Chris@1073 20 #include "ScrollableImageCache.h"
Chris@1119 21 #include "ScrollableMagRangeCache.h"
Chris@1071 22
Chris@1071 23 #include "base/ColumnOp.h"
Chris@1073 24 #include "base/MagnitudeRange.h"
Chris@1071 25
Chris@1469 26 #include "data/model/Model.h"
Chris@1469 27
Chris@1073 28 #include <QRect>
Chris@1073 29 #include <QPainter>
Chris@1073 30 #include <QImage>
Chris@1073 31
Chris@1073 32 class LayerGeometryProvider;
Chris@1082 33 class VerticalBinLayer;
Chris@1469 34 class RenderTimer;
Chris@1071 35 class Dense3DModelPeakCache;
Chris@1502 36 class DenseThreeDimensionalModel;
Chris@1071 37
Chris@1103 38 enum class BinDisplay {
Chris@1103 39 AllBins,
Chris@1103 40 PeakBins,
Chris@1103 41 PeakFrequencies
Chris@1103 42 };
Chris@1103 43
Chris@1103 44 enum class BinScale {
Chris@1103 45 Linear,
Chris@1103 46 Log
Chris@1103 47 };
Chris@1103 48
Chris@1071 49 class Colour3DPlotRenderer
Chris@1071 50 {
Chris@1071 51 public:
Chris@1073 52 struct Sources {
Chris@1469 53 Sources() : verticalBinLayer(0) { }
Chris@1073 54
Chris@1073 55 // These must all outlive this class
Chris@1469 56 const VerticalBinLayer *verticalBinLayer; // always
Chris@1469 57 ModelId source; // always; a DenseThreeDimensionalModel
Chris@1473 58 ModelId fft; // optionally; an FFTModel; used for phase/peak-freq modes
Chris@1473 59 std::vector<ModelId> peakCaches; // zero or more
Chris@1073 60 };
Chris@1073 61
Chris@1071 62 struct Parameters {
Chris@1266 63 Parameters() :
Chris@1266 64 colourScale(ColourScale::Parameters()),
Chris@1266 65 normalization(ColumnNormalization::None),
Chris@1266 66 binDisplay(BinDisplay::AllBins),
Chris@1103 67 binScale(BinScale::Linear),
Chris@1266 68 alwaysOpaque(false),
Chris@1125 69 interpolate(false),
Chris@1112 70 invertVertical(false),
Chris@1364 71 showDerivative(false),
Chris@1125 72 scaleFactor(1.0),
Chris@1112 73 colourRotation(0) { }
Chris@1071 74
Chris@1125 75 /** A complete ColourScale object by value, used for colour
Chris@1125 76 * map conversion. Note that the final display gain setting is
Chris@1125 77 * also encapsulated here. */
Chris@1266 78 ColourScale colourScale;
Chris@1125 79
Chris@1125 80 /** Type of column normalization. */
Chris@1266 81 ColumnNormalization normalization;
Chris@1125 82
Chris@1125 83 /** Selection of bins to display. */
Chris@1266 84 BinDisplay binDisplay;
Chris@1125 85
Chris@1125 86 /** Scale for vertical bin spacing (linear or logarithmic). */
Chris@1266 87 BinScale binScale;
Chris@1125 88
Chris@1125 89 /** Whether cells should always be opaque. If false, then
Chris@1125 90 * large cells (when zoomed in a long way) will be rendered
Chris@1125 91 * translucent in order not to obscure anything in a layer
Chris@1125 92 * beneath. */
Chris@1266 93 bool alwaysOpaque;
Chris@1125 94
Chris@1125 95 /** Whether to apply smoothing when rendering cells at more
Chris@1125 96 * than one pixel per cell. !!! todo: decide about separating
Chris@1125 97 * out x-interpolate and y-interpolate as the spectrogram
Chris@1125 98 * actually does (or used to)
Chris@1125 99 */
Chris@1266 100 bool interpolate;
Chris@1125 101
Chris@1125 102 /** Whether to render the whole caboodle upside-down. */
Chris@1266 103 bool invertVertical;
Chris@1125 104
Chris@1364 105 /** Whether to show the frame-to-frame difference instead of
Chris@1364 106 * the actual value */
Chris@1364 107 bool showDerivative;
Chris@1364 108
Chris@1125 109 /** Initial scale factor (e.g. for FFT scaling). This factor
Chris@1125 110 * is applied to all values read from the underlying model
Chris@1125 111 * *before* magnitude ranges are calculated, in contrast to
Chris@1125 112 * the display gain found in the ColourScale parameter. */
Chris@1125 113 double scaleFactor;
Chris@1125 114
Chris@1125 115 /** Colourmap rotation, in the range 0-255. */
Chris@1112 116 int colourRotation;
Chris@1071 117 };
Chris@1073 118
Chris@1073 119 Colour3DPlotRenderer(Sources sources, Parameters parameters) :
Chris@1073 120 m_sources(sources),
Chris@1266 121 m_params(parameters),
Chris@1221 122 m_secondsPerXPixel(0.0),
Chris@1221 123 m_secondsPerXPixelValid(false)
Chris@1071 124 { }
Chris@1071 125
Chris@1073 126 struct RenderResult {
Chris@1073 127 /**
Chris@1073 128 * The rect that was actually rendered. May be equal to the
Chris@1073 129 * rect that was requested to render, or may be smaller if
Chris@1073 130 * time ran out and the complete flag was not set.
Chris@1073 131 */
Chris@1073 132 QRect rendered;
Chris@1073 133
Chris@1073 134 /**
Chris@1248 135 * The magnitude range of the data in the rendered area, after
Chris@1248 136 * initial scaling (parameters.scaleFactor) and normalisation,
Chris@1248 137 * for use in displaying colour scale etc. (Note that the
Chris@1248 138 * magnitude range *before* normalisation would not be very
Chris@1248 139 * meaningful for this purpose, as the scale would need to be
Chris@1248 140 * different for every column if column or hybrid
Chris@1248 141 * normalisation was in use.)
Chris@1073 142 */
Chris@1073 143 MagnitudeRange range;
Chris@1073 144 };
Chris@1073 145
Chris@1073 146 /**
Chris@1073 147 * Render the requested area using the given painter, obtaining
Chris@1090 148 * geometry (e.g. start frame) from the given
Chris@1073 149 * LayerGeometryProvider.
Chris@1073 150 *
Chris@1090 151 * The whole of the supplied rect will be rendered and the
Chris@1090 152 * returned QRect will be equal to the supplied QRect. (See
Chris@1090 153 * renderTimeConstrained for an alternative that may render only
Chris@1090 154 * part of the rect in cases where obtaining source data is slow
Chris@1090 155 * and retaining responsiveness is important.)
Chris@1090 156 *
Chris@1090 157 * Note that Colour3DPlotRenderer retains internal cache state
Chris@1090 158 * related to the size and position of the supplied
Chris@1090 159 * LayerGeometryProvider. Although it is valid to call render()
Chris@1090 160 * successively on the same Colour3DPlotRenderer with different
Chris@1090 161 * LayerGeometryProviders, it will be much faster to use a
Chris@1090 162 * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
Chris@1075 163 *
Chris@1075 164 * If the model to render from is not ready, this will throw a
Chris@1075 165 * std::logic_error exception. The model must be ready and the
Chris@1075 166 * layer requesting the render must not be dormant in its view, so
Chris@1075 167 * that the LayerGeometryProvider returns valid results; it is the
Chris@1075 168 * caller's responsibility to ensure these.
Chris@1073 169 */
Chris@1125 170 RenderResult render(const LayerGeometryProvider *v,
Chris@1125 171 QPainter &paint, QRect rect);
Chris@1076 172
Chris@1076 173 /**
Chris@1076 174 * Render the requested area using the given painter, obtaining
Chris@1076 175 * geometry (e.g. start frame) from the stored
Chris@1076 176 * LayerGeometryProvider.
Chris@1076 177 *
Chris@1076 178 * As much of the rect will be rendered as can be managed given
Chris@1076 179 * internal time constraints (using a RenderTimer object
Chris@1076 180 * internally). The returned QRect (the rendered field in the
Chris@1076 181 * RenderResult struct) will contain the area that was
Chris@1076 182 * rendered. Note that we always render the full requested height,
Chris@1076 183 * it's only width that is time-constrained.
Chris@1076 184 *
Chris@1090 185 * Note that Colour3DPlotRenderer retains internal cache state
Chris@1090 186 * related to the size and position of the supplied
Chris@1090 187 * LayerGeometryProvider. Although it is valid to call render()
Chris@1090 188 * successively on the same Colour3DPlotRenderer with different
Chris@1090 189 * LayerGeometryProviders, it will be much faster to use a
Chris@1090 190 * dedicated Colour3DPlotRenderer for each LayerGeometryProvider.
Chris@1090 191 *
Chris@1076 192 * If the model to render from is not ready, this will throw a
Chris@1076 193 * std::logic_error exception. The model must be ready and the
Chris@1076 194 * layer requesting the render must not be dormant in its view, so
Chris@1076 195 * that the LayerGeometryProvider returns valid results; it is the
Chris@1076 196 * caller's responsibility to ensure these.
Chris@1076 197 */
Chris@1113 198 RenderResult renderTimeConstrained(const LayerGeometryProvider *v,
Chris@1090 199 QPainter &paint, QRect rect);
Chris@1096 200
Chris@1096 201 /**
Chris@1096 202 * Return the area of the largest rectangle within the entire area
Chris@1096 203 * of the cache that is unavailable in the cache. This is only
Chris@1096 204 * valid in relation to a preceding render() call which is
Chris@1096 205 * presumed to have set the area, start frame, and zoom level for
Chris@1096 206 * the cache. It could be used to establish a suitable region for
Chris@1096 207 * a subsequent paint request (because if an area is not in the
Chris@1096 208 * cache, it cannot have been rendered since the cache was
Chris@1096 209 * cleared).
Chris@1096 210 *
Chris@1096 211 * Returns an empty QRect if the cache is entirely valid.
Chris@1096 212 */
Chris@1121 213 QRect getLargestUncachedRect(const LayerGeometryProvider *v);
Chris@1113 214
Chris@1113 215 /**
Chris@1122 216 * Return true if the provider's geometry differs from the cache,
Chris@1122 217 * or if we are not using a cache. i.e. if the cache will be
Chris@1122 218 * regenerated for the next render, or the next render performed
Chris@1122 219 * from scratch.
Chris@1122 220 */
Chris@1122 221 bool geometryChanged(const LayerGeometryProvider *v);
Chris@1122 222
Chris@1122 223 /**
Chris@1113 224 * Return true if the rendering will be opaque. This may be used
Chris@1113 225 * by the calling layer to determine whether it can scroll
Chris@1113 226 * directly without regard to any other layers beneath.
Chris@1113 227 */
Chris@1113 228 bool willRenderOpaque(const LayerGeometryProvider *v) {
Chris@1113 229 return decideRenderType(v) != DirectTranslucent;
Chris@1113 230 }
Chris@1073 231
Chris@1125 232 /**
Chris@1125 233 * Return the colour corresponding to the given value.
Chris@1125 234 * \see ColourScale::getPixel
Chris@1125 235 * \see ColourScale::getColour
Chris@1125 236 */
Chris@1125 237 QColor getColour(double value) const {
Chris@1125 238 return m_params.colourScale.getColour(value, m_params.colourRotation);
Chris@1125 239 }
Chris@1139 240
Chris@1139 241 /**
Chris@1139 242 * Return the enclosing rectangle for the region of similar colour
Chris@1139 243 * to the given point within the cache. Return an empty QRect if
Chris@1139 244 * this is not possible. \see ImageRegionFinder
Chris@1139 245 */
Chris@1139 246 QRect findSimilarRegionExtents(QPoint point) const;
Chris@1125 247
Chris@1071 248 private:
Chris@1073 249 Sources m_sources;
Chris@1071 250 Parameters m_params;
Chris@1072 251
Chris@1073 252 // Draw buffer is the target of each partial repaint. It is always
Chris@1073 253 // at view height (not model height) and is cleared and repainted
Chris@1073 254 // on each fragment render. The only reason it's stored as a data
Chris@1073 255 // member is to avoid reallocation.
Chris@1073 256 QImage m_drawBuffer;
Chris@1072 257
Chris@1121 258 // A temporary store of magnitude ranges per-column, used when
Chris@1121 259 // rendering to the draw buffer. This always has the same length
Chris@1121 260 // as the width of the draw buffer, and the x coordinates of the
Chris@1121 261 // two containers are equivalent.
Chris@1121 262 std::vector<MagnitudeRange> m_magRanges;
Chris@1121 263
Chris@1119 264 // The image cache is our persistent record of the visible
Chris@1119 265 // area. It is always the same size as the view (i.e. the paint
Chris@1119 266 // size reported by the LayerGeometryProvider) and is scrolled and
Chris@1119 267 // partially repainted internally as appropriate. A render request
Chris@1119 268 // is carried out by repainting to cache (via the draw buffer) any
Chris@1073 269 // area that is being requested but is not valid in the cache, and
Chris@1073 270 // then repainting from cache to the requested painter.
Chris@1073 271 ScrollableImageCache m_cache;
Chris@1073 272
Chris@1119 273 // The mag range cache is our record of the column magnitude
Chris@1119 274 // ranges for each of the columns in the cache. It always has the
Chris@1119 275 // same start frame and width as the image cache, and the column
Chris@1119 276 // indices match up across both. Our cache update mechanism
Chris@1119 277 // guarantees that every valid column in the image cache has a
Chris@1119 278 // valid range in the magnitude cache, but not necessarily vice
Chris@1119 279 // versa (as the image cache is limited to contiguous ranges).
Chris@1119 280 ScrollableMagRangeCache m_magCache;
Chris@1221 281
Chris@1221 282 double m_secondsPerXPixel;
Chris@1221 283 bool m_secondsPerXPixelValid;
Chris@1119 284
Chris@1113 285 RenderResult render(const LayerGeometryProvider *v,
Chris@1090 286 QPainter &paint, QRect rect, bool timeConstrained);
Chris@1109 287
Chris@1121 288 MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1121 289 QPainter &paint, QRect rect);
Chris@1109 290
Chris@1113 291 void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0,
Chris@1094 292 int repaintWidth, bool rightToLeft,
Chris@1094 293 bool timeConstrained);
Chris@1109 294
Chris@1113 295 void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0,
Chris@1094 296 int repaintWidth);
Chris@1097 297
Chris@1083 298 int renderDrawBuffer(int w, int h,
Chris@1083 299 const std::vector<int> &binforx,
Chris@1083 300 const std::vector<double> &binfory,
Chris@1212 301 int peakCacheIndex, // -1 => don't use a peak cache
Chris@1083 302 bool rightToLeft,
Chris@1083 303 bool timeConstrained);
Chris@1097 304
Chris@1113 305 int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097 306 int w, int h,
Chris@1097 307 const std::vector<int> &binforx,
Chris@1097 308 const std::vector<double> &binfory,
Chris@1097 309 bool rightToLeft,
Chris@1097 310 bool timeConstrained);
Chris@1097 311
Chris@1095 312 void recreateDrawBuffer(int w, int h);
Chris@1079 313 void clearDrawBuffer(int w, int h);
Chris@1109 314
Chris@1109 315 enum RenderType {
Chris@1109 316 DrawBufferPixelResolution,
Chris@1109 317 DrawBufferBinResolution,
Chris@1109 318 DirectTranslucent
Chris@1109 319 };
Chris@1109 320
Chris@1113 321 RenderType decideRenderType(const LayerGeometryProvider *) const;
Chris@1138 322
Chris@1167 323 QImage scaleDrawBufferImage(QImage source, int targetWidth, int targetHeight)
Chris@1167 324 const;
Chris@1167 325
Chris@1161 326 ColumnOp::Column getColumn(int sx, int minbin, int nbins,
Chris@1502 327 std::shared_ptr<DenseThreeDimensionalModel> source) const;
Chris@1364 328 ColumnOp::Column getColumnRaw(int sx, int minbin, int nbins,
Chris@1502 329 std::shared_ptr<DenseThreeDimensionalModel> source) const;
Chris@1213 330
Chris@1213 331 void getPreferredPeakCache(const LayerGeometryProvider *,
Chris@1213 332 int &peakCacheIndex, int &binsPerPeak) const;
Chris@1221 333
Chris@1221 334 void updateTimings(const RenderTimer &timer, int xPixelCount);
Chris@1071 335 };
Chris@1071 336
Chris@1071 337 #endif
Chris@1071 338