annotate layer/Colour3DPlotLayer.cpp @ 54:8dae7f6732c1

* improve label drawing logic in colour plot layer
author Chris Cannam
date Wed, 15 Mar 2006 18:21:17 +0000
parents ad214997dddb
children 01ab51f72e84
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 A waveform viewer and audio annotation editor.
Chris@5 5 Chris Cannam, Queen Mary University of London, 2005-2006
Chris@0 6
Chris@0 7 This is experimental software. Not for distribution.
Chris@0 8 */
Chris@0 9
Chris@0 10 #include "Colour3DPlotLayer.h"
Chris@0 11
Chris@0 12 #include "base/View.h"
Chris@0 13 #include "base/Profiler.h"
Chris@0 14
Chris@0 15 #include <QPainter>
Chris@0 16 #include <QImage>
Chris@0 17 #include <QRect>
Chris@0 18
Chris@0 19 #include <iostream>
Chris@0 20
Chris@0 21 #include <cassert>
Chris@0 22
Chris@0 23
Chris@44 24 Colour3DPlotLayer::Colour3DPlotLayer() :
Chris@44 25 Layer(),
Chris@0 26 m_model(0),
Chris@0 27 m_cache(0)
Chris@0 28 {
Chris@44 29
Chris@0 30 }
Chris@0 31
Chris@0 32 Colour3DPlotLayer::~Colour3DPlotLayer()
Chris@0 33 {
Chris@0 34 }
Chris@0 35
Chris@0 36 void
Chris@0 37 Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model)
Chris@0 38 {
Chris@0 39 m_model = model;
Chris@0 40 if (!m_model || !m_model->isOK()) return;
Chris@0 41
Chris@0 42 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@0 43 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 44 this, SIGNAL(modelChanged(size_t, size_t)));
Chris@0 45
Chris@0 46 connect(m_model, SIGNAL(completionChanged()),
Chris@0 47 this, SIGNAL(modelCompletionChanged()));
Chris@0 48
Chris@0 49 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
Chris@0 50 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 51 this, SLOT(cacheInvalid(size_t, size_t)));
Chris@0 52
Chris@0 53 emit modelReplaced();
Chris@0 54 }
Chris@0 55
Chris@0 56 void
Chris@0 57 Colour3DPlotLayer::cacheInvalid()
Chris@0 58 {
Chris@0 59 delete m_cache;
Chris@0 60 m_cache = 0;
Chris@0 61 }
Chris@0 62
Chris@0 63 void
Chris@0 64 Colour3DPlotLayer::cacheInvalid(size_t, size_t)
Chris@0 65 {
Chris@0 66 cacheInvalid();
Chris@0 67 }
Chris@0 68
Chris@25 69 bool
Chris@44 70 Colour3DPlotLayer::isLayerScrollable(const View *v) const
Chris@25 71 {
Chris@25 72 QPoint discard;
Chris@44 73 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@25 74 }
Chris@25 75
Chris@25 76 QString
Chris@44 77 Colour3DPlotLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@25 78 {
Chris@25 79 if (!m_model) return "";
Chris@25 80
Chris@25 81 int x = pos.x();
Chris@25 82 int y = pos.y();
Chris@25 83
Chris@25 84 size_t modelStart = m_model->getStartFrame();
Chris@25 85 size_t modelWindow = m_model->getWindowSize();
Chris@25 86
Chris@25 87 int sx0 = modelWindow *
Chris@44 88 int((v->getFrameForX(x) - long(modelStart)) / long(modelWindow));
Chris@25 89 int sx1 = sx0 + modelWindow;
Chris@25 90
Chris@44 91 float binHeight = float(v->height()) / m_model->getYBinCount();
Chris@44 92 int sy = (v->height() - y) / binHeight;
Chris@25 93
Chris@25 94 float value = m_model->getBinValue(sx0, sy);
Chris@25 95
Chris@25 96 QString binName = m_model->getBinName(sy);
Chris@25 97 if (binName == "") binName = QString("[%1]").arg(sy + 1);
Chris@25 98 else binName = QString("%1 [%2]").arg(binName).arg(sy + 1);
Chris@25 99
Chris@25 100 QString text = tr("Time:\t%1 - %2\nBin:\t%3\nValue:\t%4")
Chris@25 101 .arg(RealTime::frame2RealTime(sx0, m_model->getSampleRate())
Chris@25 102 .toText(true).c_str())
Chris@25 103 .arg(RealTime::frame2RealTime(sx1, m_model->getSampleRate())
Chris@25 104 .toText(true).c_str())
Chris@25 105 .arg(binName)
Chris@25 106 .arg(value);
Chris@25 107
Chris@25 108 return text;
Chris@25 109 }
Chris@25 110
Chris@25 111 int
Chris@44 112 Colour3DPlotLayer::getVerticalScaleWidth(View *v, QPainter &paint) const
Chris@25 113 {
Chris@25 114 if (!m_model) return 0;
Chris@25 115
Chris@25 116 QString sampleText("123");
Chris@25 117 int tw = paint.fontMetrics().width(sampleText);
Chris@25 118
Chris@25 119 for (size_t i = 0; i < m_model->getYBinCount(); ++i) {
Chris@25 120 if (m_model->getBinName(i).length() > sampleText.length()) {
Chris@25 121 sampleText = m_model->getBinName(i);
Chris@25 122 }
Chris@25 123 }
Chris@25 124 if (sampleText != "123") {
Chris@25 125 tw = std::max(tw, paint.fontMetrics().width(sampleText));
Chris@25 126 }
Chris@25 127
Chris@25 128 return tw + 13;
Chris@25 129 }
Chris@25 130
Chris@25 131 void
Chris@44 132 Colour3DPlotLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const
Chris@25 133 {
Chris@25 134 if (!m_model) return;
Chris@25 135
Chris@25 136 int h = rect.height(), w = rect.width();
Chris@44 137 float binHeight = float(v->height()) / m_model->getYBinCount();
Chris@25 138
Chris@25 139 // int textHeight = paint.fontMetrics().height();
Chris@25 140 // int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@25 141
Chris@25 142 for (size_t i = 0; i < m_model->getYBinCount(); ++i) {
Chris@25 143
Chris@44 144 int y0 = v->height() - (i * binHeight) - 1;
Chris@25 145
Chris@25 146 QString text = m_model->getBinName(i);
Chris@25 147 if (text == "") text = QString("[%1]").arg(i + 1);
Chris@25 148
Chris@25 149 paint.drawLine(0, y0, w, y0);
Chris@25 150
Chris@25 151 int cy = y0 - binHeight/2;
Chris@25 152 int ty = cy + paint.fontMetrics().ascent()/2;
Chris@25 153
Chris@25 154 // int tx = w - 10 - paint.fontMetrics().width(text);
Chris@25 155 paint.drawText(10, ty, text);
Chris@25 156 }
Chris@25 157 }
Chris@25 158
Chris@0 159 void
Chris@44 160 Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@0 161 {
Chris@0 162 // Profiler profiler("Colour3DPlotLayer::paint");
Chris@44 163 // std::cerr << "Colour3DPlotLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << std::endl;
Chris@0 164
Chris@0 165 //!!! This doesn't yet accommodate the fact that the model may
Chris@0 166 //have a different sample rate from an underlying model. At the
Chris@0 167 //moment our paint mechanism assumes all models have the same
Chris@0 168 //sample rate. If that isn't the case, they won't align and the
Chris@0 169 //time ruler will match whichever model was used to construct it.
Chris@0 170 //Obviously it is not going to be the case in general that models
Chris@0 171 //will have the same samplerate, so we need a pane samplerate as
Chris@0 172 //well which we trivially realign to. (We can probably require
Chris@0 173 //the waveform and spectrogram layers to display at the pane
Chris@0 174 //samplerate.)
Chris@0 175
Chris@0 176 int completion = 0;
Chris@0 177 if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) {
Chris@0 178 if (completion > 0) {
Chris@44 179 paint.fillRect(0, 10, v->width() * completion / 100,
Chris@0 180 10, QColor(120, 120, 120));
Chris@0 181 }
Chris@0 182 return;
Chris@0 183 }
Chris@0 184
Chris@44 185 long startFrame = v->getStartFrame();
Chris@44 186 int zoomLevel = v->getZoomLevel();
Chris@0 187
Chris@0 188 size_t modelStart = m_model->getStartFrame();
Chris@0 189 size_t modelEnd = m_model->getEndFrame();
Chris@0 190 size_t modelWindow = m_model->getWindowSize();
Chris@0 191
Chris@12 192 size_t cacheWidth = (modelEnd - modelStart) / modelWindow + 1;
Chris@12 193 size_t cacheHeight = m_model->getYBinCount();
Chris@12 194
Chris@12 195 if (m_cache &&
Chris@12 196 (m_cache->width() != cacheWidth ||
Chris@12 197 m_cache->height() != cacheHeight)) {
Chris@12 198
Chris@12 199 delete m_cache;
Chris@12 200 m_cache = 0;
Chris@12 201 }
Chris@12 202
Chris@0 203 if (!m_cache) {
Chris@0 204
Chris@12 205 m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8);
Chris@0 206
Chris@0 207 m_cache->setNumColors(256);
Chris@0 208 DenseThreeDimensionalModel::BinValueSet values;
Chris@0 209 /*
Chris@0 210 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@0 211 int hue = 256 - pixel;
Chris@0 212 // int hue = 220 - pixel;
Chris@0 213 // if (hue < 0) hue += 360;
Chris@0 214 QColor color = QColor::fromHsv(hue, pixel/2 + 128, pixel);
Chris@0 215 m_cache->setColor(pixel, qRgb(color.red(), color.green(), color.blue()));
Chris@0 216 }
Chris@0 217 */
Chris@0 218
Chris@0 219 float min = m_model->getMinimumLevel();
Chris@0 220 float max = m_model->getMaximumLevel();
Chris@0 221
Chris@0 222 if (max == min) max = min + 1.0;
Chris@0 223
Chris@0 224 // int min = lrintf(m_model->getMinimumLevel());
Chris@0 225 // int max = lrintf(m_model->getMaximumLevel());
Chris@0 226 for (int value = 0; value < 256; ++value) {
Chris@0 227 // int spread = ((value - min) * 256) / (max - min);
Chris@0 228 // int hue = 256 - spread;
Chris@0 229 // QColor color = QColor::fromHsv(hue, spread/2 + 128, spread);
Chris@0 230 int hue = 256 - value;
Chris@26 231 QColor colour = QColor::fromHsv(hue, value/2 + 128, value);
Chris@26 232 m_cache->setColor(value, qRgba(colour.red(), colour.green(), colour.blue(), 80));
Chris@0 233 // std::cerr << "Colour3DPlotLayer: Index " << value << ": hue " << hue << std::endl;
Chris@0 234 }
Chris@0 235
Chris@0 236 m_cache->fill(min);
Chris@0 237
Chris@0 238 for (size_t f = modelStart; f <= modelEnd; f += modelWindow) {
Chris@0 239
Chris@0 240 values.clear();
Chris@0 241 m_model->getBinValues(f, values);
Chris@0 242
Chris@0 243 for (size_t y = 0; y < m_model->getYBinCount(); ++y) {
Chris@0 244
Chris@0 245 float value = min;
Chris@0 246 if (y < values.size()) value = values[y];
Chris@0 247
Chris@0 248 //!!! divide-by-zero!
Chris@0 249 int pixel = int(((value - min) * 256) / (max - min));
Chris@0 250
Chris@24 251 if (pixel == 256) pixel = 255;
Chris@24 252
Chris@0 253 m_cache->setPixel(f / modelWindow, y, pixel);
Chris@0 254 }
Chris@0 255 }
Chris@0 256 }
Chris@0 257
Chris@0 258 int x0 = rect.left();
Chris@0 259 int x1 = rect.right() + 1;
Chris@0 260
Chris@0 261 // int y0 = rect.top();
Chris@0 262 // int y1 = rect.bottom();
Chris@0 263 int w = x1 - x0;
Chris@44 264 int h = v->height();
Chris@0 265
Chris@0 266 // The cache is from the model's start frame to the model's end
Chris@0 267 // frame at the model's window increment frames per pixel. We
Chris@0 268 // want to draw from our start frame + x0 * zoomLevel to our start
Chris@0 269 // frame + x1 * zoomLevel at zoomLevel frames per pixel.
Chris@0 270
Chris@0 271 //!!! Strictly speaking we want quite different paint mechanisms
Chris@0 272 //for models that have more than one bin per pixel in either
Chris@0 273 //direction. This one is only really appropriate for models with
Chris@0 274 //far fewer bins in both directions.
Chris@0 275
Chris@44 276 int sx0 = int((v->getFrameForX(x0) - long(modelStart)) / long(modelWindow));
Chris@44 277 int sx1 = int((v->getFrameForX(x1) - long(modelStart)) / long(modelWindow));
Chris@0 278 int sw = sx1 - sx0;
Chris@0 279 int sh = m_model->getYBinCount();
Chris@0 280
Chris@0 281 /*
Chris@0 282 std::cerr << "Colour3DPlotLayer::paint: w " << w << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sw << ", sh " << sh << std::endl;
Chris@0 283 std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", window size " << m_model->getWindowSize() << std::endl;
Chris@0 284 */
Chris@0 285
Chris@25 286 QPoint illuminatePos;
Chris@44 287 bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos);
Chris@54 288 char labelbuf[10];
Chris@25 289
Chris@0 290 for (int sx = sx0 - 1; sx <= sx1; ++sx) {
Chris@0 291
Chris@0 292 int fx = sx * int(modelWindow);
Chris@0 293
Chris@0 294 if (fx + modelWindow < int(modelStart) ||
Chris@0 295 fx > int(modelEnd)) continue;
Chris@0 296
Chris@54 297 int rx0 = v->getXForFrame(fx + int(modelStart));
Chris@54 298 int rx1 = v->getXForFrame(fx + int(modelStart) + int(modelWindow));
Chris@54 299
Chris@54 300 int w = rx1 - rx0;
Chris@54 301 if (w < 1) w = 1;
Chris@54 302
Chris@54 303 bool showLabel = (w > 10 &&
Chris@54 304 paint.fontMetrics().width("0.000000") < w - 3 &&
Chris@54 305 paint.fontMetrics().height() < (h / sh));
Chris@54 306
Chris@0 307 for (int sy = 0; sy < sh; ++sy) {
Chris@0 308
Chris@0 309 int ry0 = h - (sy * h) / sh - 1;
Chris@0 310 int ry1 = h - ((sy + 1) * h) / sh - 2;
Chris@0 311 QRgb pixel = qRgb(255, 255, 255);
Chris@0 312 if (sx >= 0 && sx < m_cache->width() &&
Chris@0 313 sy >= 0 && sy < m_cache->height()) {
Chris@0 314 pixel = m_cache->pixel(sx, sy);
Chris@0 315 }
Chris@0 316
Chris@0 317 QColor pen(255, 255, 255, 80);
Chris@0 318 QColor brush(pixel);
Chris@0 319 brush.setAlpha(160);
Chris@0 320 paint.setPen(Qt::NoPen);
Chris@0 321 paint.setBrush(brush);
Chris@0 322
Chris@25 323 QRect r(rx0, ry0 - h / sh - 1, w, h / sh + 1);
Chris@25 324
Chris@25 325 if (illuminate) {
Chris@25 326 if (r.contains(illuminatePos)) {
Chris@25 327 paint.setPen(Qt::black);//!!!
Chris@25 328 }
Chris@25 329 }
Chris@25 330
Chris@25 331 paint.drawRect(r);
Chris@0 332
Chris@54 333 if (showLabel) {
Chris@54 334 if (sx >= 0 && sx < m_cache->width() &&
Chris@54 335 sy >= 0 && sy < m_cache->height()) {
Chris@24 336 int dv = m_cache->pixelIndex(sx, sy);
Chris@54 337 float value = m_model->getBinValue(fx, sy);
Chris@54 338 sprintf(labelbuf, "%06f", value);
Chris@54 339 QString text(labelbuf);
Chris@54 340 paint.setPen(Qt::white);
Chris@54 341 paint.drawText(rx0 + 2,
Chris@54 342 ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(),
Chris@54 343 text);
Chris@0 344 }
Chris@0 345 }
Chris@0 346 }
Chris@0 347 }
Chris@0 348
Chris@0 349 /*
Chris@0 350 QRect targetRect(x0, 0, w, h);
Chris@0 351 QRect sourceRect(sx0, 0, sw, sh);
Chris@0 352
Chris@0 353 QImage scaled(w, h, QImage::Format_RGB32);
Chris@0 354
Chris@0 355 for (int x = 0; x < w; ++x) {
Chris@0 356 for (int y = 0; y < h; ++y) {
Chris@0 357
Chris@0 358
Chris@0 359
Chris@0 360 int sx = sx0 + (x * sw) / w;
Chris@0 361 int sy = sh - (y * sh) / h - 1;
Chris@0 362 // std::cerr << "Colour3DPlotLayer::paint: sx " << sx << ", sy " << sy << ", cache w " << m_cache->width() << ", cache h " << m_cache->height() << std::endl;
Chris@0 363 if (sx >= 0 && sy >= 0 &&
Chris@0 364 sx < m_cache->width() && sy < m_cache->height()) {
Chris@0 365 scaled.setPixel(x, y, m_cache->pixel(sx, sy));
Chris@0 366 } else {
Chris@0 367 scaled.setPixel(x, y, qRgba(255, 255, 255, 80));
Chris@0 368 }
Chris@0 369 }
Chris@0 370 }
Chris@0 371
Chris@0 372 paint.drawImage(x0, 0, scaled);
Chris@0 373 */
Chris@0 374 }
Chris@0 375
Chris@28 376 bool
Chris@44 377 Colour3DPlotLayer::snapToFeatureFrame(View *v, int &frame,
Chris@28 378 size_t &resolution,
Chris@28 379 SnapType snap) const
Chris@24 380 {
Chris@24 381 if (!m_model) {
Chris@44 382 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@24 383 }
Chris@24 384
Chris@24 385 resolution = m_model->getWindowSize();
Chris@28 386 int left = (frame / resolution) * resolution;
Chris@28 387 int right = left + resolution;
Chris@28 388
Chris@28 389 switch (snap) {
Chris@28 390 case SnapLeft: frame = left; break;
Chris@28 391 case SnapRight: frame = right; break;
Chris@28 392 case SnapNearest:
Chris@28 393 case SnapNeighbouring:
Chris@28 394 if (frame - left > right - frame) frame = right;
Chris@28 395 else frame = left;
Chris@28 396 break;
Chris@28 397 }
Chris@24 398
Chris@28 399 return true;
Chris@24 400 }
Chris@24 401
Chris@0 402 #ifdef INCLUDE_MOCFILES
Chris@0 403 #include "Colour3DPlotLayer.moc.cpp"
Chris@0 404 #endif
Chris@0 405
Chris@0 406