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