Engauge Digitizer  2
 All Classes Functions Variables Typedefs Enumerations Friends Pages
GridHealer.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 "Logger.h"
11 #include <QImage>
12 #include <qmath.h>
13 #include <QRgb>
14 
15 // Group numbers start at this value. Each group is effectively its own pixel state
16 const BoundaryGroup BOUNDARY_GROUP_FIRST = 100;
17 
18 GridHealer::GridHealer(const QImage &imageBefore,
19  const DocumentModelGridRemoval &modelGridRemoval) :
20  m_boundaryGroupNext (BOUNDARY_GROUP_FIRST),
21  m_modelGridRemoval (modelGridRemoval)
22 {
23  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::GridHealer";
24 
25  // Prevent ambiguity between PixelState and the group numbers
26  ENGAUGE_ASSERT (NUM_PIXEL_STATES < BOUNDARY_GROUP_FIRST);
27 
28  m_pixels.resize (imageBefore.height());
29  for (int row = 0; row < imageBefore.height(); row++) {
30  m_pixels [row].resize (imageBefore.width());
31 
32  for (int col = 0; col < imageBefore.width(); col++) {
33 
34  QRgb rgb = imageBefore.pixel(col, row);
35  if (qGray (rgb) > 128) {
36  m_pixels [row] [col] = PIXEL_STATE_BACKGROUND;
37  } else {
38  m_pixels [row] [col] = PIXEL_STATE_FOREGROUND;
39  }
40  }
41  }
42 }
43 
44 void GridHealer::connectCloseGroups(QImage &imageToHeal)
45 {
46  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::connectCloseGroups";
47 
48  // N*(N-1)/2 search for groups that are close to each other
49  for (int iFrom = 0; iFrom < m_groupNumberToCentroid.count() - 1; iFrom++) {
50 
51  BoundaryGroup groupFrom = m_groupNumberToCentroid.keys().at (iFrom);
52 
53  ENGAUGE_ASSERT (m_groupNumberToCentroid.contains (groupFrom));
54  ENGAUGE_ASSERT (m_groupNumberToPixel.contains (groupFrom));
55 
56  QPointF posCentroidFrom = m_groupNumberToCentroid [groupFrom];
57  QPointF pixelPointFrom = m_groupNumberToPixel [groupFrom];
58 
59  for (int iTo = iFrom + 1; iTo < m_groupNumberToCentroid.count(); iTo++) {
60 
61  BoundaryGroup groupTo = m_groupNumberToCentroid.keys().at (iTo);
62 
63  ENGAUGE_ASSERT (m_groupNumberToCentroid.contains (groupTo));
64  ENGAUGE_ASSERT (m_groupNumberToPixel.contains (groupTo));
65 
66  QPointF posCentroidTo = m_groupNumberToCentroid [groupTo];
67  QPointF pixelPointTo = m_groupNumberToPixel [groupTo];
68 
69  QPointF separation = posCentroidFrom - posCentroidTo;
70  double separationMagnitude = qSqrt (separation.x() * separation.x() + separation.y() * separation.y());
71 
72  if (separationMagnitude < m_modelGridRemoval.closeDistance()) {
73 
74  // Draw line from pixelPointFrom to pixelPointTo
75  int count = 1 + qMax (qAbs (pixelPointFrom.x() - pixelPointTo.x()),
76  qAbs (pixelPointFrom.y() - pixelPointTo.y()));
77 
78  if (count > 1) {
79  for (int index = 0; index < count; index++) {
80 
81  // Replace PIXEL_STATE_REMOVED by PIXEL_STATE_HEALED
82  double s = (double) index / (double) (count - 1);
83  int xCol = (int) (0.5 + (1.0 - s) * pixelPointFrom.y() + s * pixelPointTo.y());
84  int yRow = (int) (0.5 + (1.0 - s) * pixelPointFrom.x() + s * pixelPointTo.x());
85  m_pixels [yRow] [xCol] = PIXEL_STATE_HEALED;
86 
87  // Fill in the pixel
88  imageToHeal.setPixel (QPoint (xCol,
89  yRow),
90  Qt::black);
91  }
92  }
93  }
94  }
95  }
96 }
97 
98 void GridHealer::erasePixel (int xCol,
99  int yRow)
100 {
101  m_pixels [yRow] [xCol] = PIXEL_STATE_REMOVED;
102 
103  for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
104  int rowSearch = yRow + rowOffset;
105  if (0 <= rowSearch && rowSearch < m_pixels.count()) {
106 
107  for (int colOffset = -1; colOffset <= 1; colOffset++) {
108  int colSearch = xCol + colOffset;
109  if (0 <= colSearch && colSearch < m_pixels[0].count()) {
110 
111  if (m_pixels [rowSearch] [colSearch] == PIXEL_STATE_FOREGROUND) {
112 
113  m_pixels [rowSearch] [colSearch] = PIXEL_STATE_ADJACENT;
114 
115  }
116  }
117  }
118  }
119  }
120 }
121 
122 void GridHealer::groupContiguousAdjacentPixels()
123 {
124  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::groupContiguousAdjacentPixels";
125 
126  for (int row = 0; row < m_pixels.count(); row++) {
127  for (int col = 0; col < m_pixels [0].count(); col++) {
128 
129  if (m_pixels [row] [col] == PIXEL_STATE_ADJACENT) {
130 
131  // This adjacent pixel will be grouped together with all touching adjacent pixels.
132  // A centroid is calculated
133  int centroidCount = 0;
134  double rowCentroidSum = 0, colCentroidSum = 0;
135 
136  recursiveSearchForAdjacentPixels (m_boundaryGroupNext,
137  row,
138  col,
139  centroidCount,
140  rowCentroidSum,
141  colCentroidSum);
142 
143  if (centroidCount > 0) {
144  // Save the centroid and a representative point in hash tables that are indexed by group number
145  m_groupNumberToCentroid [m_boundaryGroupNext] = QPointF (rowCentroidSum / centroidCount,
146  colCentroidSum / centroidCount);
147  m_groupNumberToPixel [m_boundaryGroupNext] = QPointF (row,
148  col);
149 
150  ++m_boundaryGroupNext;
151  }
152  }
153  }
154  }
155 }
156 
157 void GridHealer::heal (QImage &imageToHeal)
158 {
159  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::heal";
160 
161  groupContiguousAdjacentPixels ();
162  connectCloseGroups (imageToHeal);
163 }
164 
165 void GridHealer::recursiveSearchForAdjacentPixels (int boundaryGroup,
166  int row,
167  int col,
168  int &centroidCount,
169  double &rowCentroidSum,
170  double &colCentroidSum)
171 {
172  ENGAUGE_ASSERT (m_pixels [row] [col] == PIXEL_STATE_ADJACENT);
173 
174  // Merge coordinates into centroid
175  ++centroidCount;
176  rowCentroidSum += row;
177  colCentroidSum += col;
178 
179  m_pixels [row] [col] = boundaryGroup;
180 
181  for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
182  int rowNeighbor = row + rowOffset;
183  if (0 <= rowNeighbor && rowNeighbor < m_pixels.count()) {
184 
185  for (int colOffset = -1; colOffset <= 1; colOffset++) {
186  int colNeighbor = col + colOffset;
187  if (0 <= colNeighbor && colNeighbor < m_pixels[0].count()) {
188 
189  if (m_pixels [rowNeighbor] [colNeighbor] == PIXEL_STATE_ADJACENT) {
190 
191  recursiveSearchForAdjacentPixels (boundaryGroup,
192  rowNeighbor,
193  colNeighbor,
194  centroidCount,
195  rowCentroidSum,
196  colCentroidSum);
197  }
198  }
199  }
200  }
201  }
202 }
double closeDistance() const
Get method for close distance.
void erasePixel(int xCol, int yRow)
Remember that pixel was erased since it belongs to an grid line.
Definition: GridHealer.cpp:98
GridHealer(const QImage &imageBefore, const DocumentModelGridRemoval &modelGridRemoval)
Single constructor.
Definition: GridHealer.cpp:18
Model for DlgSettingsGridRemoval and CmdSettingsGridRemoval. The settings are unstable until the user...
void heal(QImage &imageToHeal)
Heal the broken curve lines by spanning the gaps across the newly-removed grid lines.
Definition: GridHealer.cpp:157