Engauge Digitizer  2
 All Classes Functions Variables Typedefs Enumerations Friends Pages
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 }
double maxPointSize() const
Get method for max point size.
virtual void handleContextMenuEventGraph(CmdMediator *cmdMediator, const QStringList &pointIdentifiers)
Handle a right click, on a graph point, that was intercepted earlier.
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...
Model for DlgSettingsPointMatch and CmdSettingsPointMatch.
virtual void updateModelSegments(const DocumentModelSegments &modelSegments)
Update the segments given the new settings.
DocumentModelPointMatch modelPointMatch() const
Get method for DocumentModelPointMatch.
Definition: Document.cpp:740
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:451
Transformation transformation() const
Return read-only copy of transformation.
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
Single on or off pixel out of the pixels that define the point match mode&#39;s candidate point...
void updateViewsOfSettings(const QString &activeCurve)
Update curve-specific view of settings. Private version gets active curve name from DigitizeStateCont...
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
int cursorSize() const
Get method for effective cursor size.
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...
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
DigitizeStateContext & context()
Reference to the DigitizeStateContext that contains all the DigitizeStateAbstractBase subclasses...
MainWindow & mainWindow()
Reference to the MainWindow, without const.
static QString temporaryPointIdentifier()
Point identifier for temporary point that is used by DigitzeStateAxis.
Definition: Point.cpp:519
virtual void handleMousePress(CmdMediator *cmdMediator, QPointF posScreen)
Handle a mouse press that was intercepted earlier.
void showTemporaryMessage(const QString &temporaryMessage)
Show temporary message in status bar.
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...
bool canPasteProtected(const Transformation &transformation, const QSize &viewSize) const
Protected version of canPaste method. Some, but not all, leaf classes use this method.
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.
void setCursor(CmdMediator *cmdMediator)
Update the cursor according to the current state.
CurveStyles modelCurveStyles() const
Get method for CurveStyles.
Definition: Document.cpp:698
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.
virtual QCursor cursor(CmdMediator *cmdMediator) const
Returns the state-specific cursor shape.
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...
virtual QString state() const
State name for debugging.
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
QImage imageFiltered() const
Background image that has been filtered for the current curve. This asserts if a curve-specific image...
Definition: MainWindow.cpp:798
Graphics item for drawing a circular or polygonal Point.
Definition: GraphicsPoint.h:43
Algorithm returning a list of points that match the specified point.
ColorPalette paletteColorCandidate() const
Get method for candidate color.
void removeTemporaryPointIfExists()
Remove temporary point if it exists.
bool pixelFilteredIsOn(const QImage &image, int x, int y) const
Return true if specified filtered pixel is on.
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
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:331
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.
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.
DocumentModelGeneral modelGeneral() const
Get method for DocumentModelGeneral.
Definition: Document.cpp:719
virtual QString activeCurve() const
Name of the active Curve. This can include AXIS_CURVE_NAME.