Mercurial > hg > svgui
comparison layer/Colour3DPlotRenderer.cpp @ 1094:8a815776151c spectrogram-minor-refactor
Split out cache rendering functions and some fixes to calculations
author | Chris Cannam |
---|---|
date | Thu, 07 Jul 2016 19:18:31 +0100 |
parents | c8c747783110 |
children | ba62684a4512 |
comparison
equal
deleted
inserted
replaced
1093:cd22f74dc159 | 1094:8a815776151c |
---|---|
23 #include "LayerGeometryProvider.h" | 23 #include "LayerGeometryProvider.h" |
24 #include "VerticalBinLayer.h" | 24 #include "VerticalBinLayer.h" |
25 | 25 |
26 #include <vector> | 26 #include <vector> |
27 | 27 |
28 //#define DEBUG_SPECTROGRAM_REPAINT 1 | |
29 | |
28 using namespace std; | 30 using namespace std; |
29 | 31 |
30 Colour3DPlotRenderer::RenderResult | 32 Colour3DPlotRenderer::RenderResult |
31 Colour3DPlotRenderer::render(LayerGeometryProvider *v, QPainter &paint, QRect rect) | 33 Colour3DPlotRenderer::render(LayerGeometryProvider *v, QPainter &paint, QRect rect) |
32 { | 34 { |
53 | 55 |
54 m_cache.resize(v->getPaintSize()); | 56 m_cache.resize(v->getPaintSize()); |
55 m_cache.setZoomLevel(v->getZoomLevel()); | 57 m_cache.setZoomLevel(v->getZoomLevel()); |
56 | 58 |
57 cerr << "cache start " << m_cache.getStartFrame() | 59 cerr << "cache start " << m_cache.getStartFrame() |
58 << " view start " << startFrame | |
59 << " valid left " << m_cache.getValidLeft() | 60 << " valid left " << m_cache.getValidLeft() |
60 << " valid right " << m_cache.getValidRight() | 61 << " valid right " << m_cache.getValidRight() |
62 << endl; | |
63 cerr << " view start " << startFrame | |
61 << " x0 " << x0 | 64 << " x0 " << x0 |
62 << " x1 " << x1 | 65 << " x1 " << x1 |
63 << endl; | 66 << endl; |
64 | 67 |
68 bool bufferIsBinResolution = useBinResolutionForDrawBuffer(v); | |
69 | |
70 if (bufferIsBinResolution) { | |
71 // Rendering should be fast in this situation because we are | |
72 // quite well zoomed-in, and the sums are easier this | |
73 // way. Calculating boundaries later will be fiddly for | |
74 // partial paints otherwise. | |
75 timeConstrained = false; | |
76 } | |
65 | 77 |
66 if (m_cache.isValid()) { // some part of the cache is valid | 78 if (m_cache.isValid()) { // some part of the cache is valid |
67 | 79 |
68 if (v->getXForFrame(m_cache.getStartFrame()) == | 80 if (v->getXForFrame(m_cache.getStartFrame()) == |
69 v->getXForFrame(startFrame) && | 81 v->getXForFrame(startFrame) && |
138 // sub-regions of our target region in right-to-left order in | 150 // sub-regions of our target region in right-to-left order in |
139 // order to ensure contiguity | 151 // order to ensure contiguity |
140 rightToLeft = isLeftOfValidArea; | 152 rightToLeft = isLeftOfValidArea; |
141 } | 153 } |
142 | 154 |
143 renderToCache(v, x0, x1 - x0, rightToLeft, timeConstrained); | 155 // Note, we always paint the full height. Smaller heights can be |
156 // used when painting direct from cache (outside this function), | |
157 // but we want to ensure the cache is coherent without having to | |
158 // worry about vertical matching of required and valid areas as | |
159 // well as horizontal. That's why this function didn't take any | |
160 // y/height parameters. | |
161 | |
162 if (bufferIsBinResolution) { | |
163 renderToCacheBinResolution(v, x0, x1 - x0); | |
164 } else { | |
165 renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained); | |
166 } | |
144 | 167 |
145 QRect pr = rect & m_cache.getValidArea(); | 168 QRect pr = rect & m_cache.getValidArea(); |
146 paint.drawImage(pr.x(), pr.y(), m_cache.getImage(), | 169 paint.drawImage(pr.x(), pr.y(), m_cache.getImage(), |
147 pr.x(), pr.y(), pr.width(), pr.height()); | 170 pr.x(), pr.y(), pr.width(), pr.height()); |
148 | 171 |
173 //!!! fft model scaling? | 196 //!!! fft model scaling? |
174 | 197 |
175 //!!! should we own the Dense3DModelPeakCache here? or should it persist | 198 //!!! should we own the Dense3DModelPeakCache here? or should it persist |
176 } | 199 } |
177 | 200 |
201 bool | |
202 Colour3DPlotRenderer::useBinResolutionForDrawBuffer(LayerGeometryProvider *v) const | |
203 { | |
204 DenseThreeDimensionalModel *model = m_sources.source; | |
205 if (!model) return false; | |
206 int binResolution = model->getResolution(); | |
207 int zoomLevel = v->getZoomLevel(); | |
208 return (binResolution > zoomLevel); | |
209 } | |
210 | |
178 void | 211 void |
179 Colour3DPlotRenderer::renderToCache(LayerGeometryProvider *v, | 212 Colour3DPlotRenderer::renderToCachePixelResolution(LayerGeometryProvider *v, |
180 int x0, int repaintWidth, | 213 int x0, int repaintWidth, |
181 bool rightToLeft, bool timeConstrained) | 214 bool rightToLeft, |
182 { | 215 bool timeConstrained) |
183 // Draw to the draw buffer, and then scale-copy from there. | 216 { |
217 cerr << "renderToCachePixelResolution" << endl; | |
218 | |
219 // Draw to the draw buffer, and then copy from there. The draw | |
220 // buffer is at the same resolution as the target in the cache, so | |
221 // no extra scaling needed. | |
184 | 222 |
185 DenseThreeDimensionalModel *model = m_sources.source; | 223 DenseThreeDimensionalModel *model = m_sources.source; |
186 if (!model || !model->isOK() || !model->isReady()) { | 224 if (!model || !model->isOK() || !model->isReady()) { |
187 throw std::logic_error("no source model provided, or model not ready"); | 225 throw std::logic_error("no source model provided, or model not ready"); |
188 } | 226 } |
189 | 227 |
190 // The draw buffer contains a fragment at either our pixel | |
191 // resolution (if there is more than one time-bin per pixel) or | |
192 // time-bin resolution (if a time-bin spans more than one pixel). | |
193 // We need to ensure that it starts and ends at points where a | |
194 // time-bin boundary occurs at an exact pixel boundary, and with a | |
195 // certain amount of overlap across existing pixels so that we can | |
196 // scale and draw from it without smoothing errors at the edges. | |
197 | |
198 // If (getFrameForX(x) / increment) * increment == | |
199 // getFrameForX(x), then x is a time-bin boundary. We want two | |
200 // such boundaries at either side of the draw buffer -- one which | |
201 // we draw up to, and one which we subsequently crop at. | |
202 | |
203 bool bufferIsBinResolution = false; | |
204 int binResolution = model->getResolution(); | |
205 int zoomLevel = v->getZoomLevel(); | |
206 if (binResolution > zoomLevel) bufferIsBinResolution = true; | |
207 | |
208 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; | |
209 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; | |
210 | |
211 int drawWidth; | |
212 | |
213 if (bufferIsBinResolution) { | |
214 for (int x = x0; ; --x) { | |
215 sv_frame_t f = v->getFrameForX(x); | |
216 if ((f / binResolution) * binResolution == f) { | |
217 if (leftCropFrame == -1) leftCropFrame = f; | |
218 else if (x < x0 - 2) { | |
219 leftBoundaryFrame = f; | |
220 break; | |
221 } | |
222 } | |
223 } | |
224 for (int x = x0 + repaintWidth; ; ++x) { | |
225 sv_frame_t f = v->getFrameForX(x); | |
226 if ((f / binResolution) * binResolution == f) { | |
227 if (rightCropFrame == -1) rightCropFrame = f; | |
228 else if (x > x0 + repaintWidth + 2) { | |
229 rightBoundaryFrame = f; | |
230 break; | |
231 } | |
232 } | |
233 } | |
234 drawWidth = int((rightBoundaryFrame - leftBoundaryFrame) / binResolution); | |
235 } else { | |
236 drawWidth = repaintWidth; | |
237 } | |
238 | |
239 // We always paint the full height. Smaller heights can be used | |
240 // when painting direct from cache (outside this function), but we | |
241 // want to ensure the cache is coherent without having to worry | |
242 // about vertical matching of required and valid areas as well as | |
243 // horizontal. That's why this function didn't take any y/height | |
244 // parameters. | |
245 int h = v->getPaintHeight(); | 228 int h = v->getPaintHeight(); |
246 | 229 |
247 clearDrawBuffer(drawWidth, h); | 230 clearDrawBuffer(repaintWidth, h); |
248 | 231 |
249 vector<int> binforx(drawWidth); | 232 vector<int> binforx(repaintWidth); |
250 vector<double> binfory(h); | 233 vector<double> binfory(h); |
251 | 234 |
252 bool usePeaksCache = false; | 235 bool usePeaksCache = false; |
253 int binsPerPeak = 1; | 236 int binsPerPeak = 1; |
254 | 237 int zoomLevel = v->getZoomLevel(); |
255 if (bufferIsBinResolution) { | 238 int binResolution = model->getResolution(); |
256 | 239 |
257 for (int x = 0; x < drawWidth; ++x) { | 240 for (int x = 0; x < repaintWidth; ++x) { |
258 binforx[x] = int(leftBoundaryFrame / binResolution) + x; | 241 sv_frame_t f0 = v->getFrameForX(x0 + x); |
259 } | 242 double s0 = double(f0 - model->getStartFrame()) / binResolution; |
260 | 243 binforx[x] = int(s0 + 0.0001); |
261 // calculating boundaries later will be too fiddly for partial | 244 } |
262 // paints, and painting should be fast anyway when this is the | 245 |
263 // case because it means we're well zoomed in | 246 if (m_sources.peaks) { // peaks cache exists |
264 timeConstrained = false; | 247 |
265 | 248 binsPerPeak = m_sources.peaks->getColumnsPerPeak(); |
266 } else { | 249 usePeaksCache = (binResolution * binsPerPeak) < zoomLevel; |
267 for (int x = 0; x < drawWidth; ++x) { | 250 |
268 sv_frame_t f0 = v->getFrameForX(x0 + x); | 251 if (m_params.colourScale.getScale() == |
269 double s0 = double(f0 - model->getStartFrame()) / binResolution; | 252 ColourScale::PhaseColourScale) { |
270 binforx[x] = int(s0 + 0.0001); | 253 usePeaksCache = false; |
271 } | 254 } |
272 | 255 } |
273 if (m_sources.peaks) { // peaks cache exists | 256 |
274 | 257 cerr << "[PIX] zoomLevel = " << zoomLevel |
275 binsPerPeak = m_sources.peaks->getColumnsPerPeak(); | 258 << ", binResolution " << binResolution |
276 usePeaksCache = (binResolution * binsPerPeak) < zoomLevel; | 259 << ", binsPerPeak " << binsPerPeak |
277 | 260 << ", peak cache " << m_sources.peaks |
278 if (m_params.colourScale.getScale() == | 261 << ", usePeaksCache = " << usePeaksCache |
279 ColourScale::PhaseColourScale) { | 262 << endl; |
280 usePeaksCache = false; | 263 |
281 } | |
282 } | |
283 } | |
284 | |
285 for (int y = 0; y < h; ++y) { | 264 for (int y = 0; y < h; ++y) { |
286 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); | 265 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); |
287 } | 266 } |
288 | 267 |
289 int attainedWidth = renderDrawBuffer(repaintWidth, | 268 int attainedWidth = renderDrawBuffer(repaintWidth, |
292 binfory, | 271 binfory, |
293 usePeaksCache, | 272 usePeaksCache, |
294 rightToLeft, | 273 rightToLeft, |
295 timeConstrained); | 274 timeConstrained); |
296 | 275 |
297 //!!! now scale-copy to cache | |
298 | |
299 if (attainedWidth == 0) return; | 276 if (attainedWidth == 0) return; |
277 | |
278 // draw buffer is pixel resolution, no scaling factors or padding involved | |
300 | 279 |
301 int paintedLeft = x0; | 280 int paintedLeft = x0; |
302 if (rightToLeft) { | 281 if (rightToLeft) { |
303 paintedLeft += (repaintWidth - attainedWidth); | 282 paintedLeft += (repaintWidth - attainedWidth); |
304 } | 283 } |
305 | 284 |
306 if (bufferIsBinResolution) { | 285 m_cache.drawImage(paintedLeft, attainedWidth, |
307 | 286 m_drawBuffer, |
308 int scaledLeft = v->getXForFrame(leftBoundaryFrame); | 287 paintedLeft - x0, attainedWidth); |
309 int scaledRight = v->getXForFrame(rightBoundaryFrame); | 288 } |
310 | 289 |
311 QImage scaled = m_drawBuffer.scaled | 290 void |
312 (scaledRight - scaledLeft, h, | 291 Colour3DPlotRenderer::renderToCacheBinResolution(LayerGeometryProvider *v, |
313 Qt::IgnoreAspectRatio, (m_params.interpolate ? | 292 int x0, int repaintWidth) |
314 Qt::SmoothTransformation : | 293 { |
315 Qt::FastTransformation)); | 294 cerr << "renderToCacheBinResolution" << endl; |
295 | |
296 // Draw to the draw buffer, and then scale-copy from there. Draw | |
297 // buffer is at bin resolution, i.e. buffer x == source column | |
298 // number. We use toolkit smooth scaling for interpolation. | |
299 | |
300 DenseThreeDimensionalModel *model = m_sources.source; | |
301 if (!model || !model->isOK() || !model->isReady()) { | |
302 throw std::logic_error("no source model provided, or model not ready"); | |
303 } | |
304 | |
305 // The draw buffer will contain a fragment at bin resolution. We | |
306 // need to ensure that it starts and ends at points where a | |
307 // time-bin boundary occurs at an exact pixel boundary, and with a | |
308 // certain amount of overlap across existing pixels so that we can | |
309 // scale and draw from it without smoothing errors at the edges. | |
310 | |
311 // If (getFrameForX(x) / increment) * increment == | |
312 // getFrameForX(x), then x is a time-bin boundary. We want two | |
313 // such boundaries at either side of the draw buffer -- one which | |
314 // we draw up to, and one which we subsequently crop at. | |
315 | |
316 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; | |
317 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; | |
318 | |
319 int drawBufferWidth; | |
320 int binResolution = model->getResolution(); | |
321 | |
322 for (int x = x0; ; --x) { | |
323 sv_frame_t f = v->getFrameForX(x); | |
324 if ((f / binResolution) * binResolution == f) { | |
325 if (leftCropFrame == -1) leftCropFrame = f; | |
326 else if (x < x0 - 2) { | |
327 leftBoundaryFrame = f; | |
328 break; | |
329 } | |
330 } | |
331 } | |
332 for (int x = x0 + repaintWidth; ; ++x) { | |
333 sv_frame_t f = v->getFrameForX(x); | |
334 if ((f / binResolution) * binResolution == f) { | |
335 if (rightCropFrame == -1) rightCropFrame = f; | |
336 else if (x > x0 + repaintWidth + 2) { | |
337 rightBoundaryFrame = f; | |
338 break; | |
339 } | |
340 } | |
341 } | |
342 drawBufferWidth = int | |
343 ((rightBoundaryFrame - leftBoundaryFrame) / binResolution); | |
344 | |
345 int h = v->getPaintHeight(); | |
346 | |
347 clearDrawBuffer(drawBufferWidth, h); | |
348 | |
349 vector<int> binforx(drawBufferWidth); | |
350 vector<double> binfory(h); | |
351 | |
352 for (int x = 0; x < drawBufferWidth; ++x) { | |
353 binforx[x] = int(leftBoundaryFrame / binResolution) + x; | |
354 } | |
355 | |
356 cerr << "[BIN] binResolution " << binResolution | |
357 << endl; | |
358 | |
359 for (int y = 0; y < h; ++y) { | |
360 binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); | |
361 } | |
362 | |
363 int attainedWidth = renderDrawBuffer(drawBufferWidth, | |
364 h, | |
365 binforx, | |
366 binfory, | |
367 false, | |
368 false, | |
369 false); | |
370 | |
371 if (attainedWidth == 0) return; | |
372 | |
373 int scaledLeft = v->getXForFrame(leftBoundaryFrame); | |
374 int scaledRight = v->getXForFrame(rightBoundaryFrame); | |
375 | |
376 QImage scaled = m_drawBuffer.scaled | |
377 (scaledRight - scaledLeft, h, | |
378 Qt::IgnoreAspectRatio, (m_params.interpolate ? | |
379 Qt::SmoothTransformation : | |
380 Qt::FastTransformation)); | |
316 | 381 |
317 int scaledLeftCrop = v->getXForFrame(leftCropFrame); | 382 int scaledLeftCrop = v->getXForFrame(leftCropFrame); |
318 int scaledRightCrop = v->getXForFrame(rightCropFrame); | 383 int scaledRightCrop = v->getXForFrame(rightCropFrame); |
319 | 384 |
320 int targetLeft = scaledLeftCrop; | 385 int targetLeft = scaledLeftCrop; |
321 if (targetLeft < 0) { | 386 if (targetLeft < 0) { |
322 targetLeft = 0; | 387 targetLeft = 0; |
323 } | 388 } |
324 | 389 |
325 int targetWidth = scaledRightCrop - targetLeft; | 390 int targetWidth = scaledRightCrop - targetLeft; |
326 if (targetLeft + targetWidth > m_cache.getSize().width()) { | 391 if (targetLeft + targetWidth > m_cache.getSize().width()) { |
327 targetWidth = m_cache.getSize().width() - targetLeft; | 392 targetWidth = m_cache.getSize().width() - targetLeft; |
328 } | 393 } |
329 | 394 |
330 int sourceLeft = targetLeft - scaledLeft; | 395 int sourceLeft = targetLeft - scaledLeft; |
331 if (sourceLeft < 0) { | 396 if (sourceLeft < 0) { |
332 sourceLeft = 0; | 397 sourceLeft = 0; |
333 } | 398 } |
334 | 399 |
335 int sourceWidth = targetWidth; | 400 int sourceWidth = targetWidth; |
336 | 401 |
337 if (targetWidth > 0) { | 402 cerr << "repaintWidth = " << repaintWidth |
338 m_cache.drawImage(targetLeft, targetWidth, | 403 << ", targetWidth = " << targetWidth << endl; |
339 scaled, | 404 |
340 sourceLeft, sourceWidth); | 405 if (targetWidth > 0) { |
341 } | 406 m_cache.drawImage(targetLeft, targetWidth, |
342 | 407 scaled, |
343 } else { | 408 sourceLeft, sourceWidth); |
344 | |
345 m_cache.drawImage(paintedLeft, attainedWidth, | |
346 m_drawBuffer, | |
347 paintedLeft - x0, attainedWidth); | |
348 } | 409 } |
349 } | 410 } |
350 | 411 |
351 int | 412 int |
352 Colour3DPlotRenderer::renderDrawBuffer(int w, int h, | 413 Colour3DPlotRenderer::renderDrawBuffer(int w, int h, |
389 } | 450 } |
390 | 451 |
391 int columnCount = 0; | 452 int columnCount = 0; |
392 | 453 |
393 vector<float> preparedColumn; | 454 vector<float> preparedColumn; |
455 | |
456 int modelWidth = sourceModel->getWidth(); | |
457 cerr << "modelWidth " << modelWidth << endl; | |
394 | 458 |
395 for (int x = start; x != finish; x += step) { | 459 for (int x = start; x != finish; x += step) { |
396 | 460 |
397 // x is the on-canvas pixel coord; sx (later) will be the | 461 // x is the on-canvas pixel coord; sx (later) will be the |
398 // source column index | 462 // source column index |
411 vector<float> pixelPeakColumn; | 475 vector<float> pixelPeakColumn; |
412 | 476 |
413 for (int sx = sx0; sx < sx1; ++sx) { | 477 for (int sx = sx0; sx < sx1; ++sx) { |
414 | 478 |
415 #ifdef DEBUG_SPECTROGRAM_REPAINT | 479 #ifdef DEBUG_SPECTROGRAM_REPAINT |
416 // cerr << "sx = " << sx << endl; | 480 cerr << "sx = " << sx << endl; |
417 #endif | 481 #endif |
418 | 482 |
419 if (sx < 0 || sx >= sourceModel->getWidth()) { | 483 if (sx < 0 || sx >= modelWidth) { |
420 continue; | 484 continue; |
421 } | 485 } |
422 | 486 |
423 if (sx != psx) { | 487 if (sx != psx) { |
424 | 488 |
427 // normalise -> peak pick -> apply display gain -> | 491 // normalise -> peak pick -> apply display gain -> |
428 // distribute/interpolate | 492 // distribute/interpolate |
429 | 493 |
430 ColumnOp::Column fullColumn = sourceModel->getColumn(sx); | 494 ColumnOp::Column fullColumn = sourceModel->getColumn(sx); |
431 | 495 |
432 cerr << "x " << x << ", sx " << sx << ", col height " << fullColumn.size() | 496 // cerr << "x " << x << ", sx " << sx << ", col height " << fullColumn.size() |
433 << ", minbin " << minbin << ", maxbin " << maxbin << endl; | 497 // << ", minbin " << minbin << ", maxbin " << maxbin << endl; |
434 | 498 |
435 ColumnOp::Column column = | 499 ColumnOp::Column column = |
436 vector<float>(fullColumn.data() + minbin, | 500 vector<float>(fullColumn.data() + minbin, |
437 fullColumn.data() + maxbin + 1); | 501 fullColumn.data() + maxbin + 1); |
438 | 502 |