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