Engauge Digitizer  2
GridRemoval.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 "DocumentModelGridRemoval.h"
8 #include "EngaugeAssert.h"
9 #include "GridHealer.h"
10 #include "GridRemoval.h"
11 #include "Logger.h"
12 #include <qdebug.h>
13 #include <QImage>
14 #include <qmath.h>
15 #include "Transformation.h"
16 
17 const double EPSILON = 0.000001;
18 
20 {
21 }
22 
23 QPointF GridRemoval::clipX (const QPointF &posUnprojected,
24  double xBoundary,
25  const QPointF &posOther) const
26 {
27  double s = (xBoundary - posUnprojected.x()) / (posOther.x() - posUnprojected.x());
28  ENGAUGE_ASSERT ((-1.0 * EPSILON < s) && (s < 1.0 + EPSILON));
29 
30  return QPointF ((1.0 - s) * posUnprojected.x() + s * posOther.x(),
31  (1.0 - s) * posUnprojected.y() + s * posOther.y());
32 }
33 
34 QPointF GridRemoval::clipY (const QPointF &posUnprojected,
35  double yBoundary,
36  const QPointF &posOther) const
37 {
38  double s = (yBoundary - posUnprojected.y()) / (posOther.y() - posUnprojected.y());
39  ENGAUGE_ASSERT ((-1.0 * EPSILON < s) && (s < 1.0 + EPSILON));
40 
41  return QPointF ((1.0 - s) * posUnprojected.x() + s * posOther.x(),
42  (1.0 - s) * posUnprojected.y() + s * posOther.y());
43 }
44 
45 QPixmap GridRemoval::remove (const Transformation &transformation,
46  const DocumentModelGridRemoval &modelGridRemoval,
47  const QImage &imageBefore)
48 {
49  LOG4CPP_INFO_S ((*mainCat)) << "GridRemoval::remove"
50  << " transformationIsDefined=" << (transformation.transformIsDefined() ? "true" : "false")
51  << " removeDefinedGridLines=" << (modelGridRemoval.removeDefinedGridLines() ? "true" : "false");
52 
53  QImage image = imageBefore;
54 
55  // Make sure grid line removal is wanted, and possible. Otherwise all processing is skipped
56  if (modelGridRemoval.removeDefinedGridLines() &&
57  transformation.transformIsDefined()) {
58 
59  GridHealer gridHealer (imageBefore,
60  modelGridRemoval);
61 
62  double yGraphMin = modelGridRemoval.startY();
63  double yGraphMax = modelGridRemoval.stopY();
64  for (int i = 0; i < modelGridRemoval.countX(); i++) {
65  double xGraph = modelGridRemoval.startX() + i * modelGridRemoval.stepX();
66 
67  // Convert line between graph coordinates (xGraph,yGraphMin) and (xGraph,yGraphMax) to screen coordinates
68  QPointF posScreenMin, posScreenMax;
69  transformation.transformRawGraphToScreen (QPointF (xGraph,
70  yGraphMin),
71  posScreenMin);
72  transformation.transformRawGraphToScreen (QPointF (xGraph,
73  yGraphMax),
74  posScreenMax);
75 
76  removeLine (posScreenMin,
77  posScreenMax,
78  image,
79  gridHealer);
80  }
81 
82  double xGraphMin = modelGridRemoval.startX();
83  double xGraphMax = modelGridRemoval.stopX();
84  for (int j = 0; j < modelGridRemoval.countY(); j++) {
85  double yGraph = modelGridRemoval.startY() + j * modelGridRemoval.stepY();
86 
87  // Convert line between graph coordinates (xGraphMin,yGraph) and (xGraphMax,yGraph) to screen coordinates
88  QPointF posScreenMin, posScreenMax;
89  transformation.transformRawGraphToScreen (QPointF (xGraphMin,
90  yGraph),
91  posScreenMin);
92  transformation.transformRawGraphToScreen (QPointF (xGraphMax,
93  yGraph),
94  posScreenMax);
95 
96  removeLine (posScreenMin,
97  posScreenMax,
98  image,
99  gridHealer);
100  }
101 
102  // Apply the healing process to the image
103  gridHealer.heal (image);
104  }
105 
106  return QPixmap::fromImage (image);
107 }
108 
109 void GridRemoval::removeLine (const QPointF &posMin,
110  const QPointF &posMax,
111  QImage &image,
112  GridHealer &gridHealer)
113 {
114  double w = image.width() - 1; // Inclusive width = exclusive width - 1
115  double h = image.height() - 1; // Inclusive height = exclusive height - 1
116 
117  QPointF pos1 = posMin;
118  QPointF pos2 = posMax;
119 
120  // Throw away all lines that are entirely above or below or left or right to the screen, since
121  // they cannot intersect the screen
122  bool onLeft = (pos1.x() < 0 && pos2.x () < 0);
123  bool onTop = (pos1.y() < 0 && pos2.y () < 0);
124  bool onRight = (pos1.x() > w && pos2.x () > w);
125  bool onBottom = (pos1.y() > h && pos2.y () > h);
126  if (!onLeft && !onTop && !onRight && !onBottom) {
127 
128  // Clip to within the four sides
129  if (pos1.x() < 0) { pos1 = clipX (pos1, 0, pos2); }
130  if (pos2.x() < 0) { pos2 = clipX (pos2, 0, pos1); }
131  if (pos1.y() < 0) { pos1 = clipY (pos1, 0, pos2); }
132  if (pos2.y() < 0) { pos2 = clipY (pos2, 0, pos1); }
133  if (pos1.x() > w) { pos1 = clipX (pos1, w, pos2); }
134  if (pos2.x() > w) { pos2 = clipX (pos2, w, pos1); }
135  if (pos1.y() > h) { pos1 = clipY (pos1, h, pos2); }
136  if (pos2.y() > h) { pos2 = clipY (pos2, h, pos1); }
137 
138  // Is line more horizontal or vertical?
139  double deltaX = qAbs (pos1.x() - pos2.x());
140  double deltaY = qAbs (pos1.y() - pos2.y());
141  if (deltaX > deltaY) {
142 
143  // More horizontal
144  int xMin = qMin (pos1.x(), pos2.x());
145  int xMax = qMax (pos1.x(), pos2.x());
146  int yAtXMin = (pos1.x() < pos2.x() ? pos1.y() : pos2.y());
147  int yAtXMax = (pos1.x() < pos2.x() ? pos2.y() : pos1.y());
148  for (int x = xMin; x <= xMax; x++) {
149  double s = (double) (x - xMin) / (double) (xMax - xMin);
150  double yLine = (1.0 - s) * yAtXMin + s * yAtXMax;
151  for (int yOffset = -1; yOffset <= 1; yOffset++) {
152  int y = (int) (0.5 + yLine + yOffset);
153  image.setPixel (x, y, QColor(Qt::white).rgb());
154  gridHealer.erasePixel (x, y);
155  }
156  }
157  } else {
158 
159  // More vertical
160  int yMin = qMin (pos1.y(), pos2.y());
161  int yMax = qMax (pos1.y(), pos2.y());
162  int xAtYMin = (pos1.y() < pos2.y() ? pos1.x() : pos2.x());
163  int xAtYMax = (pos1.y() < pos2.y() ? pos2.x() : pos1.x());
164  for (int y = yMin; y <= yMax; y++) {
165  double s = (double) (y - yMin) / (double) (yMax - yMin);
166  double xLine = (1.0 - s) * xAtYMin + s * xAtYMax;
167  for (int xOffset = -1; xOffset <= 1; xOffset++) {
168  int x = (int) (0.5 + xLine + xOffset);
169  image.setPixel (x, y, QColor(Qt::white).rgb());
170  gridHealer.erasePixel (x, y);
171  }
172  }
173  }
174  }
175 }
176 
double stopY() const
Get method for y stop.
double stopX() const
Get method for x stop.
int countY() const
Get method for y count.
void erasePixel(int xCol, int yRow)
Remember that pixel was erased since it belongs to an grid line.
Definition: GridHealer.cpp:96
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...
double stepX() const
Get method for x step.
Class that &#39;heals&#39; the curves after grid lines have been removed.
Definition: GridHealer.h:37
Affine transformation between screen and graph coordinates, based on digitized axis points...
bool removeDefinedGridLines() const
Get method for removing defined grid lines.
QPixmap remove(const Transformation &transformation, const DocumentModelGridRemoval &modelGridRemoval, const QImage &imageBefore)
Process QImage into QPixmap, removing the grid lines.
Definition: GridRemoval.cpp:45
bool transformIsDefined() const
Transform is defined when at least three axis points have been digitized.
int countX() const
Get method for x count.
double startX() const
Get method for x start.
Model for DlgSettingsGridRemoval and CmdSettingsGridRemoval. The settings are unstable until the user...
GridRemoval()
Single constructor.
Definition: GridRemoval.cpp:19
void heal(QImage &imageToHeal)
Heal the broken curve lines by spanning the gaps across the newly-removed grid lines.
Definition: GridHealer.cpp:153
double stepY() const
Get method for y step.