annotate layer/Colour3DPlotRenderer.cpp @ 1221:eaab8bab3522

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