comparison layer/ImageLayer.cpp @ 303:46faec7aae12

* Phase 1 of an image layer.
author Chris Cannam
date Thu, 04 Oct 2007 16:34:11 +0000
parents
children 4b7e8da8f069
comparison
equal deleted inserted replaced
302:e9549ea3f825 303:46faec7aae12
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 This file copyright 2006 Chris Cannam.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "ImageLayer.h"
17
18 #include "data/model/Model.h"
19 #include "base/RealTime.h"
20 #include "base/Profiler.h"
21 #include "view/View.h"
22
23 #include "data/model/ImageModel.h"
24
25 #include "widgets/ImageDialog.h"
26
27 #include <QPainter>
28 #include <QMouseEvent>
29 #include <QInputDialog>
30
31 #include <iostream>
32 #include <cmath>
33
34 ImageLayer::ImageMap
35 ImageLayer::m_images;
36
37 ImageLayer::ImageLayer() :
38 Layer(),
39 m_model(0),
40 m_editing(false),
41 m_originalPoint(0, "", ""),
42 m_editingPoint(0, "", ""),
43 m_editingCommand(0)
44 {
45
46 }
47
48 void
49 ImageLayer::setModel(ImageModel *model)
50 {
51 if (m_model == model) return;
52 m_model = model;
53
54 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
55 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
56 this, SIGNAL(modelChanged(size_t, size_t)));
57
58 connect(m_model, SIGNAL(completionChanged()),
59 this, SIGNAL(modelCompletionChanged()));
60
61 // std::cerr << "ImageLayer::setModel(" << model << ")" << std::endl;
62
63 emit modelReplaced();
64 }
65
66 Layer::PropertyList
67 ImageLayer::getProperties() const
68 {
69 return Layer::getProperties();
70 }
71
72 QString
73 ImageLayer::getPropertyLabel(const PropertyName &name) const
74 {
75 return "";
76 }
77
78 Layer::PropertyType
79 ImageLayer::getPropertyType(const PropertyName &name) const
80 {
81 return Layer::getPropertyType(name);
82 }
83
84 int
85 ImageLayer::getPropertyRangeAndValue(const PropertyName &name,
86 int *min, int *max, int *deflt) const
87 {
88 return Layer::getPropertyRangeAndValue(name, min, max, deflt);
89 }
90
91 QString
92 ImageLayer::getPropertyValueLabel(const PropertyName &name,
93 int value) const
94 {
95 return Layer::getPropertyValueLabel(name, value);
96 }
97
98 void
99 ImageLayer::setProperty(const PropertyName &name, int value)
100 {
101 Layer::setProperty(name, value);
102 }
103
104 bool
105 ImageLayer::getValueExtents(float &, float &, bool &, QString &) const
106 {
107 return false;
108 }
109
110 bool
111 ImageLayer::isLayerScrollable(const View *v) const
112 {
113 QPoint discard;
114 return !v->shouldIlluminateLocalFeatures(this, discard);
115 }
116
117
118 ImageModel::PointList
119 ImageLayer::getLocalPoints(View *v, int x, int y) const
120 {
121 if (!m_model) return ImageModel::PointList();
122
123 std::cerr << "ImageLayer::getLocalPoints(" << x << "," << y << "):";
124
125 long frame0 = v->getFrameForX(-150);
126 long frame1 = v->getFrameForX(v->width() + 150);
127
128 ImageModel::PointList points(m_model->getPoints(frame0, frame1));
129
130 ImageModel::PointList rv;
131
132 //!!! need to store drawn size as well as original size for each
133 //image, but for now:
134
135 for (ImageModel::PointList::iterator i = points.begin();
136 i != points.end(); ++i) {
137
138 const ImageModel::Point &p(*i);
139
140 int px = v->getXForFrame(p.frame);
141
142 if (x >= px && x < px + 100) {
143 rv.insert(p);
144 }
145 }
146
147 std::cerr << rv.size() << " point(s)" << std::endl;
148
149 return rv;
150 }
151
152 QString
153 ImageLayer::getFeatureDescription(View *v, QPoint &pos) const
154 {
155 int x = pos.x();
156
157 if (!m_model || !m_model->getSampleRate()) return "";
158
159 ImageModel::PointList points = getLocalPoints(v, x, pos.y());
160
161 if (points.empty()) {
162 if (!m_model->isReady()) {
163 return tr("In progress");
164 } else {
165 return "";
166 }
167 }
168
169 long useFrame = points.begin()->frame;
170
171 RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
172
173 QString text;
174 /*
175 if (points.begin()->label == "") {
176 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
177 .arg(rt.toText(true).c_str())
178 .arg(points.begin()->height)
179 .arg(points.begin()->label);
180 }
181
182 pos = QPoint(v->getXForFrame(useFrame),
183 getYForHeight(v, points.begin()->height));
184 */
185 return text;
186 }
187
188
189 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
190
191 bool
192 ImageLayer::snapToFeatureFrame(View *v, int &frame,
193 size_t &resolution,
194 SnapType snap) const
195 {
196 if (!m_model) {
197 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
198 }
199
200 resolution = m_model->getResolution();
201 ImageModel::PointList points;
202
203 if (snap == SnapNeighbouring) {
204
205 points = getLocalPoints(v, v->getXForFrame(frame), -1);
206 if (points.empty()) return false;
207 frame = points.begin()->frame;
208 return true;
209 }
210
211 points = m_model->getPoints(frame, frame);
212 int snapped = frame;
213 bool found = false;
214
215 for (ImageModel::PointList::const_iterator i = points.begin();
216 i != points.end(); ++i) {
217
218 if (snap == SnapRight) {
219
220 if (i->frame > frame) {
221 snapped = i->frame;
222 found = true;
223 break;
224 }
225
226 } else if (snap == SnapLeft) {
227
228 if (i->frame <= frame) {
229 snapped = i->frame;
230 found = true; // don't break, as the next may be better
231 } else {
232 break;
233 }
234
235 } else { // nearest
236
237 ImageModel::PointList::const_iterator j = i;
238 ++j;
239
240 if (j == points.end()) {
241
242 snapped = i->frame;
243 found = true;
244 break;
245
246 } else if (j->frame >= frame) {
247
248 if (j->frame - frame < frame - i->frame) {
249 snapped = j->frame;
250 } else {
251 snapped = i->frame;
252 }
253 found = true;
254 break;
255 }
256 }
257 }
258
259 frame = snapped;
260 return found;
261 }
262
263 void
264 ImageLayer::paint(View *v, QPainter &paint, QRect rect) const
265 {
266 if (!m_model || !m_model->isOK()) return;
267
268 int sampleRate = m_model->getSampleRate();
269 if (!sampleRate) return;
270
271 // Profiler profiler("ImageLayer::paint", true);
272
273 int x0 = rect.left(), x1 = rect.right();
274 long frame0 = v->getFrameForX(x0);
275 long frame1 = v->getFrameForX(x1);
276
277 ImageModel::PointList points(m_model->getPoints(frame0, frame1));
278 if (points.empty()) return;
279
280 QColor penColour;
281 penColour = v->getForeground();
282
283 // std::cerr << "ImageLayer::paint: resolution is "
284 // << m_model->getResolution() << " frames" << std::endl;
285
286 QPoint localPos;
287 long illuminateFrame = -1;
288
289 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
290 ImageModel::PointList localPoints = getLocalPoints(v, localPos.x(),
291 localPos.y());
292 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
293 }
294
295 paint.save();
296 paint.setClipRect(rect.x(), 0, rect.width(), v->height());
297
298 for (ImageModel::PointList::const_iterator i = points.begin();
299 i != points.end(); ++i) {
300
301 const ImageModel::Point &p(*i);
302
303 int x = v->getXForFrame(p.frame);
304
305 int nx = x + 2000;
306 ImageModel::PointList::const_iterator j = i;
307 ++j;
308 if (j != points.end()) {
309 int jx = v->getXForFrame(j->frame);
310 if (jx < nx) nx = jx;
311 }
312 /*
313 if (illuminateFrame == p.frame) {
314 paint.setBrush(penColour);
315 paint.setPen(v->getBackground());
316 } else {
317 paint.setPen(penColour);
318 paint.setBrush(brushColour);
319 }
320 */
321 QString label = p.label;
322 QString imageName = p.image;
323
324 int nw = nx - x;
325 if (nw < 10) nw = 20;
326
327 int top = 10;
328 if (v->height() < 50) top = 5;
329
330 int bottom = top;
331
332 QRect labelRect;
333 if (label != "") {
334 float aspect = getImageAspect(imageName);
335 int iw = lrintf((v->height() - v->height()/4 - top - bottom)
336 * aspect);
337 labelRect = paint.fontMetrics().boundingRect
338 (QRect(0, 0, iw, v->height()/4),
339 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
340 bottom += labelRect.height() + 5;
341 }
342
343 QImage image = getImage(v,
344 imageName,
345 QSize(nw, v->height() - top - bottom));
346
347 if (image.isNull()) {
348 image = QImage(":icons/emptypage.png");
349 }
350
351 paint.setRenderHint(QPainter::Antialiasing, false);
352
353 int boxWidth = image.width();
354
355 if (label != "") {
356 boxWidth = std::max(boxWidth, labelRect.width());
357 }
358
359 paint.drawRect(x-1, top-1, boxWidth+1, v->height() - 2 * top +1);
360
361 paint.setRenderHint(QPainter::Antialiasing, true);
362
363 paint.drawImage(x + (boxWidth - image.width())/2, top, image);
364
365 paint.drawText(QRect(x, v->height() - bottom + 5,
366 boxWidth, labelRect.height()),
367 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
368 label);
369 }
370
371 paint.restore();
372 paint.setRenderHint(QPainter::Antialiasing, false);
373 }
374
375 void
376 ImageLayer::setLayerDormant(const View *v, bool dormant)
377 {
378 if (dormant) {
379 // Delete the images named in the view's scaled map from the
380 // general image map as well. They can always be re-loaded
381 // if it turns out another view still needs them.
382 for (ImageMap::iterator i = m_scaled[v].begin();
383 i != m_scaled[v].end(); ++i) {
384 m_images.erase(i->first);
385 }
386 m_scaled.erase(v);
387 }
388 }
389
390 //!!! how to reap no-longer-used images?
391
392 float
393 ImageLayer::getImageAspect(QString name) const
394 {
395 if (m_images.find(name) == m_images.end()) {
396 m_images[name] = QImage(name);
397 }
398 if (m_images[name].isNull()) return 1.f;
399 return float(m_images[name].width()) / float(m_images[name].height());
400 }
401
402 QImage
403 ImageLayer::getImage(View *v, QString name, QSize maxSize) const
404 {
405 bool need = false;
406
407 // std::cerr << "ImageLayer::getImage(" << v << ", " << name.toStdString() << ", ("
408 // << maxSize.width() << "x" << maxSize.height() << "))" << std::endl;
409
410 if (!m_scaled[v][name].isNull() &&
411 (m_scaled[v][name].width() == maxSize.width() ||
412 m_scaled[v][name].height() == maxSize.height())) {
413 // std::cerr << "cache hit" << std::endl;
414 return m_scaled[v][name];
415 }
416
417 if (m_images.find(name) == m_images.end()) {
418 m_images[name] = QImage(name);
419 }
420
421 if (m_images[name].isNull()) {
422 // std::cerr << "null image" << std::endl;
423 m_scaled[v][name] = QImage();
424 } else {
425 m_scaled[v][name] =
426 m_images[name].scaled(maxSize,
427 Qt::KeepAspectRatio,
428 Qt::SmoothTransformation);
429 }
430
431 return m_scaled[v][name];
432 }
433
434 void
435 ImageLayer::drawStart(View *v, QMouseEvent *e)
436 {
437 // std::cerr << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
438
439 if (!m_model) {
440 std::cerr << "ImageLayer::drawStart: no model" << std::endl;
441 return;
442 }
443
444 long frame = v->getFrameForX(e->x());
445 if (frame < 0) frame = 0;
446 frame = frame / m_model->getResolution() * m_model->getResolution();
447
448 m_editingPoint = ImageModel::Point(frame, "", "");
449 m_originalPoint = m_editingPoint;
450
451 if (m_editingCommand) m_editingCommand->finish();
452 m_editingCommand = new ImageModel::EditCommand(m_model, "Add Image");
453 m_editingCommand->addPoint(m_editingPoint);
454
455 m_editing = true;
456 }
457
458 void
459 ImageLayer::drawDrag(View *v, QMouseEvent *e)
460 {
461 // std::cerr << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
462
463 if (!m_model || !m_editing) return;
464
465 long frame = v->getFrameForX(e->x());
466 if (frame < 0) frame = 0;
467 frame = frame / m_model->getResolution() * m_model->getResolution();
468
469 m_editingCommand->deletePoint(m_editingPoint);
470 m_editingPoint.frame = frame;
471 m_editingCommand->addPoint(m_editingPoint);
472 }
473
474 void
475 ImageLayer::drawEnd(View *v, QMouseEvent *)
476 {
477 // std::cerr << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
478 if (!m_model || !m_editing) return;
479
480 bool ok = false;
481
482 ImageDialog dialog(tr("Select image"), "", tr("<no label>"));
483 if (dialog.exec() == QDialog::Accepted) {
484 ImageModel::ChangeImageCommand *command =
485 new ImageModel::ChangeImageCommand
486 (m_model, m_editingPoint, dialog.getImage(), dialog.getLabel());
487 m_editingCommand->addCommand(command);
488 }
489
490 m_editingCommand->finish();
491 m_editingCommand = 0;
492 m_editing = false;
493 }
494
495 void
496 ImageLayer::editStart(View *v, QMouseEvent *e)
497 {
498 // std::cerr << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
499
500 if (!m_model) return;
501
502 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
503 if (points.empty()) return;
504
505 m_editOrigin = e->pos();
506 m_editingPoint = *points.begin();
507 m_originalPoint = m_editingPoint;
508
509 if (m_editingCommand) {
510 m_editingCommand->finish();
511 m_editingCommand = 0;
512 }
513
514 m_editing = true;
515 }
516
517 void
518 ImageLayer::editDrag(View *v, QMouseEvent *e)
519 {
520 if (!m_model || !m_editing) return;
521
522 long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
523 long frame = m_originalPoint.frame + frameDiff;
524
525 if (frame < 0) frame = 0;
526 frame = (frame / m_model->getResolution()) * m_model->getResolution();
527
528 if (!m_editingCommand) {
529 m_editingCommand = new ImageModel::EditCommand(m_model, tr("Move Image"));
530 }
531
532 m_editingCommand->deletePoint(m_editingPoint);
533 m_editingPoint.frame = frame;
534 m_editingCommand->addPoint(m_editingPoint);
535 }
536
537 void
538 ImageLayer::editEnd(View *, QMouseEvent *)
539 {
540 // std::cerr << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
541 if (!m_model || !m_editing) return;
542
543 if (m_editingCommand) {
544 m_editingCommand->finish();
545 }
546
547 m_editingCommand = 0;
548 m_editing = false;
549 }
550
551 bool
552 ImageLayer::editOpen(View *v, QMouseEvent *e)
553 {
554 if (!m_model) return false;
555
556 ImageModel::PointList points = getLocalPoints(v, e->x(), e->y());
557 if (points.empty()) return false;
558
559 QString image = points.begin()->image;
560 QString label = points.begin()->label;
561
562 ImageDialog dialog(tr("Select image"),
563 image,
564 label);
565
566 if (dialog.exec() == QDialog::Accepted) {
567 ImageModel::ChangeImageCommand *command =
568 new ImageModel::ChangeImageCommand
569 (m_model, *points.begin(), dialog.getImage(), dialog.getLabel());
570 CommandHistory::getInstance()->addCommand(command);
571 }
572
573 return true;
574 }
575
576 void
577 ImageLayer::moveSelection(Selection s, size_t newStartFrame)
578 {
579 if (!m_model) return;
580
581 ImageModel::EditCommand *command =
582 new ImageModel::EditCommand(m_model, tr("Drag Selection"));
583
584 ImageModel::PointList points =
585 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
586
587 for (ImageModel::PointList::iterator i = points.begin();
588 i != points.end(); ++i) {
589
590 if (s.contains(i->frame)) {
591 ImageModel::Point newPoint(*i);
592 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
593 command->deletePoint(*i);
594 command->addPoint(newPoint);
595 }
596 }
597
598 command->finish();
599 }
600
601 void
602 ImageLayer::resizeSelection(Selection s, Selection newSize)
603 {
604 if (!m_model) return;
605
606 ImageModel::EditCommand *command =
607 new ImageModel::EditCommand(m_model, tr("Resize Selection"));
608
609 ImageModel::PointList points =
610 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
611
612 double ratio =
613 double(newSize.getEndFrame() - newSize.getStartFrame()) /
614 double(s.getEndFrame() - s.getStartFrame());
615
616 for (ImageModel::PointList::iterator i = points.begin();
617 i != points.end(); ++i) {
618
619 if (s.contains(i->frame)) {
620
621 double target = i->frame;
622 target = newSize.getStartFrame() +
623 double(target - s.getStartFrame()) * ratio;
624
625 ImageModel::Point newPoint(*i);
626 newPoint.frame = lrint(target);
627 command->deletePoint(*i);
628 command->addPoint(newPoint);
629 }
630 }
631
632 command->finish();
633 }
634
635 void
636 ImageLayer::deleteSelection(Selection s)
637 {
638 if (!m_model) return;
639
640 ImageModel::EditCommand *command =
641 new ImageModel::EditCommand(m_model, tr("Delete Selection"));
642
643 ImageModel::PointList points =
644 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
645
646 for (ImageModel::PointList::iterator i = points.begin();
647 i != points.end(); ++i) {
648 if (s.contains(i->frame)) command->deletePoint(*i);
649 }
650
651 command->finish();
652 }
653
654 void
655 ImageLayer::copy(Selection s, Clipboard &to)
656 {
657 if (!m_model) return;
658
659 ImageModel::PointList points =
660 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
661
662 for (ImageModel::PointList::iterator i = points.begin();
663 i != points.end(); ++i) {
664 if (s.contains(i->frame)) {
665 //!!! inadequate
666 Clipboard::Point point(i->frame, i->label);
667 to.addPoint(point);
668 }
669 }
670 }
671
672 bool
673 ImageLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
674 {
675 if (!m_model) return false;
676
677 const Clipboard::PointList &points = from.getPoints();
678
679 ImageModel::EditCommand *command =
680 new ImageModel::EditCommand(m_model, tr("Paste"));
681
682 for (Clipboard::PointList::const_iterator i = points.begin();
683 i != points.end(); ++i) {
684
685 if (!i->haveFrame()) continue;
686 size_t frame = 0;
687 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
688 frame = i->getFrame() + frameOffset;
689 }
690 ImageModel::Point newPoint(frame);
691
692 //!!! inadequate
693
694 if (i->haveLabel()) {
695 newPoint.label = i->getLabel();
696 } else if (i->haveValue()) {
697 newPoint.label = QString("%1").arg(i->getValue());
698 } else {
699 newPoint.label = tr("New Point");
700 }
701
702 command->addPoint(newPoint);
703 }
704
705 command->finish();
706 return true;
707 }
708
709 QString
710 ImageLayer::toXmlString(QString indent, QString extraAttributes) const
711 {
712 return Layer::toXmlString(indent, extraAttributes);
713 }
714
715 void
716 ImageLayer::setProperties(const QXmlAttributes &attributes)
717 {
718 }
719