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