annotate layer/Colour3DPlotRenderer.cpp @ 1118:175d4e15884d spectrogram-minor-refactor

Introduce ScrollableMagRangeCache, plus some tidying etc
author Chris Cannam
date Wed, 20 Jul 2016 08:30:20 +0100
parents 64709d4d09ef
children be5b91ec81a0
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@1117 20
Chris@1075 21 #include "data/model/DenseThreeDimensionalModel.h"
Chris@1075 22 #include "data/model/Dense3DModelPeakCache.h"
Chris@1075 23 #include "data/model/FFTModel.h"
Chris@1075 24
Chris@1077 25 #include "LayerGeometryProvider.h"
Chris@1082 26 #include "VerticalBinLayer.h"
Chris@1109 27 #include "PaintAssistant.h"
Chris@1109 28
Chris@1109 29 #include "view/ViewManager.h" // for main model sample rate. Pity
Chris@1075 30
Chris@1079 31 #include <vector>
Chris@1079 32
Chris@1094 33 //#define DEBUG_SPECTROGRAM_REPAINT 1
Chris@1094 34
Chris@1079 35 using namespace std;
Chris@1079 36
Chris@1073 37 Colour3DPlotRenderer::RenderResult
Chris@1113 38 Colour3DPlotRenderer::render(const LayerGeometryProvider *v, QPainter &paint, QRect rect)
Chris@1076 39 {
Chris@1090 40 return render(v, paint, rect, false);
Chris@1076 41 }
Chris@1076 42
Chris@1076 43 Colour3DPlotRenderer::RenderResult
Chris@1113 44 Colour3DPlotRenderer::renderTimeConstrained(const LayerGeometryProvider *v,
Chris@1090 45 QPainter &paint, QRect rect)
Chris@1076 46 {
Chris@1090 47 return render(v, paint, rect, true);
Chris@1076 48 }
Chris@1076 49
Chris@1096 50 QRect
Chris@1096 51 Colour3DPlotRenderer::getLargestUncachedRect()
Chris@1096 52 {
Chris@1096 53 int h = m_cache.getSize().height();
Chris@1096 54
Chris@1096 55 QRect areaLeft(0, 0, m_cache.getValidLeft(), h);
Chris@1096 56 QRect areaRight(m_cache.getValidRight(), 0,
Chris@1096 57 m_cache.getSize().width() - m_cache.getValidRight(), h);
Chris@1096 58
Chris@1096 59 if (areaRight.width() > areaLeft.width()) {
Chris@1096 60 return areaRight;
Chris@1096 61 } else {
Chris@1096 62 return areaLeft;
Chris@1096 63 }
Chris@1096 64 }
Chris@1096 65
Chris@1076 66 Colour3DPlotRenderer::RenderResult
Chris@1113 67 Colour3DPlotRenderer::render(const LayerGeometryProvider *v,
Chris@1090 68 QPainter &paint, QRect rect, bool timeConstrained)
Chris@1073 69 {
Chris@1109 70 RenderType renderType = decideRenderType(v);
Chris@1109 71
Chris@1109 72 if (renderType != DrawBufferPixelResolution) {
Chris@1109 73 // Rendering should be fast in bin-resolution and direct draw
Chris@1109 74 // cases because we are quite well zoomed-in, and the sums are
Chris@1109 75 // easier this way. Calculating boundaries later will be
Chris@1109 76 // fiddly for partial paints otherwise.
Chris@1109 77 timeConstrained = false;
Chris@1109 78 }
Chris@1109 79
Chris@1109 80 if (renderType == DirectTranslucent) {
Chris@1109 81 renderDirectTranslucent(v, paint, rect);
Chris@1109 82 return { rect, {} }; //!!! this return arg is not very useful
Chris@1109 83 }
Chris@1109 84
Chris@1079 85 sv_frame_t startFrame = v->getStartFrame();
Chris@1079 86
Chris@1079 87 int x0 = v->getXForViewX(rect.x());
Chris@1079 88 int x1 = v->getXForViewX(rect.x() + rect.width());
Chris@1079 89 if (x0 < 0) x0 = 0;
Chris@1079 90 if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
Chris@1079 91
Chris@1079 92 m_cache.resize(v->getPaintSize());
Chris@1079 93 m_cache.setZoomLevel(v->getZoomLevel());
Chris@1079 94
Chris@1090 95 cerr << "cache start " << m_cache.getStartFrame()
Chris@1090 96 << " valid left " << m_cache.getValidLeft()
Chris@1090 97 << " valid right " << m_cache.getValidRight()
Chris@1094 98 << endl;
Chris@1094 99 cerr << " view start " << startFrame
Chris@1090 100 << " x0 " << x0
Chris@1090 101 << " x1 " << x1
Chris@1090 102 << endl;
Chris@1090 103
Chris@1079 104 if (m_cache.isValid()) { // some part of the cache is valid
Chris@1079 105
Chris@1079 106 if (v->getXForFrame(m_cache.getStartFrame()) ==
Chris@1079 107 v->getXForFrame(startFrame) &&
Chris@1079 108 m_cache.getValidLeft() <= x0 &&
Chris@1079 109 m_cache.getValidRight() >= x1) {
Chris@1090 110
Chris@1090 111 cerr << "cache hit" << endl;
Chris@1090 112
Chris@1079 113 // cache is valid for the complete requested area
Chris@1079 114 paint.drawImage(rect, m_cache.getImage(), rect);
Chris@1079 115 return { rect, {} };
Chris@1079 116
Chris@1079 117 } else {
Chris@1090 118 cerr << "cache partial hit" << endl;
Chris@1090 119
Chris@1079 120 // cache doesn't begin at the right frame or doesn't
Chris@1079 121 // contain the complete view, but might be scrollable or
Chris@1079 122 // partially usable
Chris@1090 123 m_cache.scrollTo(v, startFrame);
Chris@1079 124
Chris@1079 125 // if we are not time-constrained, then we want to paint
Chris@1081 126 // the whole area in one go; we don't return a partial
Chris@1081 127 // paint. To avoid providing the more complex logic to
Chris@1081 128 // handle painting discontiguous areas, if the only valid
Chris@1079 129 // part of cache is in the middle, just make the whole
Chris@1079 130 // thing invalid and start again.
Chris@1079 131 if (!timeConstrained) {
Chris@1079 132 if (m_cache.getValidLeft() > x0 &&
Chris@1079 133 m_cache.getValidRight() < x1) {
Chris@1079 134 m_cache.invalidate();
Chris@1079 135 }
Chris@1079 136 }
Chris@1079 137 }
Chris@1090 138 } else {
Chris@1118 139 // cache is completely invalid
Chris@1090 140 m_cache.setStartFrame(startFrame);
Chris@1075 141 }
Chris@1075 142
Chris@1079 143 bool rightToLeft = false;
Chris@1079 144
Chris@1079 145 if (!m_cache.isValid() && timeConstrained) {
Chris@1081 146 // When rendering the whole area, in a context where we might
Chris@1081 147 // not be able to complete the work, start from somewhere near
Chris@1081 148 // the middle so that the region of interest appears first
Chris@1079 149
Chris@1079 150 //!!! (perhaps we should avoid doing this if past repaints
Chris@1079 151 //!!! have been fast enough to do the whole in one shot)
Chris@1079 152 if (x0 == 0 && x1 == v->getPaintWidth()) {
Chris@1079 153 x0 = int(x1 * 0.3);
Chris@1079 154 }
Chris@1079 155 }
Chris@1079 156
Chris@1079 157 if (m_cache.isValid()) {
Chris@1090 158
Chris@1079 159 // When rendering only a part of the cache, we need to make
Chris@1079 160 // sure that the part we're rendering is adjacent to (or
Chris@1079 161 // overlapping) a valid area of cache, if we have one. The
Chris@1079 162 // alternative is to ditch the valid area of cache and render
Chris@1079 163 // only the requested area, but that's risky because this can
Chris@1079 164 // happen when just waving the pointer over a small part of
Chris@1079 165 // the view -- if we lose the partly-built cache every time
Chris@1079 166 // the user does that, we'll never finish building it.
Chris@1079 167 int left = x0;
Chris@1079 168 int width = x1 - x0;
Chris@1079 169 bool isLeftOfValidArea = false;
Chris@1079 170 m_cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
Chris@1079 171 x0 = left;
Chris@1079 172 x1 = x0 + width;
Chris@1079 173
Chris@1079 174 // That call also told us whether we should be painting
Chris@1079 175 // sub-regions of our target region in right-to-left order in
Chris@1079 176 // order to ensure contiguity
Chris@1079 177 rightToLeft = isLeftOfValidArea;
Chris@1079 178 }
Chris@1075 179
Chris@1109 180 // Note, we always paint the full height to cache. We want to
Chris@1109 181 // ensure the cache is coherent without having to worry about
Chris@1109 182 // vertical matching of required and valid areas as well as
Chris@1109 183 // horizontal.
Chris@1094 184
Chris@1109 185 if (renderType == DrawBufferBinResolution) {
Chris@1109 186
Chris@1094 187 renderToCacheBinResolution(v, x0, x1 - x0);
Chris@1109 188
Chris@1109 189 } else { // must be DrawBufferPixelResolution, handled DirectTranslucent earlier
Chris@1109 190
Chris@1094 191 renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained);
Chris@1094 192 }
Chris@1079 193
Chris@1079 194 QRect pr = rect & m_cache.getValidArea();
Chris@1079 195 paint.drawImage(pr.x(), pr.y(), m_cache.getImage(),
Chris@1079 196 pr.x(), pr.y(), pr.width(), pr.height());
Chris@1079 197
Chris@1079 198 if (!timeConstrained && (pr != rect)) {
Chris@1079 199 //!!! on a first cut, there is a risk that this will happen
Chris@1079 200 //!!! when we are at start/end of model -- trap, report, and
Chris@1079 201 //!!! then fix
Chris@1079 202 throw std::logic_error("internal error: failed to render entire requested rect even when not time-constrained");
Chris@1079 203 }
Chris@1079 204
Chris@1079 205 return { pr, {} };
Chris@1079 206
Chris@1073 207 //!!! todo: timing/incomplete paint
Chris@1073 208
Chris@1073 209 //!!! todo: peak frequency style
Chris@1073 210
Chris@1073 211 //!!! todo: transparent style from Colour3DPlot
Chris@1074 212
Chris@1079 213 //!!! todo: view magnitudes / normalise visible area
Chris@1079 214
Chris@1079 215 //!!! todo: alter documentation for view mag stuff (cached paints
Chris@1079 216 //!!! do not update MagnitudeRange)
Chris@1079 217
Chris@1079 218 //!!! todo, here or in caller: illuminateLocalFeatures
Chris@1079 219
Chris@1110 220 //!!! todo: colourmap rotation
Chris@1110 221
Chris@1079 222 //!!! fft model scaling?
Chris@1079 223
Chris@1079 224 //!!! should we own the Dense3DModelPeakCache here? or should it persist
Chris@1073 225 }
Chris@1073 226
Chris@1109 227 Colour3DPlotRenderer::RenderType
Chris@1113 228 Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const
Chris@1094 229 {
Chris@1100 230 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1109 231 if (!model || !v || !(v->getViewManager())) {
Chris@1109 232 return DrawBufferPixelResolution; // or anything
Chris@1109 233 }
Chris@1109 234
Chris@1094 235 int binResolution = model->getResolution();
Chris@1094 236 int zoomLevel = v->getZoomLevel();
Chris@1109 237 sv_samplerate_t modelRate = model->getSampleRate();
Chris@1109 238
Chris@1109 239 double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate;
Chris@1109 240 double relativeBinResolution = binResolution * rateRatio;
Chris@1109 241
Chris@1109 242 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1109 243 // no alternative works here
Chris@1109 244 return DrawBufferPixelResolution;
Chris@1109 245 }
Chris@1109 246
Chris@1109 247 if (!m_params.alwaysOpaque && !m_params.interpolate) {
Chris@1109 248
Chris@1109 249 // consider translucent option -- only if not smoothing & not
Chris@1109 250 // explicitly requested opaque & sufficiently zoomed-in
Chris@1109 251
Chris@1117 252 if (model->getHeight() * 3 < v->getPaintHeight() &&
Chris@1117 253 relativeBinResolution >= 3 * zoomLevel) {
Chris@1109 254 return DirectTranslucent;
Chris@1109 255 }
Chris@1109 256 }
Chris@1109 257
Chris@1109 258 if (relativeBinResolution > zoomLevel) {
Chris@1109 259 return DrawBufferBinResolution;
Chris@1109 260 } else {
Chris@1109 261 return DrawBufferPixelResolution;
Chris@1109 262 }
Chris@1109 263 }
Chris@1109 264
Chris@1109 265 void
Chris@1113 266 Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1109 267 QPainter &paint,
Chris@1109 268 QRect rect)
Chris@1109 269 {
Chris@1117 270 Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent");
Chris@1117 271
Chris@1115 272 QPoint illuminatePos;
Chris@1115 273 bool illuminate = v->shouldIlluminateLocalFeatures
Chris@1115 274 (m_sources.verticalBinLayer, illuminatePos);
Chris@1109 275
Chris@1109 276 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1109 277
Chris@1109 278 int x0 = rect.left();
Chris@1109 279 int x1 = rect.right() + 1;
Chris@1109 280
Chris@1109 281 int h = v->getPaintHeight();
Chris@1109 282
Chris@1109 283 sv_frame_t modelStart = model->getStartFrame();
Chris@1109 284 sv_frame_t modelEnd = model->getEndFrame();
Chris@1109 285 int modelResolution = model->getResolution();
Chris@1109 286
Chris@1109 287 double rateRatio =
Chris@1109 288 v->getViewManager()->getMainModelSampleRate() / model->getSampleRate();
Chris@1109 289
Chris@1109 290 // the s-prefix values are source, i.e. model, column and bin numbers
Chris@1109 291 int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart))
Chris@1109 292 / modelResolution);
Chris@1109 293 int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart))
Chris@1109 294 / modelResolution);
Chris@1109 295
Chris@1109 296 int sh = model->getHeight();
Chris@1109 297
Chris@1109 298 const int buflen = 40;
Chris@1109 299 char labelbuf[buflen];
Chris@1109 300
Chris@1115 301 int minbin = 0; //!!!
Chris@1109 302 int maxbin = sh - 1; //!!!
Chris@1109 303
Chris@1109 304 int psx = -1;
Chris@1109 305
Chris@1109 306 vector<float> preparedColumn;
Chris@1109 307
Chris@1109 308 int modelWidth = model->getWidth();
Chris@1109 309
Chris@1109 310 for (int sx = sx0; sx <= sx1; ++sx) {
Chris@1109 311
Chris@1109 312 if (sx < 0 || sx >= modelWidth) {
Chris@1109 313 continue;
Chris@1109 314 }
Chris@1109 315
Chris@1109 316 if (sx != psx) {
Chris@1109 317
Chris@1109 318 //!!! this is in common with renderDrawBuffer - pull it out
Chris@1109 319
Chris@1109 320 // order:
Chris@1109 321 // get column -> scale -> record extents ->
Chris@1109 322 // normalise -> peak pick -> apply display gain ->
Chris@1109 323 // distribute/interpolate
Chris@1109 324
Chris@1109 325 ColumnOp::Column fullColumn = model->getColumn(sx);
Chris@1109 326
Chris@1109 327 // cerr << "x " << x << ", sx " << sx << ", col height " << fullColumn.size()
Chris@1109 328 // << ", minbin " << minbin << ", maxbin " << maxbin << endl;
Chris@1109 329
Chris@1109 330 ColumnOp::Column column =
Chris@1109 331 vector<float>(fullColumn.data() + minbin,
Chris@1109 332 fullColumn.data() + maxbin + 1);
Chris@1109 333
Chris@1109 334 //!!! fft scale if (m_colourScale != ColourScaleType::Phase) {
Chris@1109 335 // column = ColumnOp::fftScale(column, m_fftSize);
Chris@1109 336 // }
Chris@1109 337
Chris@1109 338 //!!! extents recordColumnExtents(column,
Chris@1109 339 // sx,
Chris@1109 340 // overallMag,
Chris@1109 341 // overallMagChanged);
Chris@1109 342
Chris@1109 343 // if (m_colourScale != ColourScaleType::Phase) {
Chris@1115 344 preparedColumn = ColumnOp::normalize(column, m_params.normalization);
Chris@1109 345 // }
Chris@1109 346
Chris@1109 347 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1115 348 preparedColumn = ColumnOp::peakPick(preparedColumn);
Chris@1109 349 }
Chris@1109 350
Chris@1109 351 psx = sx;
Chris@1109 352 }
Chris@1109 353
Chris@1109 354 sv_frame_t fx = sx * modelResolution + modelStart;
Chris@1109 355
Chris@1109 356 if (fx + modelResolution <= modelStart || fx > modelEnd) continue;
Chris@1109 357
Chris@1109 358 int rx0 = v->getXForFrame(int(double(fx) * rateRatio));
Chris@1109 359 int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio));
Chris@1109 360
Chris@1109 361 int rw = rx1 - rx0;
Chris@1109 362 if (rw < 1) rw = 1;
Chris@1109 363
Chris@1109 364 bool showLabel = (rw > 10 &&
Chris@1109 365 paint.fontMetrics().width("0.000000") < rw - 3 &&
Chris@1109 366 paint.fontMetrics().height() < (h / sh));
Chris@1109 367
Chris@1109 368 for (int sy = minbin; sy <= maxbin; ++sy) {
Chris@1109 369
Chris@1109 370 int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy);
Chris@1109 371 int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1);
Chris@1116 372
Chris@1116 373 if (m_params.invertVertical) {
Chris@1116 374 ry0 = h - ry0 - 1;
Chris@1116 375 ry1 = h - ry1 - 1;
Chris@1116 376 }
Chris@1116 377
Chris@1109 378 QRect r(rx0, ry1, rw, ry0 - ry1);
Chris@1109 379
Chris@1109 380 float value = preparedColumn[sy - minbin];
Chris@1112 381 QColor colour = m_params.colourScale.getColour(value,
Chris@1112 382 m_params.colourRotation);
Chris@1109 383
Chris@1109 384 if (rw == 1) {
Chris@1109 385 paint.setPen(colour);
Chris@1109 386 paint.setBrush(Qt::NoBrush);
Chris@1109 387 paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1);
Chris@1109 388 continue;
Chris@1109 389 }
Chris@1109 390
Chris@1109 391 QColor pen(255, 255, 255, 80);
Chris@1109 392 QColor brush(colour);
Chris@1109 393
Chris@1109 394 if (rw > 3 && r.height() > 3) {
Chris@1109 395 brush.setAlpha(160);
Chris@1109 396 }
Chris@1109 397
Chris@1109 398 paint.setPen(Qt::NoPen);
Chris@1109 399 paint.setBrush(brush);
Chris@1109 400
Chris@1115 401 if (illuminate) {
Chris@1115 402 if (r.contains(illuminatePos)) {
Chris@1115 403 paint.setPen(v->getForeground());
Chris@1115 404 }
Chris@1115 405 }
Chris@1109 406
Chris@1109 407 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
Chris@1109 408 // cerr << "rect " << r.x() << "," << r.y() << " "
Chris@1109 409 // << r.width() << "x" << r.height() << endl;
Chris@1109 410 #endif
Chris@1109 411
Chris@1109 412 paint.drawRect(r);
Chris@1109 413
Chris@1109 414 if (showLabel) {
Chris@1109 415 double value = model->getValueAt(sx, sy);
Chris@1109 416 snprintf(labelbuf, buflen, "%06f", value);
Chris@1109 417 QString text(labelbuf);
Chris@1109 418 PaintAssistant::drawVisibleText
Chris@1109 419 (v,
Chris@1109 420 paint,
Chris@1109 421 rx0 + 2,
Chris@1109 422 ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
Chris@1109 423 text,
Chris@1109 424 PaintAssistant::OutlinedText);
Chris@1109 425 }
Chris@1109 426 }
Chris@1109 427 }
Chris@1109 428
Chris@1094 429 }
Chris@1094 430
Chris@1080 431 void
Chris@1113 432 Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v,
Chris@1094 433 int x0, int repaintWidth,
Chris@1094 434 bool rightToLeft,
Chris@1094 435 bool timeConstrained)
Chris@1079 436 {
Chris@1117 437 Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution");
Chris@1094 438 cerr << "renderToCachePixelResolution" << endl;
Chris@1094 439
Chris@1094 440 // Draw to the draw buffer, and then copy from there. The draw
Chris@1094 441 // buffer is at the same resolution as the target in the cache, so
Chris@1094 442 // no extra scaling needed.
Chris@1079 443
Chris@1100 444 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1079 445 if (!model || !model->isOK() || !model->isReady()) {
Chris@1079 446 throw std::logic_error("no source model provided, or model not ready");
Chris@1079 447 }
Chris@1079 448
Chris@1079 449 int h = v->getPaintHeight();
Chris@1079 450
Chris@1094 451 clearDrawBuffer(repaintWidth, h);
Chris@1079 452
Chris@1094 453 vector<int> binforx(repaintWidth);
Chris@1079 454 vector<double> binfory(h);
Chris@1079 455
Chris@1079 456 bool usePeaksCache = false;
Chris@1079 457 int binsPerPeak = 1;
Chris@1094 458 int zoomLevel = v->getZoomLevel();
Chris@1094 459 int binResolution = model->getResolution();
Chris@1079 460
Chris@1094 461 for (int x = 0; x < repaintWidth; ++x) {
Chris@1094 462 sv_frame_t f0 = v->getFrameForX(x0 + x);
Chris@1094 463 double s0 = double(f0 - model->getStartFrame()) / binResolution;
Chris@1094 464 binforx[x] = int(s0 + 0.0001);
Chris@1094 465 }
Chris@1080 466
Chris@1094 467 if (m_sources.peaks) { // peaks cache exists
Chris@1080 468
Chris@1094 469 binsPerPeak = m_sources.peaks->getColumnsPerPeak();
Chris@1094 470 usePeaksCache = (binResolution * binsPerPeak) < zoomLevel;
Chris@1094 471
Chris@1094 472 if (m_params.colourScale.getScale() ==
Chris@1105 473 ColourScaleType::Phase) {
Chris@1094 474 usePeaksCache = false;
Chris@1079 475 }
Chris@1079 476 }
Chris@1082 477
Chris@1094 478 cerr << "[PIX] zoomLevel = " << zoomLevel
Chris@1094 479 << ", binResolution " << binResolution
Chris@1094 480 << ", binsPerPeak " << binsPerPeak
Chris@1094 481 << ", peak cache " << m_sources.peaks
Chris@1094 482 << ", usePeaksCache = " << usePeaksCache
Chris@1094 483 << endl;
Chris@1094 484
Chris@1080 485 for (int y = 0; y < h; ++y) {
Chris@1090 486 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1080 487 }
Chris@1079 488
Chris@1097 489 int attainedWidth;
Chris@1097 490
Chris@1103 491 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1097 492 attainedWidth = renderDrawBufferPeakFrequencies(v,
Chris@1097 493 repaintWidth,
Chris@1097 494 h,
Chris@1097 495 binforx,
Chris@1097 496 binfory,
Chris@1097 497 rightToLeft,
Chris@1097 498 timeConstrained);
Chris@1097 499
Chris@1097 500 } else {
Chris@1097 501 attainedWidth = renderDrawBuffer(repaintWidth,
Chris@1080 502 h,
Chris@1080 503 binforx,
Chris@1080 504 binfory,
Chris@1080 505 usePeaksCache,
Chris@1080 506 rightToLeft,
Chris@1080 507 timeConstrained);
Chris@1097 508 }
Chris@1083 509
Chris@1094 510 if (attainedWidth == 0) return;
Chris@1084 511
Chris@1094 512 // draw buffer is pixel resolution, no scaling factors or padding involved
Chris@1084 513
Chris@1084 514 int paintedLeft = x0;
Chris@1084 515 if (rightToLeft) {
Chris@1084 516 paintedLeft += (repaintWidth - attainedWidth);
Chris@1084 517 }
Chris@1084 518
Chris@1094 519 m_cache.drawImage(paintedLeft, attainedWidth,
Chris@1094 520 m_drawBuffer,
Chris@1094 521 paintedLeft - x0, attainedWidth);
Chris@1094 522 }
Chris@1084 523
Chris@1094 524 void
Chris@1113 525 Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v,
Chris@1094 526 int x0, int repaintWidth)
Chris@1094 527 {
Chris@1117 528 Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution");
Chris@1094 529 cerr << "renderToCacheBinResolution" << endl;
Chris@1094 530
Chris@1094 531 // Draw to the draw buffer, and then scale-copy from there. Draw
Chris@1094 532 // buffer is at bin resolution, i.e. buffer x == source column
Chris@1094 533 // number. We use toolkit smooth scaling for interpolation.
Chris@1084 534
Chris@1100 535 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1094 536 if (!model || !model->isOK() || !model->isReady()) {
Chris@1094 537 throw std::logic_error("no source model provided, or model not ready");
Chris@1094 538 }
Chris@1094 539
Chris@1094 540 // The draw buffer will contain a fragment at bin resolution. We
Chris@1094 541 // need to ensure that it starts and ends at points where a
Chris@1094 542 // time-bin boundary occurs at an exact pixel boundary, and with a
Chris@1094 543 // certain amount of overlap across existing pixels so that we can
Chris@1094 544 // scale and draw from it without smoothing errors at the edges.
Chris@1094 545
Chris@1094 546 // If (getFrameForX(x) / increment) * increment ==
Chris@1094 547 // getFrameForX(x), then x is a time-bin boundary. We want two
Chris@1094 548 // such boundaries at either side of the draw buffer -- one which
Chris@1094 549 // we draw up to, and one which we subsequently crop at.
Chris@1094 550
Chris@1094 551 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
Chris@1094 552 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
Chris@1094 553
Chris@1094 554 int drawBufferWidth;
Chris@1094 555 int binResolution = model->getResolution();
Chris@1094 556
Chris@1094 557 for (int x = x0; ; --x) {
Chris@1094 558 sv_frame_t f = v->getFrameForX(x);
Chris@1094 559 if ((f / binResolution) * binResolution == f) {
Chris@1094 560 if (leftCropFrame == -1) leftCropFrame = f;
Chris@1094 561 else if (x < x0 - 2) {
Chris@1094 562 leftBoundaryFrame = f;
Chris@1094 563 break;
Chris@1094 564 }
Chris@1094 565 }
Chris@1094 566 }
Chris@1094 567 for (int x = x0 + repaintWidth; ; ++x) {
Chris@1094 568 sv_frame_t f = v->getFrameForX(x);
Chris@1094 569 if ((f / binResolution) * binResolution == f) {
Chris@1094 570 if (rightCropFrame == -1) rightCropFrame = f;
Chris@1094 571 else if (x > x0 + repaintWidth + 2) {
Chris@1094 572 rightBoundaryFrame = f;
Chris@1094 573 break;
Chris@1094 574 }
Chris@1094 575 }
Chris@1094 576 }
Chris@1094 577 drawBufferWidth = int
Chris@1094 578 ((rightBoundaryFrame - leftBoundaryFrame) / binResolution);
Chris@1094 579
Chris@1094 580 int h = v->getPaintHeight();
Chris@1094 581
Chris@1095 582 // For our purposes here, the draw buffer needs to be exactly our
Chris@1095 583 // target size (so we recreate always rather than just clear it)
Chris@1095 584
Chris@1095 585 recreateDrawBuffer(drawBufferWidth, h);
Chris@1094 586
Chris@1094 587 vector<int> binforx(drawBufferWidth);
Chris@1094 588 vector<double> binfory(h);
Chris@1094 589
Chris@1094 590 for (int x = 0; x < drawBufferWidth; ++x) {
Chris@1094 591 binforx[x] = int(leftBoundaryFrame / binResolution) + x;
Chris@1094 592 }
Chris@1094 593
Chris@1094 594 cerr << "[BIN] binResolution " << binResolution
Chris@1094 595 << endl;
Chris@1094 596
Chris@1094 597 for (int y = 0; y < h; ++y) {
Chris@1094 598 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1094 599 }
Chris@1094 600
Chris@1094 601 int attainedWidth = renderDrawBuffer(drawBufferWidth,
Chris@1094 602 h,
Chris@1094 603 binforx,
Chris@1094 604 binfory,
Chris@1094 605 false,
Chris@1094 606 false,
Chris@1094 607 false);
Chris@1094 608
Chris@1094 609 if (attainedWidth == 0) return;
Chris@1094 610
Chris@1094 611 int scaledLeft = v->getXForFrame(leftBoundaryFrame);
Chris@1094 612 int scaledRight = v->getXForFrame(rightBoundaryFrame);
Chris@1095 613
Chris@1095 614 cerr << "scaling draw buffer from width " << m_drawBuffer.width()
Chris@1095 615 << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = "
Chris@1095 616 << drawBufferWidth << ")" << endl;
Chris@1094 617
Chris@1094 618 QImage scaled = m_drawBuffer.scaled
Chris@1094 619 (scaledRight - scaledLeft, h,
Chris@1094 620 Qt::IgnoreAspectRatio, (m_params.interpolate ?
Chris@1094 621 Qt::SmoothTransformation :
Chris@1094 622 Qt::FastTransformation));
Chris@1084 623
Chris@1094 624 int scaledLeftCrop = v->getXForFrame(leftCropFrame);
Chris@1094 625 int scaledRightCrop = v->getXForFrame(rightCropFrame);
Chris@1094 626
Chris@1094 627 int targetLeft = scaledLeftCrop;
Chris@1094 628 if (targetLeft < 0) {
Chris@1094 629 targetLeft = 0;
Chris@1094 630 }
Chris@1094 631
Chris@1094 632 int targetWidth = scaledRightCrop - targetLeft;
Chris@1094 633 if (targetLeft + targetWidth > m_cache.getSize().width()) {
Chris@1094 634 targetWidth = m_cache.getSize().width() - targetLeft;
Chris@1094 635 }
Chris@1094 636
Chris@1094 637 int sourceLeft = targetLeft - scaledLeft;
Chris@1094 638 if (sourceLeft < 0) {
Chris@1094 639 sourceLeft = 0;
Chris@1094 640 }
Chris@1094 641
Chris@1094 642 int sourceWidth = targetWidth;
Chris@1094 643
Chris@1094 644 cerr << "repaintWidth = " << repaintWidth
Chris@1094 645 << ", targetWidth = " << targetWidth << endl;
Chris@1094 646
Chris@1094 647 if (targetWidth > 0) {
Chris@1094 648 m_cache.drawImage(targetLeft, targetWidth,
Chris@1094 649 scaled,
Chris@1094 650 sourceLeft, sourceWidth);
Chris@1084 651 }
Chris@1079 652 }
Chris@1083 653
Chris@1083 654 int
Chris@1083 655 Colour3DPlotRenderer::renderDrawBuffer(int w, int h,
Chris@1083 656 const vector<int> &binforx,
Chris@1083 657 const vector<double> &binfory,
Chris@1083 658 bool usePeaksCache,
Chris@1083 659 bool rightToLeft,
Chris@1083 660 bool timeConstrained)
Chris@1083 661 {
Chris@1083 662 // Callers must have checked that the appropriate subset of
Chris@1083 663 // Sources data members are set for the supplied flags (e.g. that
Chris@1083 664 // peaks model exists if usePeaksCache)
Chris@1083 665
Chris@1083 666 RenderTimer timer(timeConstrained ?
Chris@1083 667 RenderTimer::FastRender :
Chris@1083 668 RenderTimer::NoTimeout);
Chris@1083 669
Chris@1083 670 int minbin = int(binfory[0] + 0.0001);
Chris@1083 671 int maxbin = int(binfory[h-1]);
Chris@1083 672 if (minbin < 0) minbin = 0;
Chris@1083 673 if (maxbin < 0) maxbin = minbin+1;
Chris@1083 674
Chris@1083 675 int divisor = 1;
Chris@1100 676 const DenseThreeDimensionalModel *sourceModel = m_sources.source;
Chris@1083 677 if (usePeaksCache) {
Chris@1083 678 divisor = m_sources.peaks->getColumnsPerPeak();
Chris@1083 679 sourceModel = m_sources.peaks;
Chris@1083 680 }
Chris@1083 681
Chris@1083 682 int psx = -1;
Chris@1083 683
Chris@1083 684 int start = 0;
Chris@1083 685 int finish = w;
Chris@1083 686 int step = 1;
Chris@1083 687
Chris@1083 688 if (rightToLeft) {
Chris@1083 689 start = w-1;
Chris@1083 690 finish = -1;
Chris@1083 691 step = -1;
Chris@1083 692 }
Chris@1083 693
Chris@1083 694 int columnCount = 0;
Chris@1083 695
Chris@1083 696 vector<float> preparedColumn;
Chris@1094 697
Chris@1094 698 int modelWidth = sourceModel->getWidth();
Chris@1094 699 cerr << "modelWidth " << modelWidth << endl;
Chris@1083 700
Chris@1083 701 for (int x = start; x != finish; x += step) {
Chris@1083 702
Chris@1083 703 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1083 704 // source column index
Chris@1083 705
Chris@1083 706 ++columnCount;
Chris@1083 707
Chris@1083 708 if (binforx[x] < 0) continue;
Chris@1083 709
Chris@1083 710 int sx0 = binforx[x] / divisor;
Chris@1083 711 int sx1 = sx0;
Chris@1083 712 if (x+1 < w) sx1 = binforx[x+1] / divisor;
Chris@1083 713 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1083 714 if (sx0 < 0) continue;
Chris@1083 715 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1083 716
Chris@1083 717 vector<float> pixelPeakColumn;
Chris@1083 718
Chris@1083 719 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1083 720
Chris@1083 721 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1094 722 cerr << "sx = " << sx << endl;
Chris@1083 723 #endif
Chris@1083 724
Chris@1094 725 if (sx < 0 || sx >= modelWidth) {
Chris@1083 726 continue;
Chris@1083 727 }
Chris@1083 728
Chris@1083 729 if (sx != psx) {
Chris@1083 730
Chris@1083 731 // order:
Chris@1083 732 // get column -> scale -> record extents ->
Chris@1083 733 // normalise -> peak pick -> apply display gain ->
Chris@1083 734 // distribute/interpolate
Chris@1083 735
Chris@1083 736 ColumnOp::Column fullColumn = sourceModel->getColumn(sx);
Chris@1090 737
Chris@1094 738 // cerr << "x " << x << ", sx " << sx << ", col height " << fullColumn.size()
Chris@1094 739 // << ", minbin " << minbin << ", maxbin " << maxbin << endl;
Chris@1090 740
Chris@1083 741 ColumnOp::Column column =
Chris@1083 742 vector<float>(fullColumn.data() + minbin,
Chris@1083 743 fullColumn.data() + maxbin + 1);
Chris@1083 744
Chris@1105 745 //!!! fft scale if (m_colourScale != ColourScaleType::Phase) {
Chris@1083 746 // column = ColumnOp::fftScale(column, m_fftSize);
Chris@1083 747 // }
Chris@1083 748
Chris@1083 749 //!!! extents recordColumnExtents(column,
Chris@1083 750 // sx,
Chris@1083 751 // overallMag,
Chris@1083 752 // overallMagChanged);
Chris@1083 753
Chris@1105 754 // if (m_colourScale != ColourScaleType::Phase) {
Chris@1083 755 column = ColumnOp::normalize(column, m_params.normalization);
Chris@1083 756 // }
Chris@1083 757
Chris@1103 758 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1083 759 column = ColumnOp::peakPick(column);
Chris@1083 760 }
Chris@1083 761
Chris@1083 762 preparedColumn =
Chris@1083 763 ColumnOp::distribute(column, //!!! gain? ColumnOp::applyGain(column, m_gain),
Chris@1083 764 h,
Chris@1083 765 binfory,
Chris@1083 766 minbin,
Chris@1083 767 m_params.interpolate);
Chris@1083 768
Chris@1083 769 psx = sx;
Chris@1083 770 }
Chris@1083 771
Chris@1083 772 if (sx == sx0) {
Chris@1083 773 pixelPeakColumn = preparedColumn;
Chris@1083 774 } else {
Chris@1083 775 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1083 776 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1083 777 preparedColumn[i]);
Chris@1083 778 }
Chris@1083 779 }
Chris@1083 780 }
Chris@1083 781
Chris@1083 782 if (!pixelPeakColumn.empty()) {
Chris@1083 783 for (int y = 0; y < h; ++y) {
Chris@1116 784 int py;
Chris@1116 785 if (m_params.invertVertical) {
Chris@1116 786 py = y;
Chris@1116 787 } else {
Chris@1116 788 py = h - y - 1;
Chris@1116 789 }
Chris@1083 790 m_drawBuffer.setPixel
Chris@1083 791 (x,
Chris@1116 792 py,
Chris@1083 793 m_params.colourScale.getPixel(pixelPeakColumn[y]));
Chris@1083 794 }
Chris@1083 795 }
Chris@1083 796
Chris@1083 797 double fractionComplete = double(columnCount) / double(w);
Chris@1083 798 if (timer.outOfTime(fractionComplete)) {
Chris@1083 799 return columnCount;
Chris@1083 800 }
Chris@1083 801 }
Chris@1083 802
Chris@1083 803 return columnCount;
Chris@1083 804 }
Chris@1083 805
Chris@1097 806 int
Chris@1113 807 Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097 808 int w, int h,
Chris@1097 809 const vector<int> &binforx,
Chris@1097 810 const vector<double> &binfory,
Chris@1097 811 bool rightToLeft,
Chris@1097 812 bool timeConstrained)
Chris@1097 813 {
Chris@1097 814 // Callers must have checked that the appropriate subset of
Chris@1097 815 // Sources data members are set for the supplied flags (e.g. that
Chris@1097 816 // fft model exists)
Chris@1097 817
Chris@1097 818 RenderTimer timer(timeConstrained ?
Chris@1097 819 RenderTimer::FastRender :
Chris@1097 820 RenderTimer::NoTimeout);
Chris@1097 821
Chris@1097 822 int minbin = int(binfory[0] + 0.0001);
Chris@1097 823 int maxbin = int(binfory[h-1]);
Chris@1097 824 if (minbin < 0) minbin = 0;
Chris@1097 825 if (maxbin < 0) maxbin = minbin+1;
Chris@1097 826
Chris@1100 827 const FFTModel *fft = m_sources.fft;
Chris@1097 828
Chris@1097 829 FFTModel::PeakSet peakfreqs;
Chris@1097 830
Chris@1097 831 int psx = -1;
Chris@1097 832
Chris@1097 833 int start = 0;
Chris@1097 834 int finish = w;
Chris@1097 835 int step = 1;
Chris@1097 836
Chris@1097 837 if (rightToLeft) {
Chris@1097 838 start = w-1;
Chris@1097 839 finish = -1;
Chris@1097 840 step = -1;
Chris@1097 841 }
Chris@1097 842
Chris@1097 843 int columnCount = 0;
Chris@1097 844
Chris@1097 845 vector<float> preparedColumn;
Chris@1097 846
Chris@1097 847 int modelWidth = fft->getWidth();
Chris@1097 848 cerr << "modelWidth " << modelWidth << endl;
Chris@1097 849
Chris@1097 850 double minFreq = (double(minbin) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1097 851 double maxFreq = (double(maxbin) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1097 852
Chris@1103 853 bool logarithmic = (m_params.binScale == BinScale::Log);
Chris@1097 854
Chris@1097 855 for (int x = start; x != finish; x += step) {
Chris@1097 856
Chris@1097 857 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1097 858 // source column index
Chris@1097 859
Chris@1097 860 ++columnCount;
Chris@1097 861
Chris@1097 862 if (binforx[x] < 0) continue;
Chris@1097 863
Chris@1097 864 int sx0 = binforx[x];
Chris@1097 865 int sx1 = sx0;
Chris@1097 866 if (x+1 < w) sx1 = binforx[x+1];
Chris@1097 867 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1097 868 if (sx0 < 0) continue;
Chris@1097 869 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1097 870
Chris@1097 871 vector<float> pixelPeakColumn;
Chris@1097 872
Chris@1097 873 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1097 874
Chris@1097 875 if (sx < 0 || sx >= modelWidth) {
Chris@1097 876 continue;
Chris@1097 877 }
Chris@1097 878
Chris@1097 879 if (sx != psx) {
Chris@1097 880
Chris@1097 881 ColumnOp::Column fullColumn = fft->getColumn(sx);
Chris@1097 882
Chris@1097 883 ColumnOp::Column column =
Chris@1097 884 vector<float>(fullColumn.data() + minbin,
Chris@1097 885 fullColumn.data() + maxbin + 1);
Chris@1097 886
Chris@1105 887 //!!! fft scale if (m_colourScale != ColourScaleType::Phase) {
Chris@1097 888 // column = ColumnOp::fftScale(column, getFFTSize());
Chris@1097 889 // }
Chris@1097 890
Chris@1097 891 //!!! extents recordColumnExtents(column,
Chris@1097 892 // sx,
Chris@1097 893 // overallMag,
Chris@1097 894 // overallMagChanged);
Chris@1097 895
Chris@1105 896 //!!! if (m_colourScale != ColourScaleType::Phase) {
Chris@1097 897 column = ColumnOp::normalize(column, m_params.normalization);
Chris@1097 898 //!!! }
Chris@1097 899
Chris@1097 900 preparedColumn = column;
Chris@1097 901 //!!! gain? preparedColumn = ColumnOp::applyGain(column, m_params.gain);
Chris@1097 902
Chris@1097 903 psx = sx;
Chris@1097 904 }
Chris@1097 905
Chris@1097 906 if (sx == sx0) {
Chris@1097 907 pixelPeakColumn = preparedColumn;
Chris@1097 908 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx,
Chris@1097 909 minbin, maxbin - 1);
Chris@1097 910 } else {
Chris@1097 911 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1097 912 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1097 913 preparedColumn[i]);
Chris@1097 914 }
Chris@1097 915 }
Chris@1097 916 }
Chris@1097 917
Chris@1097 918 if (!pixelPeakColumn.empty()) {
Chris@1097 919 for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin();
Chris@1097 920 pi != peakfreqs.end(); ++pi) {
Chris@1097 921
Chris@1097 922 int bin = pi->first;
Chris@1097 923 double freq = pi->second;
Chris@1097 924
Chris@1097 925 if (bin < minbin) continue;
Chris@1097 926 if (bin > maxbin) break;
Chris@1097 927
Chris@1097 928 double value = pixelPeakColumn[bin - minbin];
Chris@1097 929
Chris@1097 930 double y = v->getYForFrequency
Chris@1097 931 (freq, minFreq, maxFreq, logarithmic);
Chris@1097 932
Chris@1097 933 int iy = int(y + 0.5);
Chris@1097 934 if (iy < 0 || iy >= h) continue;
Chris@1097 935
Chris@1097 936 m_drawBuffer.setPixel
Chris@1097 937 (x,
Chris@1097 938 iy,
Chris@1097 939 m_params.colourScale.getPixel(value));
Chris@1097 940 }
Chris@1097 941 }
Chris@1097 942
Chris@1097 943 double fractionComplete = double(columnCount) / double(w);
Chris@1097 944 if (timer.outOfTime(fractionComplete)) {
Chris@1097 945 return columnCount;
Chris@1097 946 }
Chris@1097 947 }
Chris@1097 948
Chris@1097 949 return columnCount;
Chris@1097 950 }
Chris@1097 951
Chris@1079 952 void
Chris@1095 953 Colour3DPlotRenderer::recreateDrawBuffer(int w, int h)
Chris@1079 954 {
Chris@1095 955 m_drawBuffer = QImage(w, h, QImage::Format_Indexed8);
Chris@1079 956
Chris@1095 957 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@1095 958 m_drawBuffer.setColor
Chris@1095 959 ((unsigned char)pixel,
Chris@1112 960 m_params.colourScale.getColourForPixel
Chris@1112 961 (pixel, m_params.colourRotation).rgb());
Chris@1079 962 }
Chris@1079 963
Chris@1079 964 m_drawBuffer.fill(0);
Chris@1079 965 }
Chris@1079 966
Chris@1095 967 void
Chris@1095 968 Colour3DPlotRenderer::clearDrawBuffer(int w, int h)
Chris@1095 969 {
Chris@1095 970 if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) {
Chris@1095 971 recreateDrawBuffer(w, h);
Chris@1095 972 } else {
Chris@1095 973 m_drawBuffer.fill(0);
Chris@1095 974 }
Chris@1095 975 }
Chris@1079 976
Chris@1095 977