annotate layer/Colour3DPlotRenderer.cpp @ 1330:c1f719094c25 zoom

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