1 /******************************************************************************************************
2  * (C) 2018 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 "GridHealerHorizontal.h"
8 #include "GridIndependentToDependent.h"
9 #include "GridLog.h"
10 #include "Pixels.h"
11 #include <qmath.h>
12 
GridHealerHorizontal(GridLog & gridLog,const DocumentModelGridRemoval & modelGridRemoval)13 GridHealerHorizontal::GridHealerHorizontal(GridLog &gridLog,
14                                            const DocumentModelGridRemoval &modelGridRemoval) :
15   GridHealerAbstractBase (gridLog,
16                           modelGridRemoval)
17 {
18 }
19 
applyMutualPairs(const QImage & image)20 void GridHealerHorizontal::applyMutualPairs (const QImage &image)
21 {
22   MutualPairHalves::const_iterator itrBelow = mutualPairHalvesBelow().begin();
23   MutualPairHalves::const_iterator itrAbove = mutualPairHalvesAbove().begin();
24 
25   while (itrBelow != mutualPairHalvesBelow().end() &&
26          itrAbove != mutualPairHalvesAbove().end()) {
27 
28     QPoint p0 = *(itrBelow++);
29     QPoint p1 = *(itrAbove++);
30 
31     // Save (independent,dependent) pairs
32     if (Pixels::pixelIsBlack (image, p0.x(), p0.y())) {
33       m_blackPixelsBelow [p0.x()] = p0.y();
34     }
35 
36     if (Pixels::pixelIsBlack (image, p1.x(), p1.y())) {
37       m_blackPixelsAbove [p1.x()] = p1.y();
38     }
39 
40     saveGapSeparation (qAbs (p1.y() - p0.y()));
41   }
42 }
43 
doHealingAcrossGaps(QImage & image)44 void GridHealerHorizontal::doHealingAcrossGaps (QImage &image)
45 {
46   // LOG4CPP_INFO_S is replaced by GridLog
47   GridIndependentToDependent::const_iterator itrBelow, itrAbove;
48   for (itrBelow = m_blackPixelsBelow.begin(); itrBelow != m_blackPixelsBelow.end(); itrBelow++) {
49     QPoint p (itrBelow.key(),
50               itrBelow.value());
51     gridLog().showInputPixel(p,
52                              HALFWIDTH_HORIZONTAL);
53   }
54   for (itrAbove = m_blackPixelsAbove.begin(); itrAbove != m_blackPixelsAbove.end(); itrAbove++) {
55     QPoint p (itrAbove.key(),
56               itrAbove.value());
57     gridLog().showInputPixel(p,
58                              HALFWIDTH_HORIZONTAL);
59   }
60 
61   // Algorithm requires at least one point in each of the lists
62   if (m_blackPixelsBelow.count() > 0 &&
63       m_blackPixelsAbove.count() > 0) {
64 
65     int xFirst = qMin (m_blackPixelsBelow.firstKey (),
66                        m_blackPixelsAbove.firstKey ());
67     int xLast = qMax (m_blackPixelsBelow.lastKey (),
68                       m_blackPixelsAbove.lastKey ());
69 
70     int xBelowEnd = 0; // Used by inner loop to skip to this iterator value
71 
72     for (int xBelowStart = xFirst; xBelowStart <= xLast; xBelowStart++) {
73 
74       if ((xBelowEnd < xBelowStart) &&
75           m_blackPixelsBelow.contains (xBelowStart)) {
76 
77         // This could be the start of a new trapezoid. Find where the range on the same side ends
78         int xBelowOutOfBounds = xLast + 1; // Value forcing transition to out of range
79 
80         for (xBelowEnd = xBelowStart + 1; xBelowEnd <= xBelowOutOfBounds; xBelowEnd++) {
81 
82           if (!m_blackPixelsBelow.contains (xBelowEnd) || (xBelowEnd == xBelowOutOfBounds)) {
83 
84             doHealingOnBelowRange (image,
85                                    xBelowStart,
86                                    xBelowEnd,
87                                    qFloor (maxPointSeparation()));
88 
89             // Go back to outer loop, which will skip to xBelowEnd
90             break;
91           }
92         }
93       }
94     }
95   }
96 }
97 
doHealingOnBelowAndAboveRangePair(QImage & image,int xBelowStart,int xBelowEnd,int xAboveStart,int xAboveEnd)98 void GridHealerHorizontal::doHealingOnBelowAndAboveRangePair (QImage &image,
99                                                               int xBelowStart,
100                                                               int xBelowEnd,
101                                                               int xAboveStart,
102                                                               int xAboveEnd)
103 {
104   // LOG4CPP_INFO_S is replaced by GridLog
105 
106   int x0 = xBelowStart;
107   int x1 = xBelowEnd;
108   int x2 = xAboveEnd;
109   int x3 = xAboveStart;
110   int y0 = m_blackPixelsBelow [xBelowStart];
111   int y1 = m_blackPixelsBelow [xBelowEnd  ];
112   int y2 = m_blackPixelsAbove [xAboveEnd  ];
113   int y3 = m_blackPixelsAbove [xAboveStart];
114 
115   gridLog().showOutputTrapezoid (QPoint (x0, y0),
116                                  QPoint (x1, y1),
117                                  QPoint (x2, y2),
118                                  QPoint (x3, y3));
119 
120   if (pointsAreGood (image, x0, y0, x2, y2)) {
121 
122     // Big enough so keep it. Four points that define the trapezoid to be filled in
123     fillTrapezoid (image,
124                    x0, y0,
125                    x1, y1,
126                    x2, y2,
127                    x3, y3);
128   }
129 }
130 
doHealingOnBelowRange(QImage & image,int xBelowStart,int xBelowEnd,int maxHorSep)131 void GridHealerHorizontal::doHealingOnBelowRange (QImage &image,
132                                                   int xBelowStart,
133                                                   int xBelowEnd,
134                                                   int maxHorSep)
135 {
136   // LOG4CPP_INFO_S is replaced by GridLog
137 
138   // Below range goes from xBelowStart (inclusive) to xBelowEnd (exclusive). There could
139   // be zero, one or more above ranges that overlap within maxHorSep, corresponding
140   // to an equal number of trapezoids to be filled in
141   //
142   // It is important to note that every above point between xBelowStart-maxHorSep to
143   // xBelowEnd+maxHorSep is close enough (<close distance) to a point in the below range
144 
145   int xAboveOutOfBounds = xBelowEnd + maxHorSep + 1; // Value forcing transition to out of range
146 
147   int xAboveEnd = 0; // Used by inner loop to skip to this iterator value
148 
149   for (int xAboveStart = xBelowStart - maxHorSep; xAboveStart <= xAboveOutOfBounds; xAboveStart++) {
150 
151     if ((xAboveEnd < xAboveStart) &&
152         m_blackPixelsAbove.contains (xAboveStart) &&
153         (xAboveStart < xAboveOutOfBounds)) {
154 
155       for (xAboveEnd = xAboveStart + 1; xAboveEnd <= xAboveOutOfBounds; xAboveEnd++) {
156 
157         if (!m_blackPixelsAbove.contains (xAboveEnd) || (xAboveEnd == xAboveOutOfBounds)) {
158 
159           int xBelowStartNearEnough = qMax (xBelowStart, xAboveStart - maxHorSep);
160           int xBelowEndNearEnough = qMin (xBelowEnd - 1, xAboveEnd + maxHorSep);
161           int xAboveEndInclusive = xAboveEnd - 1;
162 
163           if (xBelowStartNearEnough <= xBelowEndNearEnough) {
164 
165             doHealingOnBelowAndAboveRangePair (image,
166                                                xBelowStartNearEnough,
167                                                xBelowEndNearEnough,
168                                                xAboveStart,
169                                                xAboveEndInclusive);
170 
171             // Go back to outer loop, which will skip to xAboveEnd
172             break;
173           }
174         }
175       }
176     }
177   }
178 }
179