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