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 "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 
GridRemoval(bool isGnuplot)20 GridRemoval::GridRemoval (bool isGnuplot) :
21   m_gridLog (isGnuplot)
22 {
23 }
24 
clipX(const QPointF & posUnprojected,double xBoundary,const QPointF & posOther) const25 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 
clipY(const QPointF & posUnprojected,double yBoundary,const QPointF & posOther) const39 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 
remove(const Transformation & transformation,const DocumentModelGridRemoval & modelGridRemoval,const QImage & imageBefore)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 
removeLine(const QPointF & posMin,const QPointF & posMax,QImage & image,const DocumentModelGridRemoval & modelGridRemoval,GridHealers & gridHealers)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 
205