annotate layer/Colour3DPlotRenderer.cpp @ 1386:fc3d89f88690 spectrogramparam

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