annotate layer/Colour3DPlotRenderer.cpp @ 1496:d09345e578a7

Separate out handling of alignment progress bar from the layer progress bars and fix tendency to have them hanging around even when alignment has completed
author Chris Cannam
date Wed, 14 Aug 2019 10:58:24 +0100
parents 78d9282519b0
children e4c9d38d304d
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 #include "Colour3DPlotRenderer.h"
Chris@1074 17 #include "RenderTimer.h"
Chris@1071 18
Chris@1117 19 #include "base/Profiler.h"
Chris@1214 20 #include "base/HitCount.h"
Chris@1117 21
Chris@1075 22 #include "data/model/DenseThreeDimensionalModel.h"
Chris@1075 23 #include "data/model/Dense3DModelPeakCache.h"
Chris@1075 24 #include "data/model/FFTModel.h"
Chris@1075 25
Chris@1077 26 #include "LayerGeometryProvider.h"
Chris@1082 27 #include "VerticalBinLayer.h"
Chris@1109 28 #include "PaintAssistant.h"
Chris@1139 29 #include "ImageRegionFinder.h"
Chris@1109 30
Chris@1109 31 #include "view/ViewManager.h" // for main model sample rate. Pity
Chris@1075 32
Chris@1079 33 #include <vector>
Chris@1079 34
Chris@1325 35 #include <utility>
Chris@1325 36 using namespace std::rel_ops;
Chris@1325 37
Chris@1362 38 //#define DEBUG_COLOUR_PLOT_REPAINT 1
Chris@1094 39
Chris@1079 40 using namespace std;
Chris@1079 41
Chris@1073 42 Colour3DPlotRenderer::RenderResult
Chris@1113 43 Colour3DPlotRenderer::render(const LayerGeometryProvider *v, QPainter &paint, QRect rect)
Chris@1076 44 {
Chris@1090 45 return render(v, paint, rect, false);
Chris@1076 46 }
Chris@1076 47
Chris@1076 48 Colour3DPlotRenderer::RenderResult
Chris@1113 49 Colour3DPlotRenderer::renderTimeConstrained(const LayerGeometryProvider *v,
Chris@1090 50 QPainter &paint, QRect rect)
Chris@1076 51 {
Chris@1090 52 return render(v, paint, rect, true);
Chris@1076 53 }
Chris@1076 54
Chris@1096 55 QRect
Chris@1121 56 Colour3DPlotRenderer::getLargestUncachedRect(const LayerGeometryProvider *v)
Chris@1096 57 {
Chris@1121 58 RenderType renderType = decideRenderType(v);
Chris@1121 59
Chris@1121 60 if (renderType == DirectTranslucent) {
Chris@1121 61 return QRect(); // never cached
Chris@1121 62 }
Chris@1121 63
Chris@1096 64 int h = m_cache.getSize().height();
Chris@1096 65
Chris@1096 66 QRect areaLeft(0, 0, m_cache.getValidLeft(), h);
Chris@1096 67 QRect areaRight(m_cache.getValidRight(), 0,
Chris@1096 68 m_cache.getSize().width() - m_cache.getValidRight(), h);
Chris@1096 69
Chris@1096 70 if (areaRight.width() > areaLeft.width()) {
Chris@1096 71 return areaRight;
Chris@1096 72 } else {
Chris@1096 73 return areaLeft;
Chris@1096 74 }
Chris@1096 75 }
Chris@1096 76
Chris@1122 77 bool
Chris@1122 78 Colour3DPlotRenderer::geometryChanged(const LayerGeometryProvider *v)
Chris@1122 79 {
Chris@1122 80 RenderType renderType = decideRenderType(v);
Chris@1122 81
Chris@1122 82 if (renderType == DirectTranslucent) {
Chris@1122 83 return true; // never cached
Chris@1122 84 }
Chris@1122 85
Chris@1122 86 if (m_cache.getSize() == v->getPaintSize() &&
Chris@1122 87 m_cache.getZoomLevel() == v->getZoomLevel() &&
Chris@1122 88 m_cache.getStartFrame() == v->getStartFrame()) {
Chris@1122 89 return false;
Chris@1122 90 } else {
Chris@1122 91 return true;
Chris@1122 92 }
Chris@1122 93 }
Chris@1122 94
Chris@1076 95 Colour3DPlotRenderer::RenderResult
Chris@1113 96 Colour3DPlotRenderer::render(const LayerGeometryProvider *v,
Chris@1090 97 QPainter &paint, QRect rect, bool timeConstrained)
Chris@1073 98 {
Chris@1109 99 RenderType renderType = decideRenderType(v);
Chris@1109 100
Chris@1221 101 if (timeConstrained) {
Chris@1221 102 if (renderType != DrawBufferPixelResolution) {
Chris@1221 103 // Rendering should be fast in bin-resolution and direct
Chris@1221 104 // draw cases because we are quite well zoomed-in, and the
Chris@1221 105 // sums are easier this way. Calculating boundaries later
Chris@1221 106 // will be fiddly for partial paints otherwise.
Chris@1221 107 timeConstrained = false;
Chris@1221 108
Chris@1221 109 } else if (m_secondsPerXPixelValid) {
Chris@1221 110 double predicted = m_secondsPerXPixel * rect.width();
Chris@1236 111 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1221 112 SVDEBUG << "Predicted time for width " << rect.width() << " = "
Chris@1236 113 << predicted << " (" << m_secondsPerXPixel << " x "
Chris@1236 114 << rect.width() << ")" << endl;
Chris@1221 115 #endif
Chris@1450 116 if (predicted < 0.175) {
Chris@1221 117 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1221 118 SVDEBUG << "Predicted time looks fast enough: no partial renders"
Chris@1221 119 << endl;
Chris@1221 120 #endif
Chris@1221 121 timeConstrained = false;
Chris@1221 122 }
Chris@1221 123 }
Chris@1109 124 }
Chris@1221 125
Chris@1079 126 int x0 = v->getXForViewX(rect.x());
Chris@1079 127 int x1 = v->getXForViewX(rect.x() + rect.width());
Chris@1079 128 if (x0 < 0) x0 = 0;
Chris@1079 129 if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
Chris@1079 130
Chris@1120 131 sv_frame_t startFrame = v->getStartFrame();
Chris@1451 132
Chris@1451 133 bool justInvalidated =
Chris@1451 134 (m_cache.getSize() != v->getPaintSize() ||
Chris@1451 135 m_cache.getZoomLevel() != v->getZoomLevel());
Chris@1120 136
Chris@1079 137 m_cache.resize(v->getPaintSize());
Chris@1079 138 m_cache.setZoomLevel(v->getZoomLevel());
Chris@1079 139
Chris@1119 140 m_magCache.resize(v->getPaintSize().width());
Chris@1119 141 m_magCache.setZoomLevel(v->getZoomLevel());
Chris@1119 142
Chris@1120 143 if (renderType == DirectTranslucent) {
Chris@1121 144 MagnitudeRange range = renderDirectTranslucent(v, paint, rect);
Chris@1121 145 return { rect, range };
Chris@1120 146 }
Chris@1120 147
Chris@1123 148 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 149 SVDEBUG << "cache start " << m_cache.getStartFrame()
Chris@1090 150 << " valid left " << m_cache.getValidLeft()
Chris@1090 151 << " valid right " << m_cache.getValidRight()
Chris@1094 152 << endl;
Chris@1214 153 SVDEBUG << " view start " << startFrame
Chris@1090 154 << " x0 " << x0
Chris@1090 155 << " x1 " << x1
Chris@1090 156 << endl;
Chris@1123 157 #endif
Chris@1214 158
Chris@1214 159 static HitCount count("Colour3DPlotRenderer: image cache");
Chris@1451 160
Chris@1079 161 if (m_cache.isValid()) { // some part of the cache is valid
Chris@1079 162
Chris@1079 163 if (v->getXForFrame(m_cache.getStartFrame()) ==
Chris@1079 164 v->getXForFrame(startFrame) &&
Chris@1079 165 m_cache.getValidLeft() <= x0 &&
Chris@1079 166 m_cache.getValidRight() >= x1) {
Chris@1090 167
Chris@1123 168 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 169 SVDEBUG << "cache hit" << endl;
Chris@1123 170 #endif
Chris@1214 171 count.hit();
Chris@1090 172
Chris@1079 173 // cache is valid for the complete requested area
Chris@1079 174 paint.drawImage(rect, m_cache.getImage(), rect);
Chris@1119 175
Chris@1122 176 MagnitudeRange range = m_magCache.getRange(x0, x1 - x0);
Chris@1119 177
Chris@1119 178 return { rect, range };
Chris@1079 179
Chris@1079 180 } else {
Chris@1123 181 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 182 SVDEBUG << "cache partial hit" << endl;
Chris@1123 183 #endif
Chris@1214 184 count.partial();
Chris@1090 185
Chris@1079 186 // cache doesn't begin at the right frame or doesn't
Chris@1079 187 // contain the complete view, but might be scrollable or
Chris@1079 188 // partially usable
Chris@1090 189 m_cache.scrollTo(v, startFrame);
Chris@1119 190 m_magCache.scrollTo(v, startFrame);
Chris@1079 191
Chris@1079 192 // if we are not time-constrained, then we want to paint
Chris@1081 193 // the whole area in one go; we don't return a partial
Chris@1081 194 // paint. To avoid providing the more complex logic to
Chris@1081 195 // handle painting discontiguous areas, if the only valid
Chris@1079 196 // part of cache is in the middle, just make the whole
Chris@1079 197 // thing invalid and start again.
Chris@1079 198 if (!timeConstrained) {
Chris@1079 199 if (m_cache.getValidLeft() > x0 &&
Chris@1079 200 m_cache.getValidRight() < x1) {
Chris@1079 201 m_cache.invalidate();
Chris@1079 202 }
Chris@1079 203 }
Chris@1079 204 }
Chris@1090 205 } else {
Chris@1118 206 // cache is completely invalid
Chris@1214 207 count.miss();
Chris@1090 208 m_cache.setStartFrame(startFrame);
Chris@1119 209 m_magCache.setStartFrame(startFrame);
Chris@1075 210 }
Chris@1075 211
Chris@1079 212 bool rightToLeft = false;
Chris@1079 213
Chris@1122 214 int reqx0 = x0;
Chris@1122 215 int reqx1 = x1;
Chris@1122 216
Chris@1079 217 if (!m_cache.isValid() && timeConstrained) {
Chris@1079 218 if (x0 == 0 && x1 == v->getPaintWidth()) {
Chris@1237 219
Chris@1237 220 // When rendering the whole area, in a context where we
Chris@1237 221 // might not be able to complete the work, start from
Chris@1237 222 // somewhere near the middle so that the region of
Chris@1237 223 // interest appears first.
Chris@1237 224 //
Chris@1237 225 // This is very useful if we actually are slow to render,
Chris@1237 226 // but if we're not sure how fast we'll be, we should
Chris@1237 227 // prefer not to because it can be distracting to render
Chris@1237 228 // fast from the middle and then jump back to fill in the
Chris@1237 229 // start. That is:
Chris@1237 230 //
Chris@1237 231 // - if our seconds-per-x-pixel count is invalid, then we
Chris@1237 232 // don't do this: we've probably only just been created
Chris@1237 233 // and don't know how fast we'll be yet (this happens
Chris@1237 234 // often while zooming rapidly in and out). The exception
Chris@1237 235 // to the exception is if we're displaying peak
Chris@1237 236 // frequencies; this we can assume to be slow. (Note that
Chris@1237 237 // if the seconds-per-x-pixel is valid and we know we're
Chris@1237 238 // fast, then we've already set timeConstrained false
Chris@1237 239 // above so this doesn't apply)
Chris@1237 240 //
Chris@1237 241 // - if we're using a peak cache, we don't do this;
Chris@1237 242 // drawing from peak cache is often (even if not always)
Chris@1237 243 // fast.
Chris@1237 244
Chris@1237 245 bool drawFromTheMiddle = true;
Chris@1237 246
Chris@1237 247 if (!m_secondsPerXPixelValid &&
Chris@1237 248 (m_params.binDisplay != BinDisplay::PeakFrequencies)) {
Chris@1237 249 drawFromTheMiddle = false;
Chris@1237 250 } else {
Chris@1237 251 int peakCacheIndex = -1, binsPerPeak = -1;
Chris@1237 252 getPreferredPeakCache(v, peakCacheIndex, binsPerPeak);
Chris@1237 253 if (peakCacheIndex >= 0) { // have a peak cache
Chris@1237 254 drawFromTheMiddle = false;
Chris@1237 255 }
Chris@1237 256 }
Chris@1237 257
Chris@1237 258 if (drawFromTheMiddle) {
Chris@1222 259 double offset = 0.5 * (double(rand()) / double(RAND_MAX));
Chris@1222 260 x0 = int(x1 * offset);
Chris@1213 261 }
Chris@1079 262 }
Chris@1079 263 }
Chris@1079 264
Chris@1079 265 if (m_cache.isValid()) {
Chris@1090 266
Chris@1079 267 // When rendering only a part of the cache, we need to make
Chris@1079 268 // sure that the part we're rendering is adjacent to (or
Chris@1079 269 // overlapping) a valid area of cache, if we have one. The
Chris@1079 270 // alternative is to ditch the valid area of cache and render
Chris@1079 271 // only the requested area, but that's risky because this can
Chris@1079 272 // happen when just waving the pointer over a small part of
Chris@1079 273 // the view -- if we lose the partly-built cache every time
Chris@1079 274 // the user does that, we'll never finish building it.
Chris@1079 275 int left = x0;
Chris@1079 276 int width = x1 - x0;
Chris@1079 277 bool isLeftOfValidArea = false;
Chris@1079 278 m_cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
Chris@1079 279 x0 = left;
Chris@1079 280 x1 = x0 + width;
Chris@1079 281
Chris@1079 282 // That call also told us whether we should be painting
Chris@1079 283 // sub-regions of our target region in right-to-left order in
Chris@1079 284 // order to ensure contiguity
Chris@1079 285 rightToLeft = isLeftOfValidArea;
Chris@1079 286 }
Chris@1075 287
Chris@1109 288 // Note, we always paint the full height to cache. We want to
Chris@1109 289 // ensure the cache is coherent without having to worry about
Chris@1109 290 // vertical matching of required and valid areas as well as
Chris@1109 291 // horizontal.
Chris@1094 292
Chris@1109 293 if (renderType == DrawBufferBinResolution) {
Chris@1109 294
Chris@1094 295 renderToCacheBinResolution(v, x0, x1 - x0);
Chris@1109 296
Chris@1109 297 } else { // must be DrawBufferPixelResolution, handled DirectTranslucent earlier
Chris@1109 298
Chris@1451 299 if (timeConstrained && justInvalidated) {
Chris@1451 300 SVDEBUG << "render: just invalidated cache in time-constrained context, that's all we're doing for now - wait for next update to start filling" << endl;
Chris@1451 301 } else {
Chris@1451 302 renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained);
Chris@1451 303 }
Chris@1094 304 }
Chris@1079 305
Chris@1079 306 QRect pr = rect & m_cache.getValidArea();
Chris@1079 307 paint.drawImage(pr.x(), pr.y(), m_cache.getImage(),
Chris@1079 308 pr.x(), pr.y(), pr.width(), pr.height());
Chris@1079 309
Chris@1079 310 if (!timeConstrained && (pr != rect)) {
Chris@1494 311 QRect cva = m_cache.getValidArea();
Chris@1265 312 SVCERR << "WARNING: failed to render entire requested rect "
Chris@1451 313 << "even when not time-constrained: wanted "
Chris@1450 314 << rect.x() << "," << rect.y() << " "
Chris@1451 315 << rect.width() << "x" << rect.height() << ", got "
Chris@1450 316 << pr.x() << "," << pr.y() << " "
Chris@1450 317 << pr.width() << "x" << pr.height()
Chris@1451 318 << ", after request of width " << (x1 - x0)
Chris@1494 319 << endl
Chris@1494 320 << "(cache valid area is "
Chris@1494 321 << cva.x() << "," << cva.y() << " "
Chris@1494 322 << cva.width() << "x" << cva.height() << ")"
Chris@1450 323 << endl;
Chris@1079 324 }
Chris@1120 325
Chris@1122 326 MagnitudeRange range = m_magCache.getRange(reqx0, reqx1 - reqx0);
Chris@1412 327
Chris@1412 328 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1412 329 SVDEBUG << "render: returning rect rendered as " << pr.x() << "," << pr.y()
Chris@1412 330 << " " << pr.width() << "x" << pr.height() << endl;
Chris@1412 331 SVDEBUG << "render: mag range from cache in x-range " << reqx0
Chris@1412 332 << " to " << reqx1 << " is " << range.getMin() << " -> "
Chris@1412 333 << range.getMax() << endl;
Chris@1412 334 #endif
Chris@1120 335
Chris@1120 336 return { pr, range };
Chris@1073 337 }
Chris@1073 338
Chris@1109 339 Colour3DPlotRenderer::RenderType
Chris@1113 340 Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const
Chris@1094 341 {
Chris@1469 342 auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1109 343 if (!model || !v || !(v->getViewManager())) {
Chris@1109 344 return DrawBufferPixelResolution; // or anything
Chris@1109 345 }
Chris@1109 346
Chris@1094 347 int binResolution = model->getResolution();
Chris@1325 348 ZoomLevel zoomLevel = v->getZoomLevel();
Chris@1109 349 sv_samplerate_t modelRate = model->getSampleRate();
Chris@1109 350
Chris@1109 351 double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate;
Chris@1109 352 double relativeBinResolution = binResolution * rateRatio;
Chris@1109 353
Chris@1109 354 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1109 355 // no alternative works here
Chris@1109 356 return DrawBufferPixelResolution;
Chris@1109 357 }
Chris@1109 358
Chris@1109 359 if (!m_params.alwaysOpaque && !m_params.interpolate) {
Chris@1109 360
Chris@1109 361 // consider translucent option -- only if not smoothing & not
Chris@1109 362 // explicitly requested opaque & sufficiently zoomed-in
Chris@1109 363
Chris@1117 364 if (model->getHeight() * 3 < v->getPaintHeight() &&
Chris@1325 365 zoomLevel < ZoomLevel(ZoomLevel::FramesPerPixel,
Chris@1325 366 int(round(relativeBinResolution / 3)))) {
Chris@1109 367 return DirectTranslucent;
Chris@1109 368 }
Chris@1109 369 }
Chris@1109 370
Chris@1325 371 if (ZoomLevel(ZoomLevel::FramesPerPixel,
Chris@1325 372 int(round(relativeBinResolution))) > zoomLevel) {
Chris@1109 373 return DrawBufferBinResolution;
Chris@1109 374 } else {
Chris@1109 375 return DrawBufferPixelResolution;
Chris@1109 376 }
Chris@1109 377 }
Chris@1109 378
Chris@1138 379 ColumnOp::Column
Chris@1161 380 Colour3DPlotRenderer::getColumn(int sx, int minbin, int nbins,
Chris@1212 381 int peakCacheIndex) const
Chris@1138 382 {
Chris@1138 383 // order:
Chris@1138 384 // get column -> scale -> normalise -> record extents ->
Chris@1138 385 // peak pick -> distribute/interpolate -> apply display gain
Chris@1138 386
Chris@1138 387 // we do the first bit here:
Chris@1138 388 // get column -> scale -> normalise
Chris@1138 389
Chris@1138 390 ColumnOp::Column column;
Chris@1364 391
Chris@1364 392 if (m_params.showDerivative && sx > 0) {
Chris@1364 393
Chris@1364 394 auto prev = getColumnRaw(sx - 1, minbin, nbins, peakCacheIndex);
Chris@1364 395 column = getColumnRaw(sx, minbin, nbins, peakCacheIndex);
Chris@1364 396
Chris@1364 397 for (int i = 0; i < nbins; ++i) {
Chris@1364 398 column[i] -= prev[i];
Chris@1364 399 }
Chris@1364 400
Chris@1364 401 } else {
Chris@1364 402 column = getColumnRaw(sx, minbin, nbins, peakCacheIndex);
Chris@1364 403 }
Chris@1364 404
Chris@1364 405 if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
Chris@1469 406 !m_sources.fft.isNone()) {
Chris@1364 407 return column;
Chris@1364 408 } else {
Chris@1364 409 column = ColumnOp::applyGain(column, m_params.scaleFactor);
Chris@1364 410 column = ColumnOp::normalize(column, m_params.normalization);
Chris@1364 411 return column;
Chris@1364 412 }
Chris@1364 413 }
Chris@1364 414
Chris@1364 415 ColumnOp::Column
Chris@1364 416 Colour3DPlotRenderer::getColumnRaw(int sx, int minbin, int nbins,
Chris@1364 417 int peakCacheIndex) const
Chris@1364 418 {
Chris@1364 419 Profiler profiler("Colour3DPlotRenderer::getColumn");
Chris@1364 420
Chris@1364 421 ColumnOp::Column column;
Chris@1469 422 ColumnOp::Column fullColumn;
Chris@1364 423
Chris@1469 424 if (m_params.colourScale.getScale() == ColourScaleType::Phase) {
Chris@1469 425 auto fftModel = ModelById::getAs<FFTModel>(m_sources.fft);
Chris@1469 426 if (fftModel) {
Chris@1469 427 fullColumn = fftModel->getPhases(sx);
Chris@1469 428 }
Chris@1138 429 }
Chris@1138 430
Chris@1469 431 if (fullColumn.empty()) {
Chris@1469 432
Chris@1469 433 if (peakCacheIndex >= 0) {
Chris@1473 434 auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
Chris@1473 435 (m_sources.peakCaches[peakCacheIndex]);
Chris@1473 436 if (!peakCache) {
Chris@1473 437 return vector<float>(nbins, 0.f);
Chris@1473 438 }
Chris@1473 439 fullColumn = peakCache->getColumn(sx);
Chris@1469 440 } else {
Chris@1469 441 auto model = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1469 442 (m_sources.source);
Chris@1469 443 if (!model) {
Chris@1469 444 return vector<float>(nbins, 0.f);
Chris@1469 445 }
Chris@1469 446 fullColumn = model->getColumn(sx);
Chris@1469 447 }
Chris@1469 448 }
Chris@1469 449
Chris@1469 450 column = vector<float>(fullColumn.data() + minbin,
Chris@1469 451 fullColumn.data() + minbin + nbins);
Chris@1138 452 return column;
Chris@1138 453 }
Chris@1138 454
Chris@1121 455 MagnitudeRange
Chris@1113 456 Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1109 457 QPainter &paint,
Chris@1109 458 QRect rect)
Chris@1109 459 {
Chris@1117 460 Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent");
Chris@1117 461
Chris@1121 462 MagnitudeRange magRange;
Chris@1121 463
Chris@1115 464 QPoint illuminatePos;
Chris@1115 465 bool illuminate = v->shouldIlluminateLocalFeatures
Chris@1115 466 (m_sources.verticalBinLayer, illuminatePos);
Chris@1109 467
Chris@1469 468 auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1469 469 if (!model) return magRange;
Chris@1109 470
Chris@1109 471 int x0 = rect.left();
Chris@1109 472 int x1 = rect.right() + 1;
Chris@1109 473
Chris@1109 474 int h = v->getPaintHeight();
Chris@1109 475
Chris@1109 476 sv_frame_t modelStart = model->getStartFrame();
Chris@1109 477 sv_frame_t modelEnd = model->getEndFrame();
Chris@1109 478 int modelResolution = model->getResolution();
Chris@1109 479
Chris@1109 480 double rateRatio =
Chris@1109 481 v->getViewManager()->getMainModelSampleRate() / model->getSampleRate();
Chris@1109 482
Chris@1109 483 // the s-prefix values are source, i.e. model, column and bin numbers
Chris@1109 484 int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart))
Chris@1109 485 / modelResolution);
Chris@1109 486 int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart))
Chris@1109 487 / modelResolution);
Chris@1109 488
Chris@1109 489 int sh = model->getHeight();
Chris@1109 490
Chris@1109 491 const int buflen = 40;
Chris@1109 492 char labelbuf[buflen];
Chris@1109 493
Chris@1133 494 int minbin = m_sources.verticalBinLayer->getIBinForY(v, h);
Chris@1135 495 if (minbin >= sh) minbin = sh - 1;
Chris@1135 496 if (minbin < 0) minbin = 0;
Chris@1135 497
Chris@1135 498 int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1;
Chris@1135 499 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1133 500
Chris@1109 501 int psx = -1;
Chris@1109 502
Chris@1109 503 vector<float> preparedColumn;
Chris@1109 504
Chris@1109 505 int modelWidth = model->getWidth();
Chris@1109 506
Chris@1109 507 for (int sx = sx0; sx <= sx1; ++sx) {
Chris@1109 508
Chris@1109 509 if (sx < 0 || sx >= modelWidth) {
Chris@1109 510 continue;
Chris@1109 511 }
Chris@1109 512
Chris@1109 513 if (sx != psx) {
Chris@1109 514
Chris@1138 515 // order:
Chris@1138 516 // get column -> scale -> normalise -> record extents ->
Chris@1138 517 // peak pick -> distribute/interpolate -> apply display gain
Chris@1109 518
Chris@1138 519 // this does the first three:
Chris@1219 520 preparedColumn = getColumn(sx, minbin, nbins, -1);
Chris@1131 521
Chris@1131 522 magRange.sample(preparedColumn);
Chris@1109 523
Chris@1109 524 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1115 525 preparedColumn = ColumnOp::peakPick(preparedColumn);
Chris@1109 526 }
Chris@1109 527
Chris@1124 528 // Display gain belongs to the colour scale and is
Chris@1124 529 // applied by the colour scale object when mapping it
Chris@1124 530
Chris@1109 531 psx = sx;
Chris@1109 532 }
Chris@1109 533
Chris@1266 534 sv_frame_t fx = sx * modelResolution + modelStart;
Chris@1109 535
Chris@1266 536 if (fx + modelResolution <= modelStart || fx > modelEnd) continue;
Chris@1109 537
Chris@1109 538 int rx0 = v->getXForFrame(int(double(fx) * rateRatio));
Chris@1266 539 int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio));
Chris@1109 540
Chris@1266 541 int rw = rx1 - rx0;
Chris@1266 542 if (rw < 1) rw = 1;
Chris@1109 543
Chris@1471 544 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1471 545 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1471 546 // which is too new for us
Chris@1471 547 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1471 548
Chris@1266 549 bool showLabel = (rw > 10 &&
Chris@1266 550 paint.fontMetrics().width("0.000000") < rw - 3 &&
Chris@1266 551 paint.fontMetrics().height() < (h / sh));
Chris@1109 552
Chris@1266 553 for (int sy = minbin; sy < minbin + nbins; ++sy) {
Chris@1109 554
Chris@1109 555 int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy);
Chris@1109 556 int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1);
Chris@1116 557
Chris@1116 558 if (m_params.invertVertical) {
Chris@1116 559 ry0 = h - ry0 - 1;
Chris@1116 560 ry1 = h - ry1 - 1;
Chris@1116 561 }
Chris@1116 562
Chris@1109 563 QRect r(rx0, ry1, rw, ry0 - ry1);
Chris@1109 564
Chris@1109 565 float value = preparedColumn[sy - minbin];
Chris@1112 566 QColor colour = m_params.colourScale.getColour(value,
Chris@1112 567 m_params.colourRotation);
Chris@1109 568
Chris@1109 569 if (rw == 1) {
Chris@1109 570 paint.setPen(colour);
Chris@1109 571 paint.setBrush(Qt::NoBrush);
Chris@1109 572 paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1);
Chris@1109 573 continue;
Chris@1109 574 }
Chris@1109 575
Chris@1266 576 QColor pen(255, 255, 255, 80);
Chris@1266 577 QColor brush(colour);
Chris@1109 578
Chris@1109 579 if (rw > 3 && r.height() > 3) {
Chris@1109 580 brush.setAlpha(160);
Chris@1109 581 }
Chris@1109 582
Chris@1266 583 paint.setPen(Qt::NoPen);
Chris@1266 584 paint.setBrush(brush);
Chris@1109 585
Chris@1266 586 if (illuminate) {
Chris@1266 587 if (r.contains(illuminatePos)) {
Chris@1266 588 paint.setPen(v->getForeground());
Chris@1266 589 }
Chris@1266 590 }
Chris@1109 591
Chris@1214 592 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 593 // SVDEBUG << "rect " << r.x() << "," << r.y() << " "
Chris@1109 594 // << r.width() << "x" << r.height() << endl;
Chris@1109 595 #endif
Chris@1109 596
Chris@1266 597 paint.drawRect(r);
Chris@1109 598
Chris@1266 599 if (showLabel) {
Chris@1109 600 double value = model->getValueAt(sx, sy);
Chris@1109 601 snprintf(labelbuf, buflen, "%06f", value);
Chris@1109 602 QString text(labelbuf);
Chris@1109 603 PaintAssistant::drawVisibleText
Chris@1109 604 (v,
Chris@1109 605 paint,
Chris@1109 606 rx0 + 2,
Chris@1109 607 ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
Chris@1109 608 text,
Chris@1109 609 PaintAssistant::OutlinedText);
Chris@1266 610 }
Chris@1266 611 }
Chris@1109 612 }
Chris@1121 613
Chris@1121 614 return magRange;
Chris@1094 615 }
Chris@1094 616
Chris@1080 617 void
Chris@1213 618 Colour3DPlotRenderer::getPreferredPeakCache(const LayerGeometryProvider *v,
Chris@1213 619 int &peakCacheIndex,
Chris@1213 620 int &binsPerPeak) const
Chris@1213 621 {
Chris@1213 622 peakCacheIndex = -1;
Chris@1213 623 binsPerPeak = -1;
Chris@1213 624
Chris@1469 625 auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1213 626 if (!model) return;
Chris@1217 627 if (m_params.binDisplay == BinDisplay::PeakFrequencies) return;
Chris@1217 628 if (m_params.colourScale.getScale() == ColourScaleType::Phase) return;
Chris@1213 629
Chris@1325 630 ZoomLevel zoomLevel = v->getZoomLevel();
Chris@1213 631 int binResolution = model->getResolution();
Chris@1213 632
Chris@1213 633 for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) {
Chris@1473 634 auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
Chris@1473 635 (m_sources.peakCaches[ix]);
Chris@1473 636 if (!peakCache) continue;
Chris@1473 637 int bpp = peakCache->getColumnsPerPeak();
Chris@1325 638 ZoomLevel equivZoom(ZoomLevel::FramesPerPixel, binResolution * bpp);
Chris@1450 639 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1450 640 SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
Chris@1450 641 << ", cache " << ix << " has bpp = " << bpp
Chris@1450 642 << " for equivZoom = " << equivZoom << endl;
Chris@1450 643 #endif
Chris@1213 644 if (zoomLevel >= equivZoom) {
Chris@1213 645 // this peak cache would work, though it might not be best
Chris@1213 646 if (bpp > binsPerPeak) {
Chris@1213 647 // ok, it's better than the best one we've found so far
Chris@1213 648 peakCacheIndex = ix;
Chris@1213 649 binsPerPeak = bpp;
Chris@1213 650 }
Chris@1213 651 }
Chris@1213 652 }
Chris@1213 653
Chris@1214 654 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1213 655 SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
Chris@1213 656 << ", binResolution " << binResolution
Chris@1213 657 << ", peakCaches " << m_sources.peakCaches.size()
Chris@1450 658 << ": preferring peakCacheIndex " << peakCacheIndex
Chris@1450 659 << " for binsPerPeak " << binsPerPeak
Chris@1213 660 << endl;
Chris@1214 661 #endif
Chris@1213 662 }
Chris@1213 663
Chris@1213 664 void
Chris@1113 665 Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v,
Chris@1094 666 int x0, int repaintWidth,
Chris@1094 667 bool rightToLeft,
Chris@1094 668 bool timeConstrained)
Chris@1079 669 {
Chris@1117 670 Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution");
Chris@1143 671 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 672 SVDEBUG << "renderToCachePixelResolution" << endl;
Chris@1143 673 #endif
Chris@1094 674
Chris@1094 675 // Draw to the draw buffer, and then copy from there. The draw
Chris@1094 676 // buffer is at the same resolution as the target in the cache, so
Chris@1094 677 // no extra scaling needed.
Chris@1079 678
Chris@1469 679 auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1469 680 if (!model) return;
Chris@1079 681
Chris@1079 682 int h = v->getPaintHeight();
Chris@1079 683
Chris@1094 684 clearDrawBuffer(repaintWidth, h);
Chris@1079 685
Chris@1094 686 vector<int> binforx(repaintWidth);
Chris@1079 687 vector<double> binfory(h);
Chris@1079 688
Chris@1094 689 int binResolution = model->getResolution();
Chris@1079 690
Chris@1094 691 for (int x = 0; x < repaintWidth; ++x) {
Chris@1094 692 sv_frame_t f0 = v->getFrameForX(x0 + x);
Chris@1094 693 double s0 = double(f0 - model->getStartFrame()) / binResolution;
Chris@1094 694 binforx[x] = int(s0 + 0.0001);
Chris@1094 695 }
Chris@1080 696
Chris@1212 697 int peakCacheIndex = -1;
Chris@1212 698 int binsPerPeak = -1;
Chris@1212 699
Chris@1217 700 getPreferredPeakCache(v, peakCacheIndex, binsPerPeak);
Chris@1094 701
Chris@1080 702 for (int y = 0; y < h; ++y) {
Chris@1090 703 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1080 704 }
Chris@1079 705
Chris@1097 706 int attainedWidth;
Chris@1097 707
Chris@1103 708 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1097 709 attainedWidth = renderDrawBufferPeakFrequencies(v,
Chris@1097 710 repaintWidth,
Chris@1097 711 h,
Chris@1097 712 binforx,
Chris@1097 713 binfory,
Chris@1097 714 rightToLeft,
Chris@1097 715 timeConstrained);
Chris@1097 716
Chris@1097 717 } else {
Chris@1097 718 attainedWidth = renderDrawBuffer(repaintWidth,
Chris@1080 719 h,
Chris@1080 720 binforx,
Chris@1080 721 binfory,
Chris@1212 722 peakCacheIndex,
Chris@1080 723 rightToLeft,
Chris@1080 724 timeConstrained);
Chris@1097 725 }
Chris@1083 726
Chris@1094 727 if (attainedWidth == 0) return;
Chris@1084 728
Chris@1094 729 // draw buffer is pixel resolution, no scaling factors or padding involved
Chris@1084 730
Chris@1084 731 int paintedLeft = x0;
Chris@1084 732 if (rightToLeft) {
Chris@1084 733 paintedLeft += (repaintWidth - attainedWidth);
Chris@1084 734 }
Chris@1084 735
Chris@1094 736 m_cache.drawImage(paintedLeft, attainedWidth,
Chris@1094 737 m_drawBuffer,
Chris@1094 738 paintedLeft - x0, attainedWidth);
Chris@1121 739
Chris@1121 740 for (int i = 0; in_range_for(m_magRanges, i); ++i) {
Chris@1121 741 m_magCache.sampleColumn(i, m_magRanges.at(i));
Chris@1121 742 }
Chris@1094 743 }
Chris@1084 744
Chris@1167 745 QImage
Chris@1167 746 Colour3DPlotRenderer::scaleDrawBufferImage(QImage image,
Chris@1167 747 int targetWidth,
Chris@1167 748 int targetHeight) const
Chris@1167 749 {
Chris@1167 750 int sourceWidth = image.width();
Chris@1167 751 int sourceHeight = image.height();
Chris@1167 752
Chris@1167 753 // We can only do this if we're making the image larger --
Chris@1167 754 // otherwise peaks may be lost. So this should be called only when
Chris@1167 755 // rendering in DrawBufferBinResolution mode. Whenever the bin
Chris@1167 756 // size is smaller than the pixel size, in either x or y axis, we
Chris@1167 757 // should be using DrawBufferPixelResolution mode instead
Chris@1167 758
Chris@1167 759 if (targetWidth < sourceWidth || targetHeight < sourceHeight) {
Chris@1167 760 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Can only use this function when making the image larger; should be rendering DrawBufferPixelResolution instead");
Chris@1167 761 }
Chris@1167 762
Chris@1167 763 if (sourceWidth <= 0 || sourceHeight <= 0) {
Chris@1167 764 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Source image is empty");
Chris@1167 765 }
Chris@1167 766
Chris@1167 767 if (targetWidth <= 0 || targetHeight <= 0) {
Chris@1167 768 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Target image is empty");
Chris@1167 769 }
Chris@1167 770
Chris@1167 771 // This function exists because of some unpredictable behaviour
Chris@1167 772 // from Qt when scaling images with FastTransformation mode. We
Chris@1167 773 // continue to use Qt's scaler for SmoothTransformation but let's
Chris@1167 774 // bring the non-interpolated version "in-house" so we know what
Chris@1167 775 // it's really doing.
Chris@1167 776
Chris@1167 777 if (m_params.interpolate) {
Chris@1167 778 return image.scaled(targetWidth, targetHeight,
Chris@1167 779 Qt::IgnoreAspectRatio,
Chris@1167 780 Qt::SmoothTransformation);
Chris@1167 781 }
Chris@1167 782
Chris@1167 783 // Same format as the target cache
Chris@1167 784 QImage target(targetWidth, targetHeight, QImage::Format_ARGB32_Premultiplied);
Chris@1167 785
Chris@1167 786 for (int y = 0; y < targetHeight; ++y) {
Chris@1167 787
Chris@1167 788 QRgb *targetLine = reinterpret_cast<QRgb *>(target.scanLine(y));
Chris@1167 789
Chris@1167 790 int sy = int((uint64_t(y) * sourceHeight) / targetHeight);
Chris@1167 791 if (sy == sourceHeight) --sy;
Chris@1167 792
Chris@1167 793 for (int x = 0; x < targetWidth; ++x) {
Chris@1167 794
Chris@1167 795 int sx = int((uint64_t(x) * sourceWidth) / targetWidth);
Chris@1167 796 if (sx == sourceWidth) --sx;
Chris@1167 797
Chris@1167 798 targetLine[x] = image.pixel(sx, sy);
Chris@1167 799 }
Chris@1167 800 }
Chris@1167 801
Chris@1167 802 return target;
Chris@1167 803 }
Chris@1167 804
Chris@1094 805 void
Chris@1113 806 Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v,
Chris@1094 807 int x0, int repaintWidth)
Chris@1094 808 {
Chris@1117 809 Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution");
Chris@1143 810 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 811 SVDEBUG << "renderToCacheBinResolution" << endl;
Chris@1143 812 #endif
Chris@1094 813
Chris@1094 814 // Draw to the draw buffer, and then scale-copy from there. Draw
Chris@1094 815 // buffer is at bin resolution, i.e. buffer x == source column
Chris@1094 816 // number. We use toolkit smooth scaling for interpolation.
Chris@1084 817
Chris@1469 818 auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
Chris@1469 819 if (!model) return;
Chris@1094 820
Chris@1094 821 // The draw buffer will contain a fragment at bin resolution. We
Chris@1094 822 // need to ensure that it starts and ends at points where a
Chris@1094 823 // time-bin boundary occurs at an exact pixel boundary, and with a
Chris@1094 824 // certain amount of overlap across existing pixels so that we can
Chris@1094 825 // scale and draw from it without smoothing errors at the edges.
Chris@1094 826
Chris@1094 827 // If (getFrameForX(x) / increment) * increment ==
Chris@1094 828 // getFrameForX(x), then x is a time-bin boundary. We want two
Chris@1094 829 // such boundaries at either side of the draw buffer -- one which
Chris@1094 830 // we draw up to, and one which we subsequently crop at.
Chris@1094 831
Chris@1094 832 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
Chris@1094 833 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
Chris@1094 834
Chris@1094 835 int drawBufferWidth;
Chris@1094 836 int binResolution = model->getResolution();
Chris@1094 837
Chris@1330 838 // These loops should eventually terminate provided that
Chris@1330 839 // getFrameForX always returns a multiple of the zoom level,
Chris@1330 840 // i.e. there is some x for which getFrameForX(x) == 0 and
Chris@1330 841 // subsequent return values are equally spaced
Chris@1329 842
Chris@1330 843 for (int x = x0; ; --x) {
Chris@1094 844 sv_frame_t f = v->getFrameForX(x);
Chris@1330 845 if ((f / binResolution) * binResolution == f) {
Chris@1094 846 if (leftCropFrame == -1) leftCropFrame = f;
Chris@1094 847 else if (x < x0 - 2) {
Chris@1094 848 leftBoundaryFrame = f;
Chris@1094 849 break;
Chris@1094 850 }
Chris@1094 851 }
Chris@1094 852 }
Chris@1329 853
Chris@1330 854 for (int x = x0 + repaintWidth; ; ++x) {
Chris@1094 855 sv_frame_t f = v->getFrameForX(x);
Chris@1330 856 if ((f / binResolution) * binResolution == f) {
Chris@1494 857 if (v->getXForFrame(f) < x0 + repaintWidth) {
Chris@1494 858 continue;
Chris@1494 859 }
Chris@1094 860 if (rightCropFrame == -1) rightCropFrame = f;
Chris@1094 861 else if (x > x0 + repaintWidth + 2) {
Chris@1094 862 rightBoundaryFrame = f;
Chris@1094 863 break;
Chris@1094 864 }
Chris@1094 865 }
Chris@1094 866 }
Chris@1329 867
Chris@1094 868 drawBufferWidth = int
Chris@1094 869 ((rightBoundaryFrame - leftBoundaryFrame) / binResolution);
Chris@1094 870
Chris@1094 871 int h = v->getPaintHeight();
Chris@1094 872
Chris@1095 873 // For our purposes here, the draw buffer needs to be exactly our
Chris@1095 874 // target size (so we recreate always rather than just clear it)
Chris@1095 875
Chris@1095 876 recreateDrawBuffer(drawBufferWidth, h);
Chris@1094 877
Chris@1094 878 vector<int> binforx(drawBufferWidth);
Chris@1094 879 vector<double> binfory(h);
Chris@1094 880
Chris@1094 881 for (int x = 0; x < drawBufferWidth; ++x) {
Chris@1094 882 binforx[x] = int(leftBoundaryFrame / binResolution) + x;
Chris@1094 883 }
Chris@1094 884
Chris@1214 885 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1167 886 SVDEBUG << "[BIN] binResolution " << binResolution << endl;
Chris@1214 887 #endif
Chris@1094 888
Chris@1094 889 for (int y = 0; y < h; ++y) {
Chris@1094 890 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1094 891 }
Chris@1094 892
Chris@1094 893 int attainedWidth = renderDrawBuffer(drawBufferWidth,
Chris@1094 894 h,
Chris@1094 895 binforx,
Chris@1094 896 binfory,
Chris@1212 897 -1,
Chris@1094 898 false,
Chris@1094 899 false);
Chris@1094 900
Chris@1094 901 if (attainedWidth == 0) return;
Chris@1094 902
Chris@1094 903 int scaledLeft = v->getXForFrame(leftBoundaryFrame);
Chris@1094 904 int scaledRight = v->getXForFrame(rightBoundaryFrame);
Chris@1095 905
Chris@1143 906 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1212 907 SVDEBUG << "scaling draw buffer from width " << m_drawBuffer.width()
Chris@1494 908 << " to " << (scaledRight - scaledLeft)
Chris@1494 909 << " (nb drawBufferWidth = "
Chris@1494 910 << drawBufferWidth << ", attainedWidth = "
Chris@1494 911 << attainedWidth << ")" << endl;
Chris@1143 912 #endif
Chris@1167 913
Chris@1167 914 QImage scaled = scaleDrawBufferImage
Chris@1167 915 (m_drawBuffer, scaledRight - scaledLeft, h);
Chris@1084 916
Chris@1094 917 int scaledLeftCrop = v->getXForFrame(leftCropFrame);
Chris@1094 918 int scaledRightCrop = v->getXForFrame(rightCropFrame);
Chris@1094 919
Chris@1094 920 int targetLeft = scaledLeftCrop;
Chris@1094 921 if (targetLeft < 0) {
Chris@1094 922 targetLeft = 0;
Chris@1094 923 }
Chris@1094 924
Chris@1094 925 int targetWidth = scaledRightCrop - targetLeft;
Chris@1094 926 if (targetLeft + targetWidth > m_cache.getSize().width()) {
Chris@1094 927 targetWidth = m_cache.getSize().width() - targetLeft;
Chris@1094 928 }
Chris@1094 929
Chris@1094 930 int sourceLeft = targetLeft - scaledLeft;
Chris@1094 931 if (sourceLeft < 0) {
Chris@1094 932 sourceLeft = 0;
Chris@1094 933 }
Chris@1494 934
Chris@1494 935 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1494 936 SVDEBUG << "leftBoundaryFrame = " << leftBoundaryFrame
Chris@1494 937 << ", leftCropFrame = " << leftCropFrame
Chris@1494 938 << ", scaledLeft = " << scaledLeft
Chris@1494 939 << ", scaledLeftCrop = " << scaledLeftCrop
Chris@1494 940 << endl;
Chris@1494 941 SVDEBUG << "rightBoundaryFrame = " << rightBoundaryFrame
Chris@1494 942 << ", rightCropFrame = " << rightCropFrame
Chris@1494 943 << ", scaledRight = " << scaledRight
Chris@1494 944 << ", scaledRightCrop = " << scaledRightCrop
Chris@1494 945 << endl;
Chris@1494 946 #endif
Chris@1094 947
Chris@1143 948 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1494 949 SVDEBUG << "x0 = " << x0
Chris@1494 950 << ", repaintWidth = " << repaintWidth
Chris@1494 951 << ", targetLeft = " << targetLeft
Chris@1214 952 << ", targetWidth = " << targetWidth << endl;
Chris@1143 953 #endif
Chris@1094 954
Chris@1094 955 if (targetWidth > 0) {
Chris@1136 956 // we are copying from an image that has already been scaled,
Chris@1136 957 // hence using the same width in both geometries
Chris@1094 958 m_cache.drawImage(targetLeft, targetWidth,
Chris@1094 959 scaled,
Chris@1136 960 sourceLeft, targetWidth);
Chris@1084 961 }
Chris@1121 962
Chris@1121 963 for (int i = 0; i < targetWidth; ++i) {
Chris@1136 964 // but the mag range vector has not been scaled
Chris@1136 965 int sourceIx = int((double(i + sourceLeft) / scaled.width())
Chris@1136 966 * int(m_magRanges.size()));
Chris@1121 967 if (in_range_for(m_magRanges, sourceIx)) {
Chris@1121 968 m_magCache.sampleColumn(i, m_magRanges.at(sourceIx));
Chris@1121 969 }
Chris@1121 970 }
Chris@1079 971 }
Chris@1083 972
Chris@1083 973 int
Chris@1083 974 Colour3DPlotRenderer::renderDrawBuffer(int w, int h,
Chris@1083 975 const vector<int> &binforx,
Chris@1083 976 const vector<double> &binfory,
Chris@1212 977 int peakCacheIndex,
Chris@1083 978 bool rightToLeft,
Chris@1083 979 bool timeConstrained)
Chris@1083 980 {
Chris@1083 981 // Callers must have checked that the appropriate subset of
Chris@1083 982 // Sources data members are set for the supplied flags (e.g. that
Chris@1212 983 // peakCache corresponding to peakCacheIndex exists)
Chris@1083 984
Chris@1083 985 RenderTimer timer(timeConstrained ?
Chris@1083 986 RenderTimer::FastRender :
Chris@1083 987 RenderTimer::NoTimeout);
Chris@1083 988
Chris@1164 989 Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
Chris@1164 990
Chris@1083 991 int divisor = 1;
Chris@1473 992
Chris@1473 993 std::shared_ptr<DenseThreeDimensionalModel> sourceModel;
Chris@1473 994
Chris@1473 995 if (peakCacheIndex >= 0) {
Chris@1473 996 auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
Chris@1473 997 (m_sources.peakCaches[peakCacheIndex]);
Chris@1473 998 if (peakCache) {
Chris@1473 999 divisor = peakCache->getColumnsPerPeak();
Chris@1473 1000 sourceModel = peakCache;
Chris@1473 1001 }
Chris@1473 1002 }
Chris@1473 1003
Chris@1473 1004 if (!sourceModel) {
Chris@1473 1005 sourceModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 1006 (m_sources.source);
Chris@1473 1007 }
Chris@1469 1008
Chris@1473 1009 if (!sourceModel) return 0;
Chris@1083 1010
Chris@1214 1011 #ifdef DEBUG_COLOUR_PLOT_REPAINT
cannam@1171 1012 SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h
Chris@1212 1013 << ", peakCacheIndex = " << peakCacheIndex << " (divisor = "
cannam@1171 1014 << divisor << "), rightToLeft = " << rightToLeft
cannam@1171 1015 << ", timeConstrained = " << timeConstrained << endl;
cannam@1171 1016 SVDEBUG << "renderDrawBuffer: normalization = " << int(m_params.normalization)
cannam@1171 1017 << ", binDisplay = " << int(m_params.binDisplay)
cannam@1171 1018 << ", binScale = " << int(m_params.binScale)
cannam@1171 1019 << ", alwaysOpaque = " << m_params.alwaysOpaque
cannam@1171 1020 << ", interpolate = " << m_params.interpolate << endl;
Chris@1214 1021 #endif
cannam@1171 1022
Chris@1135 1023 int sh = sourceModel->getHeight();
Chris@1135 1024
Chris@1135 1025 int minbin = int(binfory[0] + 0.0001);
Chris@1135 1026 if (minbin >= sh) minbin = sh - 1;
Chris@1135 1027 if (minbin < 0) minbin = 0;
Chris@1135 1028
Chris@1170 1029 int nbins = int(binfory[h-1] + 0.0001) - minbin + 1;
Chris@1135 1030 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1162 1031
Chris@1162 1032 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1033 SVDEBUG << "minbin = " << minbin << ", nbins = " << nbins << ", last binfory = "
Chris@1170 1034 << binfory[h-1] << " (rounds to " << int(binfory[h-1]) << ") (model height " << sh << ")" << endl;
Chris@1162 1035 #endif
Chris@1135 1036
Chris@1083 1037 int psx = -1;
Chris@1083 1038
Chris@1083 1039 int start = 0;
Chris@1083 1040 int finish = w;
Chris@1083 1041 int step = 1;
Chris@1083 1042
Chris@1083 1043 if (rightToLeft) {
Chris@1083 1044 start = w-1;
Chris@1083 1045 finish = -1;
Chris@1083 1046 step = -1;
Chris@1083 1047 }
Chris@1083 1048
Chris@1221 1049 int xPixelCount = 0;
Chris@1083 1050
Chris@1083 1051 vector<float> preparedColumn;
Chris@1094 1052
Chris@1094 1053 int modelWidth = sourceModel->getWidth();
Chris@1121 1054
Chris@1143 1055 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1056 SVDEBUG << "modelWidth " << modelWidth << ", divisor " << divisor << endl;
Chris@1451 1057 SVDEBUG << "start = " << start << ", finish = " << finish << ", step = " << step << endl;
Chris@1143 1058 #endif
Chris@1143 1059
Chris@1083 1060 for (int x = start; x != finish; x += step) {
Chris@1083 1061
Chris@1083 1062 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1083 1063 // source column index
Chris@1083 1064
Chris@1221 1065 ++xPixelCount;
Chris@1083 1066
Chris@1083 1067 if (binforx[x] < 0) continue;
Chris@1083 1068
Chris@1083 1069 int sx0 = binforx[x] / divisor;
Chris@1083 1070 int sx1 = sx0;
Chris@1083 1071 if (x+1 < w) sx1 = binforx[x+1] / divisor;
Chris@1083 1072 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1083 1073 if (sx0 < 0) continue;
Chris@1083 1074 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1083 1075
Chris@1161 1076 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1077 // SVDEBUG << "x = " << x << ", binforx[x] = " << binforx[x] << ", sx range " << sx0 << " -> " << sx1 << endl;
Chris@1161 1078 #endif
Chris@1161 1079
Chris@1083 1080 vector<float> pixelPeakColumn;
Chris@1121 1081 MagnitudeRange magRange;
Chris@1083 1082
Chris@1083 1083 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1083 1084
Chris@1094 1085 if (sx < 0 || sx >= modelWidth) {
Chris@1083 1086 continue;
Chris@1083 1087 }
Chris@1083 1088
Chris@1083 1089 if (sx != psx) {
Chris@1138 1090
Chris@1138 1091 // order:
Chris@1138 1092 // get column -> scale -> normalise -> record extents ->
Chris@1138 1093 // peak pick -> distribute/interpolate -> apply display gain
Chris@1083 1094
Chris@1138 1095 // this does the first three:
Chris@1161 1096 ColumnOp::Column column = getColumn(sx, minbin, nbins,
Chris@1212 1097 peakCacheIndex);
Chris@1083 1098
Chris@1131 1099 magRange.sample(column);
Chris@1162 1100
Chris@1103 1101 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1083 1102 column = ColumnOp::peakPick(column);
Chris@1083 1103 }
Chris@1083 1104
Chris@1083 1105 preparedColumn =
Chris@1124 1106 ColumnOp::distribute(column,
Chris@1083 1107 h,
Chris@1083 1108 binfory,
Chris@1083 1109 minbin,
Chris@1083 1110 m_params.interpolate);
Chris@1124 1111
Chris@1124 1112 // Display gain belongs to the colour scale and is
Chris@1124 1113 // applied by the colour scale object when mapping it
Chris@1083 1114
Chris@1083 1115 psx = sx;
Chris@1083 1116 }
Chris@1083 1117
Chris@1083 1118 if (sx == sx0) {
Chris@1083 1119 pixelPeakColumn = preparedColumn;
Chris@1083 1120 } else {
Chris@1083 1121 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1083 1122 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1083 1123 preparedColumn[i]);
Chris@1083 1124 }
Chris@1083 1125 }
Chris@1083 1126 }
Chris@1083 1127
Chris@1083 1128 if (!pixelPeakColumn.empty()) {
Chris@1121 1129
Chris@1083 1130 for (int y = 0; y < h; ++y) {
Chris@1116 1131 int py;
Chris@1116 1132 if (m_params.invertVertical) {
Chris@1116 1133 py = y;
Chris@1116 1134 } else {
Chris@1116 1135 py = h - y - 1;
Chris@1116 1136 }
Chris@1083 1137 m_drawBuffer.setPixel
Chris@1083 1138 (x,
Chris@1116 1139 py,
Chris@1083 1140 m_params.colourScale.getPixel(pixelPeakColumn[y]));
Chris@1083 1141 }
Chris@1121 1142
Chris@1121 1143 m_magRanges.push_back(magRange);
Chris@1083 1144 }
Chris@1083 1145
Chris@1221 1146 double fractionComplete = double(xPixelCount) / double(w);
Chris@1083 1147 if (timer.outOfTime(fractionComplete)) {
Chris@1214 1148 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1451 1149 SVDEBUG << "out of time with xPixelCount = " << xPixelCount << endl;
Chris@1214 1150 #endif
Chris@1221 1151 updateTimings(timer, xPixelCount);
Chris@1221 1152 return xPixelCount;
Chris@1083 1153 }
Chris@1083 1154 }
Chris@1083 1155
Chris@1221 1156 updateTimings(timer, xPixelCount);
Chris@1451 1157
Chris@1451 1158 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1451 1159 SVDEBUG << "completed with xPixelCount = " << xPixelCount << endl;
Chris@1451 1160 #endif
Chris@1221 1161 return xPixelCount;
Chris@1083 1162 }
Chris@1083 1163
Chris@1097 1164 int
Chris@1113 1165 Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097 1166 int w, int h,
Chris@1097 1167 const vector<int> &binforx,
Chris@1097 1168 const vector<double> &binfory,
Chris@1097 1169 bool rightToLeft,
Chris@1097 1170 bool timeConstrained)
Chris@1097 1171 {
Chris@1097 1172 // Callers must have checked that the appropriate subset of
Chris@1097 1173 // Sources data members are set for the supplied flags (e.g. that
Chris@1097 1174 // fft model exists)
Chris@1097 1175
Chris@1097 1176 RenderTimer timer(timeConstrained ?
Chris@1221 1177 RenderTimer::SlowRender :
Chris@1097 1178 RenderTimer::NoTimeout);
Chris@1097 1179
Chris@1469 1180 auto fft = ModelById::getAs<FFTModel>(m_sources.fft);
Chris@1469 1181 if (!fft) return 0;
Chris@1135 1182
Chris@1135 1183 int sh = fft->getHeight();
Chris@1135 1184
Chris@1097 1185 int minbin = int(binfory[0] + 0.0001);
Chris@1135 1186 if (minbin >= sh) minbin = sh - 1;
Chris@1097 1187 if (minbin < 0) minbin = 0;
Chris@1097 1188
Chris@1135 1189 int nbins = int(binfory[h-1]) - minbin + 1;
Chris@1135 1190 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1097 1191
Chris@1097 1192 FFTModel::PeakSet peakfreqs;
Chris@1097 1193
Chris@1097 1194 int psx = -1;
Chris@1097 1195
Chris@1097 1196 int start = 0;
Chris@1097 1197 int finish = w;
Chris@1097 1198 int step = 1;
Chris@1097 1199
Chris@1097 1200 if (rightToLeft) {
Chris@1097 1201 start = w-1;
Chris@1097 1202 finish = -1;
Chris@1097 1203 step = -1;
Chris@1097 1204 }
Chris@1097 1205
Chris@1221 1206 int xPixelCount = 0;
Chris@1097 1207
Chris@1097 1208 vector<float> preparedColumn;
Chris@1097 1209
Chris@1097 1210 int modelWidth = fft->getWidth();
Chris@1143 1211 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1212 SVDEBUG << "modelWidth " << modelWidth << endl;
Chris@1143 1213 #endif
Chris@1143 1214
Chris@1135 1215 double minFreq =
Chris@1135 1216 (double(minbin) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1135 1217 double maxFreq =
Chris@1135 1218 (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1097 1219
Chris@1103 1220 bool logarithmic = (m_params.binScale == BinScale::Log);
Chris@1217 1221
Chris@1218 1222 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1223 SVDEBUG << "start = " << start << ", finish = " << finish
Chris@1218 1224 << ", step = " << step << endl;
Chris@1218 1225 #endif
Chris@1218 1226
Chris@1097 1227 for (int x = start; x != finish; x += step) {
Chris@1097 1228
Chris@1097 1229 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1097 1230 // source column index
Chris@1097 1231
Chris@1221 1232 ++xPixelCount;
Chris@1097 1233
Chris@1097 1234 if (binforx[x] < 0) continue;
Chris@1097 1235
Chris@1097 1236 int sx0 = binforx[x];
Chris@1097 1237 int sx1 = sx0;
Chris@1097 1238 if (x+1 < w) sx1 = binforx[x+1];
Chris@1097 1239 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1097 1240 if (sx0 < 0) continue;
Chris@1097 1241 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1097 1242
Chris@1097 1243 vector<float> pixelPeakColumn;
Chris@1121 1244 MagnitudeRange magRange;
Chris@1097 1245
Chris@1097 1246 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1097 1247
Chris@1097 1248 if (sx < 0 || sx >= modelWidth) {
Chris@1097 1249 continue;
Chris@1097 1250 }
Chris@1097 1251
Chris@1097 1252 if (sx != psx) {
Chris@1219 1253 preparedColumn = getColumn(sx, minbin, nbins, -1);
Chris@1131 1254 magRange.sample(preparedColumn);
Chris@1097 1255 psx = sx;
Chris@1097 1256 }
Chris@1097 1257
Chris@1097 1258 if (sx == sx0) {
Chris@1097 1259 pixelPeakColumn = preparedColumn;
Chris@1097 1260 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx,
Chris@1135 1261 minbin, minbin + nbins - 1);
Chris@1097 1262 } else {
Chris@1097 1263 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1097 1264 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1097 1265 preparedColumn[i]);
Chris@1097 1266 }
Chris@1097 1267 }
Chris@1097 1268 }
Chris@1097 1269
Chris@1097 1270 if (!pixelPeakColumn.empty()) {
Chris@1164 1271
Chris@1218 1272 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1221 1273 // SVDEBUG << "found " << peakfreqs.size() << " peak freqs at column "
Chris@1221 1274 // << sx0 << endl;
Chris@1218 1275 #endif
Chris@1218 1276
Chris@1097 1277 for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin();
Chris@1097 1278 pi != peakfreqs.end(); ++pi) {
Chris@1097 1279
Chris@1097 1280 int bin = pi->first;
Chris@1097 1281 double freq = pi->second;
Chris@1097 1282
Chris@1097 1283 if (bin < minbin) continue;
Chris@1135 1284 if (bin >= minbin + nbins) break;
Chris@1097 1285
Chris@1097 1286 double value = pixelPeakColumn[bin - minbin];
Chris@1097 1287
Chris@1097 1288 double y = v->getYForFrequency
Chris@1097 1289 (freq, minFreq, maxFreq, logarithmic);
Chris@1097 1290
Chris@1097 1291 int iy = int(y + 0.5);
Chris@1097 1292 if (iy < 0 || iy >= h) continue;
Chris@1097 1293
Chris@1219 1294 auto pixel = m_params.colourScale.getPixel(value);
Chris@1219 1295
Chris@1219 1296 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1219 1297 // SVDEBUG << "frequency " << freq << " for bin " << bin
Chris@1219 1298 // << " -> y = " << y << ", iy = " << iy << ", value = "
Chris@1219 1299 // << value << ", pixel " << pixel << "\n";
Chris@1219 1300 #endif
Chris@1219 1301
Chris@1219 1302 m_drawBuffer.setPixel(x, iy, pixel);
Chris@1097 1303 }
Chris@1121 1304
Chris@1121 1305 m_magRanges.push_back(magRange);
Chris@1218 1306
Chris@1218 1307 } else {
Chris@1218 1308 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1309 SVDEBUG << "pixel peak column for range " << sx0 << " to " << sx1
Chris@1218 1310 << " is empty" << endl;
Chris@1218 1311 #endif
Chris@1097 1312 }
Chris@1097 1313
Chris@1221 1314 double fractionComplete = double(xPixelCount) / double(w);
Chris@1097 1315 if (timer.outOfTime(fractionComplete)) {
Chris@1218 1316 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1317 SVDEBUG << "out of time" << endl;
Chris@1218 1318 #endif
Chris@1221 1319 updateTimings(timer, xPixelCount);
Chris@1221 1320 return xPixelCount;
Chris@1097 1321 }
Chris@1097 1322 }
Chris@1097 1323
Chris@1221 1324 updateTimings(timer, xPixelCount);
Chris@1221 1325 return xPixelCount;
Chris@1221 1326 }
Chris@1221 1327
Chris@1221 1328 void
Chris@1221 1329 Colour3DPlotRenderer::updateTimings(const RenderTimer &timer, int xPixelCount)
Chris@1221 1330 {
Chris@1237 1331 double secondsPerXPixel = timer.secondsPerItem(xPixelCount);
Chris@1221 1332
Chris@1237 1333 // valid if we have enough data points, or if the overall time is
Chris@1237 1334 // massively slow anyway (as we definitely need to warn about that)
Chris@1237 1335 bool valid = (xPixelCount > 20 || secondsPerXPixel > 0.01);
Chris@1237 1336
Chris@1237 1337 if (valid) {
Chris@1237 1338 m_secondsPerXPixel = secondsPerXPixel;
Chris@1237 1339 m_secondsPerXPixelValid = true;
Chris@1237 1340
Chris@1221 1341 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1236 1342 SVDEBUG << "across " << xPixelCount << " x-pixels, seconds per x-pixel = "
Chris@1450 1343 << m_secondsPerXPixel << " (total = "
Chris@1450 1344 << (xPixelCount * m_secondsPerXPixel) << endl;
Chris@1221 1345 #endif
Chris@1237 1346 }
Chris@1097 1347 }
Chris@1097 1348
Chris@1079 1349 void
Chris@1095 1350 Colour3DPlotRenderer::recreateDrawBuffer(int w, int h)
Chris@1079 1351 {
Chris@1095 1352 m_drawBuffer = QImage(w, h, QImage::Format_Indexed8);
Chris@1079 1353
Chris@1095 1354 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@1095 1355 m_drawBuffer.setColor
Chris@1095 1356 ((unsigned char)pixel,
Chris@1112 1357 m_params.colourScale.getColourForPixel
Chris@1112 1358 (pixel, m_params.colourRotation).rgb());
Chris@1079 1359 }
Chris@1079 1360
Chris@1079 1361 m_drawBuffer.fill(0);
Chris@1121 1362 m_magRanges.clear();
Chris@1079 1363 }
Chris@1079 1364
Chris@1095 1365 void
Chris@1095 1366 Colour3DPlotRenderer::clearDrawBuffer(int w, int h)
Chris@1095 1367 {
Chris@1095 1368 if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) {
Chris@1095 1369 recreateDrawBuffer(w, h);
Chris@1095 1370 } else {
Chris@1095 1371 m_drawBuffer.fill(0);
Chris@1121 1372 m_magRanges.clear();
Chris@1095 1373 }
Chris@1095 1374 }
Chris@1079 1375
Chris@1139 1376 QRect
Chris@1139 1377 Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const
Chris@1139 1378 {
Chris@1139 1379 QImage image = m_cache.getImage();
Chris@1139 1380 ImageRegionFinder finder;
Chris@1139 1381 QRect rect = finder.findRegionExtents(&image, p);
Chris@1139 1382 return rect;
Chris@1139 1383 }