annotate layer/ImageLayer.cpp @ 1212:a1ee3108d1d3 3.0-integration

Make the colour 3d plot renderer able to support more than one level of peak cache; introduce a second "peak" cache for the spectrogram layer that actually has a 1-1 column relationship with the underlying FFT model, and use it in addition to the existing peak cache if memory is plentiful. Makes spectrograms appear much faster in many common situations.
author Chris Cannam
date Thu, 05 Jan 2017 14:02:54 +0000
parents 4fe7a09be0fe
children a34a2a25907c
rev   line source
Chris@303 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@303 2
Chris@303 3 /*
Chris@303 4 Sonic Visualiser
Chris@303 5 An audio file viewer and annotation editor.
Chris@303 6 Centre for Digital Music, Queen Mary, University of London.
Chris@303 7 This file copyright 2006 Chris Cannam.
Chris@303 8
Chris@303 9 This program is free software; you can redistribute it and/or
Chris@303 10 modify it under the terms of the GNU General Public License as
Chris@303 11 published by the Free Software Foundation; either version 2 of the
Chris@303 12 License, or (at your option) any later version. See the file
Chris@303 13 COPYING included with this distribution for more information.
Chris@303 14 */
Chris@303 15
Chris@303 16 #include "ImageLayer.h"
Chris@303 17
Chris@303 18 #include "data/model/Model.h"
Chris@303 19 #include "base/RealTime.h"
Chris@303 20 #include "base/Profiler.h"
Chris@303 21 #include "view/View.h"
Chris@303 22
Chris@303 23 #include "data/model/ImageModel.h"
Chris@318 24 #include "data/fileio/FileSource.h"
Chris@303 25
Chris@303 26 #include "widgets/ImageDialog.h"
Chris@378 27 #include "widgets/ProgressDialog.h"
Chris@303 28
Chris@303 29 #include <QPainter>
Chris@303 30 #include <QMouseEvent>
Chris@303 31 #include <QInputDialog>
Chris@305 32 #include <QMutexLocker>
Chris@316 33 #include <QTextStream>
Chris@360 34 #include <QMessageBox>
Chris@303 35
Chris@303 36 #include <iostream>
Chris@303 37 #include <cmath>
Chris@303 38
Chris@303 39 ImageLayer::ImageMap
Chris@303 40 ImageLayer::m_images;
Chris@303 41
Chris@305 42 QMutex
Chris@305 43 ImageLayer::m_imageMapMutex;
Chris@305 44
Chris@303 45 ImageLayer::ImageLayer() :
Chris@303 46 Layer(),
Chris@303 47 m_model(0),
Chris@303 48 m_editing(false),
Chris@303 49 m_originalPoint(0, "", ""),
Chris@303 50 m_editingPoint(0, "", ""),
Chris@303 51 m_editingCommand(0)
Chris@303 52 {
Chris@305 53 }
Chris@305 54
Chris@305 55 ImageLayer::~ImageLayer()
Chris@305 56 {
Chris@464 57 for (FileSourceMap::iterator i = m_fileSources.begin();
Chris@464 58 i != m_fileSources.end(); ++i) {
Chris@305 59 delete i->second;
Chris@305 60 }
Chris@303 61 }
Chris@303 62
Chris@303 63 void
Chris@303 64 ImageLayer::setModel(ImageModel *model)
Chris@303 65 {
Chris@303 66 if (m_model == model) return;
Chris@303 67 m_model = model;
Chris@303 68
Chris@320 69 connectSignals(m_model);
Chris@305 70
Chris@303 71 emit modelReplaced();
Chris@303 72 }
Chris@303 73
Chris@303 74 Layer::PropertyList
Chris@303 75 ImageLayer::getProperties() const
Chris@303 76 {
Chris@303 77 return Layer::getProperties();
Chris@303 78 }
Chris@303 79
Chris@303 80 QString
Chris@805 81 ImageLayer::getPropertyLabel(const PropertyName &) const
Chris@303 82 {
Chris@303 83 return "";
Chris@303 84 }
Chris@303 85
Chris@303 86 Layer::PropertyType
Chris@303 87 ImageLayer::getPropertyType(const PropertyName &name) const
Chris@303 88 {
Chris@303 89 return Layer::getPropertyType(name);
Chris@303 90 }
Chris@303 91
Chris@303 92 int
Chris@303 93 ImageLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@303 94 int *min, int *max, int *deflt) const
Chris@303 95 {
Chris@303 96 return Layer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@303 97 }
Chris@303 98
Chris@303 99 QString
Chris@303 100 ImageLayer::getPropertyValueLabel(const PropertyName &name,
Chris@303 101 int value) const
Chris@303 102 {
Chris@303 103 return Layer::getPropertyValueLabel(name, value);
Chris@303 104 }
Chris@303 105
Chris@303 106 void
Chris@303 107 ImageLayer::setProperty(const PropertyName &name, int value)
Chris@303 108 {
Chris@303 109 Layer::setProperty(name, value);
Chris@303 110 }
Chris@303 111
Chris@303 112 bool
Chris@905 113 ImageLayer::getValueExtents(double &, double &, bool &, QString &) const
Chris@303 114 {
Chris@303 115 return false;
Chris@303 116 }
Chris@303 117
Chris@303 118 bool
Chris@918 119 ImageLayer::isLayerScrollable(const LayerGeometryProvider *) const
Chris@303 120 {
Chris@304 121 return true;
Chris@303 122 }
Chris@303 123
Chris@303 124
Chris@303 125 ImageModel::PointList
Chris@918 126 ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
Chris@303 127 {
Chris@303 128 if (!m_model) return ImageModel::PointList();
Chris@303 129
Chris@587 130 // SVDEBUG << "ImageLayer::getLocalPoints(" << x << "," << y << "):";
Chris@304 131 const ImageModel::PointList &points(m_model->getPoints());
Chris@303 132
Chris@303 133 ImageModel::PointList rv;
Chris@303 134
Chris@304 135 for (ImageModel::PointList::const_iterator i = points.begin();
Chris@304 136 i != points.end(); ) {
Chris@303 137
Chris@303 138 const ImageModel::Point &p(*i);
Chris@304 139 int px = v->getXForFrame(p.frame);
Chris@304 140 if (px > x) break;
Chris@303 141
Chris@304 142 ++i;
Chris@304 143 if (i != points.end()) {
Chris@304 144 int nx = v->getXForFrame((*i).frame);
Chris@304 145 if (nx < x) {
Chris@304 146 // as we aim not to overlap the images, if the following
Chris@304 147 // image begins to the left of a point then the current
Chris@304 148 // one may be assumed to end to the left of it as well.
Chris@304 149 continue;
Chris@304 150 }
Chris@304 151 }
Chris@303 152
Chris@304 153 // this image is a candidate, test it properly
Chris@304 154
Chris@304 155 int width = 32;
Chris@304 156 if (m_scaled[v].find(p.image) != m_scaled[v].end()) {
Chris@304 157 width = m_scaled[v][p.image].width();
Chris@587 158 // SVDEBUG << "scaled width = " << width << endl;
Chris@304 159 }
Chris@304 160
Chris@304 161 if (x >= px && x < px + width) {
Chris@303 162 rv.insert(p);
Chris@303 163 }
Chris@303 164 }
Chris@303 165
Chris@682 166 // cerr << rv.size() << " point(s)" << endl;
Chris@303 167
Chris@303 168 return rv;
Chris@303 169 }
Chris@303 170
Chris@303 171 QString
Chris@918 172 ImageLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@303 173 {
Chris@303 174 int x = pos.x();
Chris@303 175
Chris@303 176 if (!m_model || !m_model->getSampleRate()) return "";
Chris@303 177
Chris@303 178 ImageModel::PointList points = getLocalPoints(v, x, pos.y());
Chris@303 179
Chris@303 180 if (points.empty()) {
Chris@303 181 if (!m_model->isReady()) {
Chris@303 182 return tr("In progress");
Chris@303 183 } else {
Chris@303 184 return "";
Chris@303 185 }
Chris@303 186 }
Chris@303 187
Chris@806 188 // int useFrame = points.begin()->frame;
Chris@303 189
Chris@805 190 // RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
Chris@303 191
Chris@303 192 QString text;
Chris@303 193 /*
Chris@303 194 if (points.begin()->label == "") {
Chris@303 195 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
Chris@303 196 .arg(rt.toText(true).c_str())
Chris@303 197 .arg(points.begin()->height)
Chris@303 198 .arg(points.begin()->label);
Chris@303 199 }
Chris@303 200
Chris@303 201 pos = QPoint(v->getXForFrame(useFrame),
Chris@303 202 getYForHeight(v, points.begin()->height));
Chris@303 203 */
Chris@303 204 return text;
Chris@303 205 }
Chris@303 206
Chris@303 207
Chris@303 208 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
Chris@303 209
Chris@303 210 bool
Chris@918 211 ImageLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 212 int &resolution,
Chris@303 213 SnapType snap) const
Chris@303 214 {
Chris@303 215 if (!m_model) {
Chris@303 216 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@303 217 }
Chris@303 218
Chris@303 219 resolution = m_model->getResolution();
Chris@303 220 ImageModel::PointList points;
Chris@303 221
Chris@303 222 if (snap == SnapNeighbouring) {
Chris@303 223
Chris@303 224 points = getLocalPoints(v, v->getXForFrame(frame), -1);
Chris@303 225 if (points.empty()) return false;
Chris@303 226 frame = points.begin()->frame;
Chris@303 227 return true;
Chris@303 228 }
Chris@303 229
Chris@303 230 points = m_model->getPoints(frame, frame);
Chris@905 231 sv_frame_t snapped = frame;
Chris@303 232 bool found = false;
Chris@303 233
Chris@303 234 for (ImageModel::PointList::const_iterator i = points.begin();
Chris@303 235 i != points.end(); ++i) {
Chris@303 236
Chris@303 237 if (snap == SnapRight) {
Chris@303 238
Chris@303 239 if (i->frame > frame) {
Chris@303 240 snapped = i->frame;
Chris@303 241 found = true;
Chris@303 242 break;
Chris@303 243 }
Chris@303 244
Chris@303 245 } else if (snap == SnapLeft) {
Chris@303 246
Chris@303 247 if (i->frame <= frame) {
Chris@303 248 snapped = i->frame;
Chris@303 249 found = true; // don't break, as the next may be better
Chris@303 250 } else {
Chris@303 251 break;
Chris@303 252 }
Chris@303 253
Chris@303 254 } else { // nearest
Chris@303 255
Chris@303 256 ImageModel::PointList::const_iterator j = i;
Chris@303 257 ++j;
Chris@303 258
Chris@303 259 if (j == points.end()) {
Chris@303 260
Chris@303 261 snapped = i->frame;
Chris@303 262 found = true;
Chris@303 263 break;
Chris@303 264
Chris@303 265 } else if (j->frame >= frame) {
Chris@303 266
Chris@303 267 if (j->frame - frame < frame - i->frame) {
Chris@303 268 snapped = j->frame;
Chris@303 269 } else {
Chris@303 270 snapped = i->frame;
Chris@303 271 }
Chris@303 272 found = true;
Chris@303 273 break;
Chris@303 274 }
Chris@303 275 }
Chris@303 276 }
Chris@303 277
Chris@303 278 frame = snapped;
Chris@303 279 return found;
Chris@303 280 }
Chris@303 281
Chris@303 282 void
Chris@916 283 ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@303 284 {
Chris@303 285 if (!m_model || !m_model->isOK()) return;
Chris@303 286
Chris@905 287 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@303 288 if (!sampleRate) return;
Chris@303 289
Chris@303 290 // Profiler profiler("ImageLayer::paint", true);
Chris@303 291
Chris@304 292 // int x0 = rect.left(), x1 = rect.right();
Chris@918 293 int x0 = 0, x1 = v->getPaintWidth();
Chris@304 294
Chris@905 295 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@905 296 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@303 297
Chris@303 298 ImageModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@303 299 if (points.empty()) return;
Chris@303 300
Chris@304 301 paint.save();
Chris@918 302 paint.setClipRect(rect.x(), 0, rect.width(), v->getPaintHeight());
Chris@304 303
Chris@303 304 QColor penColour;
Chris@303 305 penColour = v->getForeground();
Chris@303 306
Chris@304 307 QColor brushColour;
Chris@304 308 brushColour = v->getBackground();
Chris@303 309
Chris@304 310 int h, s, val;
Chris@304 311 brushColour.getHsv(&h, &s, &val);
Chris@304 312 brushColour.setHsv(h, s, 255, 240);
Chris@303 313
Chris@304 314 paint.setPen(penColour);
Chris@304 315 paint.setBrush(brushColour);
Chris@304 316 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@303 317
Chris@303 318 for (ImageModel::PointList::const_iterator i = points.begin();
Chris@303 319 i != points.end(); ++i) {
Chris@303 320
Chris@303 321 const ImageModel::Point &p(*i);
Chris@303 322
Chris@303 323 int x = v->getXForFrame(p.frame);
Chris@303 324
Chris@303 325 int nx = x + 2000;
Chris@303 326 ImageModel::PointList::const_iterator j = i;
Chris@303 327 ++j;
Chris@303 328 if (j != points.end()) {
Chris@303 329 int jx = v->getXForFrame(j->frame);
Chris@303 330 if (jx < nx) nx = jx;
Chris@303 331 }
Chris@303 332
Chris@304 333 drawImage(v, paint, p, x, nx);
Chris@304 334 }
Chris@303 335
Chris@304 336 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@304 337 paint.restore();
Chris@304 338 }
Chris@303 339
Chris@304 340 void
Chris@918 341 ImageLayer::drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p,
Chris@304 342 int x, int nx) const
Chris@304 343 {
Chris@304 344 QString label = p.label;
Chris@304 345 QString imageName = p.image;
Chris@304 346
Chris@304 347 QImage image;
Chris@304 348 QString additionalText;
Chris@304 349
Chris@304 350 QSize imageSize;
Chris@304 351 if (!getImageOriginalSize(imageName, imageSize)) {
Chris@304 352 image = QImage(":icons/emptypage.png");
Chris@304 353 imageSize = image.size();
Chris@304 354 additionalText = imageName;
Chris@304 355 }
Chris@304 356
Chris@304 357 int topMargin = 10;
Chris@304 358 int bottomMargin = 10;
Chris@304 359 int spacing = 5;
Chris@304 360
Chris@918 361 if (v->getPaintHeight() < 100) {
Chris@304 362 topMargin = 5;
Chris@304 363 bottomMargin = 5;
Chris@304 364 }
Chris@304 365
Chris@918 366 int maxBoxHeight = v->getPaintHeight() - topMargin - bottomMargin;
Chris@304 367
Chris@304 368 int availableWidth = nx - x - 3;
Chris@304 369 if (availableWidth < 20) availableWidth = 20;
Chris@304 370
Chris@304 371 QRect labelRect;
Chris@304 372
Chris@304 373 if (label != "") {
Chris@304 374
Chris@918 375 int likelyHeight = v->getPaintHeight() / 4;
Chris@304 376
Chris@304 377 int likelyWidth = // available height times image aspect
Chris@304 378 ((maxBoxHeight - likelyHeight) * imageSize.width())
Chris@304 379 / imageSize.height();
Chris@304 380
Chris@304 381 if (likelyWidth > imageSize.width()) {
Chris@304 382 likelyWidth = imageSize.width();
Chris@303 383 }
Chris@303 384
Chris@304 385 if (likelyWidth > availableWidth) {
Chris@304 386 likelyWidth = availableWidth;
Chris@303 387 }
Chris@303 388
Chris@304 389 int singleWidth = paint.fontMetrics().width(label);
Chris@304 390 if (singleWidth < availableWidth && singleWidth < likelyWidth * 2) {
Chris@304 391 likelyWidth = singleWidth + 4;
Chris@303 392 }
Chris@303 393
Chris@304 394 labelRect = paint.fontMetrics().boundingRect
Chris@304 395 (QRect(0, 0, likelyWidth, likelyHeight),
Chris@304 396 Qt::AlignCenter | Qt::TextWordWrap, label);
Chris@303 397
Chris@304 398 labelRect.setWidth(labelRect.width() + 6);
Chris@303 399 }
Chris@303 400
Chris@304 401 if (image.isNull()) {
Chris@304 402 image = getImage(v, imageName,
Chris@304 403 QSize(availableWidth,
Chris@304 404 maxBoxHeight - labelRect.height()));
Chris@304 405 }
Chris@304 406
Chris@304 407 int boxWidth = image.width();
Chris@304 408 if (boxWidth < labelRect.width()) {
Chris@304 409 boxWidth = labelRect.width();
Chris@304 410 }
Chris@304 411
Chris@304 412 int boxHeight = image.height();
Chris@304 413 if (label != "") {
Chris@304 414 boxHeight += labelRect.height() + spacing;
Chris@304 415 }
Chris@304 416
Chris@304 417 int division = image.height();
Chris@304 418
Chris@304 419 if (additionalText != "") {
Chris@304 420
Chris@304 421 paint.save();
Chris@304 422
Chris@304 423 QFont font(paint.font());
Chris@304 424 font.setItalic(true);
Chris@304 425 paint.setFont(font);
Chris@304 426
Chris@304 427 int tw = paint.fontMetrics().width(additionalText);
Chris@304 428 if (tw > availableWidth) {
Chris@304 429 tw = availableWidth;
Chris@304 430 }
Chris@304 431 if (boxWidth < tw) {
Chris@304 432 boxWidth = tw;
Chris@304 433 }
Chris@304 434 boxHeight += paint.fontMetrics().height();
Chris@304 435 division += paint.fontMetrics().height();
Chris@304 436 }
Chris@304 437
Chris@918 438 bottomMargin = v->getPaintHeight() - topMargin - boxHeight;
Chris@918 439 if (bottomMargin > topMargin + v->getPaintHeight()/7) {
Chris@918 440 topMargin += v->getPaintHeight()/8;
Chris@918 441 bottomMargin -= v->getPaintHeight()/8;
Chris@304 442 }
Chris@304 443
Chris@304 444 paint.drawRect(x - 1,
Chris@304 445 topMargin - 1,
Chris@304 446 boxWidth + 2,
Chris@304 447 boxHeight + 2);
Chris@304 448
Chris@304 449 int imageY;
Chris@304 450 if (label != "") {
Chris@304 451 imageY = topMargin + labelRect.height() + spacing;
Chris@304 452 } else {
Chris@304 453 imageY = topMargin;
Chris@304 454 }
Chris@304 455
Chris@304 456 paint.drawImage(x + (boxWidth - image.width())/2,
Chris@304 457 imageY,
Chris@304 458 image);
Chris@304 459
Chris@304 460 if (additionalText != "") {
Chris@304 461 paint.drawText(x,
Chris@304 462 imageY + image.height() + paint.fontMetrics().ascent(),
Chris@304 463 additionalText);
Chris@304 464 paint.restore();
Chris@304 465 }
Chris@304 466
Chris@304 467 if (label != "") {
Chris@304 468 paint.drawLine(x,
Chris@304 469 topMargin + labelRect.height() + spacing,
Chris@304 470 x + boxWidth,
Chris@304 471 topMargin + labelRect.height() + spacing);
Chris@304 472
Chris@304 473 paint.drawText(QRect(x,
Chris@304 474 topMargin,
Chris@304 475 boxWidth,
Chris@304 476 labelRect.height()),
Chris@304 477 Qt::AlignCenter | Qt::TextWordWrap,
Chris@304 478 label);
Chris@304 479 }
Chris@303 480 }
Chris@303 481
Chris@303 482 void
Chris@918 483 ImageLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
Chris@303 484 {
Chris@303 485 if (dormant) {
Chris@303 486 // Delete the images named in the view's scaled map from the
Chris@303 487 // general image map as well. They can always be re-loaded
Chris@303 488 // if it turns out another view still needs them.
Chris@305 489 QMutexLocker locker(&m_imageMapMutex);
Chris@303 490 for (ImageMap::iterator i = m_scaled[v].begin();
Chris@303 491 i != m_scaled[v].end(); ++i) {
Chris@303 492 m_images.erase(i->first);
Chris@303 493 }
Chris@303 494 m_scaled.erase(v);
Chris@303 495 }
Chris@303 496 }
Chris@303 497
Chris@303 498 //!!! how to reap no-longer-used images?
Chris@303 499
Chris@304 500 bool
Chris@304 501 ImageLayer::getImageOriginalSize(QString name, QSize &size) const
Chris@303 502 {
Chris@682 503 // cerr << "getImageOriginalSize: \"" << name << "\"" << endl;
Chris@305 504
Chris@305 505 QMutexLocker locker(&m_imageMapMutex);
Chris@303 506 if (m_images.find(name) == m_images.end()) {
Chris@682 507 // cerr << "don't have, trying to open local" << endl;
Chris@305 508 m_images[name] = QImage(getLocalFilename(name));
Chris@303 509 }
Chris@304 510 if (m_images[name].isNull()) {
Chris@682 511 // cerr << "null image" << endl;
Chris@304 512 return false;
Chris@304 513 } else {
Chris@304 514 size = m_images[name].size();
Chris@304 515 return true;
Chris@304 516 }
Chris@303 517 }
Chris@303 518
Chris@303 519 QImage
Chris@918 520 ImageLayer::getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const
Chris@303 521 {
Chris@587 522 // SVDEBUG << "ImageLayer::getImage(" << v << ", " << name << ", ("
Chris@585 523 // << maxSize.width() << "x" << maxSize.height() << "))" << endl;
Chris@303 524
Chris@304 525 if (!m_scaled[v][name].isNull() &&
Chris@304 526 ((m_scaled[v][name].width() == maxSize.width() &&
Chris@304 527 m_scaled[v][name].height() <= maxSize.height()) ||
Chris@304 528 (m_scaled[v][name].width() <= maxSize.width() &&
Chris@304 529 m_scaled[v][name].height() == maxSize.height()))) {
Chris@682 530 // cerr << "cache hit" << endl;
Chris@303 531 return m_scaled[v][name];
Chris@303 532 }
Chris@303 533
Chris@305 534 QMutexLocker locker(&m_imageMapMutex);
Chris@305 535
Chris@303 536 if (m_images.find(name) == m_images.end()) {
Chris@305 537 m_images[name] = QImage(getLocalFilename(name));
Chris@303 538 }
Chris@303 539
Chris@303 540 if (m_images[name].isNull()) {
Chris@682 541 // cerr << "null image" << endl;
Chris@303 542 m_scaled[v][name] = QImage();
Chris@304 543 } else if (m_images[name].width() <= maxSize.width() &&
Chris@304 544 m_images[name].height() <= maxSize.height()) {
Chris@304 545 m_scaled[v][name] = m_images[name];
Chris@303 546 } else {
Chris@303 547 m_scaled[v][name] =
Chris@303 548 m_images[name].scaled(maxSize,
Chris@303 549 Qt::KeepAspectRatio,
Chris@303 550 Qt::SmoothTransformation);
Chris@303 551 }
Chris@303 552
Chris@303 553 return m_scaled[v][name];
Chris@303 554 }
Chris@303 555
Chris@303 556 void
Chris@918 557 ImageLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 558 {
Chris@587 559 // SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 560
Chris@303 561 if (!m_model) {
Chris@587 562 SVDEBUG << "ImageLayer::drawStart: no model" << endl;
Chris@303 563 return;
Chris@303 564 }
Chris@303 565
Chris@905 566 sv_frame_t frame = v->getFrameForX(e->x());
Chris@303 567 if (frame < 0) frame = 0;
Chris@303 568 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@303 569
Chris@303 570 m_editingPoint = ImageModel::Point(frame, "", "");
Chris@303 571 m_originalPoint = m_editingPoint;
Chris@303 572
Chris@376 573 if (m_editingCommand) finish(m_editingCommand);
Chris@303 574 m_editingCommand = new ImageModel::EditCommand(m_model, "Add Image");
Chris@303 575 m_editingCommand->addPoint(m_editingPoint);
Chris@303 576
Chris@303 577 m_editing = true;
Chris@303 578 }
Chris@303 579
Chris@303 580 void
Chris@918 581 ImageLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 582 {
Chris@587 583 // SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 584
Chris@303 585 if (!m_model || !m_editing) return;
Chris@303 586
Chris@905 587 sv_frame_t frame = v->getFrameForX(e->x());
Chris@303 588 if (frame < 0) frame = 0;
Chris@303 589 frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@303 590
Chris@303 591 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 592 m_editingPoint.frame = frame;
Chris@303 593 m_editingCommand->addPoint(m_editingPoint);
Chris@303 594 }
Chris@303 595
Chris@303 596 void
Chris@918 597 ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@303 598 {
Chris@587 599 // SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 600 if (!m_model || !m_editing) return;
Chris@303 601
Chris@307 602 ImageDialog dialog(tr("Select image"), "", "");
Chris@305 603
Chris@303 604 if (dialog.exec() == QDialog::Accepted) {
Chris@305 605
Chris@464 606 checkAddSource(dialog.getImage());
Chris@305 607
Chris@303 608 ImageModel::ChangeImageCommand *command =
Chris@303 609 new ImageModel::ChangeImageCommand
Chris@303 610 (m_model, m_editingPoint, dialog.getImage(), dialog.getLabel());
Chris@303 611 m_editingCommand->addCommand(command);
Chris@307 612 } else {
Chris@307 613 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 614 }
Chris@303 615
Chris@376 616 finish(m_editingCommand);
Chris@303 617 m_editingCommand = 0;
Chris@303 618 m_editing = false;
Chris@303 619 }
Chris@303 620
Chris@312 621 bool
Chris@905 622 ImageLayer::addImage(sv_frame_t frame, QString url)
Chris@312 623 {
Chris@312 624 QImage image(getLocalFilename(url));
Chris@312 625 if (image.isNull()) {
Chris@682 626 cerr << "Failed to open image from url \"" << url << "\" (local filename \"" << getLocalFilename(url) << "\"" << endl;
Chris@464 627 delete m_fileSources[url];
Chris@464 628 m_fileSources.erase(url);
Chris@312 629 return false;
Chris@312 630 }
Chris@312 631
Chris@312 632 ImageModel::Point point(frame, url, "");
Chris@312 633 ImageModel::EditCommand *command =
Chris@312 634 new ImageModel::EditCommand(m_model, "Add Image");
Chris@312 635 command->addPoint(point);
Chris@376 636 finish(command);
Chris@312 637 return true;
Chris@312 638 }
Chris@312 639
Chris@303 640 void
Chris@918 641 ImageLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 642 {
Chris@587 643 // SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 644
Chris@303 645 if (!m_model) return;
Chris@303 646
Chris@303 647 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 648 if (points.empty()) return;
Chris@303 649
Chris@303 650 m_editOrigin = e->pos();
Chris@303 651 m_editingPoint = *points.begin();
Chris@303 652 m_originalPoint = m_editingPoint;
Chris@303 653
Chris@303 654 if (m_editingCommand) {
Chris@376 655 finish(m_editingCommand);
Chris@303 656 m_editingCommand = 0;
Chris@303 657 }
Chris@303 658
Chris@303 659 m_editing = true;
Chris@303 660 }
Chris@303 661
Chris@303 662 void
Chris@918 663 ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 664 {
Chris@303 665 if (!m_model || !m_editing) return;
Chris@303 666
Chris@905 667 sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@905 668 sv_frame_t frame = m_originalPoint.frame + frameDiff;
Chris@303 669
Chris@303 670 if (frame < 0) frame = 0;
Chris@303 671 frame = (frame / m_model->getResolution()) * m_model->getResolution();
Chris@303 672
Chris@303 673 if (!m_editingCommand) {
Chris@303 674 m_editingCommand = new ImageModel::EditCommand(m_model, tr("Move Image"));
Chris@303 675 }
Chris@303 676
Chris@303 677 m_editingCommand->deletePoint(m_editingPoint);
Chris@303 678 m_editingPoint.frame = frame;
Chris@303 679 m_editingCommand->addPoint(m_editingPoint);
Chris@303 680 }
Chris@303 681
Chris@303 682 void
Chris@918 683 ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@303 684 {
Chris@587 685 // SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@303 686 if (!m_model || !m_editing) return;
Chris@303 687
Chris@303 688 if (m_editingCommand) {
Chris@376 689 finish(m_editingCommand);
Chris@303 690 }
Chris@303 691
Chris@303 692 m_editingCommand = 0;
Chris@303 693 m_editing = false;
Chris@303 694 }
Chris@303 695
Chris@303 696 bool
Chris@918 697 ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@303 698 {
Chris@303 699 if (!m_model) return false;
Chris@303 700
Chris@303 701 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
Chris@303 702 if (points.empty()) return false;
Chris@303 703
Chris@303 704 QString image = points.begin()->image;
Chris@303 705 QString label = points.begin()->label;
Chris@303 706
Chris@303 707 ImageDialog dialog(tr("Select image"),
Chris@303 708 image,
Chris@303 709 label);
Chris@303 710
Chris@303 711 if (dialog.exec() == QDialog::Accepted) {
Chris@305 712
Chris@464 713 checkAddSource(dialog.getImage());
Chris@305 714
Chris@303 715 ImageModel::ChangeImageCommand *command =
Chris@303 716 new ImageModel::ChangeImageCommand
Chris@303 717 (m_model, *points.begin(), dialog.getImage(), dialog.getLabel());
Chris@305 718
Chris@303 719 CommandHistory::getInstance()->addCommand(command);
Chris@303 720 }
Chris@303 721
Chris@303 722 return true;
Chris@303 723 }
Chris@303 724
Chris@303 725 void
Chris@905 726 ImageLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@303 727 {
Chris@303 728 if (!m_model) return;
Chris@303 729
Chris@303 730 ImageModel::EditCommand *command =
Chris@303 731 new ImageModel::EditCommand(m_model, tr("Drag Selection"));
Chris@303 732
Chris@303 733 ImageModel::PointList points =
Chris@303 734 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 735
Chris@303 736 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 737 i != points.end(); ++i) {
Chris@303 738
Chris@303 739 if (s.contains(i->frame)) {
Chris@303 740 ImageModel::Point newPoint(*i);
Chris@303 741 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@303 742 command->deletePoint(*i);
Chris@303 743 command->addPoint(newPoint);
Chris@303 744 }
Chris@303 745 }
Chris@303 746
Chris@376 747 finish(command);
Chris@303 748 }
Chris@303 749
Chris@303 750 void
Chris@303 751 ImageLayer::resizeSelection(Selection s, Selection newSize)
Chris@303 752 {
Chris@303 753 if (!m_model) return;
Chris@303 754
Chris@303 755 ImageModel::EditCommand *command =
Chris@303 756 new ImageModel::EditCommand(m_model, tr("Resize Selection"));
Chris@303 757
Chris@303 758 ImageModel::PointList points =
Chris@303 759 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 760
Chris@303 761 double ratio =
Chris@303 762 double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@303 763 double(s.getEndFrame() - s.getStartFrame());
Chris@303 764
Chris@303 765 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 766 i != points.end(); ++i) {
Chris@303 767
Chris@303 768 if (s.contains(i->frame)) {
Chris@303 769
Chris@905 770 double target = double(i->frame);
Chris@905 771 target = double(newSize.getStartFrame()) +
Chris@905 772 target - double(s.getStartFrame()) * ratio;
Chris@303 773
Chris@303 774 ImageModel::Point newPoint(*i);
Chris@303 775 newPoint.frame = lrint(target);
Chris@303 776 command->deletePoint(*i);
Chris@303 777 command->addPoint(newPoint);
Chris@303 778 }
Chris@303 779 }
Chris@303 780
Chris@376 781 finish(command);
Chris@303 782 }
Chris@303 783
Chris@303 784 void
Chris@303 785 ImageLayer::deleteSelection(Selection s)
Chris@303 786 {
Chris@303 787 if (!m_model) return;
Chris@303 788
Chris@303 789 ImageModel::EditCommand *command =
Chris@303 790 new ImageModel::EditCommand(m_model, tr("Delete Selection"));
Chris@303 791
Chris@303 792 ImageModel::PointList points =
Chris@303 793 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 794
Chris@303 795 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 796 i != points.end(); ++i) {
Chris@303 797 if (s.contains(i->frame)) command->deletePoint(*i);
Chris@303 798 }
Chris@303 799
Chris@376 800 finish(command);
Chris@303 801 }
Chris@303 802
Chris@303 803 void
Chris@918 804 ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@303 805 {
Chris@303 806 if (!m_model) return;
Chris@303 807
Chris@303 808 ImageModel::PointList points =
Chris@303 809 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@303 810
Chris@303 811 for (ImageModel::PointList::iterator i = points.begin();
Chris@303 812 i != points.end(); ++i) {
Chris@303 813 if (s.contains(i->frame)) {
Chris@303 814 Clipboard::Point point(i->frame, i->label);
Chris@360 815 point.setReferenceFrame(alignToReference(v, i->frame));
Chris@303 816 to.addPoint(point);
Chris@303 817 }
Chris@303 818 }
Chris@303 819 }
Chris@303 820
Chris@303 821 bool
Chris@918 822 ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@303 823 {
Chris@303 824 if (!m_model) return false;
Chris@303 825
Chris@303 826 const Clipboard::PointList &points = from.getPoints();
Chris@303 827
Chris@360 828 bool realign = false;
Chris@360 829
Chris@360 830 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 831
Chris@360 832 QMessageBox::StandardButton button =
Chris@918 833 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 834 tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"),
Chris@360 835 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 836 QMessageBox::Yes);
Chris@360 837
Chris@360 838 if (button == QMessageBox::Cancel) {
Chris@360 839 return false;
Chris@360 840 }
Chris@360 841
Chris@360 842 if (button == QMessageBox::Yes) {
Chris@360 843 realign = true;
Chris@360 844 }
Chris@360 845 }
Chris@360 846
Chris@303 847 ImageModel::EditCommand *command =
Chris@303 848 new ImageModel::EditCommand(m_model, tr("Paste"));
Chris@303 849
Chris@303 850 for (Clipboard::PointList::const_iterator i = points.begin();
Chris@303 851 i != points.end(); ++i) {
Chris@303 852
Chris@303 853 if (!i->haveFrame()) continue;
Chris@360 854
Chris@905 855 sv_frame_t frame = 0;
Chris@360 856
Chris@360 857 if (!realign) {
Chris@360 858
Chris@360 859 frame = i->getFrame();
Chris@360 860
Chris@360 861 } else {
Chris@360 862
Chris@360 863 if (i->haveReferenceFrame()) {
Chris@360 864 frame = i->getReferenceFrame();
Chris@360 865 frame = alignFromReference(v, frame);
Chris@360 866 } else {
Chris@360 867 frame = i->getFrame();
Chris@360 868 }
Chris@303 869 }
Chris@360 870
Chris@303 871 ImageModel::Point newPoint(frame);
Chris@303 872
Chris@303 873 //!!! inadequate
Chris@303 874
Chris@303 875 if (i->haveLabel()) {
Chris@303 876 newPoint.label = i->getLabel();
Chris@303 877 } else if (i->haveValue()) {
Chris@303 878 newPoint.label = QString("%1").arg(i->getValue());
Chris@303 879 } else {
Chris@303 880 newPoint.label = tr("New Point");
Chris@303 881 }
Chris@303 882
Chris@303 883 command->addPoint(newPoint);
Chris@303 884 }
Chris@303 885
Chris@376 886 finish(command);
Chris@303 887 return true;
Chris@303 888 }
Chris@303 889
Chris@303 890 QString
Chris@305 891 ImageLayer::getLocalFilename(QString img) const
Chris@305 892 {
Chris@464 893 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@464 894 checkAddSource(img);
Chris@464 895 if (m_fileSources.find(img) == m_fileSources.end()) {
Chris@312 896 return img;
Chris@312 897 }
Chris@305 898 }
Chris@464 899 return m_fileSources[img]->getLocalFilename();
Chris@305 900 }
Chris@305 901
Chris@305 902 void
Chris@464 903 ImageLayer::checkAddSource(QString img) const
Chris@305 904 {
Chris@587 905 SVDEBUG << "ImageLayer::checkAddSource(" << img << "): yes, trying..." << endl;
Chris@305 906
Chris@464 907 if (m_fileSources.find(img) != m_fileSources.end()) {
Chris@464 908 return;
Chris@464 909 }
Chris@312 910
Chris@464 911 ProgressDialog dialog(tr("Opening image URL..."), true, 2000);
Chris@464 912 FileSource *rf = new FileSource(img, &dialog);
Chris@464 913 if (rf->isOK()) {
Chris@682 914 cerr << "ok, adding it (local filename = " << rf->getLocalFilename() << ")" << endl;
Chris@464 915 m_fileSources[img] = rf;
Chris@464 916 connect(rf, SIGNAL(ready()), this, SLOT(fileSourceReady()));
Chris@464 917 } else {
Chris@464 918 delete rf;
Chris@305 919 }
Chris@305 920 }
Chris@305 921
Chris@305 922 void
Chris@464 923 ImageLayer::checkAddSources()
Chris@305 924 {
Chris@305 925 const ImageModel::PointList &points(m_model->getPoints());
Chris@305 926
Chris@305 927 for (ImageModel::PointList::const_iterator i = points.begin();
Chris@305 928 i != points.end(); ++i) {
Chris@305 929
Chris@464 930 checkAddSource((*i).image);
Chris@305 931 }
Chris@305 932 }
Chris@305 933
Chris@305 934 void
Chris@464 935 ImageLayer::fileSourceReady()
Chris@305 936 {
Chris@587 937 // SVDEBUG << "ImageLayer::fileSourceReady" << endl;
Chris@305 938
Chris@318 939 FileSource *rf = dynamic_cast<FileSource *>(sender());
Chris@305 940 if (!rf) return;
Chris@305 941
Chris@305 942 QString img;
Chris@464 943 for (FileSourceMap::const_iterator i = m_fileSources.begin();
Chris@464 944 i != m_fileSources.end(); ++i) {
Chris@305 945 if (i->second == rf) {
Chris@305 946 img = i->first;
Chris@682 947 // cerr << "it's image \"" << img << "\"" << endl;
Chris@305 948 break;
Chris@305 949 }
Chris@305 950 }
Chris@305 951 if (img == "") return;
Chris@305 952
Chris@305 953 QMutexLocker locker(&m_imageMapMutex);
Chris@305 954 m_images.erase(img);
Chris@305 955 for (ViewImageMap::iterator i = m_scaled.begin(); i != m_scaled.end(); ++i) {
Chris@305 956 i->second.erase(img);
Chris@306 957 emit modelChanged();
Chris@305 958 }
Chris@305 959 }
Chris@305 960
Chris@316 961 void
Chris@316 962 ImageLayer::toXml(QTextStream &stream,
Chris@316 963 QString indent, QString extraAttributes) const
Chris@303 964 {
Chris@316 965 Layer::toXml(stream, indent, extraAttributes);
Chris@303 966 }
Chris@303 967
Chris@303 968 void
Chris@805 969 ImageLayer::setProperties(const QXmlAttributes &)
Chris@303 970 {
Chris@303 971 }
Chris@303 972