Engauge Digitizer  2
Segment.cpp
1 #include "DocumentModelSegments.h"
2 #include "EngaugeAssert.h"
3 #include <iostream>
4 #include "Logger.h"
5 #include "mmsubs.h"
6 #include <qdebug.h>
7 #include <QFile>
8 #include <QGraphicsScene>
9 #include <qmath.h>
10 #include <QTextStream>
11 #include "QtToString.h"
12 #include "Segment.h"
13 #include "SegmentLine.h"
14 
15 Segment::Segment(QGraphicsScene &scene,
16  int y,
17  bool isGnuplot) :
18  m_scene (scene),
19  m_yLast (y),
20  m_isGnuplot (isGnuplot)
21 {
22  LOG4CPP_INFO_S ((*mainCat)) << "Segment::Segment"
23  << " address=0x" << hex << (unsigned long) this;
24 }
25 
26 Segment::~Segment()
27 {
28  LOG4CPP_INFO_S ((*mainCat)) << "Segment::~Segment"
29  << " address=0x" << hex << (unsigned long) this;
30 
31  QList<SegmentLine*>::iterator itr;
32  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
33 
34  SegmentLine *segmentLine = *itr;
35  m_scene.removeItem (segmentLine);
36  }
37 }
38 
40  int y,
41  const DocumentModelSegments &modelSegments)
42 {
43  int xOld = x - 1;
44  int yOld = m_yLast;
45  int xNew = x;
46  int yNew = y;
47 
48  LOG4CPP_DEBUG_S ((*mainCat)) << "Segment::appendColumn"
49  << " segment=0x" << std::hex << (unsigned long) this << std::dec
50  << " adding ("
51  << xOld << "," << yOld << ") to ("
52  << xNew << "," << yNew << ")";
53 
54  SegmentLine* line = new SegmentLine(m_scene,
55  modelSegments,
56  this);
57  ENGAUGE_CHECK_PTR(line);
58  line->setLine(QLineF (xOld,
59  yOld,
60  xNew,
61  yNew));
62 
63  // Do not show this line or its segment. this is handled later
64 
65  m_lines.append(line);
66 
67  // Update total length using distance formula
68  m_length += qSqrt((1.0) * (1.0) + (y - m_yLast) * (y - m_yLast));
69 
70  m_yLast = y;
71 }
72 
73 void Segment::createAcceptablePoint(bool *pFirst,
74  QList<QPoint> *pList,
75  double *xPrev,
76  double *yPrev,
77  double x,
78  double y)
79 {
80  int iOld = (int) (*xPrev + 0.5);
81  int jOld = (int) (*yPrev + 0.5);
82  int i = (int) (x + 0.5);
83  int j = (int) (y + 0.5);
84 
85  if (*pFirst || (iOld != i) || (jOld != j)) {
86  *xPrev = x;
87  *yPrev = y;
88 
89  ENGAUGE_CHECK_PTR(pList);
90  pList->append(QPoint(i, j));
91  }
92 
93  *pFirst = false;
94 }
95 
96 void Segment::dumpToGnuplot (QTextStream &strDump,
97  int xInt,
98  int yInt,
99  const SegmentLine *lineOld,
100  const SegmentLine *lineNew) const
101 {
102  // Only show this dump spew when logging is opened up completely
103  if (mainCat->getPriority() == log4cpp::Priority::DEBUG) {
104 
105  // Show "before" and "after" line info. Note that the merged line starts with lineOld->line().p1()
106  // and ends with lineNew->line().p2()
107  QString label = QString ("Old: (%1,%2) to (%3,%4), New: (%5,%6) to (%7,%8)")
108  .arg (lineOld->line().x1())
109  .arg (lineOld->line().y1())
110  .arg (lineOld->line().x2())
111  .arg (lineOld->line().y2())
112  .arg (lineNew->line().x1())
113  .arg (lineNew->line().y1())
114  .arg (lineNew->line().x2())
115  .arg (lineNew->line().y2());
116 
117  strDump << "unset label\n";
118  strDump << "set label \"" << label << "\" at graph 0, graph 0.02\n";
119  strDump << "set grid xtics\n";
120  strDump << "set grid ytics\n";
121 
122  // Get the bounds
123  int rows = 0, cols = 0;
124  QList<SegmentLine*>::const_iterator itr;
125  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
126 
127  SegmentLine *line = *itr;
128  ENGAUGE_CHECK_PTR (line);
129 
130  int x1 = line->line().x1();
131  int y1 = line->line().y1();
132  int x2 = line->line().x2();
133  int y2 = line->line().y2();
134 
135  rows = qMax (rows, y1 + 1);
136  rows = qMax (rows, y2 + 1);
137  cols = qMax (cols, x1 + 1);
138  cols = qMax (cols, x2 + 1);
139  }
140 
141  // Horizontal and vertical width is computed so merged line mostly fills the plot window,
142  // and (xInt,yInt) is at the center
143  int halfWidthX = 1.5 * qMax (qAbs (lineOld->line().dx()),
144  qAbs (lineNew->line().dx()));
145  int halfWidthY = 1.5 * qMax (qAbs (lineOld->line().dy()),
146  qAbs (lineNew->line().dy()));
147 
148  // Zoom in so changes are easier to see
149  strDump << "set xrange [" << (xInt - halfWidthX - 1) << ":" << (xInt + halfWidthX + 1) << "]\n";
150  strDump << "set yrange [" << (yInt - halfWidthY - 1) << ":" << (yInt + halfWidthY + 1) << "]\n";
151 
152  // One small curve shows xInt as horizontal line, and another shows yInt as vertical line.
153  // A small curve shows the replacement line
154  // Then two hhuge piecewise-defined curve show the pre-merge Segment pixels as two alternating colors
155  strDump << "plot \\\n"
156  << "\"-\" title \"\" with lines, \\\n"
157  << "\"-\" title \"\" with lines, \\\n"
158  << "\"-\" title \"Replacement\" with lines, \\\n"
159  << "\"-\" title \"Segment pixels Even\" with linespoints, \\\n"
160  << "\"-\" title \"Segment pixels Odd\" with linespoints\n"
161  << xInt << " " << (yInt - halfWidthY) << "\n"
162  << xInt << " " << (yInt + halfWidthY) << "\n"
163  << "end\n"
164  << (xInt - halfWidthX) << " " << yInt << "\n"
165  << (xInt + halfWidthY) << " " << yInt << "\n"
166  << "end\n"
167  << lineOld->line().x1() << " " << lineOld->line().y1() << "\n"
168  << lineNew->line().x2() << " " << lineNew->line().y2() << "\n"
169  << "end\n";
170 
171  // Fill the array from the list
172  QString even, odd;
173  QTextStream strEven (&even), strOdd (&odd);
174  for (int index = 0; index < m_lines.count(); index++) {
175 
176  SegmentLine *line = m_lines.at (index);
177  int x1 = line->line().x1();
178  int y1 = line->line().y1();
179  int x2 = line->line().x2();
180  int y2 = line->line().y2();
181 
182  if (index % 2 == 0) {
183  strEven << x1 << " " << y1 << "\n";
184  strEven << x2 << " " << y2 << "\n";
185  strEven << "\n";
186  } else {
187  strOdd << x1 << " " << y1 << "\n";
188  strOdd << x2 << " " << y2 << "\n";
189  strOdd << "\n";
190  }
191  }
192 
193  strDump << even << "\n";
194  strDump << "end\n";
195  strDump << odd << "\n";
196  strDump << "end\n";
197  strDump << "pause -1 \"Hit Enter to continue\"\n";
198  strDump << flush;
199  }
200 }
201 
202 QList<QPoint> Segment::fillPoints(const DocumentModelSegments &modelSegments)
203 {
204  LOG4CPP_INFO_S ((*mainCat)) << "Segment::fillPoints";
205 
206  if (modelSegments.fillCorners()) {
207  return fillPointsFillingCorners(modelSegments);
208  } else {
209  return fillPointsWithoutFillingCorners(modelSegments);
210  }
211 }
212 
213 QList<QPoint> Segment::fillPointsFillingCorners(const DocumentModelSegments &modelSegments)
214 {
215  QList<QPoint> list;
216 
217  if (m_lines.count() > 0) {
218 
219  double xLast = m_lines.first()->line().x1();
220  double yLast = m_lines.first()->line().y1();
221  double x, xNext;
222  double y, yNext;
223  double distanceCompleted = 0.0;
224 
225  // Variables for createAcceptablePoint
226  bool firstPoint = true;
227  double xPrev = m_lines.first()->line().x1();
228  double yPrev = m_lines.first()->line().y1();
229 
230  QList<SegmentLine*>::iterator itr;
231  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
232 
233  SegmentLine *line = *itr;
234 
235  ENGAUGE_CHECK_PTR(line);
236  xNext = (double) line->line().x2();
237  yNext = (double) line->line().y2();
238 
239  double xStart = (double) line->line().x1();
240  double yStart = (double) line->line().y1();
241  if (isCorner (yPrev, yStart, yNext)) {
242 
243  // Insert a corner point
244  createAcceptablePoint(&firstPoint, &list, &xPrev, &yPrev, xStart, yStart);
245  distanceCompleted = 0.0;
246  }
247 
248  // Distance formula
249  double segmentLength = sqrt((xNext - xLast) * (xNext - xLast) + (yNext - yLast) * (yNext - yLast));
250  if (segmentLength > 0.0) {
251 
252  // Loop since we might need to insert multiple points within a single line. This
253  // is the case when removeUnneededLines has consolidated many segment lines
254  while (distanceCompleted <= segmentLength) {
255 
256  double s = distanceCompleted / segmentLength;
257 
258  // Coordinates of new point
259  x = (1.0 - s) * xLast + s * xNext;
260  y = (1.0 - s) * yLast + s * yNext;
261 
262  createAcceptablePoint(&firstPoint, &list, &xPrev, &yPrev, x, y);
263 
264  distanceCompleted += modelSegments.pointSeparation();
265  }
266 
267  distanceCompleted -= segmentLength;
268  }
269 
270  xLast = xNext;
271  yLast = yNext;
272  }
273  }
274 
275  return list;
276 }
277 
278 QPointF Segment::firstPoint () const
279 {
280  LOG4CPP_INFO_S ((*mainCat)) << "Segment::firstPoint"
281  << " lineCount=" << m_lines.count();
282 
283  // There has to be at least one SegmentLine since this only gets called when a SegmentLine is clicked on
284  ENGAUGE_ASSERT (m_lines.count () > 0);
285 
286  SegmentLine *line = m_lines.first();
287  QPointF pos = line->line().p1();
288 
289  LOG4CPP_INFO_S ((*mainCat)) << "Segment::firstPoint"
290  << " pos=" << QPointFToString (pos).toLatin1().data();
291 
292  return pos;
293 }
294 
296 {
297  LOG4CPP_INFO_S ((*mainCat)) << "Segment::forwardMousePress"
298  << " segmentLines=" << m_lines.count();
299 
300  emit signalMouseClickOnSegment (firstPoint ());
301 }
302 
303 bool Segment::isCorner (double yLast,
304  double yPrev,
305  double yNext) const
306 {
307  // Rather than deal with slopes, and a risk of dividing by zero, we just use the y deltas
308  double deltaYBefore = yPrev - yLast;
309  double deltaYAfter = yNext - yPrev;
310  bool upThenAcrossOrDown = (deltaYBefore > 0) && (deltaYAfter <= 0);
311  bool downThenAcrossOrUp = (deltaYBefore < 0) && (deltaYAfter >= 0);
312 
313  return upThenAcrossOrDown || downThenAcrossOrUp;
314 }
315 
316 QList<QPoint> Segment::fillPointsWithoutFillingCorners(const DocumentModelSegments &modelSegments)
317 {
318  QList<QPoint> list;
319 
320  if (m_lines.count() > 0) {
321 
322  double xLast = m_lines.first()->line().x1();
323  double yLast = m_lines.first()->line().y1();
324  double x, xNext;
325  double y, yNext;
326  double distanceCompleted = 0.0;
327 
328  // Variables for createAcceptablePoint
329  bool firstPoint = true;
330  double xPrev = m_lines.first()->line().x1();
331  double yPrev = m_lines.first()->line().y1();
332 
333  QList<SegmentLine*>::iterator itr;
334  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
335 
336  SegmentLine *line = *itr;
337 
338  ENGAUGE_CHECK_PTR(line);
339  xNext = (double) line->line().x2();
340  yNext = (double) line->line().y2();
341 
342  // Distance formula
343  double segmentLength = sqrt((xNext - xLast) * (xNext - xLast) + (yNext - yLast) * (yNext - yLast));
344  if (segmentLength > 0.0) {
345 
346  // Loop since we might need to insert multiple points within a single line. This
347  // is the case when removeUnneededLines has consolidated many segment lines
348  while (distanceCompleted <= segmentLength) {
349 
350  double s = distanceCompleted / segmentLength;
351 
352  // Coordinates of new point
353  x = (1.0 - s) * xLast + s * xNext;
354  y = (1.0 - s) * yLast + s * yNext;
355 
356  createAcceptablePoint(&firstPoint, &list, &xPrev, &yPrev, x, y);
357 
358  distanceCompleted += modelSegments.pointSeparation();
359  }
360 
361  distanceCompleted -= segmentLength;
362  }
363 
364  xLast = xNext;
365  yLast = yNext;
366  }
367  }
368 
369  return list;
370 }
371 
372 double Segment::length() const
373 {
374  return m_length;
375 }
376 
378 {
379  return m_lines.count();
380 }
381 
382 bool Segment::pointIsCloseToLine(double xLeft,
383  double yLeft,
384  double xInt,
385  double yInt,
386  double xRight,
387  double yRight)
388 {
389  double xProj, yProj, projectedDistanceOutsideLine, distanceToLine;
390  projectPointOntoLine(xInt, yInt, xLeft, yLeft, xRight, yRight, &xProj, &yProj, &projectedDistanceOutsideLine, &distanceToLine);
391 
392  return (
393  (xInt - xProj) * (xInt - xProj) +
394  (yInt - yProj) * (yInt - yProj) < 0.5 * 0.5);
395 }
396 
397 bool Segment::pointsAreCloseToLine(double xLeft,
398  double yLeft,
399  QList<QPoint> removedPoints,
400  double xRight,
401  double yRight)
402 {
403  QList<QPoint>::iterator itr;
404  for (itr = removedPoints.begin(); itr != removedPoints.end(); ++itr) {
405  if (!pointIsCloseToLine(xLeft, yLeft, (double) (*itr).x(), (double) (*itr).y(), xRight, yRight)) {
406  return false;
407  }
408  }
409 
410  return true;
411 }
412 
413 void Segment::removeUnneededLines (int *foldedLines)
414 {
415  LOG4CPP_INFO_S ((*mainCat)) << "Segment::removeUnneededLines";
416 
417  QFile *fileDump = 0;
418  QTextStream *strDump = 0;
419  if (m_isGnuplot) {
420 
421  QString filename ("segment.gnuplot");
422 
423  std::cout << "Writing gnuplot file: " << filename.toLatin1().data() << "\n";
424 
425  fileDump = new QFile (filename);
426  fileDump->open (QIODevice::WriteOnly | QIODevice::Text);
427  strDump = new QTextStream (fileDump);
428 
429  }
430 
431  // Pathological case is y=0.001*x*x, since the small slope can fool a naive algorithm
432  // into optimizing away all but one point at the origin and another point at the far right.
433  // From this we see that we cannot simply throw away points that were optimized away since they
434  // are needed later to see if we have diverged from the curve
435  SegmentLine *linePrevious = 0; // Previous line which corresponds to itrPrevious
436  QList<SegmentLine*>::iterator itr, itrPrevious;
437  QList<QPoint> removedPoints;
438  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
439 
440  SegmentLine *line = *itr;
441  ENGAUGE_CHECK_PTR(line);
442 
443  if (linePrevious != 0) {
444 
445  double xLeft = linePrevious->line().x1();
446  double yLeft = linePrevious->line().y1();
447  double xInt = linePrevious->line().x2();
448  double yInt = linePrevious->line().y2();
449 
450  // If linePrevious is the last line of one Segment and line is the first line of another Segment then
451  // it makes no sense to remove any point so we continue the loop
452  if (linePrevious->line().p2() == line->line().p1()) {
453 
454  double xRight = line->line().x2();
455  double yRight = line->line().y2();
456 
457  if (pointIsCloseToLine(xLeft, yLeft, xInt, yInt, xRight, yRight) &&
458  pointsAreCloseToLine(xLeft, yLeft, removedPoints, xRight, yRight)) {
459 
460  if (m_isGnuplot) {
461 
462  // Dump
463  dumpToGnuplot (*strDump,
464  xInt,
465  yInt,
466  linePrevious,
467  line);
468  }
469 
470  // Remove intermediate point, by removing older line and stretching new line to first point
471  ++(*foldedLines);
472 
473  LOG4CPP_DEBUG_S ((*mainCat)) << "Segment::removeUnneededLines"
474  << " segment=0x" << std::hex << (unsigned long) this << std::dec
475  << " removing ("
476  << linePrevious->line().x1() << "," << linePrevious->line().y1() << ") to ("
477  << linePrevious->line().x2() << "," << linePrevious->line().y2() << ") "
478  << " and modifying ("
479  << line->line().x1() << "," << line->line().y1() << ") to ("
480  << line->line().x2() << "," << line->line().y2() << ") into ("
481  << xLeft << "," << yLeft << ") to ("
482  << xRight << "," << yRight << ")";
483 
484  removedPoints.append(QPoint((int) xInt, (int) yInt));
485  m_lines.erase (itrPrevious);
486  delete linePrevious;
487 
488  // New line
489  line->setLine (xLeft, yLeft, xRight, yRight);
490 
491  } else {
492 
493  // Keeping this intermediate point and clear out the removed points list
494  removedPoints.clear();
495  }
496  }
497  }
498 
499  linePrevious = line;
500  itrPrevious = itr;
501 
502  // This theoretically should not be needed, but for some reason modifying the last point triggers a segfault
503  if (itr == m_lines.end()) {
504  break;
505  }
506  }
507 
508  if (m_isGnuplot) {
509 
510  // Final gnuplot processing
511  *strDump << "set terminal x11 persist\n";
512  fileDump->close ();
513  delete strDump;
514  delete fileDump;
515 
516  }
517 }
518 
519 void Segment::slotHover (bool hover)
520 {
521  LOG4CPP_INFO_S ((*mainCat)) << "Segment::slotHover";
522 
523  QList<SegmentLine*>::iterator itr, itrPrevious;
524  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
525 
526  SegmentLine *line = *itr;
527  line->setHover(hover);
528  }
529 }
530 
532 {
533  LOG4CPP_INFO_S ((*mainCat)) << "Segment::updateModelSegment";
534 
535  QList<SegmentLine*>::iterator itr;
536  for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
537 
538  SegmentLine *line = *itr;
539  line->updateModelSegment (modelSegments);
540  }
541 }
int lineCount() const
Get method for number of lines.
Definition: Segment.cpp:377
void updateModelSegment(const DocumentModelSegments &modelSegments)
Update this segment line with new settings.
Definition: SegmentLine.cpp:83
void removeUnneededLines(int *foldedLines)
Try to compress a segment that was just completed, by folding together line from point i to point i+1...
Definition: Segment.cpp:413
double pointSeparation() const
Get method for point separation.
void slotHover(bool hover)
Slot for hover enter/leave events in the associated SegmentLines.
Definition: Segment.cpp:519
void forwardMousePress()
Forward mouse press event from a component SegmentLine that was just clicked on.
Definition: Segment.cpp:295
void appendColumn(int x, int y, const DocumentModelSegments &modelSegments)
Add some more pixels in a new column to an active segment.
Definition: Segment.cpp:39
double length() const
Get method for length in pixels.
Definition: Segment.cpp:372
QList< QPoint > fillPoints(const DocumentModelSegments &modelSegments)
Create evenly spaced points along the segment.
Definition: Segment.cpp:202
void signalMouseClickOnSegment(QPointF posSegmentStart)
Pass mouse press event, with coordinates of first point in the Segment since that info uniquely ident...
void updateModelSegment(const DocumentModelSegments &modelSegments)
Update this segment given the new settings.
Definition: Segment.cpp:531
QPointF firstPoint() const
Coordinates of first point in Segment.
Definition: Segment.cpp:278
Model for DlgSettingsSegments and CmdSettingsSegments.
This class is a special case of the standard QGraphicsLineItem for segments.
Definition: SegmentLine.h:11
void setHover(bool hover)
Apply/remove highlighting triggered by hover enter/leave.
Definition: SegmentLine.cpp:67
Segment(QGraphicsScene &scene, int yLast, bool isGnuplot)
Single constructor.
Definition: Segment.cpp:15
bool fillCorners() const
Get method for fill corners.