Engauge Digitizer  2
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  for (int index = 0; index < count; index++) {
79 
80  // Replace PIXEL_STATE_REMOVED by PIXEL_STATE_HEALED
81  double s = (double) index / (double) (count - 1);
82  int xCol = (int) (0.5 + (1.0 - s) * pixelPointFrom.y() + s * pixelPointTo.y());
83  int yRow = (int) (0.5 + (1.0 - s) * pixelPointFrom.x() + s * pixelPointTo.x());
84  m_pixels [yRow] [xCol] = PIXEL_STATE_HEALED;
85 
86  // Fill in the pixel
87  imageToHeal.setPixel (QPoint (xCol,
88  yRow),
89  Qt::black);
90  }
91  }
92  }
93  }
94 }
95 
96 void GridHealer::erasePixel (int xCol,
97  int yRow)
98 {
99  m_pixels [yRow] [xCol] = PIXEL_STATE_REMOVED;
100 
101  for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
102  int rowSearch = yRow + rowOffset;
103  if (0 <= rowSearch && rowSearch < m_pixels.count()) {
104 
105  for (int colOffset = -1; colOffset <= 1; colOffset++) {
106  int colSearch = xCol + colOffset;
107  if (0 <= colSearch && colSearch < m_pixels[0].count()) {
108 
109  if (m_pixels [rowSearch] [colSearch] == PIXEL_STATE_FOREGROUND) {
110 
111  m_pixels [rowSearch] [colSearch] = PIXEL_STATE_ADJACENT;
112 
113  }
114  }
115  }
116  }
117  }
118 }
119 
120 void GridHealer::groupContiguousAdjacentPixels()
121 {
122  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::groupContiguousAdjacentPixels";
123 
124  for (int row = 0; row < m_pixels.count(); row++) {
125  for (int col = 0; col < m_pixels [0].count(); col++) {
126 
127  if (m_pixels [row] [col] == PIXEL_STATE_ADJACENT) {
128 
129  // This adjacent pixel will be grouped together with all touching adjacent pixels.
130  // A centroid is calculated
131  int centroidCount = 0;
132  double rowCentroidSum = 0, colCentroidSum = 0;
133 
134  recursiveSearchForAdjacentPixels (m_boundaryGroupNext,
135  row,
136  col,
137  centroidCount,
138  rowCentroidSum,
139  colCentroidSum);
140 
141  // Save the centroid and a representative point in hash tables that are indexed by group number
142  m_groupNumberToCentroid [m_boundaryGroupNext] = QPointF (rowCentroidSum / centroidCount,
143  colCentroidSum / centroidCount);
144  m_groupNumberToPixel [m_boundaryGroupNext] = QPointF (row,
145  col);
146 
147  ++m_boundaryGroupNext;
148  }
149  }
150  }
151 }
152 
153 void GridHealer::heal (QImage &imageToHeal)
154 {
155  LOG4CPP_INFO_S ((*mainCat)) << "GridHealer::heal";
156 
157  groupContiguousAdjacentPixels ();
158  connectCloseGroups (imageToHeal);
159 }
160 
161 void GridHealer::recursiveSearchForAdjacentPixels (int boundaryGroup,
162  int row,
163  int col,
164  int &centroidCount,
165  double &rowCentroidSum,
166  double &colCentroidSum)
167 {
168  ENGAUGE_ASSERT (m_pixels [row] [col] == PIXEL_STATE_ADJACENT);
169 
170  // Merge coordinates into centroid
171  ++centroidCount;
172  rowCentroidSum += row;
173  colCentroidSum += col;
174 
175  m_pixels [row] [col] = boundaryGroup;
176 
177  for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
178  int rowNeighbor = row + rowOffset;
179  if (0 <= rowNeighbor && rowNeighbor < m_pixels.count()) {
180 
181  for (int colOffset = -1; colOffset <= 1; colOffset++) {
182  int colNeighbor = col + colOffset;
183  if (0 <= colNeighbor && colNeighbor < m_pixels[0].count()) {
184 
185  if (m_pixels [rowNeighbor] [colNeighbor] == PIXEL_STATE_ADJACENT) {
186 
187  recursiveSearchForAdjacentPixels (boundaryGroup,
188  rowNeighbor,
189  colNeighbor,
190  centroidCount,
191  rowCentroidSum,
192  colCentroidSum);
193  }
194  }
195  }
196  }
197  }
198 }
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:96
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:153