Engauge Digitizer 2
Loading...
Searching...
No Matches
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.
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: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