annotate 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
rev   line source
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