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