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