Engauge Digitizer  2
DigitizeStatePointMatch.cpp
1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "CmdAddPointGraph.h"
8 #include "CmdMediator.h"
9 #include "ColorFilter.h"
10 #include "CurveStyles.h"
11 #include "DigitizeStateContext.h"
12 #include "DigitizeStatePointMatch.h"
13 #include "EngaugeAssert.h"
14 #include "EnumsToQt.h"
15 #include "GraphicsPoint.h"
16 #include "GraphicsScene.h"
17 #include "GraphicsView.h"
18 #include "Logger.h"
19 #include "MainWindow.h"
20 #include "OrdinalGenerator.h"
21 #include "PointMatchAlgorithm.h"
22 #include "PointStyle.h"
23 #include <QApplication>
24 #include <QCursor>
25 #include <QGraphicsEllipseItem>
26 #include <QGraphicsScene>
27 #include <QImage>
28 #include <qmath.h>
29 #include <QMessageBox>
30 #include <QPen>
31 #include <QSize>
32 #include "Transformation.h"
33 
34 const double Z_VALUE = 200.0;
35 
37  DigitizeStateAbstractBase (context),
38  m_outline (0),
39  m_candidatePoint (0)
40 {
41 }
42 
43 DigitizeStatePointMatch::~DigitizeStatePointMatch ()
44 {
45 }
46 
48 {
50 }
51 
53  DigitizeState /* previousState */)
54 {
55  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::begin";
56 
57  setCursor(cmdMediator);
58  context().setDragMode(QGraphicsView::NoDrag);
60 
61  // Add outline that will move with the cursor
62  m_outline = new QGraphicsEllipseItem ();
63  context().mainWindow().scene().addItem (m_outline);
64  m_outline->setPen (QPen (Qt::black));
65  m_outline->setVisible (true);
66  m_outline->setZValue (Z_VALUE);
67 }
68 
70  const QSize &viewSize) const
71 {
72  return canPasteProtected (transformation,
73  viewSize);
74 }
75 
76 void DigitizeStatePointMatch::createPermanentPoint (CmdMediator *cmdMediator,
77  const QPointF &posScreen)
78 {
79  // Create command to add point
80  OrdinalGenerator ordinalGenerator;
81  Document &document = cmdMediator->document ();
82  const Transformation &transformation = context ().mainWindow ().transformation();
83  QUndoCommand *cmd = new CmdAddPointGraph (context ().mainWindow(),
84  document,
85  context ().mainWindow().selectedGraphCurve(),
86  posScreen,
87  ordinalGenerator.generateCurvePointOrdinal(document,
88  transformation,
89  posScreen,
90  activeCurve ()));
91  context().appendNewCmd(cmdMediator,
92  cmd);
93 
94 }
95 
96 void DigitizeStatePointMatch::createTemporaryPoint (CmdMediator *cmdMediator,
97  const QPoint &posScreen)
98 {
99  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::createTemporaryPoint";
100 
101  GeometryWindow *NULL_GEOMETRY_WINDOW = 0;
102 
103  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
104 
105  // Get point style for current graph, and then override with candidate color
106  const CurveStyles &curveStyles = cmdMediator->document().modelCurveStyles();
107  PointStyle pointStyle = curveStyles.pointStyle (activeCurve());
108  pointStyle.setPaletteColor (modelPointMatch.paletteColorCandidate());
109 
110  // Temporary point that user can see while DlgEditPoint is active
112  pointStyle,
113  posScreen,
114  NULL_GEOMETRY_WINDOW);
115 
116  context().mainWindow().scene().removeTemporaryPointIfExists(); // Only one temporary point at a time is allowed
117 
119  point);
120  m_posCandidatePoint = posScreen;
121 }
122 
123 QCursor DigitizeStatePointMatch::cursor(CmdMediator * /* cmdMediator */) const
124 {
125  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::cursor";
126 
127  return QCursor (Qt::ArrowCursor);
128 }
129 
131 {
132  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::end";
133 
134  // Remove candidate point which may or may not exist at this point
136 
137  // Remove outline before leaving state
138  ENGAUGE_CHECK_PTR (m_outline);
139  context().mainWindow().scene().removeItem (m_outline);
140  m_outline = 0;
141 }
142 
143 QList<PointMatchPixel> DigitizeStatePointMatch::extractSamplePointPixels (const QImage &img,
144  const DocumentModelPointMatch &modelPointMatch,
145  const QPointF &posScreen) const
146 {
147  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::extractSamplePointPixels";
148 
149  // All points inside modelPointMatch.maxPointSize() are collected, whether or not they
150  // are on or off. Originally only the on points were collected, but obvious mismatches
151  // were happening (example, 3x3 point would appear to be found in several places inside 8x32 rectangle)
152  QList<PointMatchPixel> samplePointPixels;
153 
154  int radiusMax = modelPointMatch.maxPointSize() / 2;
155 
156  ColorFilter colorFilter;
157  for (int xOffset = -radiusMax; xOffset <= radiusMax; xOffset++) {
158  for (int yOffset = -radiusMax; yOffset <= radiusMax; yOffset++) {
159 
160  int x = posScreen.x() + xOffset;
161  int y = posScreen.y() + yOffset;
162  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
163 
164  if (radius <= radiusMax) {
165 
166  bool pixelIsOn = colorFilter.pixelFilteredIsOn (img,
167  x,
168  y);
169 
170  PointMatchPixel point (xOffset,
171  yOffset,
172  pixelIsOn);
173 
174  samplePointPixels.push_back (point);
175  }
176  }
177  }
178 
179  return samplePointPixels;
180 }
181 
183  const QString &pointIdentifier)
184 {
185  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleContextMenuEventAxis "
186  << " point=" << pointIdentifier.toLatin1 ().data ();
187 }
188 
190  const QStringList &pointIdentifiers)
191 {
192  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch ::handleContextMenuEventGraph "
193  << "points=" << pointIdentifiers.join(",").toLatin1 ().data ();
194 }
195 
197 {
198  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleCurveChange";
199 }
200 
202  Qt::Key key,
203  bool /* atLeastOneSelectedItem */)
204 {
205  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleKeyPress"
206  << " key=" << QKeySequence (key).toString ().toLatin1 ().data ();
207 
208  // The selected key button has to be compatible with GraphicsView::keyPressEvent
209  if (key == Qt::Key_Right) {
210 
211  promoteCandidatePointToPermanentPoint (cmdMediator); // This removes the current temporary point
212 
213  popCandidatePoint(cmdMediator); // This creates a new temporary point
214 
215  }
216 }
217 
219  QPointF posScreen)
220 {
221 // LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseMove";
222 
223  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
224 
225  m_outline->setRect (posScreen.x() - modelPointMatch.maxPointSize() / 2.0,
226  posScreen.y() - modelPointMatch.maxPointSize() / 2.0,
227  modelPointMatch.maxPointSize(),
228  modelPointMatch.maxPointSize());
229 
230  const QImage &img = context().mainWindow().imageFiltered();
231  int radiusLimit = cmdMediator->document().modelGeneral().cursorSize();
232  bool pixelShouldBeOn = pixelIsOnInImage (img,
233  posScreen.x(),
234  posScreen.y(),
235  radiusLimit);
236 
237  QColor penColorIs = m_outline->pen().color();
238  bool pixelIsOn = (penColorIs.red () != penColorIs.green()); // Considered on if not gray scale
239  if (pixelShouldBeOn != pixelIsOn) {
240  QColor penColorShouldBe (pixelShouldBeOn ? Qt::green : Qt::black);
241  m_outline->setPen (QPen (penColorShouldBe));
242  }
243 }
244 
246  QPointF /* posScreen */)
247 {
248  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMousePress";
249 }
250 
252  QPointF posScreen)
253 {
254  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::handleMouseRelease";
255 
256  createPermanentPoint (cmdMediator,
257  posScreen);
258 
259  findPointsAndShowFirstCandidate (cmdMediator,
260  posScreen);
261 }
262 
263 void DigitizeStatePointMatch::findPointsAndShowFirstCandidate (CmdMediator *cmdMediator,
264  const QPointF &posScreen)
265 {
266  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::findPointsAndShowFirstCandidate";
267 
268  const DocumentModelPointMatch &modelPointMatch = cmdMediator->document().modelPointMatch();
269  const QImage &img = context().mainWindow().imageFiltered();
270 
271  QList<PointMatchPixel> samplePointPixels = extractSamplePointPixels (img,
272  modelPointMatch,
273  posScreen);
274 
275  QString curveName = activeCurve();
276  const Document &doc = cmdMediator->document();
277  const Curve *curve = doc.curveForCurveName (curveName);
278 
279  // The point match algorithm takes a few seconds, so set the cursor so user knows we are processing
280  QApplication::setOverrideCursor(Qt::WaitCursor);
281 
282  PointMatchAlgorithm pointMatchAlgorithm (context().isGnuplot());
283  m_candidatePoints = pointMatchAlgorithm.findPoints (samplePointPixels,
284  img,
285  modelPointMatch,
286  curve->points());
287 
288  QApplication::restoreOverrideCursor(); // Heavy duty processing has finished
289  context().mainWindow().showTemporaryMessage ("Right arrow adds next matched point");
290 
291  popCandidatePoint (cmdMediator);
292 }
293 
294 bool DigitizeStatePointMatch::pixelIsOnInImage (const QImage &img,
295  int x,
296  int y,
297  int radiusLimit) const
298 {
299  ColorFilter filter;
300 
301  // Examine all nearby pixels
302  bool pixelShouldBeOn = false;
303  for (int xOffset = -radiusLimit; xOffset <= radiusLimit; xOffset++) {
304  for (int yOffset = -radiusLimit; yOffset <= radiusLimit; yOffset++) {
305 
306  int radius = qSqrt (xOffset * xOffset + yOffset * yOffset);
307 
308  if (radius <= radiusLimit) {
309 
310  int xNearby = x + xOffset;
311  int yNearby = y + yOffset;
312 
313  if ((0 <= xNearby) &&
314  (0 <= yNearby) &&
315  (xNearby < img.width()) &&
316  (yNearby < img.height())) {
317 
318  if (filter.pixelFilteredIsOn (img,
319  xNearby,
320  yNearby)) {
321 
322  pixelShouldBeOn = true;
323  break;
324  }
325  }
326  }
327  }
328  }
329 
330  return pixelShouldBeOn;
331 }
332 
333 void DigitizeStatePointMatch::popCandidatePoint (CmdMediator *cmdMediator)
334 {
335  LOG4CPP_DEBUG_S ((*mainCat)) << "DigitizeStatePointMatch::popCandidatePoint";
336 
337  if (m_candidatePoints.count() > 0) {
338 
339  // Pop next point from list onto screen
340  QPoint posScreen = m_candidatePoints.first();
341  m_candidatePoints.pop_front ();
342 
343  createTemporaryPoint(cmdMediator,
344  posScreen);
345 
346  } else {
347 
348  // No more points. Inform user
349  QMessageBox::information (0,
350  QObject::tr ("Point Match"),
351  QObject::tr ("There are no more matching points"));
352 
353  }
354 }
355 
356 void DigitizeStatePointMatch::promoteCandidatePointToPermanentPoint(CmdMediator *cmdMediator)
357 {
358  createPermanentPoint (cmdMediator,
359  m_posCandidatePoint);
360 }
361 
363 {
364  return "DigitizeStatePointMatch";
365 }
366 
368 {
369  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateAfterPointAddition";
370 }
371 
373  const DocumentModelDigitizeCurve & /*modelDigitizeCurve */)
374 {
375  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelDigitizeCurve";
376 }
377 
379 {
380  LOG4CPP_INFO_S ((*mainCat)) << "DigitizeStatePointMatch::updateModelSegments";
381 }
QImage imageFiltered() const
Background image that has been filtered for the current curve. This asserts if a curve-specific image...
virtual void handleContextMenuEventGraph(CmdMediator *cmdMediator, const QStringList &pointIdentifiers)
Handle a right click, on a graph point, that was intercepted earlier.
Model for DlgSettingsPointMatch and CmdSettingsPointMatch.
virtual void updateModelSegments(const DocumentModelSegments &modelSegments)
Update the segments given the new settings.
void setDragMode(QGraphicsView::DragMode dragMode)
Set QGraphicsView drag mode (in m_view). Called from DigitizeStateAbstractBase subclasses.
Model for DlgSettingsCurveProperties and CmdSettingsCurveProperties.
Definition: CurveStyles.h:22
bool canPasteProtected(const Transformation &transformation, const QSize &viewSize) const
Protected version of canPaste method. Some, but not all, leaf classes use this method.
Single on or off pixel out of the pixels that define the point match mode&#39;s candidate point...
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:332
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:717
const PointStyle pointStyle(const QString &curveName) const
Get method for copying one point style. Cannot return just a reference or else there is a warning abo...
virtual void updateAfterPointAddition()
Update graphics attributes after possible new points. This is useful for highlight opacity...
Window that displays the geometry information, as a table, for the current curve. ...
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:72
Class for filtering image to remove unimportant information.
Definition: ColorFilter.h:20
DocumentModelPointMatch modelPointMatch() const
Get method for DocumentModelPointMatch.
Definition: Document.cpp:738
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
DigitizeStateContext & context()
Reference to the DigitizeStateContext that contains all the DigitizeStateAbstractBase subclasses...
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:451
MainWindow & mainWindow()
Reference to the MainWindow, without const.
static QString temporaryPointIdentifier()
Point identifier for temporary point that is used by DigitzeStateAxis.
Definition: Point.cpp:507
virtual void handleMousePress(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse press that was intercepted earlier.
Transformation transformation() const
Return read-only copy of transformation.
void showTemporaryMessage(const QString &temporaryMessage)
Show temporary message in status bar.
ColorPalette paletteColorCandidate() const
Get method for candidate color.
virtual void handleMouseMove(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse move. This is part of an experiment to see if augmenting the cursor in Point Match mod...
GraphicsPoint * createPoint(const QString &identifier, const PointStyle &pointStyle, const QPointF &posScreen, GeometryWindow *geometryWindow)
Create one QGraphicsItem-based object that represents one Point. It is NOT added to m_graphicsLinesFo...
Model for DlgSettingsDigitizeCurve and CmdSettingsDigitizeCurve.
virtual void handleKeyPress(CmdMediator *cmdMediator, Qt::Key key, bool atLeastOneSelectedItem)
Handle a key press that was intercepted earlier.
Affine transformation between screen and graph coordinates, based on digitized axis points...
Details for a specific Point.
Definition: PointStyle.h:20
GraphicsScene & scene()
Scene container for the QImage and QGraphicsItems.
virtual QString state() const
State name for debugging.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
int cursorSize() const
Get method for effective cursor size.
Container for all DigitizeStateAbstractBase subclasses. This functions as the context class in a stan...
void appendNewCmd(CmdMediator *cmdMediator, QUndoCommand *cmd)
Append just-created QUndoCommand to command stack. This is called from DigitizeStateAbstractBase subc...
virtual void handleMouseRelease(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse release that was intercepted earlier.
DigitizeStatePointMatch(DigitizeStateContext &context)
Single constructor.
Command for adding one graph point.
virtual void handleContextMenuEventAxis(CmdMediator *cmdMediator, const QString &pointIdentifier)
Handle a right click, on an axis point, that was intercepted earlier.
virtual void handleCurveChange(CmdMediator *cmdMediator)
Handle the selection of a new curve. At a minimum, DigitizeStateSegment will generate a new set of Se...
void setPaletteColor(ColorPalette paletteColor)
Set method for point color.
Definition: PointStyle.cpp:277
Storage of one imported image and the data attached to that image.
Definition: Document.h:41
Container for one set of digitized Points.
Definition: Curve.h:33
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:43
Algorithm returning a list of points that match the specified point.
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
virtual void begin(CmdMediator *cmdMediator, DigitizeState previousState)
Method that is called at the exact moment a state is entered.
virtual void end()
Method that is called at the exact moment a state is exited. Typically called just before begin for t...
Utility class for generating ordinal numbers.
Command queue stack.
Definition: CmdMediator.h:23
void addTemporaryPoint(const QString &identifier, GraphicsPoint *point)
Add one temporary point to m_graphicsLinesForCurves. Non-temporary points are handled by the updateLi...
Model for DlgSettingsSegments and CmdSettingsSegments.
Base class for all digitizing states. This serves as an interface to DigitizeStateContext.
virtual void updateModelDigitizeCurve(CmdMediator *cmdMediator, const DocumentModelDigitizeCurve &modelDigitizeCurve)
Update the digitize curve settings.
CurveStyles modelCurveStyles() const
Get method for CurveStyles.
Definition: Document.cpp:696
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
QList< QPoint > findPoints(const QList< PointMatchPixel > &samplePointPixels, const QImage &imageProcessed, const DocumentModelPointMatch &modelPointMatch, const Points &pointsExisting)
Find points that match the specified sample point pixels. They are sorted by best-to-worst match...
double generateCurvePointOrdinal(const Document &document, const Transformation &transformation, const QPointF &posScreen, const QString &curveName)
Select ordinal so new point curve passes smoothly through existing points.
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.
virtual bool canPaste(const Transformation &transformation, const QSize &viewSize) const
Return true if there is good data in the clipboard for pasting, and that is compatible with the curre...
double maxPointSize() const
Get method for max point size.