changeset 283:86a112b5b319

* Make it possible to "measure" a feature on the spectrogram by double- clicking in measure mode * Make shift-click-drag (for zoom to region) work in measure mode as well as navigate mode. It would be nice to be able to shift-doubleclick to zoom on a feature directly using a combination of these last two features, but that isn't possible yet. * Make Del delete the measurement under the mouse pointer.
author Chris Cannam
date Thu, 05 Jul 2007 15:36:37 +0000
parents 4edaff85875d
children 1284955856ab
files layer/ImageRegionFinder.cpp layer/ImageRegionFinder.h layer/Layer.cpp layer/Layer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h layer/layer.pro view/Pane.cpp view/View.cpp
diffstat 9 files changed, 297 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ImageRegionFinder.cpp	Thu Jul 05 15:36:37 2007 +0000
@@ -0,0 +1,125 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2007 QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ImageRegionFinder.h"
+
+#include <QImage>
+#include <cmath>
+#include <stack>
+#include <iostream>
+
+ImageRegionFinder::ImageRegionFinder()
+{
+}
+
+ImageRegionFinder::~ImageRegionFinder()
+{
+}
+
+QRect
+ImageRegionFinder::findRegionExtents(QImage *image, QPoint origin) const
+{
+    int w = image->width(), h = image->height();
+
+    QImage visited(w, h, QImage::Format_Mono);
+    visited.fill(0);
+
+    std::stack<QPoint> s;
+    s.push(origin);
+
+    int xmin = origin.x();
+    int xmax = xmin;
+    int ymin = origin.y();
+    int ymax = ymin;
+
+    QRgb opix = image->pixel(origin);
+
+    while (!s.empty()) {
+
+        QPoint p = s.top();
+        s.pop();
+
+        visited.setPixel(p, 1);
+
+        int x = p.x(), y = p.y();
+
+        if (x < xmin) xmin = x;
+        if (x > xmax) xmax = x;
+
+        if (y < ymin) ymin = y;
+        if (y > ymax) ymax = y;
+
+        std::stack<QPoint> neighbours;
+
+        int similarNeighbourCount = 0;
+
+        for (int dx = -1; dx <= 1; ++dx) {
+            for (int dy = -1; dy <= 1; ++dy) {
+
+                if ((dx != 0 && dy != 0) ||
+                    (dx == 0 && dy == 0)) 
+                    continue;
+
+                if (x + dx < 0 || x + dx >= w ||
+                    y + dy < 0 || y + dy >= h)
+                    continue;
+
+                if (visited.pixelIndex(x + dx, y + dy) != 0)
+                    continue;
+
+                if (!similar(opix, image->pixel(x + dx, y + dy))) 
+                    continue;
+
+                neighbours.push(QPoint(x + dx, y + dy));
+                ++similarNeighbourCount;
+            }
+        }
+
+        if (similarNeighbourCount >= 2) {
+            while (!neighbours.empty()) {
+                s.push(neighbours.top());
+                neighbours.pop();
+            }
+        }
+    }
+
+    return QRect(xmin, ymin, xmax - xmin, ymax - ymin);
+}
+
+bool
+ImageRegionFinder::similar(QRgb a, QRgb b) const
+{
+    if (b == qRgb(0, 0, 0) || b == qRgb(255, 255, 255)) {
+        // black and white are boundary cases, don't compare similar
+        // to anything -- not even themselves
+        return false;
+    }
+
+    float ar = float(qRed(a) / 255.f);
+    float ag = float(qGreen(a) / 255.f);
+    float ab = float(qBlue(a) / 255.f);
+    float amag = sqrtf(ar * ar + ag * ag + ab * ab);
+    float thresh = amag / 2;
+
+    float dr = float(qRed(a) - qRed(b)) / 255.f;
+    float dg = float(qGreen(a) - qGreen(b)) / 255.f;
+    float db = float(qBlue(a) - qBlue(b)) / 255.f;
+    float dist = sqrtf(dr * dr + dg * dg + db * db);
+
+//    std::cerr << "thresh=" << thresh << ", dist=" << dist << std::endl;
+
+    return (dist < thresh);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ImageRegionFinder.h	Thu Jul 05 15:36:37 2007 +0000
@@ -0,0 +1,37 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2007 QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _IMAGE_REGION_FINDER_H_
+#define _IMAGE_REGION_FINDER_H_
+
+#include <QColor>
+#include <QRect>
+
+class QImage;
+
+class ImageRegionFinder
+{
+public:
+    ImageRegionFinder();
+    virtual ~ImageRegionFinder();
+
+    QRect findRegionExtents(QImage *image, QPoint origin) const;
+
+protected:
+    bool similar(QRgb a, QRgb b) const;
+};
+
+#endif
+
--- a/layer/Layer.cpp	Thu Jul 05 11:07:01 2007 +0000
+++ b/layer/Layer.cpp	Thu Jul 05 15:36:37 2007 +0000
@@ -29,7 +29,8 @@
 #include <cmath>
 
 Layer::Layer() :
-    m_haveDraggingRect(false)
+    m_haveDraggingRect(false),
+    m_haveCurrentMeasureRect(false)
 {
 }
 
@@ -227,18 +228,29 @@
     m_layer->deleteMeasureRectFromSet(m_rect);
 }
 
+QString
+Layer::DeleteMeasurementRectCommand::getName() const
+{
+    return tr("Delete Measurement");
+}
+
+void
+Layer::DeleteMeasurementRectCommand::execute()
+{
+    m_layer->deleteMeasureRectFromSet(m_rect);
+}
+
+void
+Layer::DeleteMeasurementRectCommand::unexecute()
+{
+    m_layer->addMeasureRectToSet(m_rect);
+}
+
 void
 Layer::measureStart(View *v, QMouseEvent *e)
 {
-    m_draggingRect.pixrect = QRect(e->x(), e->y(), 0, 0);
-    if (hasTimeXAxis()) {
-        m_draggingRect.haveFrames = true;
-        m_draggingRect.startFrame = v->getFrameForX(e->x());
-        m_draggingRect.endFrame = m_draggingRect.startFrame;
-    } else {
-        m_draggingRect.haveFrames = false;
-    }
-    setMeasureRectYCoord(v, m_draggingRect, true, e->y());
+    setMeasureRectFromPixrect(v, m_draggingRect,
+                              QRect(e->x(), e->y(), 0, 0));
     m_haveDraggingRect = true;
 }
 
@@ -247,16 +259,11 @@
 {
     if (!m_haveDraggingRect) return;
 
-    m_draggingRect.pixrect = QRect(m_draggingRect.pixrect.x(),
-                                   m_draggingRect.pixrect.y(),
-                                   e->x() - m_draggingRect.pixrect.x(),
-                                   e->y() - m_draggingRect.pixrect.y());
-
-    setMeasureRectYCoord(v, m_draggingRect, false, e->y());
-    
-    if (hasTimeXAxis()) {
-        m_draggingRect.endFrame = v->getFrameForX(e->x());
-    }
+    setMeasureRectFromPixrect(v, m_draggingRect,
+                              QRect(m_draggingRect.pixrect.x(),
+                                    m_draggingRect.pixrect.y(),
+                                    e->x() - m_draggingRect.pixrect.x(),
+                                    e->y() - m_draggingRect.pixrect.y()));
 }
 
 void
@@ -264,9 +271,11 @@
 {
     if (!m_haveDraggingRect) return;
     measureDrag(v, e);
-    
-    CommandHistory::getInstance()->addCommand
-        (new AddMeasurementRectCommand(this, m_draggingRect));
+
+    if (!m_draggingRect.pixrect.isNull()) {
+        CommandHistory::getInstance()->addCommand
+            (new AddMeasurementRectCommand(this, m_draggingRect));
+    }
 
     m_haveDraggingRect = false;
 }
@@ -274,7 +283,21 @@
 void
 Layer::measureDoubleClick(View *v, QMouseEvent *e)
 {
-    // nothing
+    // nothing, in the base class
+}
+
+void
+Layer::deleteCurrentMeasureRect()
+{
+    if (!m_haveCurrentMeasureRect) return;
+    
+    MeasureRectSet::const_iterator focusRectItr =
+        findFocusedMeasureRect(m_currentMeasureRectPoint);
+
+    if (focusRectItr == m_measureRects.end()) return;
+
+    CommandHistory::getInstance()->addCommand
+        (new DeleteMeasurementRectCommand(this, *focusRectItr));
 }
 
 void
@@ -294,9 +317,18 @@
         focusRectItr = findFocusedMeasureRect(focusPoint);
     }
 
+    m_haveCurrentMeasureRect = false;
+
     for (MeasureRectSet::const_iterator i = m_measureRects.begin(); 
          i != m_measureRects.end(); ++i) {
-        paintMeasurementRect(v, paint, *i, i == focusRectItr);
+
+        bool focused = (i == focusRectItr);
+        paintMeasurementRect(v, paint, *i, focused);
+
+        if (focused) {
+            m_haveCurrentMeasureRect = true;
+            m_currentMeasureRectPoint = focusPoint;
+        }
     }
 }
 
@@ -369,6 +401,19 @@
     }
 }
 
+void
+Layer::setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const
+{
+    r.pixrect = pixrect;
+    r.haveFrames = hasTimeXAxis();
+    if (r.haveFrames) {
+        r.startFrame = v->getFrameForX(pixrect.x());
+        r.endFrame = v->getFrameForX(pixrect.x() + pixrect.width());
+    }
+    setMeasureRectYCoord(v, r, true, pixrect.y());
+    setMeasureRectYCoord(v, r, false, pixrect.y() + pixrect.height());
+}
+
 Layer::MeasureRectSet::const_iterator
 Layer::findFocusedMeasureRect(QPoint focusPoint) const
 {
--- a/layer/Layer.h	Thu Jul 05 11:07:01 2007 +0000
+++ b/layer/Layer.h	Thu Jul 05 15:36:37 2007 +0000
@@ -1,4 +1,3 @@
-
 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
 
 /*
@@ -172,6 +171,11 @@
     virtual void measureEnd(View *, QMouseEvent *);
     virtual void measureDoubleClick(View *, QMouseEvent *);
 
+    virtual bool haveCurrentMeasureRect() const {
+        return m_haveCurrentMeasureRect;
+    }
+    virtual void deleteCurrentMeasureRect(); // using a command
+
     /**
      * Open an editor on the item under the mouse (e.g. on
      * double-click).  If there is no item or editing is not
@@ -461,6 +465,21 @@
         MeasureRect m_rect;
     };
 
+    class DeleteMeasurementRectCommand : public Command
+    {
+    public:
+        DeleteMeasurementRectCommand(Layer *layer, MeasureRect rect) :
+            m_layer(layer), m_rect(rect) { }
+
+        virtual QString getName() const;
+        virtual void execute();
+        virtual void unexecute();
+
+    private:
+        Layer *m_layer;
+        MeasureRect m_rect;
+    };
+
     void addMeasureRectToSet(const MeasureRect &r) {
         m_measureRects.insert(r);
         emit layerMeasurementRectsChanged();
@@ -475,6 +494,8 @@
     MeasureRectSet m_measureRects;
     MeasureRect m_draggingRect;
     bool m_haveDraggingRect;
+    mutable bool m_haveCurrentMeasureRect;
+    mutable QPoint m_currentMeasureRectPoint;
    
     // Note that pixrects are only correct for a single view.
     // So we should update them at the start of the paint procedure
@@ -483,6 +504,7 @@
 
     virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
     virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
+    virtual void setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const;
 
     // This assumes updateMeasurementPixrects has been called
     MeasureRectSet::const_iterator findFocusedMeasureRect(QPoint) const;
--- a/layer/SpectrogramLayer.cpp	Thu Jul 05 11:07:01 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Thu Jul 05 15:36:37 2007 +0000
@@ -23,7 +23,9 @@
 #include "base/Preferences.h"
 #include "base/RangeMapper.h"
 #include "base/LogRange.h"
+#include "base/CommandHistory.h"
 #include "ColourMapper.h"
+#include "ImageRegionFinder.h"
 
 #include <QPainter>
 #include <QImage>
@@ -32,6 +34,7 @@
 #include <QTimer>
 #include <QApplication>
 #include <QMessageBox>
+#include <QMouseEvent>
 
 #include <iostream>
 
@@ -2458,6 +2461,26 @@
     return true;
 } 
 
+void
+SpectrogramLayer::measureDoubleClick(View *v, QMouseEvent *e)
+{
+    PixmapCache &cache = m_pixmapCaches[v];
+
+    std::cerr << "cache width: " << cache.pixmap.width() << ", height: "
+              << cache.pixmap.height() << std::endl;
+
+    QImage image = cache.pixmap.toImage();
+
+    ImageRegionFinder finder;
+    QRect rect = finder.findRegionExtents(&image, e->pos());
+    if (rect.isValid()) {
+        MeasureRect mr;
+        setMeasureRectFromPixrect(v, mr, rect);
+        CommandHistory::getInstance()->addCommand
+            (new AddMeasurementRectCommand(this, mr));
+    }
+}
+
 bool
 SpectrogramLayer::getCrosshairExtents(View *v, QPainter &paint,
                                       QPoint cursorPos,
@@ -2502,6 +2525,9 @@
                                   QPoint cursorPos) const
 {
     paint.save();
+
+    int sw = getVerticalScaleWidth(v, paint);
+
     QFont fn = paint.font();
     if (fn.pointSize() > 8) {
         fn.setPointSize(fn.pointSize() - 1);
@@ -2514,7 +2540,6 @@
     
     float fundamental = getFrequencyForY(v, cursorPos.y());
 
-    int sw = getVerticalScaleWidth(v, paint);
     v->drawVisibleText(paint,
                        sw + 2,
                        cursorPos.y() - 2,
--- a/layer/SpectrogramLayer.h	Thu Jul 05 11:07:01 2007 +0000
+++ b/layer/SpectrogramLayer.h	Thu Jul 05 15:36:37 2007 +0000
@@ -71,6 +71,8 @@
 				    size_t &resolution,
 				    SnapType snap) const;
 
+    virtual void measureDoubleClick(View *, QMouseEvent *);
+
     virtual bool hasLightBackground() const;
 
     void setModel(const DenseTimeValueModel *model);
--- a/layer/layer.pro	Thu Jul 05 11:07:01 2007 +0000
+++ b/layer/layer.pro	Thu Jul 05 15:36:37 2007 +0000
@@ -16,6 +16,7 @@
 # Input
 HEADERS += Colour3DPlotLayer.h \
            ColourMapper.h \
+           ImageRegionFinder.h \
            Layer.h \
            LayerFactory.h \
            NoteLayer.h \
@@ -31,6 +32,7 @@
            WaveformLayer.h
 SOURCES += Colour3DPlotLayer.cpp \
            ColourMapper.cpp \
+           ImageRegionFinder.cpp \
            Layer.cpp \
            LayerFactory.cpp \
            NoteLayer.cpp \
--- a/view/Pane.cpp	Thu Jul 05 11:07:01 2007 +0000
+++ b/view/Pane.cpp	Thu Jul 05 15:36:37 2007 +0000
@@ -446,7 +446,7 @@
     }
 
     if (m_shiftPressed && m_clickedInRange &&
-        toolMode == ViewManager::NavigateMode) {
+        (toolMode == ViewManager::NavigateMode || m_navigating)) {
 
         //!!! be nice if this looked a bit more in keeping with the
         //selection block
@@ -1045,6 +1045,8 @@
                         tr("Click left button and drag to select region; drag region edge to resize"));
     kr.registerShortcut(tr("Multi Select"), tr("Ctrl+Left"), 
                         tr("Ctrl-click left button and drag to select an additional region"));
+    kr.registerShortcut(tr("Fine Select"), tr("Shift+Left"), 
+                        tr("Shift-click left button and drag to select without snapping to items or grid"));
     
     kr.setCategory(tr("Edit Tool Mouse Actions"));
     kr.registerShortcut(tr("Move"), tr("Left"), 
@@ -1061,6 +1063,8 @@
                         tr("Click left button and drag to measure a rectangular area"));
     kr.registerShortcut(tr("Measure Item"), tr("Double-Click Left"), 
                         tr("Click left button and drag to measure extents of an item or shape"));
+    kr.registerShortcut(tr("Zoom to Area"), tr("Shift+Left"), 
+                        tr("Shift-click left button and drag to zoom to a rectangular area"));
 }
 
 void
@@ -1086,7 +1090,10 @@
 
     m_navigating = false;
 
-    if (mode == ViewManager::NavigateMode || (e->buttons() & Qt::MidButton)) {
+    if (mode == ViewManager::NavigateMode ||
+        (e->buttons() & Qt::MidButton) ||
+        (mode == ViewManager::MeasureMode &&
+         (e->buttons() & Qt::LeftButton) && m_shiftPressed)) {
 
 	if (mode != ViewManager::NavigateMode) {
 	    setCursor(Qt::PointingHandCursor);
--- a/view/View.cpp	Thu Jul 05 11:07:01 2007 +0000
+++ b/view/View.cpp	Thu Jul 05 15:36:37 2007 +0000
@@ -1737,7 +1737,7 @@
         }
     }
         
-    if (b0 && b1 && u0 == u1) {
+    if (b0 && b1 && v1 != v0 && u0 == u1) {
         dxs = QString("(%1 %2)").arg(fabs(v1 - v0)).arg(u1);
         dw = paint.fontMetrics().width(dxs);
     }
@@ -1771,7 +1771,8 @@
     QString du;
 
     if ((bd = topLayer->getYScaleDifference(this, r.y(), r.y() + r.height(),
-                                            dy, du))) {
+                                            dy, du)) &&
+        dy != 0) {
         if (du != "") {
             dys = QString("(%1 %2)").arg(dy).arg(du);
         } else {