Engauge Digitizer  2
GridRemoval.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 "GridHealerHorizontal.h"
10 #include "GridHealerVertical.h"
11 #include "GridRemoval.h"
12 #include "Logger.h"
13 #include "Pixels.h"
14 #include <QImage>
15 #include <qmath.h>
16 #include "Transformation.h"
17 
18 const double EPSILON = 0.000001;
19 
20 GridRemoval::GridRemoval (bool isGnuplot) :
21  m_gridLog (isGnuplot)
22 {
23 }
24 
25 QPointF GridRemoval::clipX (const QPointF &posUnprojected,
26  double xBoundary,
27  const QPointF &posOther) const
28 {
29  double s = 0;
30  if (posOther.x() != posUnprojected.x()) {
31  s = (xBoundary - posUnprojected.x()) / (posOther.x() - posUnprojected.x());
32  }
33  ENGAUGE_ASSERT ((-1.0 * EPSILON < s) && (s < 1.0 + EPSILON));
34 
35  return QPointF ((1.0 - s) * posUnprojected.x() + s * posOther.x(),
36  (1.0 - s) * posUnprojected.y() + s * posOther.y());
37 }
38 
39 QPointF GridRemoval::clipY (const QPointF &posUnprojected,
40  double yBoundary,
41  const QPointF &posOther) const
42 {
43  double s = 0;
44  if (posOther.y() != posUnprojected.y()) {
45  s = (yBoundary - posUnprojected.y()) / (posOther.y() - posUnprojected.y());
46  }
47  ENGAUGE_ASSERT ((-1.0 * EPSILON < s) && (s < 1.0 + EPSILON));
48 
49  return QPointF ((1.0 - s) * posUnprojected.x() + s * posOther.x(),
50  (1.0 - s) * posUnprojected.y() + s * posOther.y());
51 }
52 
53 QPixmap GridRemoval::remove (const Transformation &transformation,
54  const DocumentModelGridRemoval &modelGridRemoval,
55  const QImage &imageBefore)
56 {
57  LOG4CPP_INFO_S ((*mainCat)) << "GridRemoval::remove"
58  << " transformationIsDefined=" << (transformation.transformIsDefined() ? "true" : "false")
59  << " removeDefinedGridLines=" << (modelGridRemoval.removeDefinedGridLines() ? "true" : "false");
60 
61  QImage image = imageBefore;
62 
63  // Collect GridHealers instances, one per grid line
64  GridHealers gridHealers;
65 
66  // Make sure grid line removal is wanted, and possible. Otherwise all processing is skipped
67  if (modelGridRemoval.removeDefinedGridLines() &&
68  transformation.transformIsDefined()) {
69 
70  double yGraphMin = modelGridRemoval.startY();
71  double yGraphMax = modelGridRemoval.stopY();
72  for (int i = 0; i < modelGridRemoval.countX(); i++) {
73  double xGraph = modelGridRemoval.startX() + i * modelGridRemoval.stepX();
74 
75  // Convert line between graph coordinates (xGraph,yGraphMin) and (xGraph,yGraphMax) to screen coordinates
76  QPointF posScreenMin, posScreenMax;
77  transformation.transformRawGraphToScreen (QPointF (xGraph,
78  yGraphMin),
79  posScreenMin);
80  transformation.transformRawGraphToScreen (QPointF (xGraph,
81  yGraphMax),
82  posScreenMax);
83 
84  removeLine (posScreenMin,
85  posScreenMax,
86  image,
87  modelGridRemoval,
88  gridHealers);
89  }
90 
91  double xGraphMin = modelGridRemoval.startX();
92  double xGraphMax = modelGridRemoval.stopX();
93  for (int j = 0; j < modelGridRemoval.countY(); j++) {
94  double yGraph = modelGridRemoval.startY() + j * modelGridRemoval.stepY();
95 
96  // Convert line between graph coordinates (xGraphMin,yGraph) and (xGraphMax,yGraph) to screen coordinates
97  QPointF posScreenMin, posScreenMax;
98  transformation.transformRawGraphToScreen (QPointF (xGraphMin,
99  yGraph),
100  posScreenMin);
101  transformation.transformRawGraphToScreen (QPointF (xGraphMax,
102  yGraph),
103  posScreenMax);
104 
105  removeLine (posScreenMin,
106  posScreenMax,
107  image,
108  modelGridRemoval,
109  gridHealers);
110  }
111 
112  // Heal the broken lines now that all grid lines have been removed and the image has stabilized
113  GridHealers::iterator itr;
114  for (itr = gridHealers.begin(); itr != gridHealers.end(); itr++) {
115  GridHealerAbstractBase *gridHealer = *itr;
116  gridHealer->healed (image);
117  delete gridHealer;
118  }
119  }
120 
121  return QPixmap::fromImage (image);
122 }
123 
124 void GridRemoval::removeLine (const QPointF &posMin,
125  const QPointF &posMax,
126  QImage &image,
127  const DocumentModelGridRemoval &modelGridRemoval,
128  GridHealers &gridHealers)
129 {
130  const int HALF_WIDTH = 1;
131 
132  double w = image.width() - 1; // Inclusive width = exclusive width - 1
133  double h = image.height() - 1; // Inclusive height = exclusive height - 1
134 
135  QPointF pos1 = posMin;
136  QPointF pos2 = posMax;
137 
138  // Throw away all lines that are entirely above or below or left or right to the screen, since
139  // they cannot intersect the screen
140  bool onLeft = (pos1.x() < 0 && pos2.x () < 0);
141  bool onTop = (pos1.y() < 0 && pos2.y () < 0);
142  bool onRight = (pos1.x() > w && pos2.x () > w);
143  bool onBottom = (pos1.y() > h && pos2.y () > h);
144  if (!onLeft && !onTop && !onRight && !onBottom) {
145 
146  // Clip to within the four sides
147  if (pos1.x() < 0) { pos1 = clipX (pos1, 0, pos2); }
148  if (pos2.x() < 0) { pos2 = clipX (pos2, 0, pos1); }
149  if (pos1.y() < 0) { pos1 = clipY (pos1, 0, pos2); }
150  if (pos2.y() < 0) { pos2 = clipY (pos2, 0, pos1); }
151  if (pos1.x() > w) { pos1 = clipX (pos1, w, pos2); }
152  if (pos2.x() > w) { pos2 = clipX (pos2, w, pos1); }
153  if (pos1.y() > h) { pos1 = clipY (pos1, h, pos2); }
154  if (pos2.y() > h) { pos2 = clipY (pos2, h, pos1); }
155 
156  // Is line more horizontal or vertical?
157  double deltaX = qAbs (pos1.x() - pos2.x());
158  double deltaY = qAbs (pos1.y() - pos2.y());
159  if (deltaX > deltaY) {
160 
161  // More horizontal
162  GridHealerAbstractBase *gridHealer = new GridHealerHorizontal (m_gridLog,
163  modelGridRemoval);
164  gridHealers.push_back (gridHealer);
165 
166  int xMin = qMin (qFloor (pos1.x()), qFloor (pos2.x()));
167  int xMax = qMax (qFloor (pos1.x()), qFloor (pos2.x()));
168  int yAtXMin = (pos1.x() < pos2.x() ? qFloor (pos1.y()) : qFloor (pos2.y()));
169  int yAtXMax = (pos1.x() < pos2.x() ? qFloor (pos2.y()) : qFloor (pos1.y()));
170  for (int x = xMin; x <= xMax; x++) {
171  double s = double (x - xMin) / double (xMax - xMin);
172  int yLine = qFloor (0.5 + (1.0 - s) * yAtXMin + s * yAtXMax);
173  for (int yOffset = -HALF_WIDTH; yOffset <= HALF_WIDTH; yOffset++) {
174  int y = yLine + yOffset;
175  image.setPixel (x, y, QColor(Qt::white).rgb());
176  }
177  gridHealer->addMutualPair (x, yLine - HALF_WIDTH - 1, x, yLine + HALF_WIDTH + 1);
178  }
179 
180  } else {
181 
182  // More vertical
183  GridHealerAbstractBase *gridHealer = new GridHealerVertical (m_gridLog,
184  modelGridRemoval);
185  gridHealers.push_back (gridHealer);
186 
187  int yMin = qMin (qFloor (pos1.y()), qFloor (pos2.y()));
188  int yMax = qMax (qFloor (pos1.y()), qFloor (pos2.y()));
189  int xAtYMin = (pos1.y() < pos2.y() ? qFloor (pos1.x()) : qFloor (pos2.x()));
190  int xAtYMax = (pos1.y() < pos2.y() ? qFloor (pos2.x()) : qFloor (pos1.x()));
191  for (int y = yMin; y <= yMax; y++) {
192  double s = double (y - yMin) / double (yMax - yMin);
193  int xLine = qFloor (0.5 + (1.0 - s) * xAtYMin + s * xAtYMax);
194  for (int xOffset = -HALF_WIDTH; xOffset <= HALF_WIDTH; xOffset++) {
195  int x = xLine + xOffset;
196  image.setPixel (x, y, QColor(Qt::white).rgb());
197  }
198  gridHealer->addMutualPair (xLine - HALF_WIDTH - 1, y, xLine + HALF_WIDTH + 1, y);
199  }
200 
201  }
202  }
203 }
204 
double stopY() const
Get method for y stop.
double stopX() const
Get method for x stop.
int countY() const
Get method for y count.
double startY() const
Get method for y start.
void transformRawGraphToScreen(const QPointF &pointRaw, QPointF &pointScreen) const
Transform from raw graph coordinates to linear cartesian graph coordinates, then to screen coordinate...
#define LOG4CPP_INFO_S(logger)
Definition: convenience.h:18
void healed(QImage &image)
Return healed image after grid removal.
double stepX() const
Get method for x step.
Class that 'heals' the curves after one grid line has been removed.
Subclass of GridHealerAbstractBase for horizontal lines.
Affine transformation between screen and graph coordinates, based on digitized axis points.
bool removeDefinedGridLines() const
Get method for removing defined grid lines.
GridRemoval(bool isGnuplot)
Single constructor.
Definition: GridRemoval.cpp:20
QPixmap remove(const Transformation &transformation, const DocumentModelGridRemoval &modelGridRemoval, const QImage &imageBefore)
Process QImage into QPixmap, removing the grid lines.
Definition: GridRemoval.cpp:53
const double EPSILON
Definition: GridRemoval.cpp:18
Subclass of GridHealerAbstractBase for vertical lines.
bool transformIsDefined() const
Transform is defined when at least three axis points have been digitized.
log4cpp::Category * mainCat
Definition: Logger.cpp:14
int countX() const
Get method for x count.
double startX() const
Get method for x start.
void addMutualPair(int x0, int y0, int x1, int y1)
Add two points on either side of a gap. Later, after removal, the black points will be processed.
Model for DlgSettingsGridRemoval and CmdSettingsGridRemoval. The settings are unstable until the user...
QList< GridHealerAbstractBase * > GridHealers
Storage of GridHealer instances.
Definition: GridRemoval.h:18
#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
double stepY() const
Get method for y step.