annotate layer/Colour3DPlotRenderer.cpp @ 1457:160e6d010141 single-point

Avoid showing progress bars for dormant layers. They'll typically be duplicates, e.g. in the vect app you can get many dormant layers all reporting the alignment completion at the same time. (Maybe it would be better just to reject progress bars for alignment in dormant layers? Hm)
author Chris Cannam
date Thu, 16 May 2019 15:50:02 +0100
parents f72fb7fac92a
children 11a150e65ee1
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@1265 311 SVCERR << "WARNING: failed to render entire requested rect "
Chris@1451 312 << "even when not time-constrained: wanted "
Chris@1450 313 << rect.x() << "," << rect.y() << " "
Chris@1451 314 << rect.width() << "x" << rect.height() << ", got "
Chris@1450 315 << pr.x() << "," << pr.y() << " "
Chris@1450 316 << pr.width() << "x" << pr.height()
Chris@1451 317 << ", after request of width " << (x1 - x0)
Chris@1450 318 << endl;
Chris@1079 319 }
Chris@1120 320
Chris@1122 321 MagnitudeRange range = m_magCache.getRange(reqx0, reqx1 - reqx0);
Chris@1412 322
Chris@1412 323 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1412 324 SVDEBUG << "render: returning rect rendered as " << pr.x() << "," << pr.y()
Chris@1412 325 << " " << pr.width() << "x" << pr.height() << endl;
Chris@1412 326 SVDEBUG << "render: mag range from cache in x-range " << reqx0
Chris@1412 327 << " to " << reqx1 << " is " << range.getMin() << " -> "
Chris@1412 328 << range.getMax() << endl;
Chris@1412 329 #endif
Chris@1120 330
Chris@1120 331 return { pr, range };
Chris@1073 332 }
Chris@1073 333
Chris@1109 334 Colour3DPlotRenderer::RenderType
Chris@1113 335 Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const
Chris@1094 336 {
Chris@1100 337 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1109 338 if (!model || !v || !(v->getViewManager())) {
Chris@1109 339 return DrawBufferPixelResolution; // or anything
Chris@1109 340 }
Chris@1109 341
Chris@1094 342 int binResolution = model->getResolution();
Chris@1325 343 ZoomLevel zoomLevel = v->getZoomLevel();
Chris@1109 344 sv_samplerate_t modelRate = model->getSampleRate();
Chris@1109 345
Chris@1109 346 double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate;
Chris@1109 347 double relativeBinResolution = binResolution * rateRatio;
Chris@1109 348
Chris@1109 349 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1109 350 // no alternative works here
Chris@1109 351 return DrawBufferPixelResolution;
Chris@1109 352 }
Chris@1109 353
Chris@1109 354 if (!m_params.alwaysOpaque && !m_params.interpolate) {
Chris@1109 355
Chris@1109 356 // consider translucent option -- only if not smoothing & not
Chris@1109 357 // explicitly requested opaque & sufficiently zoomed-in
Chris@1109 358
Chris@1117 359 if (model->getHeight() * 3 < v->getPaintHeight() &&
Chris@1325 360 zoomLevel < ZoomLevel(ZoomLevel::FramesPerPixel,
Chris@1325 361 int(round(relativeBinResolution / 3)))) {
Chris@1109 362 return DirectTranslucent;
Chris@1109 363 }
Chris@1109 364 }
Chris@1109 365
Chris@1325 366 if (ZoomLevel(ZoomLevel::FramesPerPixel,
Chris@1325 367 int(round(relativeBinResolution))) > zoomLevel) {
Chris@1109 368 return DrawBufferBinResolution;
Chris@1109 369 } else {
Chris@1109 370 return DrawBufferPixelResolution;
Chris@1109 371 }
Chris@1109 372 }
Chris@1109 373
Chris@1138 374 ColumnOp::Column
Chris@1161 375 Colour3DPlotRenderer::getColumn(int sx, int minbin, int nbins,
Chris@1212 376 int peakCacheIndex) const
Chris@1138 377 {
Chris@1138 378 // order:
Chris@1138 379 // get column -> scale -> normalise -> record extents ->
Chris@1138 380 // peak pick -> distribute/interpolate -> apply display gain
Chris@1138 381
Chris@1138 382 // we do the first bit here:
Chris@1138 383 // get column -> scale -> normalise
Chris@1138 384
Chris@1138 385 ColumnOp::Column column;
Chris@1364 386
Chris@1364 387 if (m_params.showDerivative && sx > 0) {
Chris@1364 388
Chris@1364 389 auto prev = getColumnRaw(sx - 1, minbin, nbins, peakCacheIndex);
Chris@1364 390 column = getColumnRaw(sx, minbin, nbins, peakCacheIndex);
Chris@1364 391
Chris@1364 392 for (int i = 0; i < nbins; ++i) {
Chris@1364 393 column[i] -= prev[i];
Chris@1364 394 }
Chris@1364 395
Chris@1364 396 } else {
Chris@1364 397 column = getColumnRaw(sx, minbin, nbins, peakCacheIndex);
Chris@1364 398 }
Chris@1364 399
Chris@1364 400 if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
Chris@1364 401 m_sources.fft) {
Chris@1364 402 return column;
Chris@1364 403 } else {
Chris@1364 404 column = ColumnOp::applyGain(column, m_params.scaleFactor);
Chris@1364 405 column = ColumnOp::normalize(column, m_params.normalization);
Chris@1364 406 return column;
Chris@1364 407 }
Chris@1364 408 }
Chris@1364 409
Chris@1364 410 ColumnOp::Column
Chris@1364 411 Colour3DPlotRenderer::getColumnRaw(int sx, int minbin, int nbins,
Chris@1364 412 int peakCacheIndex) const
Chris@1364 413 {
Chris@1364 414 Profiler profiler("Colour3DPlotRenderer::getColumn");
Chris@1364 415
Chris@1364 416 ColumnOp::Column column;
Chris@1364 417
Chris@1138 418 if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
Chris@1138 419 m_sources.fft) {
Chris@1138 420
Chris@1138 421 ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx);
Chris@1138 422
Chris@1138 423 column = vector<float>(fullColumn.data() + minbin,
Chris@1138 424 fullColumn.data() + minbin + nbins);
Chris@1138 425
Chris@1138 426 } else {
Chris@1212 427
Chris@1161 428 ColumnOp::Column fullColumn =
Chris@1212 429 (peakCacheIndex >= 0 ?
Chris@1212 430 m_sources.peakCaches[peakCacheIndex] :
Chris@1212 431 m_sources.source)
Chris@1212 432 ->getColumn(sx);
Chris@1138 433
Chris@1138 434 column = vector<float>(fullColumn.data() + minbin,
Chris@1138 435 fullColumn.data() + minbin + nbins);
Chris@1138 436 }
Chris@1138 437
Chris@1138 438 return column;
Chris@1138 439 }
Chris@1138 440
Chris@1121 441 MagnitudeRange
Chris@1113 442 Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1109 443 QPainter &paint,
Chris@1109 444 QRect rect)
Chris@1109 445 {
Chris@1117 446 Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent");
Chris@1117 447
Chris@1121 448 MagnitudeRange magRange;
Chris@1121 449
Chris@1115 450 QPoint illuminatePos;
Chris@1115 451 bool illuminate = v->shouldIlluminateLocalFeatures
Chris@1115 452 (m_sources.verticalBinLayer, illuminatePos);
Chris@1109 453
Chris@1109 454 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1109 455
Chris@1109 456 int x0 = rect.left();
Chris@1109 457 int x1 = rect.right() + 1;
Chris@1109 458
Chris@1109 459 int h = v->getPaintHeight();
Chris@1109 460
Chris@1109 461 sv_frame_t modelStart = model->getStartFrame();
Chris@1109 462 sv_frame_t modelEnd = model->getEndFrame();
Chris@1109 463 int modelResolution = model->getResolution();
Chris@1109 464
Chris@1109 465 double rateRatio =
Chris@1109 466 v->getViewManager()->getMainModelSampleRate() / model->getSampleRate();
Chris@1109 467
Chris@1109 468 // the s-prefix values are source, i.e. model, column and bin numbers
Chris@1109 469 int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart))
Chris@1109 470 / modelResolution);
Chris@1109 471 int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart))
Chris@1109 472 / modelResolution);
Chris@1109 473
Chris@1109 474 int sh = model->getHeight();
Chris@1109 475
Chris@1109 476 const int buflen = 40;
Chris@1109 477 char labelbuf[buflen];
Chris@1109 478
Chris@1133 479 int minbin = m_sources.verticalBinLayer->getIBinForY(v, h);
Chris@1135 480 if (minbin >= sh) minbin = sh - 1;
Chris@1135 481 if (minbin < 0) minbin = 0;
Chris@1135 482
Chris@1135 483 int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1;
Chris@1135 484 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1133 485
Chris@1109 486 int psx = -1;
Chris@1109 487
Chris@1109 488 vector<float> preparedColumn;
Chris@1109 489
Chris@1109 490 int modelWidth = model->getWidth();
Chris@1109 491
Chris@1109 492 for (int sx = sx0; sx <= sx1; ++sx) {
Chris@1109 493
Chris@1109 494 if (sx < 0 || sx >= modelWidth) {
Chris@1109 495 continue;
Chris@1109 496 }
Chris@1109 497
Chris@1109 498 if (sx != psx) {
Chris@1109 499
Chris@1138 500 // order:
Chris@1138 501 // get column -> scale -> normalise -> record extents ->
Chris@1138 502 // peak pick -> distribute/interpolate -> apply display gain
Chris@1109 503
Chris@1138 504 // this does the first three:
Chris@1219 505 preparedColumn = getColumn(sx, minbin, nbins, -1);
Chris@1131 506
Chris@1131 507 magRange.sample(preparedColumn);
Chris@1109 508
Chris@1109 509 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1115 510 preparedColumn = ColumnOp::peakPick(preparedColumn);
Chris@1109 511 }
Chris@1109 512
Chris@1124 513 // Display gain belongs to the colour scale and is
Chris@1124 514 // applied by the colour scale object when mapping it
Chris@1124 515
Chris@1109 516 psx = sx;
Chris@1109 517 }
Chris@1109 518
Chris@1266 519 sv_frame_t fx = sx * modelResolution + modelStart;
Chris@1109 520
Chris@1266 521 if (fx + modelResolution <= modelStart || fx > modelEnd) continue;
Chris@1109 522
Chris@1109 523 int rx0 = v->getXForFrame(int(double(fx) * rateRatio));
Chris@1266 524 int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio));
Chris@1109 525
Chris@1266 526 int rw = rx1 - rx0;
Chris@1266 527 if (rw < 1) rw = 1;
Chris@1109 528
Chris@1266 529 bool showLabel = (rw > 10 &&
Chris@1266 530 paint.fontMetrics().width("0.000000") < rw - 3 &&
Chris@1266 531 paint.fontMetrics().height() < (h / sh));
Chris@1109 532
Chris@1266 533 for (int sy = minbin; sy < minbin + nbins; ++sy) {
Chris@1109 534
Chris@1109 535 int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy);
Chris@1109 536 int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1);
Chris@1116 537
Chris@1116 538 if (m_params.invertVertical) {
Chris@1116 539 ry0 = h - ry0 - 1;
Chris@1116 540 ry1 = h - ry1 - 1;
Chris@1116 541 }
Chris@1116 542
Chris@1109 543 QRect r(rx0, ry1, rw, ry0 - ry1);
Chris@1109 544
Chris@1109 545 float value = preparedColumn[sy - minbin];
Chris@1112 546 QColor colour = m_params.colourScale.getColour(value,
Chris@1112 547 m_params.colourRotation);
Chris@1109 548
Chris@1109 549 if (rw == 1) {
Chris@1109 550 paint.setPen(colour);
Chris@1109 551 paint.setBrush(Qt::NoBrush);
Chris@1109 552 paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1);
Chris@1109 553 continue;
Chris@1109 554 }
Chris@1109 555
Chris@1266 556 QColor pen(255, 255, 255, 80);
Chris@1266 557 QColor brush(colour);
Chris@1109 558
Chris@1109 559 if (rw > 3 && r.height() > 3) {
Chris@1109 560 brush.setAlpha(160);
Chris@1109 561 }
Chris@1109 562
Chris@1266 563 paint.setPen(Qt::NoPen);
Chris@1266 564 paint.setBrush(brush);
Chris@1109 565
Chris@1266 566 if (illuminate) {
Chris@1266 567 if (r.contains(illuminatePos)) {
Chris@1266 568 paint.setPen(v->getForeground());
Chris@1266 569 }
Chris@1266 570 }
Chris@1109 571
Chris@1214 572 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 573 // SVDEBUG << "rect " << r.x() << "," << r.y() << " "
Chris@1109 574 // << r.width() << "x" << r.height() << endl;
Chris@1109 575 #endif
Chris@1109 576
Chris@1266 577 paint.drawRect(r);
Chris@1109 578
Chris@1266 579 if (showLabel) {
Chris@1109 580 double value = model->getValueAt(sx, sy);
Chris@1109 581 snprintf(labelbuf, buflen, "%06f", value);
Chris@1109 582 QString text(labelbuf);
Chris@1109 583 PaintAssistant::drawVisibleText
Chris@1109 584 (v,
Chris@1109 585 paint,
Chris@1109 586 rx0 + 2,
Chris@1109 587 ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
Chris@1109 588 text,
Chris@1109 589 PaintAssistant::OutlinedText);
Chris@1266 590 }
Chris@1266 591 }
Chris@1109 592 }
Chris@1121 593
Chris@1121 594 return magRange;
Chris@1094 595 }
Chris@1094 596
Chris@1080 597 void
Chris@1213 598 Colour3DPlotRenderer::getPreferredPeakCache(const LayerGeometryProvider *v,
Chris@1213 599 int &peakCacheIndex,
Chris@1213 600 int &binsPerPeak) const
Chris@1213 601 {
Chris@1213 602 peakCacheIndex = -1;
Chris@1213 603 binsPerPeak = -1;
Chris@1213 604
Chris@1213 605 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1213 606 if (!model) return;
Chris@1217 607 if (m_params.binDisplay == BinDisplay::PeakFrequencies) return;
Chris@1217 608 if (m_params.colourScale.getScale() == ColourScaleType::Phase) return;
Chris@1213 609
Chris@1325 610 ZoomLevel zoomLevel = v->getZoomLevel();
Chris@1213 611 int binResolution = model->getResolution();
Chris@1213 612
Chris@1213 613 for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) {
Chris@1213 614 int bpp = m_sources.peakCaches[ix]->getColumnsPerPeak();
Chris@1325 615 ZoomLevel equivZoom(ZoomLevel::FramesPerPixel, binResolution * bpp);
Chris@1450 616 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1450 617 SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
Chris@1450 618 << ", cache " << ix << " has bpp = " << bpp
Chris@1450 619 << " for equivZoom = " << equivZoom << endl;
Chris@1450 620 #endif
Chris@1213 621 if (zoomLevel >= equivZoom) {
Chris@1213 622 // this peak cache would work, though it might not be best
Chris@1213 623 if (bpp > binsPerPeak) {
Chris@1213 624 // ok, it's better than the best one we've found so far
Chris@1213 625 peakCacheIndex = ix;
Chris@1213 626 binsPerPeak = bpp;
Chris@1213 627 }
Chris@1213 628 }
Chris@1213 629 }
Chris@1213 630
Chris@1214 631 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1213 632 SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
Chris@1213 633 << ", binResolution " << binResolution
Chris@1213 634 << ", peakCaches " << m_sources.peakCaches.size()
Chris@1450 635 << ": preferring peakCacheIndex " << peakCacheIndex
Chris@1450 636 << " for binsPerPeak " << binsPerPeak
Chris@1213 637 << endl;
Chris@1214 638 #endif
Chris@1213 639 }
Chris@1213 640
Chris@1213 641 void
Chris@1113 642 Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v,
Chris@1094 643 int x0, int repaintWidth,
Chris@1094 644 bool rightToLeft,
Chris@1094 645 bool timeConstrained)
Chris@1079 646 {
Chris@1117 647 Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution");
Chris@1143 648 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 649 SVDEBUG << "renderToCachePixelResolution" << endl;
Chris@1143 650 #endif
Chris@1094 651
Chris@1094 652 // Draw to the draw buffer, and then copy from there. The draw
Chris@1094 653 // buffer is at the same resolution as the target in the cache, so
Chris@1094 654 // no extra scaling needed.
Chris@1079 655
Chris@1100 656 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1079 657 if (!model || !model->isOK() || !model->isReady()) {
Chris@1266 658 throw std::logic_error("no source model provided, or model not ready");
Chris@1079 659 }
Chris@1079 660
Chris@1079 661 int h = v->getPaintHeight();
Chris@1079 662
Chris@1094 663 clearDrawBuffer(repaintWidth, h);
Chris@1079 664
Chris@1094 665 vector<int> binforx(repaintWidth);
Chris@1079 666 vector<double> binfory(h);
Chris@1079 667
Chris@1094 668 int binResolution = model->getResolution();
Chris@1079 669
Chris@1094 670 for (int x = 0; x < repaintWidth; ++x) {
Chris@1094 671 sv_frame_t f0 = v->getFrameForX(x0 + x);
Chris@1094 672 double s0 = double(f0 - model->getStartFrame()) / binResolution;
Chris@1094 673 binforx[x] = int(s0 + 0.0001);
Chris@1094 674 }
Chris@1080 675
Chris@1212 676 int peakCacheIndex = -1;
Chris@1212 677 int binsPerPeak = -1;
Chris@1212 678
Chris@1217 679 getPreferredPeakCache(v, peakCacheIndex, binsPerPeak);
Chris@1094 680
Chris@1080 681 for (int y = 0; y < h; ++y) {
Chris@1090 682 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1080 683 }
Chris@1079 684
Chris@1097 685 int attainedWidth;
Chris@1097 686
Chris@1103 687 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1097 688 attainedWidth = renderDrawBufferPeakFrequencies(v,
Chris@1097 689 repaintWidth,
Chris@1097 690 h,
Chris@1097 691 binforx,
Chris@1097 692 binfory,
Chris@1097 693 rightToLeft,
Chris@1097 694 timeConstrained);
Chris@1097 695
Chris@1097 696 } else {
Chris@1097 697 attainedWidth = renderDrawBuffer(repaintWidth,
Chris@1080 698 h,
Chris@1080 699 binforx,
Chris@1080 700 binfory,
Chris@1212 701 peakCacheIndex,
Chris@1080 702 rightToLeft,
Chris@1080 703 timeConstrained);
Chris@1097 704 }
Chris@1083 705
Chris@1094 706 if (attainedWidth == 0) return;
Chris@1084 707
Chris@1094 708 // draw buffer is pixel resolution, no scaling factors or padding involved
Chris@1084 709
Chris@1084 710 int paintedLeft = x0;
Chris@1084 711 if (rightToLeft) {
Chris@1084 712 paintedLeft += (repaintWidth - attainedWidth);
Chris@1084 713 }
Chris@1084 714
Chris@1094 715 m_cache.drawImage(paintedLeft, attainedWidth,
Chris@1094 716 m_drawBuffer,
Chris@1094 717 paintedLeft - x0, attainedWidth);
Chris@1121 718
Chris@1121 719 for (int i = 0; in_range_for(m_magRanges, i); ++i) {
Chris@1121 720 m_magCache.sampleColumn(i, m_magRanges.at(i));
Chris@1121 721 }
Chris@1094 722 }
Chris@1084 723
Chris@1167 724 QImage
Chris@1167 725 Colour3DPlotRenderer::scaleDrawBufferImage(QImage image,
Chris@1167 726 int targetWidth,
Chris@1167 727 int targetHeight) const
Chris@1167 728 {
Chris@1167 729 int sourceWidth = image.width();
Chris@1167 730 int sourceHeight = image.height();
Chris@1167 731
Chris@1167 732 // We can only do this if we're making the image larger --
Chris@1167 733 // otherwise peaks may be lost. So this should be called only when
Chris@1167 734 // rendering in DrawBufferBinResolution mode. Whenever the bin
Chris@1167 735 // size is smaller than the pixel size, in either x or y axis, we
Chris@1167 736 // should be using DrawBufferPixelResolution mode instead
Chris@1167 737
Chris@1167 738 if (targetWidth < sourceWidth || targetHeight < sourceHeight) {
Chris@1167 739 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Can only use this function when making the image larger; should be rendering DrawBufferPixelResolution instead");
Chris@1167 740 }
Chris@1167 741
Chris@1167 742 if (sourceWidth <= 0 || sourceHeight <= 0) {
Chris@1167 743 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Source image is empty");
Chris@1167 744 }
Chris@1167 745
Chris@1167 746 if (targetWidth <= 0 || targetHeight <= 0) {
Chris@1167 747 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Target image is empty");
Chris@1167 748 }
Chris@1167 749
Chris@1167 750 // This function exists because of some unpredictable behaviour
Chris@1167 751 // from Qt when scaling images with FastTransformation mode. We
Chris@1167 752 // continue to use Qt's scaler for SmoothTransformation but let's
Chris@1167 753 // bring the non-interpolated version "in-house" so we know what
Chris@1167 754 // it's really doing.
Chris@1167 755
Chris@1167 756 if (m_params.interpolate) {
Chris@1167 757 return image.scaled(targetWidth, targetHeight,
Chris@1167 758 Qt::IgnoreAspectRatio,
Chris@1167 759 Qt::SmoothTransformation);
Chris@1167 760 }
Chris@1167 761
Chris@1167 762 // Same format as the target cache
Chris@1167 763 QImage target(targetWidth, targetHeight, QImage::Format_ARGB32_Premultiplied);
Chris@1167 764
Chris@1167 765 for (int y = 0; y < targetHeight; ++y) {
Chris@1167 766
Chris@1167 767 QRgb *targetLine = reinterpret_cast<QRgb *>(target.scanLine(y));
Chris@1167 768
Chris@1167 769 int sy = int((uint64_t(y) * sourceHeight) / targetHeight);
Chris@1167 770 if (sy == sourceHeight) --sy;
Chris@1167 771
Chris@1167 772 for (int x = 0; x < targetWidth; ++x) {
Chris@1167 773
Chris@1167 774 int sx = int((uint64_t(x) * sourceWidth) / targetWidth);
Chris@1167 775 if (sx == sourceWidth) --sx;
Chris@1167 776
Chris@1167 777 targetLine[x] = image.pixel(sx, sy);
Chris@1167 778 }
Chris@1167 779 }
Chris@1167 780
Chris@1167 781 return target;
Chris@1167 782 }
Chris@1167 783
Chris@1094 784 void
Chris@1113 785 Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v,
Chris@1094 786 int x0, int repaintWidth)
Chris@1094 787 {
Chris@1117 788 Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution");
Chris@1143 789 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 790 SVDEBUG << "renderToCacheBinResolution" << endl;
Chris@1143 791 #endif
Chris@1094 792
Chris@1094 793 // Draw to the draw buffer, and then scale-copy from there. Draw
Chris@1094 794 // buffer is at bin resolution, i.e. buffer x == source column
Chris@1094 795 // number. We use toolkit smooth scaling for interpolation.
Chris@1084 796
Chris@1100 797 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1094 798 if (!model || !model->isOK() || !model->isReady()) {
Chris@1266 799 throw std::logic_error("no source model provided, or model not ready");
Chris@1094 800 }
Chris@1094 801
Chris@1094 802 // The draw buffer will contain a fragment at bin resolution. We
Chris@1094 803 // need to ensure that it starts and ends at points where a
Chris@1094 804 // time-bin boundary occurs at an exact pixel boundary, and with a
Chris@1094 805 // certain amount of overlap across existing pixels so that we can
Chris@1094 806 // scale and draw from it without smoothing errors at the edges.
Chris@1094 807
Chris@1094 808 // If (getFrameForX(x) / increment) * increment ==
Chris@1094 809 // getFrameForX(x), then x is a time-bin boundary. We want two
Chris@1094 810 // such boundaries at either side of the draw buffer -- one which
Chris@1094 811 // we draw up to, and one which we subsequently crop at.
Chris@1094 812
Chris@1094 813 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
Chris@1094 814 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
Chris@1094 815
Chris@1094 816 int drawBufferWidth;
Chris@1094 817 int binResolution = model->getResolution();
Chris@1094 818
Chris@1330 819 // These loops should eventually terminate provided that
Chris@1330 820 // getFrameForX always returns a multiple of the zoom level,
Chris@1330 821 // i.e. there is some x for which getFrameForX(x) == 0 and
Chris@1330 822 // subsequent return values are equally spaced
Chris@1329 823
Chris@1330 824 for (int x = x0; ; --x) {
Chris@1094 825 sv_frame_t f = v->getFrameForX(x);
Chris@1330 826 if ((f / binResolution) * binResolution == f) {
Chris@1094 827 if (leftCropFrame == -1) leftCropFrame = f;
Chris@1094 828 else if (x < x0 - 2) {
Chris@1094 829 leftBoundaryFrame = f;
Chris@1094 830 break;
Chris@1094 831 }
Chris@1094 832 }
Chris@1094 833 }
Chris@1329 834
Chris@1330 835 for (int x = x0 + repaintWidth; ; ++x) {
Chris@1094 836 sv_frame_t f = v->getFrameForX(x);
Chris@1330 837 if ((f / binResolution) * binResolution == f) {
Chris@1094 838 if (rightCropFrame == -1) rightCropFrame = f;
Chris@1094 839 else if (x > x0 + repaintWidth + 2) {
Chris@1094 840 rightBoundaryFrame = f;
Chris@1094 841 break;
Chris@1094 842 }
Chris@1094 843 }
Chris@1094 844 }
Chris@1329 845
Chris@1094 846 drawBufferWidth = int
Chris@1094 847 ((rightBoundaryFrame - leftBoundaryFrame) / binResolution);
Chris@1094 848
Chris@1094 849 int h = v->getPaintHeight();
Chris@1094 850
Chris@1095 851 // For our purposes here, the draw buffer needs to be exactly our
Chris@1095 852 // target size (so we recreate always rather than just clear it)
Chris@1095 853
Chris@1095 854 recreateDrawBuffer(drawBufferWidth, h);
Chris@1094 855
Chris@1094 856 vector<int> binforx(drawBufferWidth);
Chris@1094 857 vector<double> binfory(h);
Chris@1094 858
Chris@1094 859 for (int x = 0; x < drawBufferWidth; ++x) {
Chris@1094 860 binforx[x] = int(leftBoundaryFrame / binResolution) + x;
Chris@1094 861 }
Chris@1094 862
Chris@1214 863 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1167 864 SVDEBUG << "[BIN] binResolution " << binResolution << endl;
Chris@1214 865 #endif
Chris@1094 866
Chris@1094 867 for (int y = 0; y < h; ++y) {
Chris@1094 868 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1094 869 }
Chris@1094 870
Chris@1094 871 int attainedWidth = renderDrawBuffer(drawBufferWidth,
Chris@1094 872 h,
Chris@1094 873 binforx,
Chris@1094 874 binfory,
Chris@1212 875 -1,
Chris@1094 876 false,
Chris@1094 877 false);
Chris@1094 878
Chris@1094 879 if (attainedWidth == 0) return;
Chris@1094 880
Chris@1094 881 int scaledLeft = v->getXForFrame(leftBoundaryFrame);
Chris@1094 882 int scaledRight = v->getXForFrame(rightBoundaryFrame);
Chris@1095 883
Chris@1143 884 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1212 885 SVDEBUG << "scaling draw buffer from width " << m_drawBuffer.width()
Chris@1212 886 << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = "
Chris@1212 887 << drawBufferWidth << ")" << endl;
Chris@1143 888 #endif
Chris@1167 889
Chris@1167 890 QImage scaled = scaleDrawBufferImage
Chris@1167 891 (m_drawBuffer, scaledRight - scaledLeft, h);
Chris@1084 892
Chris@1094 893 int scaledLeftCrop = v->getXForFrame(leftCropFrame);
Chris@1094 894 int scaledRightCrop = v->getXForFrame(rightCropFrame);
Chris@1094 895
Chris@1094 896 int targetLeft = scaledLeftCrop;
Chris@1094 897 if (targetLeft < 0) {
Chris@1094 898 targetLeft = 0;
Chris@1094 899 }
Chris@1094 900
Chris@1094 901 int targetWidth = scaledRightCrop - targetLeft;
Chris@1094 902 if (targetLeft + targetWidth > m_cache.getSize().width()) {
Chris@1094 903 targetWidth = m_cache.getSize().width() - targetLeft;
Chris@1094 904 }
Chris@1094 905
Chris@1094 906 int sourceLeft = targetLeft - scaledLeft;
Chris@1094 907 if (sourceLeft < 0) {
Chris@1094 908 sourceLeft = 0;
Chris@1094 909 }
Chris@1094 910
Chris@1143 911 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 912 SVDEBUG << "repaintWidth = " << repaintWidth
Chris@1214 913 << ", targetWidth = " << targetWidth << endl;
Chris@1143 914 #endif
Chris@1094 915
Chris@1094 916 if (targetWidth > 0) {
Chris@1136 917 // we are copying from an image that has already been scaled,
Chris@1136 918 // hence using the same width in both geometries
Chris@1094 919 m_cache.drawImage(targetLeft, targetWidth,
Chris@1094 920 scaled,
Chris@1136 921 sourceLeft, targetWidth);
Chris@1084 922 }
Chris@1121 923
Chris@1121 924 for (int i = 0; i < targetWidth; ++i) {
Chris@1136 925 // but the mag range vector has not been scaled
Chris@1136 926 int sourceIx = int((double(i + sourceLeft) / scaled.width())
Chris@1136 927 * int(m_magRanges.size()));
Chris@1121 928 if (in_range_for(m_magRanges, sourceIx)) {
Chris@1121 929 m_magCache.sampleColumn(i, m_magRanges.at(sourceIx));
Chris@1121 930 }
Chris@1121 931 }
Chris@1079 932 }
Chris@1083 933
Chris@1083 934 int
Chris@1083 935 Colour3DPlotRenderer::renderDrawBuffer(int w, int h,
Chris@1083 936 const vector<int> &binforx,
Chris@1083 937 const vector<double> &binfory,
Chris@1212 938 int peakCacheIndex,
Chris@1083 939 bool rightToLeft,
Chris@1083 940 bool timeConstrained)
Chris@1083 941 {
Chris@1083 942 // Callers must have checked that the appropriate subset of
Chris@1083 943 // Sources data members are set for the supplied flags (e.g. that
Chris@1212 944 // peakCache corresponding to peakCacheIndex exists)
Chris@1083 945
Chris@1083 946 RenderTimer timer(timeConstrained ?
Chris@1083 947 RenderTimer::FastRender :
Chris@1083 948 RenderTimer::NoTimeout);
Chris@1083 949
Chris@1164 950 Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
Chris@1164 951
Chris@1083 952 int divisor = 1;
Chris@1100 953 const DenseThreeDimensionalModel *sourceModel = m_sources.source;
Chris@1212 954 if (peakCacheIndex >= 0) {
Chris@1212 955 divisor = m_sources.peakCaches[peakCacheIndex]->getColumnsPerPeak();
Chris@1212 956 sourceModel = m_sources.peakCaches[peakCacheIndex];
Chris@1083 957 }
Chris@1083 958
Chris@1214 959 #ifdef DEBUG_COLOUR_PLOT_REPAINT
cannam@1171 960 SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h
Chris@1212 961 << ", peakCacheIndex = " << peakCacheIndex << " (divisor = "
cannam@1171 962 << divisor << "), rightToLeft = " << rightToLeft
cannam@1171 963 << ", timeConstrained = " << timeConstrained << endl;
cannam@1171 964 SVDEBUG << "renderDrawBuffer: normalization = " << int(m_params.normalization)
cannam@1171 965 << ", binDisplay = " << int(m_params.binDisplay)
cannam@1171 966 << ", binScale = " << int(m_params.binScale)
cannam@1171 967 << ", alwaysOpaque = " << m_params.alwaysOpaque
cannam@1171 968 << ", interpolate = " << m_params.interpolate << endl;
Chris@1214 969 #endif
cannam@1171 970
Chris@1135 971 int sh = sourceModel->getHeight();
Chris@1135 972
Chris@1135 973 int minbin = int(binfory[0] + 0.0001);
Chris@1135 974 if (minbin >= sh) minbin = sh - 1;
Chris@1135 975 if (minbin < 0) minbin = 0;
Chris@1135 976
Chris@1170 977 int nbins = int(binfory[h-1] + 0.0001) - minbin + 1;
Chris@1135 978 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1162 979
Chris@1162 980 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 981 SVDEBUG << "minbin = " << minbin << ", nbins = " << nbins << ", last binfory = "
Chris@1170 982 << binfory[h-1] << " (rounds to " << int(binfory[h-1]) << ") (model height " << sh << ")" << endl;
Chris@1162 983 #endif
Chris@1135 984
Chris@1083 985 int psx = -1;
Chris@1083 986
Chris@1083 987 int start = 0;
Chris@1083 988 int finish = w;
Chris@1083 989 int step = 1;
Chris@1083 990
Chris@1083 991 if (rightToLeft) {
Chris@1083 992 start = w-1;
Chris@1083 993 finish = -1;
Chris@1083 994 step = -1;
Chris@1083 995 }
Chris@1083 996
Chris@1221 997 int xPixelCount = 0;
Chris@1083 998
Chris@1083 999 vector<float> preparedColumn;
Chris@1094 1000
Chris@1094 1001 int modelWidth = sourceModel->getWidth();
Chris@1121 1002
Chris@1143 1003 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1004 SVDEBUG << "modelWidth " << modelWidth << ", divisor " << divisor << endl;
Chris@1451 1005 SVDEBUG << "start = " << start << ", finish = " << finish << ", step = " << step << endl;
Chris@1143 1006 #endif
Chris@1143 1007
Chris@1083 1008 for (int x = start; x != finish; x += step) {
Chris@1083 1009
Chris@1083 1010 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1083 1011 // source column index
Chris@1083 1012
Chris@1221 1013 ++xPixelCount;
Chris@1083 1014
Chris@1083 1015 if (binforx[x] < 0) continue;
Chris@1083 1016
Chris@1083 1017 int sx0 = binforx[x] / divisor;
Chris@1083 1018 int sx1 = sx0;
Chris@1083 1019 if (x+1 < w) sx1 = binforx[x+1] / divisor;
Chris@1083 1020 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1083 1021 if (sx0 < 0) continue;
Chris@1083 1022 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1083 1023
Chris@1161 1024 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1025 // SVDEBUG << "x = " << x << ", binforx[x] = " << binforx[x] << ", sx range " << sx0 << " -> " << sx1 << endl;
Chris@1161 1026 #endif
Chris@1161 1027
Chris@1083 1028 vector<float> pixelPeakColumn;
Chris@1121 1029 MagnitudeRange magRange;
Chris@1083 1030
Chris@1083 1031 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1083 1032
Chris@1094 1033 if (sx < 0 || sx >= modelWidth) {
Chris@1083 1034 continue;
Chris@1083 1035 }
Chris@1083 1036
Chris@1083 1037 if (sx != psx) {
Chris@1138 1038
Chris@1138 1039 // order:
Chris@1138 1040 // get column -> scale -> normalise -> record extents ->
Chris@1138 1041 // peak pick -> distribute/interpolate -> apply display gain
Chris@1083 1042
Chris@1138 1043 // this does the first three:
Chris@1161 1044 ColumnOp::Column column = getColumn(sx, minbin, nbins,
Chris@1212 1045 peakCacheIndex);
Chris@1083 1046
Chris@1131 1047 magRange.sample(column);
Chris@1162 1048
Chris@1103 1049 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1083 1050 column = ColumnOp::peakPick(column);
Chris@1083 1051 }
Chris@1083 1052
Chris@1083 1053 preparedColumn =
Chris@1124 1054 ColumnOp::distribute(column,
Chris@1083 1055 h,
Chris@1083 1056 binfory,
Chris@1083 1057 minbin,
Chris@1083 1058 m_params.interpolate);
Chris@1124 1059
Chris@1124 1060 // Display gain belongs to the colour scale and is
Chris@1124 1061 // applied by the colour scale object when mapping it
Chris@1083 1062
Chris@1083 1063 psx = sx;
Chris@1083 1064 }
Chris@1083 1065
Chris@1083 1066 if (sx == sx0) {
Chris@1083 1067 pixelPeakColumn = preparedColumn;
Chris@1083 1068 } else {
Chris@1083 1069 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1083 1070 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1083 1071 preparedColumn[i]);
Chris@1083 1072 }
Chris@1083 1073 }
Chris@1083 1074 }
Chris@1083 1075
Chris@1083 1076 if (!pixelPeakColumn.empty()) {
Chris@1121 1077
Chris@1083 1078 for (int y = 0; y < h; ++y) {
Chris@1116 1079 int py;
Chris@1116 1080 if (m_params.invertVertical) {
Chris@1116 1081 py = y;
Chris@1116 1082 } else {
Chris@1116 1083 py = h - y - 1;
Chris@1116 1084 }
Chris@1083 1085 m_drawBuffer.setPixel
Chris@1083 1086 (x,
Chris@1116 1087 py,
Chris@1083 1088 m_params.colourScale.getPixel(pixelPeakColumn[y]));
Chris@1083 1089 }
Chris@1121 1090
Chris@1121 1091 m_magRanges.push_back(magRange);
Chris@1083 1092 }
Chris@1083 1093
Chris@1221 1094 double fractionComplete = double(xPixelCount) / double(w);
Chris@1083 1095 if (timer.outOfTime(fractionComplete)) {
Chris@1214 1096 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1451 1097 SVDEBUG << "out of time with xPixelCount = " << xPixelCount << endl;
Chris@1214 1098 #endif
Chris@1221 1099 updateTimings(timer, xPixelCount);
Chris@1221 1100 return xPixelCount;
Chris@1083 1101 }
Chris@1083 1102 }
Chris@1083 1103
Chris@1221 1104 updateTimings(timer, xPixelCount);
Chris@1451 1105
Chris@1451 1106 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1451 1107 SVDEBUG << "completed with xPixelCount = " << xPixelCount << endl;
Chris@1451 1108 #endif
Chris@1221 1109 return xPixelCount;
Chris@1083 1110 }
Chris@1083 1111
Chris@1097 1112 int
Chris@1113 1113 Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097 1114 int w, int h,
Chris@1097 1115 const vector<int> &binforx,
Chris@1097 1116 const vector<double> &binfory,
Chris@1097 1117 bool rightToLeft,
Chris@1097 1118 bool timeConstrained)
Chris@1097 1119 {
Chris@1097 1120 // Callers must have checked that the appropriate subset of
Chris@1097 1121 // Sources data members are set for the supplied flags (e.g. that
Chris@1097 1122 // fft model exists)
Chris@1097 1123
Chris@1097 1124 RenderTimer timer(timeConstrained ?
Chris@1221 1125 RenderTimer::SlowRender :
Chris@1097 1126 RenderTimer::NoTimeout);
Chris@1097 1127
Chris@1135 1128 const FFTModel *fft = m_sources.fft;
Chris@1135 1129
Chris@1135 1130 int sh = fft->getHeight();
Chris@1135 1131
Chris@1097 1132 int minbin = int(binfory[0] + 0.0001);
Chris@1135 1133 if (minbin >= sh) minbin = sh - 1;
Chris@1097 1134 if (minbin < 0) minbin = 0;
Chris@1097 1135
Chris@1135 1136 int nbins = int(binfory[h-1]) - minbin + 1;
Chris@1135 1137 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1097 1138
Chris@1097 1139 FFTModel::PeakSet peakfreqs;
Chris@1097 1140
Chris@1097 1141 int psx = -1;
Chris@1097 1142
Chris@1097 1143 int start = 0;
Chris@1097 1144 int finish = w;
Chris@1097 1145 int step = 1;
Chris@1097 1146
Chris@1097 1147 if (rightToLeft) {
Chris@1097 1148 start = w-1;
Chris@1097 1149 finish = -1;
Chris@1097 1150 step = -1;
Chris@1097 1151 }
Chris@1097 1152
Chris@1221 1153 int xPixelCount = 0;
Chris@1097 1154
Chris@1097 1155 vector<float> preparedColumn;
Chris@1097 1156
Chris@1097 1157 int modelWidth = fft->getWidth();
Chris@1143 1158 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1214 1159 SVDEBUG << "modelWidth " << modelWidth << endl;
Chris@1143 1160 #endif
Chris@1143 1161
Chris@1135 1162 double minFreq =
Chris@1135 1163 (double(minbin) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1135 1164 double maxFreq =
Chris@1135 1165 (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1097 1166
Chris@1103 1167 bool logarithmic = (m_params.binScale == BinScale::Log);
Chris@1217 1168
Chris@1218 1169 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1170 SVDEBUG << "start = " << start << ", finish = " << finish
Chris@1218 1171 << ", step = " << step << endl;
Chris@1218 1172 #endif
Chris@1218 1173
Chris@1097 1174 for (int x = start; x != finish; x += step) {
Chris@1097 1175
Chris@1097 1176 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1097 1177 // source column index
Chris@1097 1178
Chris@1221 1179 ++xPixelCount;
Chris@1097 1180
Chris@1097 1181 if (binforx[x] < 0) continue;
Chris@1097 1182
Chris@1097 1183 int sx0 = binforx[x];
Chris@1097 1184 int sx1 = sx0;
Chris@1097 1185 if (x+1 < w) sx1 = binforx[x+1];
Chris@1097 1186 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1097 1187 if (sx0 < 0) continue;
Chris@1097 1188 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1097 1189
Chris@1097 1190 vector<float> pixelPeakColumn;
Chris@1121 1191 MagnitudeRange magRange;
Chris@1097 1192
Chris@1097 1193 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1097 1194
Chris@1097 1195 if (sx < 0 || sx >= modelWidth) {
Chris@1097 1196 continue;
Chris@1097 1197 }
Chris@1097 1198
Chris@1097 1199 if (sx != psx) {
Chris@1219 1200 preparedColumn = getColumn(sx, minbin, nbins, -1);
Chris@1131 1201 magRange.sample(preparedColumn);
Chris@1097 1202 psx = sx;
Chris@1097 1203 }
Chris@1097 1204
Chris@1097 1205 if (sx == sx0) {
Chris@1097 1206 pixelPeakColumn = preparedColumn;
Chris@1097 1207 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx,
Chris@1135 1208 minbin, minbin + nbins - 1);
Chris@1097 1209 } else {
Chris@1097 1210 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1097 1211 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1097 1212 preparedColumn[i]);
Chris@1097 1213 }
Chris@1097 1214 }
Chris@1097 1215 }
Chris@1097 1216
Chris@1097 1217 if (!pixelPeakColumn.empty()) {
Chris@1164 1218
Chris@1218 1219 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1221 1220 // SVDEBUG << "found " << peakfreqs.size() << " peak freqs at column "
Chris@1221 1221 // << sx0 << endl;
Chris@1218 1222 #endif
Chris@1218 1223
Chris@1097 1224 for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin();
Chris@1097 1225 pi != peakfreqs.end(); ++pi) {
Chris@1097 1226
Chris@1097 1227 int bin = pi->first;
Chris@1097 1228 double freq = pi->second;
Chris@1097 1229
Chris@1097 1230 if (bin < minbin) continue;
Chris@1135 1231 if (bin >= minbin + nbins) break;
Chris@1097 1232
Chris@1097 1233 double value = pixelPeakColumn[bin - minbin];
Chris@1097 1234
Chris@1097 1235 double y = v->getYForFrequency
Chris@1097 1236 (freq, minFreq, maxFreq, logarithmic);
Chris@1097 1237
Chris@1097 1238 int iy = int(y + 0.5);
Chris@1097 1239 if (iy < 0 || iy >= h) continue;
Chris@1097 1240
Chris@1219 1241 auto pixel = m_params.colourScale.getPixel(value);
Chris@1219 1242
Chris@1219 1243 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1219 1244 // SVDEBUG << "frequency " << freq << " for bin " << bin
Chris@1219 1245 // << " -> y = " << y << ", iy = " << iy << ", value = "
Chris@1219 1246 // << value << ", pixel " << pixel << "\n";
Chris@1219 1247 #endif
Chris@1219 1248
Chris@1219 1249 m_drawBuffer.setPixel(x, iy, pixel);
Chris@1097 1250 }
Chris@1121 1251
Chris@1121 1252 m_magRanges.push_back(magRange);
Chris@1218 1253
Chris@1218 1254 } else {
Chris@1218 1255 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1256 SVDEBUG << "pixel peak column for range " << sx0 << " to " << sx1
Chris@1218 1257 << " is empty" << endl;
Chris@1218 1258 #endif
Chris@1097 1259 }
Chris@1097 1260
Chris@1221 1261 double fractionComplete = double(xPixelCount) / double(w);
Chris@1097 1262 if (timer.outOfTime(fractionComplete)) {
Chris@1218 1263 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1218 1264 SVDEBUG << "out of time" << endl;
Chris@1218 1265 #endif
Chris@1221 1266 updateTimings(timer, xPixelCount);
Chris@1221 1267 return xPixelCount;
Chris@1097 1268 }
Chris@1097 1269 }
Chris@1097 1270
Chris@1221 1271 updateTimings(timer, xPixelCount);
Chris@1221 1272 return xPixelCount;
Chris@1221 1273 }
Chris@1221 1274
Chris@1221 1275 void
Chris@1221 1276 Colour3DPlotRenderer::updateTimings(const RenderTimer &timer, int xPixelCount)
Chris@1221 1277 {
Chris@1237 1278 double secondsPerXPixel = timer.secondsPerItem(xPixelCount);
Chris@1221 1279
Chris@1237 1280 // valid if we have enough data points, or if the overall time is
Chris@1237 1281 // massively slow anyway (as we definitely need to warn about that)
Chris@1237 1282 bool valid = (xPixelCount > 20 || secondsPerXPixel > 0.01);
Chris@1237 1283
Chris@1237 1284 if (valid) {
Chris@1237 1285 m_secondsPerXPixel = secondsPerXPixel;
Chris@1237 1286 m_secondsPerXPixelValid = true;
Chris@1237 1287
Chris@1221 1288 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1236 1289 SVDEBUG << "across " << xPixelCount << " x-pixels, seconds per x-pixel = "
Chris@1450 1290 << m_secondsPerXPixel << " (total = "
Chris@1450 1291 << (xPixelCount * m_secondsPerXPixel) << endl;
Chris@1221 1292 #endif
Chris@1237 1293 }
Chris@1097 1294 }
Chris@1097 1295
Chris@1079 1296 void
Chris@1095 1297 Colour3DPlotRenderer::recreateDrawBuffer(int w, int h)
Chris@1079 1298 {
Chris@1095 1299 m_drawBuffer = QImage(w, h, QImage::Format_Indexed8);
Chris@1079 1300
Chris@1095 1301 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@1095 1302 m_drawBuffer.setColor
Chris@1095 1303 ((unsigned char)pixel,
Chris@1112 1304 m_params.colourScale.getColourForPixel
Chris@1112 1305 (pixel, m_params.colourRotation).rgb());
Chris@1079 1306 }
Chris@1079 1307
Chris@1079 1308 m_drawBuffer.fill(0);
Chris@1121 1309 m_magRanges.clear();
Chris@1079 1310 }
Chris@1079 1311
Chris@1095 1312 void
Chris@1095 1313 Colour3DPlotRenderer::clearDrawBuffer(int w, int h)
Chris@1095 1314 {
Chris@1095 1315 if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) {
Chris@1095 1316 recreateDrawBuffer(w, h);
Chris@1095 1317 } else {
Chris@1095 1318 m_drawBuffer.fill(0);
Chris@1121 1319 m_magRanges.clear();
Chris@1095 1320 }
Chris@1095 1321 }
Chris@1079 1322
Chris@1139 1323 QRect
Chris@1139 1324 Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const
Chris@1139 1325 {
Chris@1139 1326 QImage image = m_cache.getImage();
Chris@1139 1327 ImageRegionFinder finder;
Chris@1139 1328 QRect rect = finder.findRegionExtents(&image, p);
Chris@1139 1329 return rect;
Chris@1139 1330 }