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