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 (nullptr),
39  m_candidatePoint (nullptr)
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 = nullptr;
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 = nullptr;
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 = qFloor (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 = qFloor (posScreen.x() + xOffset);
161  int y = qFloor (posScreen.y() + yOffset);
162  int radius = qFloor (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  qFloor (posScreen.x()),
234  qFloor (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 = qFloor (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 (nullptr,
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...
Definition: MainWindow.cpp:834
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'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:335
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:723
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:744
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:519
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:300
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:702
QString selectedGraphCurve() const
Curve name that is currently selected in m_cmbCurve.
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.