# HG changeset patch # User Chris Cannam # Date 1183649797 0 # Node ID 86a112b5b3195a6eee1708ab7243000d867627f4 # Parent 4edaff85875d4fed29ff34cc7ef7cfbfd4c36862 * 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. diff -r 4edaff85875d -r 86a112b5b319 layer/ImageRegionFinder.cpp --- /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 +#include +#include +#include + +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 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 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); +} + diff -r 4edaff85875d -r 86a112b5b319 layer/ImageRegionFinder.h --- /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 +#include + +class QImage; + +class ImageRegionFinder +{ +public: + ImageRegionFinder(); + virtual ~ImageRegionFinder(); + + QRect findRegionExtents(QImage *image, QPoint origin) const; + +protected: + bool similar(QRgb a, QRgb b) const; +}; + +#endif + diff -r 4edaff85875d -r 86a112b5b319 layer/Layer.cpp --- 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 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 { diff -r 4edaff85875d -r 86a112b5b319 layer/Layer.h --- 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; diff -r 4edaff85875d -r 86a112b5b319 layer/SpectrogramLayer.cpp --- 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 #include @@ -32,6 +34,7 @@ #include #include #include +#include #include @@ -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, diff -r 4edaff85875d -r 86a112b5b319 layer/SpectrogramLayer.h --- 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); diff -r 4edaff85875d -r 86a112b5b319 layer/layer.pro --- 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 \ diff -r 4edaff85875d -r 86a112b5b319 view/Pane.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); diff -r 4edaff85875d -r 86a112b5b319 view/View.cpp --- 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 {