annotate layer/Colour3DPlotRenderer.cpp @ 1204:d421df27e184 3.0-integration

Further PropertyBox layout overhaul: avoid crash (/ assertion failure) when property type changes from e.g. colour to colourmap, by replacing the existing widget within the layout rather than trying to repopulate it
author Chris Cannam
date Tue, 20 Dec 2016 10:49:24 +0000
parents 84042e8c3c03
children a1ee3108d1d3
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@1163 290 bool usePeakCache) 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@1138 312
Chris@1161 313 ColumnOp::Column fullColumn =
Chris@1163 314 (usePeakCache ? m_sources.peakCache : m_sources.source)->
Chris@1161 315 getColumn(sx);
Chris@1138 316
Chris@1138 317 column = vector<float>(fullColumn.data() + minbin,
Chris@1138 318 fullColumn.data() + minbin + nbins);
Chris@1138 319
Chris@1138 320 column = ColumnOp::applyGain(column, m_params.scaleFactor);
Chris@1138 321
Chris@1138 322 column = ColumnOp::normalize(column, m_params.normalization);
Chris@1138 323 }
Chris@1138 324
Chris@1138 325 return column;
Chris@1138 326 }
Chris@1138 327
Chris@1121 328 MagnitudeRange
Chris@1113 329 Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v,
Chris@1109 330 QPainter &paint,
Chris@1109 331 QRect rect)
Chris@1109 332 {
Chris@1117 333 Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent");
Chris@1117 334
Chris@1121 335 MagnitudeRange magRange;
Chris@1121 336
Chris@1115 337 QPoint illuminatePos;
Chris@1115 338 bool illuminate = v->shouldIlluminateLocalFeatures
Chris@1115 339 (m_sources.verticalBinLayer, illuminatePos);
Chris@1109 340
Chris@1109 341 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1109 342
Chris@1109 343 int x0 = rect.left();
Chris@1109 344 int x1 = rect.right() + 1;
Chris@1109 345
Chris@1109 346 int h = v->getPaintHeight();
Chris@1109 347
Chris@1109 348 sv_frame_t modelStart = model->getStartFrame();
Chris@1109 349 sv_frame_t modelEnd = model->getEndFrame();
Chris@1109 350 int modelResolution = model->getResolution();
Chris@1109 351
Chris@1109 352 double rateRatio =
Chris@1109 353 v->getViewManager()->getMainModelSampleRate() / model->getSampleRate();
Chris@1109 354
Chris@1109 355 // the s-prefix values are source, i.e. model, column and bin numbers
Chris@1109 356 int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart))
Chris@1109 357 / modelResolution);
Chris@1109 358 int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart))
Chris@1109 359 / modelResolution);
Chris@1109 360
Chris@1109 361 int sh = model->getHeight();
Chris@1109 362
Chris@1109 363 const int buflen = 40;
Chris@1109 364 char labelbuf[buflen];
Chris@1109 365
Chris@1133 366 int minbin = m_sources.verticalBinLayer->getIBinForY(v, h);
Chris@1135 367 if (minbin >= sh) minbin = sh - 1;
Chris@1135 368 if (minbin < 0) minbin = 0;
Chris@1135 369
Chris@1135 370 int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1;
Chris@1135 371 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1133 372
Chris@1109 373 int psx = -1;
Chris@1109 374
Chris@1109 375 vector<float> preparedColumn;
Chris@1109 376
Chris@1109 377 int modelWidth = model->getWidth();
Chris@1109 378
Chris@1109 379 for (int sx = sx0; sx <= sx1; ++sx) {
Chris@1109 380
Chris@1109 381 if (sx < 0 || sx >= modelWidth) {
Chris@1109 382 continue;
Chris@1109 383 }
Chris@1109 384
Chris@1109 385 if (sx != psx) {
Chris@1109 386
Chris@1138 387 // order:
Chris@1138 388 // get column -> scale -> normalise -> record extents ->
Chris@1138 389 // peak pick -> distribute/interpolate -> apply display gain
Chris@1109 390
Chris@1138 391 // this does the first three:
Chris@1161 392 preparedColumn = getColumn(sx, minbin, nbins, false);
Chris@1131 393
Chris@1131 394 magRange.sample(preparedColumn);
Chris@1109 395
Chris@1109 396 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1115 397 preparedColumn = ColumnOp::peakPick(preparedColumn);
Chris@1109 398 }
Chris@1109 399
Chris@1124 400 // Display gain belongs to the colour scale and is
Chris@1124 401 // applied by the colour scale object when mapping it
Chris@1124 402
Chris@1109 403 psx = sx;
Chris@1109 404 }
Chris@1109 405
Chris@1109 406 sv_frame_t fx = sx * modelResolution + modelStart;
Chris@1109 407
Chris@1109 408 if (fx + modelResolution <= modelStart || fx > modelEnd) continue;
Chris@1109 409
Chris@1109 410 int rx0 = v->getXForFrame(int(double(fx) * rateRatio));
Chris@1109 411 int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio));
Chris@1109 412
Chris@1109 413 int rw = rx1 - rx0;
Chris@1109 414 if (rw < 1) rw = 1;
Chris@1109 415
Chris@1109 416 bool showLabel = (rw > 10 &&
Chris@1109 417 paint.fontMetrics().width("0.000000") < rw - 3 &&
Chris@1109 418 paint.fontMetrics().height() < (h / sh));
Chris@1109 419
Chris@1135 420 for (int sy = minbin; sy < minbin + nbins; ++sy) {
Chris@1109 421
Chris@1109 422 int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy);
Chris@1109 423 int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1);
Chris@1116 424
Chris@1116 425 if (m_params.invertVertical) {
Chris@1116 426 ry0 = h - ry0 - 1;
Chris@1116 427 ry1 = h - ry1 - 1;
Chris@1116 428 }
Chris@1116 429
Chris@1109 430 QRect r(rx0, ry1, rw, ry0 - ry1);
Chris@1109 431
Chris@1109 432 float value = preparedColumn[sy - minbin];
Chris@1112 433 QColor colour = m_params.colourScale.getColour(value,
Chris@1112 434 m_params.colourRotation);
Chris@1109 435
Chris@1109 436 if (rw == 1) {
Chris@1109 437 paint.setPen(colour);
Chris@1109 438 paint.setBrush(Qt::NoBrush);
Chris@1109 439 paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1);
Chris@1109 440 continue;
Chris@1109 441 }
Chris@1109 442
Chris@1109 443 QColor pen(255, 255, 255, 80);
Chris@1109 444 QColor brush(colour);
Chris@1109 445
Chris@1109 446 if (rw > 3 && r.height() > 3) {
Chris@1109 447 brush.setAlpha(160);
Chris@1109 448 }
Chris@1109 449
Chris@1109 450 paint.setPen(Qt::NoPen);
Chris@1109 451 paint.setBrush(brush);
Chris@1109 452
Chris@1115 453 if (illuminate) {
Chris@1115 454 if (r.contains(illuminatePos)) {
Chris@1115 455 paint.setPen(v->getForeground());
Chris@1115 456 }
Chris@1115 457 }
Chris@1109 458
Chris@1109 459 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
Chris@1109 460 // cerr << "rect " << r.x() << "," << r.y() << " "
Chris@1109 461 // << r.width() << "x" << r.height() << endl;
Chris@1109 462 #endif
Chris@1109 463
Chris@1109 464 paint.drawRect(r);
Chris@1109 465
Chris@1109 466 if (showLabel) {
Chris@1109 467 double value = model->getValueAt(sx, sy);
Chris@1109 468 snprintf(labelbuf, buflen, "%06f", value);
Chris@1109 469 QString text(labelbuf);
Chris@1109 470 PaintAssistant::drawVisibleText
Chris@1109 471 (v,
Chris@1109 472 paint,
Chris@1109 473 rx0 + 2,
Chris@1109 474 ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
Chris@1109 475 text,
Chris@1109 476 PaintAssistant::OutlinedText);
Chris@1109 477 }
Chris@1109 478 }
Chris@1109 479 }
Chris@1121 480
Chris@1121 481 return magRange;
Chris@1094 482 }
Chris@1094 483
Chris@1080 484 void
Chris@1113 485 Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v,
Chris@1094 486 int x0, int repaintWidth,
Chris@1094 487 bool rightToLeft,
Chris@1094 488 bool timeConstrained)
Chris@1079 489 {
Chris@1117 490 Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution");
Chris@1143 491 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1094 492 cerr << "renderToCachePixelResolution" << endl;
Chris@1143 493 #endif
Chris@1094 494
Chris@1094 495 // Draw to the draw buffer, and then copy from there. The draw
Chris@1094 496 // buffer is at the same resolution as the target in the cache, so
Chris@1094 497 // no extra scaling needed.
Chris@1079 498
Chris@1100 499 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1079 500 if (!model || !model->isOK() || !model->isReady()) {
Chris@1079 501 throw std::logic_error("no source model provided, or model not ready");
Chris@1079 502 }
Chris@1079 503
Chris@1079 504 int h = v->getPaintHeight();
Chris@1079 505
Chris@1094 506 clearDrawBuffer(repaintWidth, h);
Chris@1079 507
Chris@1094 508 vector<int> binforx(repaintWidth);
Chris@1079 509 vector<double> binfory(h);
Chris@1079 510
Chris@1163 511 bool usePeakCache = false;
Chris@1079 512 int binsPerPeak = 1;
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@1163 522 if (m_sources.peakCache) {
Chris@1163 523 binsPerPeak = m_sources.peakCache->getColumnsPerPeak();
Chris@1163 524 usePeakCache = (zoomLevel >= binResolution * binsPerPeak);
Chris@1094 525 if (m_params.colourScale.getScale() ==
Chris@1105 526 ColourScaleType::Phase) {
Chris@1163 527 usePeakCache = false;
Chris@1079 528 }
Chris@1079 529 }
Chris@1082 530
Chris@1163 531 SVDEBUG << "[PIX] zoomLevel = " << zoomLevel
Chris@1163 532 << ", binResolution " << binResolution
Chris@1163 533 << ", binsPerPeak " << binsPerPeak
Chris@1163 534 << ", peak cache " << m_sources.peakCache
Chris@1163 535 << ", usePeakCache = " << usePeakCache
Chris@1163 536 << endl;
Chris@1094 537
Chris@1080 538 for (int y = 0; y < h; ++y) {
Chris@1090 539 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1080 540 }
Chris@1079 541
Chris@1097 542 int attainedWidth;
Chris@1097 543
Chris@1103 544 if (m_params.binDisplay == BinDisplay::PeakFrequencies) {
Chris@1097 545 attainedWidth = renderDrawBufferPeakFrequencies(v,
Chris@1097 546 repaintWidth,
Chris@1097 547 h,
Chris@1097 548 binforx,
Chris@1097 549 binfory,
Chris@1097 550 rightToLeft,
Chris@1097 551 timeConstrained);
Chris@1097 552
Chris@1097 553 } else {
Chris@1097 554 attainedWidth = renderDrawBuffer(repaintWidth,
Chris@1080 555 h,
Chris@1080 556 binforx,
Chris@1080 557 binfory,
Chris@1163 558 usePeakCache,
Chris@1080 559 rightToLeft,
Chris@1080 560 timeConstrained);
Chris@1097 561 }
Chris@1083 562
Chris@1094 563 if (attainedWidth == 0) return;
Chris@1084 564
Chris@1094 565 // draw buffer is pixel resolution, no scaling factors or padding involved
Chris@1084 566
Chris@1084 567 int paintedLeft = x0;
Chris@1084 568 if (rightToLeft) {
Chris@1084 569 paintedLeft += (repaintWidth - attainedWidth);
Chris@1084 570 }
Chris@1084 571
Chris@1094 572 m_cache.drawImage(paintedLeft, attainedWidth,
Chris@1094 573 m_drawBuffer,
Chris@1094 574 paintedLeft - x0, attainedWidth);
Chris@1121 575
Chris@1121 576 for (int i = 0; in_range_for(m_magRanges, i); ++i) {
Chris@1121 577 m_magCache.sampleColumn(i, m_magRanges.at(i));
Chris@1121 578 }
Chris@1094 579 }
Chris@1084 580
Chris@1167 581 QImage
Chris@1167 582 Colour3DPlotRenderer::scaleDrawBufferImage(QImage image,
Chris@1167 583 int targetWidth,
Chris@1167 584 int targetHeight) const
Chris@1167 585 {
Chris@1167 586 int sourceWidth = image.width();
Chris@1167 587 int sourceHeight = image.height();
Chris@1167 588
Chris@1167 589 // We can only do this if we're making the image larger --
Chris@1167 590 // otherwise peaks may be lost. So this should be called only when
Chris@1167 591 // rendering in DrawBufferBinResolution mode. Whenever the bin
Chris@1167 592 // size is smaller than the pixel size, in either x or y axis, we
Chris@1167 593 // should be using DrawBufferPixelResolution mode instead
Chris@1167 594
Chris@1167 595 if (targetWidth < sourceWidth || targetHeight < sourceHeight) {
Chris@1167 596 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Can only use this function when making the image larger; should be rendering DrawBufferPixelResolution instead");
Chris@1167 597 }
Chris@1167 598
Chris@1167 599 if (sourceWidth <= 0 || sourceHeight <= 0) {
Chris@1167 600 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Source image is empty");
Chris@1167 601 }
Chris@1167 602
Chris@1167 603 if (targetWidth <= 0 || targetHeight <= 0) {
Chris@1167 604 throw std::logic_error("Colour3DPlotRenderer::scaleDrawBufferImage: Target image is empty");
Chris@1167 605 }
Chris@1167 606
Chris@1167 607 // This function exists because of some unpredictable behaviour
Chris@1167 608 // from Qt when scaling images with FastTransformation mode. We
Chris@1167 609 // continue to use Qt's scaler for SmoothTransformation but let's
Chris@1167 610 // bring the non-interpolated version "in-house" so we know what
Chris@1167 611 // it's really doing.
Chris@1167 612
Chris@1167 613 if (m_params.interpolate) {
Chris@1167 614 return image.scaled(targetWidth, targetHeight,
Chris@1167 615 Qt::IgnoreAspectRatio,
Chris@1167 616 Qt::SmoothTransformation);
Chris@1167 617 }
Chris@1167 618
Chris@1167 619 // Same format as the target cache
Chris@1167 620 QImage target(targetWidth, targetHeight, QImage::Format_ARGB32_Premultiplied);
Chris@1167 621
Chris@1167 622 for (int y = 0; y < targetHeight; ++y) {
Chris@1167 623
Chris@1167 624 QRgb *targetLine = reinterpret_cast<QRgb *>(target.scanLine(y));
Chris@1167 625
Chris@1167 626 int sy = int((uint64_t(y) * sourceHeight) / targetHeight);
Chris@1167 627 if (sy == sourceHeight) --sy;
Chris@1167 628
Chris@1167 629 for (int x = 0; x < targetWidth; ++x) {
Chris@1167 630
Chris@1167 631 int sx = int((uint64_t(x) * sourceWidth) / targetWidth);
Chris@1167 632 if (sx == sourceWidth) --sx;
Chris@1167 633
Chris@1167 634 targetLine[x] = image.pixel(sx, sy);
Chris@1167 635 }
Chris@1167 636 }
Chris@1167 637
Chris@1167 638 return target;
Chris@1167 639 }
Chris@1167 640
Chris@1094 641 void
Chris@1113 642 Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v,
Chris@1094 643 int x0, int repaintWidth)
Chris@1094 644 {
Chris@1117 645 Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution");
Chris@1143 646 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1094 647 cerr << "renderToCacheBinResolution" << endl;
Chris@1143 648 #endif
Chris@1094 649
Chris@1094 650 // Draw to the draw buffer, and then scale-copy from there. Draw
Chris@1094 651 // buffer is at bin resolution, i.e. buffer x == source column
Chris@1094 652 // number. We use toolkit smooth scaling for interpolation.
Chris@1084 653
Chris@1100 654 const DenseThreeDimensionalModel *model = m_sources.source;
Chris@1094 655 if (!model || !model->isOK() || !model->isReady()) {
Chris@1094 656 throw std::logic_error("no source model provided, or model not ready");
Chris@1094 657 }
Chris@1094 658
Chris@1094 659 // The draw buffer will contain a fragment at bin resolution. We
Chris@1094 660 // need to ensure that it starts and ends at points where a
Chris@1094 661 // time-bin boundary occurs at an exact pixel boundary, and with a
Chris@1094 662 // certain amount of overlap across existing pixels so that we can
Chris@1094 663 // scale and draw from it without smoothing errors at the edges.
Chris@1094 664
Chris@1094 665 // If (getFrameForX(x) / increment) * increment ==
Chris@1094 666 // getFrameForX(x), then x is a time-bin boundary. We want two
Chris@1094 667 // such boundaries at either side of the draw buffer -- one which
Chris@1094 668 // we draw up to, and one which we subsequently crop at.
Chris@1094 669
Chris@1094 670 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
Chris@1094 671 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
Chris@1094 672
Chris@1094 673 int drawBufferWidth;
Chris@1094 674 int binResolution = model->getResolution();
Chris@1094 675
Chris@1094 676 for (int x = x0; ; --x) {
Chris@1094 677 sv_frame_t f = v->getFrameForX(x);
Chris@1094 678 if ((f / binResolution) * binResolution == f) {
Chris@1094 679 if (leftCropFrame == -1) leftCropFrame = f;
Chris@1094 680 else if (x < x0 - 2) {
Chris@1094 681 leftBoundaryFrame = f;
Chris@1094 682 break;
Chris@1094 683 }
Chris@1094 684 }
Chris@1094 685 }
Chris@1094 686 for (int x = x0 + repaintWidth; ; ++x) {
Chris@1094 687 sv_frame_t f = v->getFrameForX(x);
Chris@1094 688 if ((f / binResolution) * binResolution == f) {
Chris@1094 689 if (rightCropFrame == -1) rightCropFrame = f;
Chris@1094 690 else if (x > x0 + repaintWidth + 2) {
Chris@1094 691 rightBoundaryFrame = f;
Chris@1094 692 break;
Chris@1094 693 }
Chris@1094 694 }
Chris@1094 695 }
Chris@1094 696 drawBufferWidth = int
Chris@1094 697 ((rightBoundaryFrame - leftBoundaryFrame) / binResolution);
Chris@1094 698
Chris@1094 699 int h = v->getPaintHeight();
Chris@1094 700
Chris@1095 701 // For our purposes here, the draw buffer needs to be exactly our
Chris@1095 702 // target size (so we recreate always rather than just clear it)
Chris@1095 703
Chris@1095 704 recreateDrawBuffer(drawBufferWidth, h);
Chris@1094 705
Chris@1094 706 vector<int> binforx(drawBufferWidth);
Chris@1094 707 vector<double> binfory(h);
Chris@1094 708
Chris@1094 709 for (int x = 0; x < drawBufferWidth; ++x) {
Chris@1094 710 binforx[x] = int(leftBoundaryFrame / binResolution) + x;
Chris@1094 711 }
Chris@1094 712
Chris@1167 713 SVDEBUG << "[BIN] binResolution " << binResolution << endl;
Chris@1094 714
Chris@1094 715 for (int y = 0; y < h; ++y) {
Chris@1094 716 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1);
Chris@1094 717 }
Chris@1094 718
Chris@1094 719 int attainedWidth = renderDrawBuffer(drawBufferWidth,
Chris@1094 720 h,
Chris@1094 721 binforx,
Chris@1094 722 binfory,
Chris@1094 723 false,
Chris@1094 724 false,
Chris@1094 725 false);
Chris@1094 726
Chris@1094 727 if (attainedWidth == 0) return;
Chris@1094 728
Chris@1094 729 int scaledLeft = v->getXForFrame(leftBoundaryFrame);
Chris@1094 730 int scaledRight = v->getXForFrame(rightBoundaryFrame);
Chris@1095 731
Chris@1143 732 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1095 733 cerr << "scaling draw buffer from width " << m_drawBuffer.width()
Chris@1095 734 << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = "
Chris@1095 735 << drawBufferWidth << ")" << endl;
Chris@1143 736 #endif
Chris@1167 737
Chris@1167 738 QImage scaled = scaleDrawBufferImage
Chris@1167 739 (m_drawBuffer, scaledRight - scaledLeft, h);
Chris@1084 740
Chris@1094 741 int scaledLeftCrop = v->getXForFrame(leftCropFrame);
Chris@1094 742 int scaledRightCrop = v->getXForFrame(rightCropFrame);
Chris@1094 743
Chris@1094 744 int targetLeft = scaledLeftCrop;
Chris@1094 745 if (targetLeft < 0) {
Chris@1094 746 targetLeft = 0;
Chris@1094 747 }
Chris@1094 748
Chris@1094 749 int targetWidth = scaledRightCrop - targetLeft;
Chris@1094 750 if (targetLeft + targetWidth > m_cache.getSize().width()) {
Chris@1094 751 targetWidth = m_cache.getSize().width() - targetLeft;
Chris@1094 752 }
Chris@1094 753
Chris@1094 754 int sourceLeft = targetLeft - scaledLeft;
Chris@1094 755 if (sourceLeft < 0) {
Chris@1094 756 sourceLeft = 0;
Chris@1094 757 }
Chris@1094 758
Chris@1143 759 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1094 760 cerr << "repaintWidth = " << repaintWidth
Chris@1094 761 << ", targetWidth = " << targetWidth << endl;
Chris@1143 762 #endif
Chris@1094 763
Chris@1094 764 if (targetWidth > 0) {
Chris@1136 765 // we are copying from an image that has already been scaled,
Chris@1136 766 // hence using the same width in both geometries
Chris@1094 767 m_cache.drawImage(targetLeft, targetWidth,
Chris@1094 768 scaled,
Chris@1136 769 sourceLeft, targetWidth);
Chris@1084 770 }
Chris@1121 771
Chris@1121 772 for (int i = 0; i < targetWidth; ++i) {
Chris@1136 773 // but the mag range vector has not been scaled
Chris@1136 774 int sourceIx = int((double(i + sourceLeft) / scaled.width())
Chris@1136 775 * int(m_magRanges.size()));
Chris@1121 776 if (in_range_for(m_magRanges, sourceIx)) {
Chris@1121 777 m_magCache.sampleColumn(i, m_magRanges.at(sourceIx));
Chris@1121 778 }
Chris@1121 779 }
Chris@1079 780 }
Chris@1083 781
Chris@1083 782 int
Chris@1083 783 Colour3DPlotRenderer::renderDrawBuffer(int w, int h,
Chris@1083 784 const vector<int> &binforx,
Chris@1083 785 const vector<double> &binfory,
Chris@1163 786 bool usePeakCache,
Chris@1083 787 bool rightToLeft,
Chris@1083 788 bool timeConstrained)
Chris@1083 789 {
Chris@1083 790 // Callers must have checked that the appropriate subset of
Chris@1083 791 // Sources data members are set for the supplied flags (e.g. that
Chris@1163 792 // peakCache model exists if usePeakCache)
Chris@1083 793
Chris@1083 794 RenderTimer timer(timeConstrained ?
Chris@1083 795 RenderTimer::FastRender :
Chris@1083 796 RenderTimer::NoTimeout);
Chris@1083 797
Chris@1164 798 Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
Chris@1164 799
Chris@1083 800 int divisor = 1;
Chris@1100 801 const DenseThreeDimensionalModel *sourceModel = m_sources.source;
Chris@1163 802 if (usePeakCache) {
Chris@1163 803 divisor = m_sources.peakCache->getColumnsPerPeak();
Chris@1163 804 sourceModel = m_sources.peakCache;
Chris@1083 805 }
Chris@1083 806
cannam@1171 807 SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h
cannam@1171 808 << ", usePeakCache = " << usePeakCache << " (divisor = "
cannam@1171 809 << divisor << "), rightToLeft = " << rightToLeft
cannam@1171 810 << ", timeConstrained = " << timeConstrained << endl;
cannam@1171 811 SVDEBUG << "renderDrawBuffer: normalization = " << int(m_params.normalization)
cannam@1171 812 << ", binDisplay = " << int(m_params.binDisplay)
cannam@1171 813 << ", binScale = " << int(m_params.binScale)
cannam@1171 814 << ", alwaysOpaque = " << m_params.alwaysOpaque
cannam@1171 815 << ", interpolate = " << m_params.interpolate << endl;
cannam@1171 816
Chris@1135 817 int sh = sourceModel->getHeight();
Chris@1135 818
Chris@1135 819 int minbin = int(binfory[0] + 0.0001);
Chris@1135 820 if (minbin >= sh) minbin = sh - 1;
Chris@1135 821 if (minbin < 0) minbin = 0;
Chris@1135 822
Chris@1170 823 int nbins = int(binfory[h-1] + 0.0001) - minbin + 1;
Chris@1135 824 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1162 825
Chris@1162 826 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1162 827 cerr << "minbin = " << minbin << ", nbins = " << nbins << ", last binfory = "
Chris@1170 828 << binfory[h-1] << " (rounds to " << int(binfory[h-1]) << ") (model height " << sh << ")" << endl;
Chris@1162 829 #endif
Chris@1135 830
Chris@1083 831 int psx = -1;
Chris@1083 832
Chris@1083 833 int start = 0;
Chris@1083 834 int finish = w;
Chris@1083 835 int step = 1;
Chris@1083 836
Chris@1083 837 if (rightToLeft) {
Chris@1083 838 start = w-1;
Chris@1083 839 finish = -1;
Chris@1083 840 step = -1;
Chris@1083 841 }
Chris@1083 842
Chris@1083 843 int columnCount = 0;
Chris@1083 844
Chris@1083 845 vector<float> preparedColumn;
Chris@1094 846
Chris@1094 847 int modelWidth = sourceModel->getWidth();
Chris@1121 848
Chris@1143 849 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1121 850 cerr << "modelWidth " << modelWidth << ", divisor " << divisor << endl;
Chris@1143 851 #endif
Chris@1143 852
Chris@1083 853 for (int x = start; x != finish; x += step) {
Chris@1083 854
Chris@1083 855 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1083 856 // source column index
Chris@1083 857
Chris@1083 858 ++columnCount;
Chris@1083 859
Chris@1083 860 if (binforx[x] < 0) continue;
Chris@1083 861
Chris@1083 862 int sx0 = binforx[x] / divisor;
Chris@1083 863 int sx1 = sx0;
Chris@1083 864 if (x+1 < w) sx1 = binforx[x+1] / divisor;
Chris@1083 865 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1083 866 if (sx0 < 0) continue;
Chris@1083 867 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1083 868
Chris@1161 869 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1167 870 // cerr << "x = " << x << ", binforx[x] = " << binforx[x] << ", sx range " << sx0 << " -> " << sx1 << endl;
Chris@1161 871 #endif
Chris@1161 872
Chris@1083 873 vector<float> pixelPeakColumn;
Chris@1121 874 MagnitudeRange magRange;
Chris@1083 875
Chris@1083 876 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1083 877
Chris@1094 878 if (sx < 0 || sx >= modelWidth) {
Chris@1083 879 continue;
Chris@1083 880 }
Chris@1083 881
Chris@1083 882 if (sx != psx) {
Chris@1138 883
Chris@1138 884 // order:
Chris@1138 885 // get column -> scale -> normalise -> record extents ->
Chris@1138 886 // peak pick -> distribute/interpolate -> apply display gain
Chris@1083 887
Chris@1138 888 // this does the first three:
Chris@1161 889 ColumnOp::Column column = getColumn(sx, minbin, nbins,
Chris@1163 890 usePeakCache);
Chris@1083 891
Chris@1131 892 magRange.sample(column);
Chris@1162 893
Chris@1103 894 if (m_params.binDisplay == BinDisplay::PeakBins) {
Chris@1083 895 column = ColumnOp::peakPick(column);
Chris@1083 896 }
Chris@1083 897
Chris@1083 898 preparedColumn =
Chris@1124 899 ColumnOp::distribute(column,
Chris@1083 900 h,
Chris@1083 901 binfory,
Chris@1083 902 minbin,
Chris@1083 903 m_params.interpolate);
Chris@1124 904
Chris@1124 905 // Display gain belongs to the colour scale and is
Chris@1124 906 // applied by the colour scale object when mapping it
Chris@1083 907
Chris@1083 908 psx = sx;
Chris@1083 909 }
Chris@1083 910
Chris@1083 911 if (sx == sx0) {
Chris@1083 912 pixelPeakColumn = preparedColumn;
Chris@1083 913 } else {
Chris@1083 914 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1083 915 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1083 916 preparedColumn[i]);
Chris@1083 917 }
Chris@1083 918 }
Chris@1083 919 }
Chris@1083 920
Chris@1083 921 if (!pixelPeakColumn.empty()) {
Chris@1121 922
Chris@1083 923 for (int y = 0; y < h; ++y) {
Chris@1116 924 int py;
Chris@1116 925 if (m_params.invertVertical) {
Chris@1116 926 py = y;
Chris@1116 927 } else {
Chris@1116 928 py = h - y - 1;
Chris@1116 929 }
Chris@1083 930 m_drawBuffer.setPixel
Chris@1083 931 (x,
Chris@1116 932 py,
Chris@1083 933 m_params.colourScale.getPixel(pixelPeakColumn[y]));
Chris@1083 934 }
Chris@1121 935
Chris@1121 936 m_magRanges.push_back(magRange);
Chris@1083 937 }
Chris@1083 938
Chris@1083 939 double fractionComplete = double(columnCount) / double(w);
Chris@1083 940 if (timer.outOfTime(fractionComplete)) {
Chris@1121 941 cerr << "out of time" << endl;
Chris@1083 942 return columnCount;
Chris@1083 943 }
Chris@1083 944 }
Chris@1083 945
Chris@1083 946 return columnCount;
Chris@1083 947 }
Chris@1083 948
Chris@1097 949 int
Chris@1113 950 Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v,
Chris@1097 951 int w, int h,
Chris@1097 952 const vector<int> &binforx,
Chris@1097 953 const vector<double> &binfory,
Chris@1097 954 bool rightToLeft,
Chris@1097 955 bool timeConstrained)
Chris@1097 956 {
Chris@1097 957 // Callers must have checked that the appropriate subset of
Chris@1097 958 // Sources data members are set for the supplied flags (e.g. that
Chris@1097 959 // fft model exists)
Chris@1097 960
Chris@1097 961 RenderTimer timer(timeConstrained ?
Chris@1097 962 RenderTimer::FastRender :
Chris@1097 963 RenderTimer::NoTimeout);
Chris@1097 964
Chris@1135 965 const FFTModel *fft = m_sources.fft;
Chris@1135 966
Chris@1135 967 int sh = fft->getHeight();
Chris@1135 968
Chris@1097 969 int minbin = int(binfory[0] + 0.0001);
Chris@1135 970 if (minbin >= sh) minbin = sh - 1;
Chris@1097 971 if (minbin < 0) minbin = 0;
Chris@1097 972
Chris@1135 973 int nbins = int(binfory[h-1]) - minbin + 1;
Chris@1135 974 if (minbin + nbins > sh) nbins = sh - minbin;
Chris@1097 975
Chris@1097 976 FFTModel::PeakSet peakfreqs;
Chris@1097 977
Chris@1097 978 int psx = -1;
Chris@1097 979
Chris@1097 980 int start = 0;
Chris@1097 981 int finish = w;
Chris@1097 982 int step = 1;
Chris@1097 983
Chris@1097 984 if (rightToLeft) {
Chris@1097 985 start = w-1;
Chris@1097 986 finish = -1;
Chris@1097 987 step = -1;
Chris@1097 988 }
Chris@1097 989
Chris@1097 990 int columnCount = 0;
Chris@1097 991
Chris@1097 992 vector<float> preparedColumn;
Chris@1097 993
Chris@1097 994 int modelWidth = fft->getWidth();
Chris@1143 995 #ifdef DEBUG_COLOUR_PLOT_REPAINT
Chris@1097 996 cerr << "modelWidth " << modelWidth << endl;
Chris@1143 997 #endif
Chris@1143 998
Chris@1135 999 double minFreq =
Chris@1135 1000 (double(minbin) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1135 1001 double maxFreq =
Chris@1135 1002 (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize();
Chris@1097 1003
Chris@1103 1004 bool logarithmic = (m_params.binScale == BinScale::Log);
Chris@1097 1005
Chris@1097 1006 for (int x = start; x != finish; x += step) {
Chris@1097 1007
Chris@1097 1008 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1097 1009 // source column index
Chris@1097 1010
Chris@1097 1011 ++columnCount;
Chris@1097 1012
Chris@1097 1013 if (binforx[x] < 0) continue;
Chris@1097 1014
Chris@1097 1015 int sx0 = binforx[x];
Chris@1097 1016 int sx1 = sx0;
Chris@1097 1017 if (x+1 < w) sx1 = binforx[x+1];
Chris@1097 1018 if (sx0 < 0) sx0 = sx1 - 1;
Chris@1097 1019 if (sx0 < 0) continue;
Chris@1097 1020 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@1097 1021
Chris@1097 1022 vector<float> pixelPeakColumn;
Chris@1121 1023 MagnitudeRange magRange;
Chris@1097 1024
Chris@1097 1025 for (int sx = sx0; sx < sx1; ++sx) {
Chris@1097 1026
Chris@1097 1027 if (sx < 0 || sx >= modelWidth) {
Chris@1097 1028 continue;
Chris@1097 1029 }
Chris@1097 1030
Chris@1097 1031 if (sx != psx) {
Chris@1161 1032 preparedColumn = getColumn(sx, minbin, nbins, false);
Chris@1131 1033 magRange.sample(preparedColumn);
Chris@1097 1034 psx = sx;
Chris@1097 1035 }
Chris@1097 1036
Chris@1097 1037 if (sx == sx0) {
Chris@1097 1038 pixelPeakColumn = preparedColumn;
Chris@1097 1039 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx,
Chris@1135 1040 minbin, minbin + nbins - 1);
Chris@1097 1041 } else {
Chris@1097 1042 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1097 1043 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1097 1044 preparedColumn[i]);
Chris@1097 1045 }
Chris@1097 1046 }
Chris@1097 1047 }
Chris@1097 1048
Chris@1097 1049 if (!pixelPeakColumn.empty()) {
Chris@1164 1050
Chris@1097 1051 for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin();
Chris@1097 1052 pi != peakfreqs.end(); ++pi) {
Chris@1097 1053
Chris@1097 1054 int bin = pi->first;
Chris@1097 1055 double freq = pi->second;
Chris@1097 1056
Chris@1097 1057 if (bin < minbin) continue;
Chris@1135 1058 if (bin >= minbin + nbins) break;
Chris@1097 1059
Chris@1097 1060 double value = pixelPeakColumn[bin - minbin];
Chris@1097 1061
Chris@1097 1062 double y = v->getYForFrequency
Chris@1097 1063 (freq, minFreq, maxFreq, logarithmic);
Chris@1097 1064
Chris@1097 1065 int iy = int(y + 0.5);
Chris@1097 1066 if (iy < 0 || iy >= h) continue;
Chris@1097 1067
Chris@1097 1068 m_drawBuffer.setPixel
Chris@1097 1069 (x,
Chris@1097 1070 iy,
Chris@1097 1071 m_params.colourScale.getPixel(value));
Chris@1097 1072 }
Chris@1121 1073
Chris@1121 1074 m_magRanges.push_back(magRange);
Chris@1097 1075 }
Chris@1097 1076
Chris@1097 1077 double fractionComplete = double(columnCount) / double(w);
Chris@1097 1078 if (timer.outOfTime(fractionComplete)) {
Chris@1097 1079 return columnCount;
Chris@1097 1080 }
Chris@1097 1081 }
Chris@1097 1082
Chris@1097 1083 return columnCount;
Chris@1097 1084 }
Chris@1097 1085
Chris@1079 1086 void
Chris@1095 1087 Colour3DPlotRenderer::recreateDrawBuffer(int w, int h)
Chris@1079 1088 {
Chris@1095 1089 m_drawBuffer = QImage(w, h, QImage::Format_Indexed8);
Chris@1079 1090
Chris@1095 1091 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@1095 1092 m_drawBuffer.setColor
Chris@1095 1093 ((unsigned char)pixel,
Chris@1112 1094 m_params.colourScale.getColourForPixel
Chris@1112 1095 (pixel, m_params.colourRotation).rgb());
Chris@1079 1096 }
Chris@1079 1097
Chris@1079 1098 m_drawBuffer.fill(0);
Chris@1121 1099 m_magRanges.clear();
Chris@1079 1100 }
Chris@1079 1101
Chris@1095 1102 void
Chris@1095 1103 Colour3DPlotRenderer::clearDrawBuffer(int w, int h)
Chris@1095 1104 {
Chris@1095 1105 if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) {
Chris@1095 1106 recreateDrawBuffer(w, h);
Chris@1095 1107 } else {
Chris@1095 1108 m_drawBuffer.fill(0);
Chris@1121 1109 m_magRanges.clear();
Chris@1095 1110 }
Chris@1095 1111 }
Chris@1079 1112
Chris@1139 1113 QRect
Chris@1139 1114 Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const
Chris@1139 1115 {
Chris@1139 1116 QImage image = m_cache.getImage();
Chris@1139 1117 ImageRegionFinder finder;
Chris@1139 1118 QRect rect = finder.findRegionExtents(&image, p);
Chris@1139 1119 return rect;
Chris@1139 1120 }