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
21Segment::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
32Segment::~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
79void 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
102void 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
208QList<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
219QList<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
284QPointF 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
307}
308
309bool 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
322QList<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
378double Segment::length() const
379{
380 return m_length;
381}
382
384{
385 return m_lines.count();
386}
387
388bool 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
403bool 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
419void 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
525void 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}
Model for DlgSettingsSegments and CmdSettingsSegments.
bool fillCorners() const
Get method for fill corners.
double pointSeparation() const
Get method for point separation.
This class is a special case of the standard QGraphicsLineItem for segments.
Definition: SegmentLine.h:18
void setHover(bool hover)
Apply/remove highlighting triggered by hover enter/leave.
Definition: SegmentLine.cpp:73
void updateModelSegment(const DocumentModelSegments &modelSegments)
Update this segment line with new settings.
Definition: SegmentLine.cpp:89
void signalMouseClickOnSegment(QPointF posSegmentStart)
Pass mouse press event, with coordinates of first point in the Segment since that info uniquely ident...
double length() const
Get method for length in pixels.
Definition: Segment.cpp:378
int lineCount() const
Get method for number of lines.
Definition: Segment.cpp:383
QList< QPoint > fillPoints(const DocumentModelSegments &modelSegments)
Create evenly spaced points along the segment.
Definition: Segment.cpp:208
Segment(QGraphicsScene &scene, int yLast, bool isGnuplot)
Single constructor.
Definition: Segment.cpp:21
void forwardMousePress()
Forward mouse press event from a component SegmentLine that was just clicked on.
Definition: Segment.cpp:301
void slotHover(bool hover)
Slot for hover enter/leave events in the associated SegmentLines.
Definition: Segment.cpp:525
void updateModelSegment(const DocumentModelSegments &modelSegments)
Update this segment given the new settings.
Definition: Segment.cpp:537
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
QPointF firstPoint() const
Coordinates of first point in Segment.
Definition: Segment.cpp:284
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