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