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