Mercurial > hg > svgui
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 } |