Coverage Report

Created: 2026-07-25 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/textord/bbgrid.cpp
Line
Count
Source
1
///////////////////////////////////////////////////////////////////////
2
// File:        bbgrid.cpp
3
// Description: Class to hold BLOBNBOXs in a grid for fast access
4
//              to neighbours.
5
// Author:      Ray Smith
6
//
7
// (C) Copyright 2007, Google Inc.
8
// Licensed under the Apache License, Version 2.0 (the "License");
9
// you may not use this file except in compliance with the License.
10
// You may obtain a copy of the License at
11
// http://www.apache.org/licenses/LICENSE-2.0
12
// Unless required by applicable law or agreed to in writing, software
13
// distributed under the License is distributed on an "AS IS" BASIS,
14
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
// See the License for the specific language governing permissions and
16
// limitations under the License.
17
//
18
///////////////////////////////////////////////////////////////////////
19
20
#include "bbgrid.h"
21
#include "helpers.h"
22
#include "image.h"    // for Image
23
#include "ocrblock.h"
24
25
namespace tesseract {
26
27
///////////////////////////////////////////////////////////////////////
28
// BBGrid IMPLEMENTATION.
29
///////////////////////////////////////////////////////////////////////
30
0
GridBase::GridBase(int gridsize, const ICOORD &bleft, const ICOORD &tright) {
31
0
  Init(gridsize, bleft, tright);
32
0
}
33
34
// Destructor.
35
// It is defined here, so the compiler can create a single vtable
36
// instead of weak vtables in every compilation unit.
37
13.8k
GridBase::~GridBase() = default;
38
39
// (Re)Initialize the grid. The gridsize is the size in pixels of each cell,
40
// and bleft, tright are the bounding box of everything to go in it.
41
13.8k
void GridBase::Init(int gridsize, const ICOORD &bleft, const ICOORD &tright) {
42
13.8k
  gridsize_ = gridsize;
43
13.8k
  bleft_ = bleft;
44
13.8k
  tright_ = tright;
45
13.8k
  if (gridsize_ == 0) {
46
0
    gridsize_ = 1;
47
0
  }
48
13.8k
  gridwidth_ = (tright.x() - bleft.x() + gridsize_ - 1) / gridsize_;
49
13.8k
  gridheight_ = (tright.y() - bleft.y() + gridsize_ - 1) / gridsize_;
50
13.8k
  gridbuckets_ = gridwidth_ * gridheight_;
51
13.8k
}
52
53
// Compute the given grid coordinates from image coords.
54
392k
void GridBase::GridCoords(int x, int y, int *grid_x, int *grid_y) const {
55
392k
  *grid_x = (x - bleft_.x()) / gridsize_;
56
392k
  *grid_y = (y - bleft_.y()) / gridsize_;
57
392k
  ClipGridCoords(grid_x, grid_y);
58
392k
}
59
60
// Clip the given grid coordinates to fit within the grid.
61
392k
void GridBase::ClipGridCoords(int *x, int *y) const {
62
392k
  *x = ClipToRange(*x, 0, gridwidth_ - 1);
63
392k
  *y = ClipToRange(*y, 0, gridheight_ - 1);
64
392k
}
65
66
0
IntGrid::IntGrid() {
67
0
  grid_ = nullptr;
68
0
}
69
70
0
IntGrid::IntGrid(int gridsize, const ICOORD &bleft, const ICOORD &tright) : grid_(nullptr) {
71
0
  Init(gridsize, bleft, tright);
72
0
}
73
74
0
IntGrid::~IntGrid() {
75
0
  delete[] grid_;
76
0
}
77
78
// (Re)Initialize the grid. The gridsize is the size in pixels of each cell,
79
// and bleft, tright are the bounding box of everything to go in it.
80
0
void IntGrid::Init(int gridsize, const ICOORD &bleft, const ICOORD &tright) {
81
0
  GridBase::Init(gridsize, bleft, tright);
82
0
  delete[] grid_;
83
0
  grid_ = new int[gridbuckets_];
84
0
  Clear();
85
0
}
86
87
// Clear all the ints in the grid to zero.
88
0
void IntGrid::Clear() {
89
0
  for (int i = 0; i < gridbuckets_; ++i) {
90
0
    grid_[i] = 0;
91
0
  }
92
0
}
93
94
// Rotate the grid by rotation, keeping cell contents.
95
// rotation must be a multiple of 90 degrees.
96
// NOTE: due to partial cells, cell coverage in the rotated grid will be
97
// inexact. This is why there is no Rotate for the generic BBGrid.
98
// TODO(rays) investigate fixing this inaccuracy by moving the origin after
99
// rotation.
100
0
void IntGrid::Rotate(const FCOORD &rotation) {
101
0
  ASSERT_HOST(rotation.x() == 0.0f || rotation.y() == 0.0f);
102
0
  ICOORD old_bleft(bleft());
103
  // ICOORD old_tright(tright());
104
0
  int old_width = gridwidth();
105
0
  int old_height = gridheight();
106
0
  TBOX box(bleft(), tright());
107
0
  box.rotate(rotation);
108
0
  int *old_grid = grid_;
109
0
  grid_ = nullptr;
110
0
  Init(gridsize(), box.botleft(), box.topright());
111
  // Iterate over the old grid, copying data to the rotated position in the new.
112
0
  int oldi = 0;
113
0
  FCOORD x_step(rotation);
114
0
  x_step *= gridsize();
115
0
  for (int oldy = 0; oldy < old_height; ++oldy) {
116
0
    FCOORD line_pos(old_bleft.x(), old_bleft.y() + gridsize() * oldy);
117
0
    line_pos.rotate(rotation);
118
0
    for (int oldx = 0; oldx < old_width; ++oldx, line_pos += x_step, ++oldi) {
119
0
      int grid_x, grid_y;
120
0
      GridCoords(static_cast<int>(line_pos.x() + 0.5), static_cast<int>(line_pos.y() + 0.5),
121
0
                 &grid_x, &grid_y);
122
0
      grid_[grid_y * gridwidth() + grid_x] = old_grid[oldi];
123
0
    }
124
0
  }
125
0
  delete[] old_grid;
126
0
}
127
128
// Returns a new IntGrid containing values equal to the sum of all the
129
// neighbouring cells. The returned grid must be deleted after use.
130
// For ease of implementation, edge cells are double counted, to make them
131
// have the same range as the non-edge cells.
132
0
IntGrid *IntGrid::NeighbourhoodSum() const {
133
0
  auto *sumgrid = new IntGrid(gridsize(), bleft(), tright());
134
0
  for (int y = 0; y < gridheight(); ++y) {
135
0
    for (int x = 0; x < gridwidth(); ++x) {
136
0
      int cell_count = 0;
137
0
      for (int yoffset = -1; yoffset <= 1; ++yoffset) {
138
0
        for (int xoffset = -1; xoffset <= 1; ++xoffset) {
139
0
          int grid_x = x + xoffset;
140
0
          int grid_y = y + yoffset;
141
0
          ClipGridCoords(&grid_x, &grid_y);
142
0
          cell_count += GridCellValue(grid_x, grid_y);
143
0
        }
144
0
      }
145
0
      if (GridCellValue(x, y) > 1) {
146
0
        sumgrid->SetGridCell(x, y, cell_count);
147
0
      }
148
0
    }
149
0
  }
150
0
  return sumgrid;
151
0
}
152
153
// Returns true if more than half the area of the rect is covered by grid
154
// cells that are over the threshold.
155
0
bool IntGrid::RectMostlyOverThreshold(const TBOX &rect, int threshold) const {
156
0
  int min_x, min_y, max_x, max_y;
157
0
  GridCoords(rect.left(), rect.bottom(), &min_x, &min_y);
158
0
  GridCoords(rect.right(), rect.top(), &max_x, &max_y);
159
0
  int total_area = 0;
160
0
  for (int y = min_y; y <= max_y; ++y) {
161
0
    for (int x = min_x; x <= max_x; ++x) {
162
0
      int value = GridCellValue(x, y);
163
0
      if (value > threshold) {
164
0
        TBOX cell_box(x * gridsize_, y * gridsize_, (x + 1) * gridsize_, (y + 1) * gridsize_);
165
0
        cell_box &= rect; // This is in-place box intersection.
166
0
        total_area += cell_box.area();
167
0
      }
168
0
    }
169
0
  }
170
0
  return total_area * 2 > rect.area();
171
0
}
172
173
// Returns true if any cell value in the given rectangle is zero.
174
0
bool IntGrid::AnyZeroInRect(const TBOX &rect) const {
175
0
  int min_x, min_y, max_x, max_y;
176
0
  GridCoords(rect.left(), rect.bottom(), &min_x, &min_y);
177
0
  GridCoords(rect.right(), rect.top(), &max_x, &max_y);
178
0
  for (int y = min_y; y <= max_y; ++y) {
179
0
    for (int x = min_x; x <= max_x; ++x) {
180
0
      if (GridCellValue(x, y) == 0) {
181
0
        return true;
182
0
      }
183
0
    }
184
0
  }
185
0
  return false;
186
0
}
187
188
// Returns a full-resolution binary pix in which each cell over the given
189
// threshold is filled as a black square. pixDestroy after use.
190
// Edge cells, which have a zero 4-neighbour, are not marked.
191
0
Image IntGrid::ThresholdToPix(int threshold) const {
192
0
  Image pix = pixCreate(tright().x() - bleft().x(), tright().y() - bleft().y(), 1);
193
0
  int cellsize = gridsize();
194
0
  for (int y = 0; y < gridheight(); ++y) {
195
0
    for (int x = 0; x < gridwidth(); ++x) {
196
0
      if (GridCellValue(x, y) > threshold && GridCellValue(x - 1, y) > 0 &&
197
0
          GridCellValue(x + 1, y) > 0 && GridCellValue(x, y - 1) > 0 &&
198
0
          GridCellValue(x, y + 1) > 0) {
199
0
        pixRasterop(pix, x * cellsize, tright().y() - ((y + 1) * cellsize), cellsize, cellsize,
200
0
                    PIX_SET, nullptr, 0, 0);
201
0
      }
202
0
    }
203
0
  }
204
0
  return pix;
205
0
}
206
207
// Make a Pix of the correct scaled size for the TraceOutline functions.
208
0
static Image GridReducedPix(const TBOX &box, int gridsize, ICOORD bleft, int *left, int *bottom) {
209
  // Compute grid bounds of the outline and pad all round by 1.
210
0
  int grid_left = (box.left() - bleft.x()) / gridsize - 1;
211
0
  int grid_bottom = (box.bottom() - bleft.y()) / gridsize - 1;
212
0
  int grid_right = (box.right() - bleft.x()) / gridsize + 1;
213
0
  int grid_top = (box.top() - bleft.y()) / gridsize + 1;
214
0
  *left = grid_left;
215
0
  *bottom = grid_bottom;
216
0
  return pixCreate(grid_right - grid_left + 1, grid_top - grid_bottom + 1, 1);
217
0
}
218
219
// Helper function to return a scaled Pix with one pixel per grid cell,
220
// set (black) where the given outline enters the corresponding grid cell,
221
// and clear where the outline does not touch the grid cell.
222
// Also returns the grid coords of the bottom-left of the Pix, in *left
223
// and *bottom, which corresponds to (0, 0) on the Pix.
224
// Note that the Pix is used upside-down, with (0, 0) being the bottom-left.
225
Image TraceOutlineOnReducedPix(C_OUTLINE *outline, int gridsize, ICOORD bleft, int *left,
226
0
                              int *bottom) {
227
0
  const TBOX &box = outline->bounding_box();
228
0
  Image pix = GridReducedPix(box, gridsize, bleft, left, bottom);
229
0
  int wpl = pixGetWpl(pix);
230
0
  l_uint32 *data = pixGetData(pix);
231
0
  int length = outline->pathlength();
232
0
  ICOORD pos = outline->start_pos();
233
0
  for (int i = 0; i < length; ++i) {
234
0
    int grid_x = (pos.x() - bleft.x()) / gridsize - *left;
235
0
    int grid_y = (pos.y() - bleft.y()) / gridsize - *bottom;
236
0
    Image::setDataBit(data + grid_y * wpl, grid_x);
237
0
    pos += outline->step(i);
238
0
  }
239
0
  return pix;
240
0
}
241
#if 0 // Example code shows how to use TraceOutlineOnReducedPix.
242
  C_OUTLINE_IT ol_it(blob->cblob()->out_list());
243
  int grid_left, grid_bottom;
244
  Pix* pix = TraceOutlineOnReducedPix(ol_it.data(), gridsize_, bleft_,
245
                                      &grid_left, &grid_bottom);
246
  grid->InsertPixPtBBox(grid_left, grid_bottom, pix, blob);
247
  pix.destroy();
248
#endif
249
250
// As TraceOutlineOnReducedPix above, but on a BLOCK instead of a C_OUTLINE.
251
0
Image TraceBlockOnReducedPix(BLOCK *block, int gridsize, ICOORD bleft, int *left, int *bottom) {
252
0
  const TBOX &box = block->pdblk.bounding_box();
253
0
  Image pix = GridReducedPix(box, gridsize, bleft, left, bottom);
254
0
  int wpl = pixGetWpl(pix);
255
0
  l_uint32 *data = pixGetData(pix);
256
0
  ICOORDELT_IT it(block->pdblk.poly_block()->points());
257
0
  for (it.mark_cycle_pt(); !it.cycled_list();) {
258
0
    ICOORD pos = *it.data();
259
0
    it.forward();
260
0
    ICOORD next_pos = *it.data();
261
0
    ICOORD line_vector = next_pos - pos;
262
0
    int major, minor;
263
0
    ICOORD major_step, minor_step;
264
0
    line_vector.setup_render(&major_step, &minor_step, &major, &minor);
265
0
    int accumulator = major / 2;
266
0
    while (pos != next_pos) {
267
0
      int grid_x = (pos.x() - bleft.x()) / gridsize - *left;
268
0
      int grid_y = (pos.y() - bleft.y()) / gridsize - *bottom;
269
0
      Image::setDataBit(data + grid_y * wpl, grid_x);
270
0
      pos += major_step;
271
0
      accumulator += minor;
272
0
      if (accumulator >= major) {
273
0
        accumulator -= major;
274
0
        pos += minor_step;
275
0
      }
276
0
    }
277
0
  }
278
0
  return pix;
279
0
}
280
281
} // namespace tesseract.