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 #include "ScrollableImageCache.h"
16 
17 #include "base/HitCount.h"
18 
19 #include <iostream>
20 using namespace std;
21 
22 //#define DEBUG_SCROLLABLE_IMAGE_CACHE 1
23 
24 void
scrollTo(const LayerGeometryProvider * v,sv_frame_t newStartFrame)25 ScrollableImageCache::scrollTo(const LayerGeometryProvider *v,
26                                sv_frame_t newStartFrame)
27 {
28     static HitCount count("ScrollableImageCache: scrolling");
29 
30     int dx = (v->getXForFrame(m_startFrame) -
31               v->getXForFrame(newStartFrame));
32 
33 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
34     cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame
35          << " -> " << newStartFrame << ", dx = " << dx << endl;
36 #endif
37 
38     if (m_startFrame == newStartFrame) {
39         // haven't moved
40         count.hit();
41         return;
42     }
43 
44     m_startFrame = newStartFrame;
45 
46     if (!isValid()) {
47         count.miss();
48         return;
49     }
50 
51     int w = m_image.width();
52 
53     if (dx == 0) {
54         // haven't moved visibly (even though start frame may have changed)
55         count.hit();
56         return;
57     }
58 
59     if (dx <= -w || dx >= w) {
60         // scrolled entirely off
61         invalidate();
62         count.miss();
63         return;
64     }
65 
66     count.partial();
67 
68     // dx is in range, cache is scrollable
69 
70     int dxp = dx;
71     if (dxp < 0) dxp = -dxp;
72 
73     int copylen = (w - dxp) * int(sizeof(QRgb));
74     for (int y = 0; y < m_image.height(); ++y) {
75         QRgb *line = (QRgb *)m_image.scanLine(y);
76         if (dx < 0) {
77             memmove(line, line + dxp, copylen);
78         } else {
79             memmove(line + dxp, line, copylen);
80         }
81     }
82 
83     // update valid area
84 
85     int px = m_validLeft;
86     int pw = m_validWidth;
87 
88     px += dx;
89 
90     if (dx < 0) {
91         // we scrolled left
92         if (px < 0) {
93             pw += px;
94             px = 0;
95             if (pw < 0) {
96                 pw = 0;
97             }
98         }
99     } else {
100         // we scrolled right
101         if (px + pw > w) {
102             pw = w - px;
103             if (pw < 0) {
104                 pw = 0;
105             }
106         }
107     }
108 
109     m_validLeft = px;
110     m_validWidth = pw;
111 }
112 
113 void
adjustToTouchValidArea(int & left,int & width,bool & isLeftOfValidArea) const114 ScrollableImageCache::adjustToTouchValidArea(int &left, int &width,
115                                              bool &isLeftOfValidArea) const
116 {
117 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
118     cerr << "ScrollableImageCache::adjustToTouchValidArea: left " << left
119          << ", width " << width << endl;
120     cerr << "ScrollableImageCache: my left " << m_validLeft
121          << ", width " << m_validWidth << " so right " << (m_validLeft + m_validWidth) << endl;
122 #endif
123     if (left < m_validLeft) {
124         isLeftOfValidArea = true;
125         if (left + width <= m_validLeft + m_validWidth) {
126             width = m_validLeft - left;
127         }
128 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
129         cerr << "ScrollableImageCache: we're left of valid area, adjusted width to " << width << endl;
130 #endif
131     } else {
132         isLeftOfValidArea = false;
133         width = left + width - (m_validLeft + m_validWidth);
134         left = m_validLeft + m_validWidth;
135         if (width < 0) width = 0;
136 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
137         cerr << "ScrollableImageCache: we're right of valid area, adjusted left to " << left << ", width to " << width << endl;
138 #endif
139     }
140 }
141 
142 void
drawImage(int left,int width,QImage image,int imageLeft,int imageWidth)143 ScrollableImageCache::drawImage(int left,
144                                 int width,
145                                 QImage image,
146                                 int imageLeft,
147                                 int imageWidth)
148 {
149     if (image.height() != m_image.height()) {
150         cerr << "ScrollableImageCache::drawImage: ERROR: Supplied image height "
151              << image.height() << " does not match cache height "
152              << m_image.height() << endl;
153         throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage");
154     }
155     if (left < 0 || width < 0 || left + width > m_image.width()) {
156         cerr << "ScrollableImageCache::drawImage: ERROR: Target area (left = "
157              << left << ", width = " << width << ", so right = " << left + width
158              << ") out of bounds for cache of width " << m_image.width() << endl;
159         throw std::logic_error("Target area out of bounds in ScrollableImageCache::drawImage");
160     }
161     if (imageLeft < 0 || imageWidth < 0 ||
162         imageLeft + imageWidth > image.width()) {
163         cerr << "ScrollableImageCache::drawImage: ERROR: Source area (left = "
164              << imageLeft << ", width = " << imageWidth << ", so right = "
165              << imageLeft + imageWidth << ") out of bounds for image of "
166              << "width " << image.width() << endl;
167         throw std::logic_error("Source area out of bounds in ScrollableImageCache::drawImage");
168     }
169 
170     QPainter painter(&m_image);
171     painter.drawImage(QRect(left, 0, width, m_image.height()),
172                       image,
173                       QRect(imageLeft, 0, imageWidth, image.height()));
174     painter.end();
175 
176     if (!isValid()) {
177         m_validLeft = left;
178         m_validWidth = width;
179         return;
180     }
181 
182     if (left < m_validLeft) {
183         if (left + width > m_validLeft + m_validWidth) {
184             // new image completely contains the old valid area --
185             // use the new area as is
186             m_validLeft = left;
187             m_validWidth = width;
188         } else if (left + width < m_validLeft) {
189             // new image completely off left of old valid area --
190             // we can't extend the valid area because the bit in
191             // between is not valid, so must use the new area only
192             m_validLeft = left;
193             m_validWidth = width;
194         } else {
195             // new image overlaps old valid area on left side --
196             // use new left edge, and extend width to existing
197             // right edge
198             m_validWidth = (m_validLeft + m_validWidth) - left;
199             m_validLeft = left;
200         }
201     } else {
202         if (left > m_validLeft + m_validWidth) {
203             // new image completely off right of old valid area --
204             // we can't extend the valid area because the bit in
205             // between is not valid, so must use the new area only
206             m_validLeft = left;
207             m_validWidth = width;
208         } else if (left + width > m_validLeft + m_validWidth) {
209             // new image overlaps old valid area on right side --
210             // use existing left edge, and extend width to new
211             // right edge
212             m_validWidth = (left + width) - m_validLeft;
213             // (m_validLeft unchanged)
214         } else {
215             // new image completely contained within old valid
216             // area -- leave the old area unchanged
217         }
218     }
219 }
220 
221