comparison layer/Colour3DPlotRenderer.cpp @ 1216:dc2af6616c83

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