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
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,
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
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;
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
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
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{
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
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
258
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
271 }
272
273 xLast = xNext;
274 yLast = yNext;
275 }
276 }
277
278 return list;
279}
280
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
317}
318
319QList<QPoint> Segment::fillPointsWithoutFillingCorners(const DocumentModelSegments &modelSegments)
320{
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
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
352
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
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{
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,
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
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);
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;
446 for (itr = m_lines.begin(); itr != m_lines.end(); itr++) {
447
448 SegmentLine *line = *itr;
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),
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
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
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
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;
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}
const int INNER_RADIUS_MIN
#define ENGAUGE_ASSERT(cond)
Drop in replacement for Q_ASSERT if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) define ENGAUGE...
#define ENGAUGE_CHECK_PTR(ptr)
#endif
log4cpp::Category * mainCat
Definition Logger.cpp:14
QString QPointFToString(const QPointF &pos)
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 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