annotate layer/Colour3DPlotRenderer.h @ 1614:c6f5c822b10d

Fix potential divide-by-zero (depending on a race elsewhere)
author Chris Cannam
date Tue, 30 Jun 2020 10:56:56 +0100
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