annotate layer/Colour3DPlotRenderer.cpp @ 1245:f0e291fa7b9c

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