comparison layer/Colour3DPlotRenderer.cpp @ 1079:7ebfb61b1701 spectrogram-minor-refactor

More filling in render & cache code
author Chris Cannam
date Thu, 30 Jun 2016 15:46:14 +0100
parents 5144d7185fb5
children 2e5945b87aca
comparison
equal deleted inserted replaced
1078:ee01a4062747 1079:7ebfb61b1701
20 #include "data/model/Dense3DModelPeakCache.h" 20 #include "data/model/Dense3DModelPeakCache.h"
21 #include "data/model/FFTModel.h" 21 #include "data/model/FFTModel.h"
22 22
23 #include "LayerGeometryProvider.h" 23 #include "LayerGeometryProvider.h"
24 24
25 #include <vector>
26
27 using namespace std;
28
25 Colour3DPlotRenderer::RenderResult 29 Colour3DPlotRenderer::RenderResult
26 Colour3DPlotRenderer::render(QPainter &paint, QRect rect) 30 Colour3DPlotRenderer::render(QPainter &paint, QRect rect)
27 { 31 {
28 return render(paint, rect, false); 32 return render(paint, rect, false);
29 } 33 }
40 LayerGeometryProvider *v = m_sources.geometryProvider; 44 LayerGeometryProvider *v = m_sources.geometryProvider;
41 if (!v) { 45 if (!v) {
42 throw std::logic_error("no LayerGeometryProvider provided"); 46 throw std::logic_error("no LayerGeometryProvider provided");
43 } 47 }
44 48
49 sv_frame_t startFrame = v->getStartFrame();
50 int zoomLevel = v->getZoomLevel();
51
52 int x0 = v->getXForViewX(rect.x());
53 int x1 = v->getXForViewX(rect.x() + rect.width());
54 if (x0 < 0) x0 = 0;
55 if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
56
57 m_cache.resize(v->getPaintSize());
58 m_cache.setZoomLevel(v->getZoomLevel());
59
60 if (m_cache.isValid()) { // some part of the cache is valid
61
62 if (v->getXForFrame(m_cache.getStartFrame()) ==
63 v->getXForFrame(startFrame) &&
64 m_cache.getValidLeft() <= x0 &&
65 m_cache.getValidRight() >= x1) {
66
67 // cache is valid for the complete requested area
68 paint.drawImage(rect, m_cache.getImage(), rect);
69 return { rect, {} };
70
71 } else {
72 // cache doesn't begin at the right frame or doesn't
73 // contain the complete view, but might be scrollable or
74 // partially usable
75 m_cache.scrollTo(startFrame);
76
77 // if we are not time-constrained, then we want to paint
78 // the whole area in one go, and we're not going to
79 // provide the more complex logic to handle that if there
80 // are discontiguous areas involved. So if the only valid
81 // part of cache is in the middle, just make the whole
82 // thing invalid and start again.
83 if (!timeConstrained) {
84 if (m_cache.getValidLeft() > x0 &&
85 m_cache.getValidRight() < x1) {
86 m_cache.invalidate();
87 }
88 }
89 }
90 }
91
92 bool rightToLeft = false;
93
94 if (!m_cache.isValid() && timeConstrained) {
95 // When rendering the whole thing in a context where we
96 // might not be able to complete the work, start from
97 // somewhere near the middle so that the region of
98 // interest appears first
99
100 //!!! (perhaps we should avoid doing this if past repaints
101 //!!! have been fast enough to do the whole in one shot)
102 if (x0 == 0 && x1 == v->getPaintWidth()) {
103 x0 = int(x1 * 0.3);
104 }
105 }
106
107 if (m_cache.isValid()) {
108 // When rendering only a part of the cache, we need to make
109 // sure that the part we're rendering is adjacent to (or
110 // overlapping) a valid area of cache, if we have one. The
111 // alternative is to ditch the valid area of cache and render
112 // only the requested area, but that's risky because this can
113 // happen when just waving the pointer over a small part of
114 // the view -- if we lose the partly-built cache every time
115 // the user does that, we'll never finish building it.
116 int left = x0;
117 int width = x1 - x0;
118 bool isLeftOfValidArea = false;
119 m_cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
120 x0 = left;
121 x1 = x0 + width;
122
123 // That call also told us whether we should be painting
124 // sub-regions of our target region in right-to-left order in
125 // order to ensure contiguity
126 rightToLeft = isLeftOfValidArea;
127 }
128
129 int repaintWidth = x1 - x0;
130
131 QRect rendered = renderToCache(x0, repaintWidth, timeConstrained);
132
133 QRect pr = rect & m_cache.getValidArea();
134 paint.drawImage(pr.x(), pr.y(), m_cache.getImage(),
135 pr.x(), pr.y(), pr.width(), pr.height());
136
137 if (!timeConstrained && (pr != rect)) {
138 //!!! on a first cut, there is a risk that this will happen
139 //!!! when we are at start/end of model -- trap, report, and
140 //!!! then fix
141 throw std::logic_error("internal error: failed to render entire requested rect even when not time-constrained");
142 }
143
144 return { pr, {} };
145
146 //!!! todo: timing/incomplete paint
147
148 //!!! todo: peak frequency style
149
150 //!!! todo: transparent style from Colour3DPlot
151
152 //!!! todo: bin boundary alignment when in BinResolution
153
154 //!!! todo: view magnitudes / normalise visible area
155
156 //!!! todo: alter documentation for view mag stuff (cached paints
157 //!!! do not update MagnitudeRange)
158
159 //!!! todo, here or in caller: illuminateLocalFeatures
160
161 //!!! fft model scaling?
162
163 //!!! should we own the Dense3DModelPeakCache here? or should it persist
164 }
165
166 QRect
167 Colour3DPlotRenderer::renderToCache(int x0, int repaintWidth, bool timeConstrained)
168 {
169 // Draw to the draw buffer, and then scale-copy from there.
170
45 DenseThreeDimensionalModel *model = m_sources.source; 171 DenseThreeDimensionalModel *model = m_sources.source;
46 if (!model || !model->isOK() || !model->isReady()) { 172 if (!model || !model->isOK() || !model->isReady()) {
47 throw std::logic_error("no source model provided, or model not ready"); 173 throw std::logic_error("no source model provided, or model not ready");
48 } 174 }
49 175
50 sv_frame_t startFrame = v->getStartFrame(); 176 LayerGeometryProvider *v = m_sources.geometryProvider; // already checked
51 177
52 178 // The draw buffer contains a fragment at either our pixel
53 //!!! todo: timing/incomplete paint 179 // resolution (if there is more than one time-bin per pixel) or
54 180 // time-bin resolution (if a time-bin spans more than one pixel).
55 //!!! todo: peak frequency style 181 // We need to ensure that it starts and ends at points where a
56 182 // time-bin boundary occurs at an exact pixel boundary, and with a
57 //!!! todo: transparent style from Colour3DPlot 183 // certain amount of overlap across existing pixels so that we can
58 184 // scale and draw from it without smoothing errors at the edges.
59 //!!! todo: bin boundary alignment when in BinResolution 185
60 186 // If (getFrameForX(x) / increment) * increment ==
61 return { rect, {} }; 187 // getFrameForX(x), then x is a time-bin boundary. We want two
62 } 188 // such boundaries at either side of the draw buffer -- one which
63 189 // we draw up to, and one which we subsequently crop at.
190
191 bool bufferIsBinResolution = false;
192 int binResolution = model->getResolution();
193 int zoomLevel = v->getZoomLevel();
194 if (binResolution > zoomLevel) bufferIsBinResolution = true;
195
196 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
197 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
198
199 int drawWidth;
200
201 if (bufferIsBinResolution) {
202 for (int x = x0; ; --x) {
203 sv_frame_t f = v->getFrameForX(x);
204 if ((f / binResolution) * binResolution == f) {
205 if (leftCropFrame == -1) leftCropFrame = f;
206 else if (x < x0 - 2) {
207 leftBoundaryFrame = f;
208 break;
209 }
210 }
211 }
212 for (int x = x0 + repaintWidth; ; ++x) {
213 sv_frame_t f = v->getFrameForX(x);
214 if ((f / binResolution) * binResolution == f) {
215 if (rightCropFrame == -1) rightCropFrame = f;
216 else if (x > x0 + repaintWidth + 2) {
217 rightBoundaryFrame = f;
218 break;
219 }
220 }
221 }
222 drawWidth = int((rightBoundaryFrame - leftBoundaryFrame) / binResolution);
223 } else {
224 drawWidth = repaintWidth;
225 }
226
227 // We always paint the full height. Smaller heights can be used
228 // when painting direct from cache (outside this function), but we
229 // want to ensure the cache is coherent without having to worry
230 // about vertical matching of required and valid areas as well as
231 // horizontal. That's why this function didn't take any y/height
232 // parameters.
233 int h = v->getPaintHeight();
234
235 clearDrawBuffer(drawWidth, h);
236
237 vector<int> binforx(drawWidth);
238 vector<double> binfory(h);
239
240 bool usePeaksCache = false;
241 int binsPerPeak = 1;
242
243 if (bufferIsBinResolution) {
244 for (int x = 0; x < drawWidth; ++x) {
245 binforx[x] = int(leftBoundaryFrame / binResolution) + x;
246 }
247 } else {
248 for (int x = 0; x < drawWidth; ++x) {
249 sv_frame_t f0 = v->getFrameForX(x);
250 double s0 = double(f0 - model->getStartFrame()) / binResolution;
251 binforx[x] = int(s0 + 0.0001);
252 }
253
254 if (m_sources.peaks) { // peaks cache exists
255
256 binsPerPeak = m_sources.peaks->getColumnsPerPeak();
257 usePeaksCache = (binResolution * binsPerPeak) < zoomLevel;
258
259 if (m_params.colourScale.getScale() ==
260 ColourScale::PhaseColourScale) {
261 usePeaksCache = false;
262 }
263 }
264 }
265
266 int attainedDrawWidth = drawWidth;
267
268 //!!! todo: all
269
270 }
271
272 void
273 Colour3DPlotRenderer::clearDrawBuffer(int w, int h)
274 {
275 if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) {
276
277 m_drawBuffer = QImage(w, h, QImage::Format_Indexed8);
278
279 for (int pixel = 0; pixel < 256; ++pixel) {
280 //!!! todo: colour rotation (here 0)
281 m_drawBuffer.setColor
282 ((unsigned char)pixel,
283 m_params.colourScale.getColourForPixel(pixel, 0).rgb());
284 }
285 }
286
287 m_drawBuffer.fill(0);
288 }
289
290