annotate layer/Colour3DPlotRenderer.cpp @ 1212:a1ee3108d1d3 3.0-integration

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