annotate layer/Colour3DPlotRenderer.cpp @ 1447:8afea53332f3 single-point

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