comparison layer/ScrollableImageCache.h @ 1030:0be17aafa935 spectrogram-minor-refactor

Start refactoring out the spectrogram image cache
author Chris Cannam
date Fri, 29 Jan 2016 15:08:01 +0000
parents
children 55ac6ac1982e
comparison
equal deleted inserted replaced
1029:fdfd84b022df 1030:0be17aafa935
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
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. See the file
12 COPYING included with this distribution for more information.
13 */
14
15 #ifndef SCROLLABLE_IMAGE_CACHE_H
16 #define SCROLLABLE_IMAGE_CACHE_H
17
18 #include "base/BaseTypes.h"
19
20 #include "view/LayerGeometryProvider.h"
21
22 #include <QImage>
23 #include <QRect>
24 #include <QPainter>
25
26 /**
27 * A cached image for a view that scrolls horizontally, primarily the
28 * spectrogram. The cache object holds an image, reports the size of
29 * the image (likely the same as the underlying view, but it's the
30 * caller's responsibility to set the size appropriately), can scroll
31 * the image, and can report and update which contiguous horizontal
32 * range of the image is valid.
33 *
34 * The only way to *update* the valid area in a cache is to draw to it
35 * using the drawImage call.
36 */
37 class ScrollableImageCache
38 {
39 public:
40 ScrollableImageCache(const LayerGeometryProvider *v = 0) :
41 m_v(v),
42 m_left(0),
43 m_width(0),
44 m_startFrame(0),
45 m_zoomLevel(0)
46 {}
47
48 void invalidate() {
49 m_width = 0;
50 }
51
52 bool isValid() const {
53 return m_width > 0;
54 }
55
56 bool spans(int left, int right) const {
57 return (getValidLeft() <= left &&
58 getValidRight() >= right);
59 }
60
61 QSize getSize() const {
62 return m_image.size();
63 }
64
65 void resize(QSize newSize) {
66 m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied);
67 invalidate();
68 }
69
70 int getValidLeft() const {
71 return m_left;
72 }
73
74 int getValidWidth() const {
75 return m_width;
76 }
77
78 int getValidRight() const {
79 return m_left + m_width;
80 }
81
82 QRect getValidArea() const {
83 return QRect(m_left, 0, m_width, m_image.height());
84 }
85
86 int getZoomLevel() const {
87 return m_zoomLevel;
88 }
89
90 sv_frame_t getStartFrame() const {
91 return m_startFrame;
92 }
93
94 void setZoomLevel(int zoom) {
95 m_zoomLevel = zoom;
96 invalidate();
97 }
98
99 const QImage &getImage() const {
100 return m_image;
101 }
102
103 void scrollTo(sv_frame_t newStartFrame) {
104
105 if (!m_v) throw std::logic_error("ScrollableImageCache: not associated with a LayerGeometryProvider");
106
107 int dx = (m_v->getXForFrame(m_startFrame) -
108 m_v->getXForFrame(newStartFrame));
109
110 m_startFrame = newStartFrame;
111
112 if (!isValid()) {
113 return;
114 }
115
116 int w = m_image.width();
117
118 if (dx == 0) {
119 // haven't moved
120 return;
121 }
122
123 if (dx <= -w || dx >= w) {
124 // scrolled entirely off
125 invalidate();
126 return;
127 }
128
129 // dx is in range, cache is scrollable
130
131 int dxp = dx;
132 if (dxp < 0) dxp = -dxp;
133
134 int copylen = (w - dxp) * int(sizeof(QRgb));
135 for (int y = 0; y < m_image.height(); ++y) {
136 QRgb *line = (QRgb *)m_image.scanLine(y);
137 if (dx < 0) {
138 memmove(line, line + dxp, copylen);
139 } else {
140 memmove(line + dxp, line, copylen);
141 }
142 }
143
144 // update valid area
145
146 int px = m_left;
147 int pw = m_width;
148
149 px += dx;
150
151 if (dx < 0) {
152 // we scrolled left
153 if (px < 0) {
154 pw += px;
155 px = 0;
156 if (pw < 0) {
157 pw = 0;
158 }
159 }
160 } else {
161 // we scrolled right
162 if (px + pw > w) {
163 pw = w - px;
164 if (pw < 0) {
165 pw = 0;
166 }
167 }
168 }
169
170 m_left = px;
171 m_width = pw;
172 }
173
174 void resizeToTouchValidArea(int &left, int &width,
175 bool &isLeftOfValidArea) const {
176 if (left < m_left) {
177 isLeftOfValidArea = true;
178 if (left + width < m_left + m_width) {
179 width = m_left - left;
180 }
181 } else {
182 isLeftOfValidArea = false;
183 width = left + width - (m_left + m_width);
184 left = m_left + m_width;
185 if (width < 0) width = 0;
186 }
187 }
188
189 void drawImage(int left,
190 int width,
191 QImage image,
192 int imageLeft,
193 int imageWidth) {
194
195 if (image.height() != m_image.height()) {
196 throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage");
197 }
198 if (left < 0 || left + width > m_image.width()) {
199 throw std::logic_error("Drawing area out of bounds in ScrollableImageCache::drawImage");
200 }
201
202 QPainter painter(&m_image);
203 painter.drawImage(QRect(left, 0, width, m_image.height()),
204 image,
205 QRect(imageLeft, 0, imageWidth, image.height()));
206 painter.end();
207
208 if (!isValid()) {
209 m_left = left;
210 m_width = width;
211 return;
212 }
213
214 if (left < m_left) {
215 if (left + width > m_left + m_width) {
216 // new image completely contains the old valid area --
217 // use the new area as is
218 m_left = left;
219 m_width = width;
220 } else if (left + width < m_left) {
221 // new image completely off left of old valid area --
222 // we can't extend the valid area because the bit in
223 // between is not valid, so must use the new area only
224 m_left = left;
225 m_width = width;
226 } else {
227 // new image overlaps old valid area on left side --
228 // use new left edge, and extend width to existing
229 // right edge
230 m_width = (m_left + m_width) - left;
231 m_left = left;
232 }
233 } else {
234 if (left > m_left + m_width) {
235 // new image completely off right of old valid area --
236 // we can't extend the valid area because the bit in
237 // between is not valid, so must use the new area only
238 m_left = left;
239 m_width = width;
240 } else if (left + width > m_left + m_width) {
241 // new image overlaps old valid area on right side --
242 // use existing left edge, and extend width to new
243 // right edge
244 m_width = (left + width) - m_left;
245 // (m_left unchanged)
246 } else {
247 // new image completely contained within old valid
248 // area -- leave the old area unchanged
249 }
250 }
251 }
252
253 private:
254 const LayerGeometryProvider *m_v;
255 QImage m_image;
256 int m_left; // of valid region
257 int m_width; // of valid region
258 sv_frame_t m_startFrame;
259 int m_zoomLevel;
260 };
261
262 #endif