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