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 "GridHealerAbstractBase.h"
10 #include "GridLog.h"
11 #include "GridTriangleFill.h"
12 #include "Logger.h"
13 #include "Pixels.h"
14 #include <QFile>
15 #include <QImage>
16 #include <qmath.h>
17 #include <QRgb>
18 #include <QTextStream>
19 
GridHealerAbstractBase(GridLog & gridLog,const DocumentModelGridRemoval & modelGridRemoval)20 GridHealerAbstractBase::GridHealerAbstractBase(GridLog &gridLog,
21                                                const DocumentModelGridRemoval &modelGridRemoval) :
22   m_modelGridRemoval (modelGridRemoval),
23   m_maxPointSeparation (0),
24   m_gridLog (gridLog)
25 {
26 }
27 
~GridHealerAbstractBase()28 GridHealerAbstractBase::~GridHealerAbstractBase()
29 {
30 }
31 
addMutualPair(int x0,int y0,int x1,int y1)32 void GridHealerAbstractBase::addMutualPair (int x0,
33                                             int y0,
34                                             int x1,
35                                             int y1)
36 {
37   m_mutualPairHalvesBelow.push_back (QPoint (x0, y0));
38   m_mutualPairHalvesAbove.push_back (QPoint (x1, y1));
39 }
40 
fillTrapezoid(QImage & image,int xBL,int yBL,int xBR,int yBR,int xTR,int yTR,int xTL,int yTL)41 void GridHealerAbstractBase::fillTrapezoid (QImage &image,
42                                             int xBL, int yBL,
43                                             int xBR, int yBR,
44                                             int xTR, int yTR,
45                                             int xTL, int yTL)
46 {
47   // Sanity checks
48   if (xBL == 0 || yBL == 0 || xBR == 0 || yBR == 0 || xTR == 0 || yTR == 0 || xTL == 0 || yTL == 0) {
49     LOG4CPP_ERROR_S ((*mainCat)) << "GridHealerAbstractBase::fillTrapezoid received undefined corner coordinate "
50                                  << "xBL=" << xBL << " yBL=" << yBL << " xBR=" << xBR << " yBR=" << yBR
51                                  << "xTR=" << xTR << " yTR=" << yTR << " xTL=" << xTL << " yTL=" << yTL;
52   }
53 
54   if (!Pixels::pixelIsBlack(image, xBL, yBL)) {
55     LOG4CPP_ERROR_S ((*mainCat)) << "GridHealerAbstractBase::fillTrapezoid has bad bottom left point";
56   }
57   if (!Pixels::pixelIsBlack(image, xBR, yBR)) {
58     LOG4CPP_ERROR_S ((*mainCat)) << "GridHealerAbstractBase::fillTrapezoid has bad bottom right point";
59   }
60   if (!Pixels::pixelIsBlack(image, xTR, yTR)) {
61     LOG4CPP_ERROR_S ((*mainCat)) << "GridHealerAbstractBase::fillTrapezoid has bad top right point";
62   }
63   if (!Pixels::pixelIsBlack(image, xTL, yTL)) {
64     LOG4CPP_ERROR_S ((*mainCat)) << "GridHealerAbstractBase::fillTrapezoid has bad top left point";
65   }
66 
67   // Any quadrilateral (including this trapezoid) can be considered the union of two triangles
68   GridTriangleFill triangleFill;
69   triangleFill.fill (m_gridLog,
70                      image,
71                      QPoint (xBL, yBL),
72                      QPoint (xBR, yBR),
73                      QPoint (xTR, yTR));
74   triangleFill.fill (m_gridLog,
75                      image,
76                      QPoint (xBL, yBL),
77                      QPoint (xTL, yTL),
78                      QPoint (xTR, yTR));
79 }
80 
gridLog()81 GridLog &GridHealerAbstractBase::gridLog ()
82 {
83   return m_gridLog;
84 }
85 
healed(QImage & image)86 void GridHealerAbstractBase::healed (QImage &image)
87 {
88   applyMutualPairs (image);
89   doHealingAcrossGaps (image);
90 }
91 
maxPointSeparation() const92 double GridHealerAbstractBase::maxPointSeparation () const
93 {
94   return m_maxPointSeparation;
95 }
96 
modelGridRemoval()97 DocumentModelGridRemoval &GridHealerAbstractBase::modelGridRemoval()
98 {
99   return m_modelGridRemoval;
100 }
101 
mutualPairHalvesAbove() const102 const MutualPairHalves &GridHealerAbstractBase::mutualPairHalvesAbove () const
103 {
104   return m_mutualPairHalvesAbove;
105 }
106 
mutualPairHalvesBelow() const107 const MutualPairHalves &GridHealerAbstractBase::mutualPairHalvesBelow () const
108 {
109   return m_mutualPairHalvesBelow;
110 }
111 
pixelCountInRegionThreshold(const DocumentModelGridRemoval & modelGridRemoval)112 int GridHealerAbstractBase::pixelCountInRegionThreshold (const DocumentModelGridRemoval &modelGridRemoval)
113 {
114   // For now we will use the close distance as the minimum pixel count
115   return qFloor (modelGridRemoval.closeDistance());
116 }
117 
pointsAreGood(const QImage & image,int x0,int y0,int x1,int y1) const118 bool GridHealerAbstractBase::pointsAreGood (const QImage &image,
119                                             int x0,
120                                             int y0,
121                                             int x1,
122                                             int y1) const
123 {
124   Pixels pixels;
125 
126   int stopCountAt = pixelCountInRegionThreshold (m_modelGridRemoval);
127 
128   // Skip if either endpoint is an unwanted artifact. Look at start point below (since it is connected
129   // to the end point below), and the start point above (which is connected to the end point above)
130   return ((pixels.countBlackPixelsAroundPoint (image, x0, y0, stopCountAt) >= stopCountAt) &&
131           (pixels.countBlackPixelsAroundPoint (image, x1, y1, stopCountAt) >= stopCountAt));
132 }
133 
saveGapSeparation(double gapSeparation)134 void GridHealerAbstractBase::saveGapSeparation (double gapSeparation)
135 {
136   // Right triangle with one edge crossing the gap (separation value) and hypotenuse giving
137   // maximum point separation (closest distance) gives the maximum horizontal separation
138   m_maxPointSeparation = qFloor (qSqrt (qPow (modelGridRemoval().closeDistance(), 2) -
139                                         qPow (gapSeparation, 2)));
140 }
141