tesseract  4.00.00dev
blobbox.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: blobbox.cpp (Formerly blobnbox.c)
3  * Description: Code for the textord blob class.
4  * Author: Ray Smith
5  * Created: Thu Jul 30 09:08:51 BST 1992
6  *
7  * (C) Copyright 1992, Hewlett-Packard Ltd.
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 automatically generated configuration file if running autoconf.
21 #ifdef HAVE_CONFIG_H
22 #include "config_auto.h"
23 #endif
24 
25 #include "blobbox.h"
26 #include "allheaders.h"
27 #include "blobs.h"
28 #include "helpers.h"
29 #include "normalis.h"
30 
31 #define PROJECTION_MARGIN 10 //arbitrary
32 #define EXTERN
33 
37 
38 // Up to 30 degrees is allowed for rotations of diacritic blobs.
39 const double kCosSmallAngle = 0.866;
40 // Min aspect ratio for a joined word to indicate an obvious flow direction.
41 const double kDefiniteAspectRatio = 2.0;
42 // Multiple of short length in perimeter to make a joined word.
43 const double kComplexShapePerimeterRatio = 1.5;
44 // Min multiple of linesize for medium-sized blobs in ReFilterBlobs.
45 const double kMinMediumSizeRatio = 0.25;
46 // Max multiple of linesize for medium-sized blobs in ReFilterBlobs.
47 const double kMaxMediumSizeRatio = 4.0;
48 
49 // Rotates the box and the underlying blob.
50 void BLOBNBOX::rotate(FCOORD rotation) {
51  cblob_ptr->rotate(rotation);
52  rotate_box(rotation);
53  compute_bounding_box();
54 }
55 
56 // Reflect the box in the y-axis, leaving the underlying blob untouched.
58  int left = -box.right();
59  box.set_right(-box.left());
60  box.set_left(left);
61 }
62 
63 // Rotates the box by the angle given by rotation.
64 // If the blob is a diacritic, then only small rotations for skew
65 // correction can be applied.
66 void BLOBNBOX::rotate_box(FCOORD rotation) {
67  if (IsDiacritic()) {
68  ASSERT_HOST(rotation.x() >= kCosSmallAngle)
69  ICOORD top_pt((box.left() + box.right()) / 2, base_char_top_);
70  ICOORD bottom_pt(top_pt.x(), base_char_bottom_);
71  top_pt.rotate(rotation);
72  base_char_top_ = top_pt.y();
73  bottom_pt.rotate(rotation);
74  base_char_bottom_ = bottom_pt.y();
75  box.rotate(rotation);
76  } else {
77  box.rotate(rotation);
78  set_diacritic_box(box);
79  }
80 }
81 
82 /**********************************************************************
83  * BLOBNBOX::merge
84  *
85  * Merge this blob with the given blob, which should be after this.
86  **********************************************************************/
87 void BLOBNBOX::merge( //merge blobs
88  BLOBNBOX *nextblob //blob to join with
89  ) {
90  box += nextblob->box; //merge boxes
91  set_diacritic_box(box);
92  nextblob->joined = TRUE;
93 }
94 
95 
96 // Merge this with other, taking the outlines from other.
97 // Other is not deleted, but left for the caller to handle.
99  if (cblob_ptr != NULL && other->cblob_ptr != NULL) {
100  C_OUTLINE_IT ol_it(cblob_ptr->out_list());
101  ol_it.add_list_after(other->cblob_ptr->out_list());
102  }
104 }
105 
106 
107 /**********************************************************************
108  * BLOBNBOX::chop
109  *
110  * Chop this blob into equal sized pieces using the x height as a guide.
111  * The blob is not actually chopped. Instead, fake blobs are inserted
112  * with the relevant bounding boxes.
113  **********************************************************************/
114 
115 void BLOBNBOX::chop( //chop blobs
116  BLOBNBOX_IT *start_it, //location of this
117  BLOBNBOX_IT *end_it, //iterator
118  FCOORD rotation, //for landscape
119  float xheight //of line
120  ) {
121  inT16 blobcount; //no of blobs
122  BLOBNBOX *newblob; //fake blob
123  BLOBNBOX *blob; //current blob
124  inT16 blobindex; //number of chop
125  inT16 leftx; //left edge of blob
126  float blobwidth; //width of each
127  float rightx; //right edge to scan
128  float ymin, ymax; //limits of new blob
129  float test_ymin, test_ymax; //limits of part blob
130  ICOORD bl, tr; //corners of box
131  BLOBNBOX_IT blob_it; //blob iterator
132 
133  //get no of chops
134  blobcount = (inT16) floor (box.width () / xheight);
135  if (blobcount > 1 && cblob_ptr != NULL) {
136  //width of each
137  blobwidth = (float) (box.width () + 1) / blobcount;
138  for (blobindex = blobcount - 1, rightx = box.right ();
139  blobindex >= 0; blobindex--, rightx -= blobwidth) {
140  ymin = (float) MAX_INT32;
141  ymax = (float) -MAX_INT32;
142  blob_it = *start_it;
143  do {
144  blob = blob_it.data ();
145  find_cblob_vlimits(blob->cblob_ptr, rightx - blobwidth,
146  rightx,
147  /*rotation, */ test_ymin, test_ymax);
148  blob_it.forward ();
149  UpdateRange(test_ymin, test_ymax, &ymin, &ymax);
150  }
151  while (blob != end_it->data ());
152  if (ymin < ymax) {
153  leftx = (inT16) floor (rightx - blobwidth);
154  if (leftx < box.left ())
155  leftx = box.left (); //clip to real box
156  bl = ICOORD (leftx, (inT16) floor (ymin));
157  tr = ICOORD ((inT16) ceil (rightx), (inT16) ceil (ymax));
158  if (blobindex == 0)
159  box = TBOX (bl, tr); //change box
160  else {
161  newblob = new BLOBNBOX;
162  //box is all it has
163  newblob->box = TBOX (bl, tr);
164  //stay on current
165  newblob->base_char_top_ = tr.y();
166  newblob->base_char_bottom_ = bl.y();
167  end_it->add_after_stay_put (newblob);
168  }
169  }
170  }
171  }
172 }
173 
174 // Returns the box gaps between this and its neighbours_ in an array
175 // indexed by BlobNeighbourDir.
176 void BLOBNBOX::NeighbourGaps(int gaps[BND_COUNT]) const {
177  for (int dir = 0; dir < BND_COUNT; ++dir) {
178  gaps[dir] = MAX_INT16;
179  BLOBNBOX* neighbour = neighbours_[dir];
180  if (neighbour != NULL) {
181  const TBOX& n_box = neighbour->bounding_box();
182  if (dir == BND_LEFT || dir == BND_RIGHT) {
183  gaps[dir] = box.x_gap(n_box);
184  } else {
185  gaps[dir] = box.y_gap(n_box);
186  }
187  }
188  }
189 }
190 // Returns the min and max horizontal and vertical gaps (from NeighbourGaps)
191 // modified so that if the max exceeds the max dimension of the blob, and
192 // the min is less, the max is replaced with the min.
193 // The objective is to catch cases where there is only a single neighbour
194 // and avoid reporting the other gap as a ridiculously large number
195 void BLOBNBOX::MinMaxGapsClipped(int* h_min, int* h_max,
196  int* v_min, int* v_max) const {
197  int max_dimension = MAX(box.width(), box.height());
198  int gaps[BND_COUNT];
199  NeighbourGaps(gaps);
200  *h_min = MIN(gaps[BND_LEFT], gaps[BND_RIGHT]);
201  *h_max = MAX(gaps[BND_LEFT], gaps[BND_RIGHT]);
202  if (*h_max > max_dimension && *h_min < max_dimension) *h_max = *h_min;
203  *v_min = MIN(gaps[BND_ABOVE], gaps[BND_BELOW]);
204  *v_max = MAX(gaps[BND_ABOVE], gaps[BND_BELOW]);
205  if (*v_max > max_dimension && *v_min < max_dimension) *v_max = *v_min;
206 }
207 
208 // NULLs out any neighbours that are DeletableNoise to remove references.
210  for (int dir = 0; dir < BND_COUNT; ++dir) {
211  BLOBNBOX* neighbour = neighbours_[dir];
212  if (neighbour != NULL && neighbour->DeletableNoise()) {
213  neighbours_[dir] = NULL;
214  good_stroke_neighbours_[dir] = false;
215  }
216  }
217 }
218 
219 // Returns positive if there is at least one side neighbour that has a similar
220 // stroke width and is not on the other side of a rule line.
222  int score = 0;
223  for (int dir = 0; dir < BND_COUNT; ++dir) {
224  BlobNeighbourDir bnd = static_cast<BlobNeighbourDir>(dir);
225  if (good_stroke_neighbour(bnd))
226  ++score;
227  }
228  return score;
229 }
230 
231 // Returns the number of side neighbours that are of type BRT_NOISE.
233  int count = 0;
234  for (int dir = 0; dir < BND_COUNT; ++dir) {
235  BlobNeighbourDir bnd = static_cast<BlobNeighbourDir>(dir);
236  BLOBNBOX* blob = neighbour(bnd);
237  if (blob != NULL && blob->region_type() == BRT_NOISE)
238  ++count;
239  }
240  return count;
241 }
242 
243 // Returns true, and sets vert_possible/horz_possible if the blob has some
244 // feature that makes it individually appear to flow one way.
245 // eg if it has a high aspect ratio, yet has a complex shape, such as a
246 // joined word in Latin, Arabic, or Hindi, rather than being a -, I, l, 1 etc.
248  if (cblob() == NULL) return false;
249  int box_perimeter = 2 * (box.height() + box.width());
250  if (box.width() > box.height() * kDefiniteAspectRatio) {
251  // Attempt to distinguish a wide joined word from a dash.
252  // If it is a dash, then its perimeter is approximately
253  // 2 * (box width + stroke width), but more if the outline is noisy,
254  // so perimeter - 2*(box width + stroke width) should be close to zero.
255  // A complex shape such as a joined word should have a much larger value.
256  int perimeter = cblob()->perimeter();
257  if (vert_stroke_width() > 0 || perimeter <= 0)
258  perimeter -= 2 * vert_stroke_width();
259  else
260  perimeter -= 4 * cblob()->area() / perimeter;
261  perimeter -= 2 * box.width();
262  // Use a multiple of the box perimeter as a threshold.
263  if (perimeter > kComplexShapePerimeterRatio * box_perimeter) {
264  set_vert_possible(false);
265  set_horz_possible(true);
266  return true;
267  }
268  }
269  if (box.height() > box.width() * kDefiniteAspectRatio) {
270  // As above, but for a putative vertical word vs a I/1/l.
271  int perimeter = cblob()->perimeter();
272  if (horz_stroke_width() > 0 || perimeter <= 0)
273  perimeter -= 2 * horz_stroke_width();
274  else
275  perimeter -= 4 * cblob()->area() / perimeter;
276  perimeter -= 2 * box.height();
277  if (perimeter > kComplexShapePerimeterRatio * box_perimeter) {
278  set_vert_possible(true);
279  set_horz_possible(false);
280  return true;
281  }
282  }
283  return false;
284 }
285 
286 // Returns true if there is no tabstop violation in merging this and other.
287 bool BLOBNBOX::ConfirmNoTabViolation(const BLOBNBOX& other) const {
288  if (box.left() < other.box.left() && box.left() < other.left_rule_)
289  return false;
290  if (other.box.left() < box.left() && other.box.left() < left_rule_)
291  return false;
292  if (box.right() > other.box.right() && box.right() > other.right_rule_)
293  return false;
294  if (other.box.right() > box.right() && other.box.right() > right_rule_)
295  return false;
296  return true;
297 }
298 
299 // Returns true if other has a similar stroke width to this.
301  double fractional_tolerance,
302  double constant_tolerance) const {
303  // The perimeter-based width is used as a backup in case there is
304  // no information in the blob.
305  double p_width = area_stroke_width();
306  double n_p_width = other.area_stroke_width();
307  float h_tolerance = horz_stroke_width_ * fractional_tolerance
308  + constant_tolerance;
309  float v_tolerance = vert_stroke_width_ * fractional_tolerance
310  + constant_tolerance;
311  double p_tolerance = p_width * fractional_tolerance
312  + constant_tolerance;
313  bool h_zero = horz_stroke_width_ == 0.0f || other.horz_stroke_width_ == 0.0f;
314  bool v_zero = vert_stroke_width_ == 0.0f || other.vert_stroke_width_ == 0.0f;
315  bool h_ok = !h_zero && NearlyEqual(horz_stroke_width_,
316  other.horz_stroke_width_, h_tolerance);
317  bool v_ok = !v_zero && NearlyEqual(vert_stroke_width_,
318  other.vert_stroke_width_, v_tolerance);
319  bool p_ok = h_zero && v_zero && NearlyEqual(p_width, n_p_width, p_tolerance);
320  // For a match, at least one of the horizontal and vertical widths
321  // must match, and the other one must either match or be zero.
322  // Only if both are zero will we look at the perimeter metric.
323  return p_ok || ((v_ok || h_ok) && (h_ok || h_zero) && (v_ok || v_zero));
324 }
325 
326 // Returns a bounding box of the outline contained within the
327 // given horizontal range.
328 TBOX BLOBNBOX::BoundsWithinLimits(int left, int right) {
329  FCOORD no_rotation(1.0f, 0.0f);
330  float top = box.top();
331  float bottom = box.bottom();
332  if (cblob_ptr != NULL) {
333  find_cblob_limits(cblob_ptr, static_cast<float>(left),
334  static_cast<float>(right), no_rotation,
335  bottom, top);
336  }
337 
338  if (top < bottom) {
339  top = box.top();
340  bottom = box.bottom();
341  }
342  FCOORD bot_left(left, bottom);
343  FCOORD top_right(right, top);
344  TBOX shrunken_box(bot_left);
345  TBOX shrunken_box2(top_right);
346  shrunken_box += shrunken_box2;
347  return shrunken_box;
348 }
349 
350 // Estimates and stores the baseline position based on the shape of the
351 // outline.
353  baseline_y_ = box.bottom(); // The default.
354  if (cblob_ptr == NULL) return;
355  baseline_y_ = cblob_ptr->EstimateBaselinePosition();
356 }
357 
358 // Helper to call CleanNeighbours on all blobs on the list.
359 void BLOBNBOX::CleanNeighbours(BLOBNBOX_LIST* blobs) {
360  BLOBNBOX_IT blob_it(blobs);
361  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
362  blob_it.data()->CleanNeighbours();
363  }
364 }
365 
366 // Helper to delete all the deletable blobs on the list.
367 void BLOBNBOX::DeleteNoiseBlobs(BLOBNBOX_LIST* blobs) {
368  BLOBNBOX_IT blob_it(blobs);
369  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
370  BLOBNBOX* blob = blob_it.data();
371  if (blob->DeletableNoise()) {
372  delete blob->cblob();
373  delete blob_it.extract();
374  }
375  }
376 }
377 
378 // Helper to compute edge offsets for all the blobs on the list.
379 // See coutln.h for an explanation of edge offsets.
380 void BLOBNBOX::ComputeEdgeOffsets(Pix* thresholds, Pix* grey,
381  BLOBNBOX_LIST* blobs) {
382  int grey_height = 0;
383  int thr_height = 0;
384  int scale_factor = 1;
385  if (thresholds != NULL && grey != NULL) {
386  grey_height = pixGetHeight(grey);
387  thr_height = pixGetHeight(thresholds);
388  scale_factor =
389  IntCastRounded(static_cast<double>(grey_height) / thr_height);
390  }
391  BLOBNBOX_IT blob_it(blobs);
392  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
393  BLOBNBOX* blob = blob_it.data();
394  if (blob->cblob() != NULL) {
395  // Get the threshold that applies to this blob.
396  l_uint32 threshold = 128;
397  if (thresholds != NULL && grey != NULL) {
398  const TBOX& box = blob->cblob()->bounding_box();
399  // Transform the coordinates if required.
400  TPOINT pt((box.left() + box.right()) / 2,
401  (box.top() + box.bottom()) / 2);
402  pixGetPixel(thresholds, pt.x / scale_factor,
403  thr_height - 1 - pt.y / scale_factor, &threshold);
404  }
405  blob->cblob()->ComputeEdgeOffsets(threshold, grey);
406  }
407  }
408 }
409 
410 
411 #ifndef GRAPHICS_DISABLED
412 // Helper to draw all the blobs on the list in the given body_colour,
413 // with child outlines in the child_colour.
414 void BLOBNBOX::PlotBlobs(BLOBNBOX_LIST* list,
415  ScrollView::Color body_colour,
416  ScrollView::Color child_colour,
417  ScrollView* win) {
418  BLOBNBOX_IT it(list);
419  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
420  it.data()->plot(win, body_colour, child_colour);
421  }
422 }
423 
424 // Helper to draw only DeletableNoise blobs (unowned, BRT_NOISE) on the
425 // given list in the given body_colour, with child outlines in the
426 // child_colour.
427 void BLOBNBOX::PlotNoiseBlobs(BLOBNBOX_LIST* list,
428  ScrollView::Color body_colour,
429  ScrollView::Color child_colour,
430  ScrollView* win) {
431  BLOBNBOX_IT it(list);
432  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
433  BLOBNBOX* blob = it.data();
434  if (blob->DeletableNoise())
435  blob->plot(win, body_colour, child_colour);
436  }
437 }
438 
440  BlobTextFlowType flow_type) {
441  switch (region_type) {
442  case BRT_HLINE:
443  return ScrollView::BROWN;
444  case BRT_VLINE:
445  return ScrollView::DARK_GREEN;
446  case BRT_RECTIMAGE:
447  return ScrollView::RED;
448  case BRT_POLYIMAGE:
449  return ScrollView::ORANGE;
450  case BRT_UNKNOWN:
451  return flow_type == BTFT_NONTEXT ? ScrollView::CYAN : ScrollView::WHITE;
452  case BRT_VERT_TEXT:
453  if (flow_type == BTFT_STRONG_CHAIN || flow_type == BTFT_TEXT_ON_IMAGE)
454  return ScrollView::GREEN;
455  if (flow_type == BTFT_CHAIN)
456  return ScrollView::LIME_GREEN;
457  return ScrollView::YELLOW;
458  case BRT_TEXT:
459  if (flow_type == BTFT_STRONG_CHAIN)
460  return ScrollView::BLUE;
461  if (flow_type == BTFT_TEXT_ON_IMAGE)
462  return ScrollView::LIGHT_BLUE;
463  if (flow_type == BTFT_CHAIN)
465  if (flow_type == BTFT_LEADER)
466  return ScrollView::WHEAT;
467  if (flow_type == BTFT_NONTEXT)
468  return ScrollView::PINK;
469  return ScrollView::MAGENTA;
470  default:
471  return ScrollView::GREY;
472  }
473 }
474 
475 // Keep in sync with BlobRegionType.
477  return TextlineColor(region_type_, flow_);
478 }
479 
480 void BLOBNBOX::plot(ScrollView* window, // window to draw in
481  ScrollView::Color blob_colour, // for outer bits
482  ScrollView::Color child_colour) { // for holes
483  if (cblob_ptr != NULL)
484  cblob_ptr->plot(window, blob_colour, child_colour);
485 }
486 #endif
487 /**********************************************************************
488  * find_cblob_limits
489  *
490  * Scan the outlines of the cblob to locate the y min and max
491  * between the given x limits.
492  **********************************************************************/
493 
494 void find_cblob_limits( //get y limits
495  C_BLOB *blob, //blob to search
496  float leftx, //x limits
497  float rightx,
498  FCOORD rotation, //for landscape
499  float &ymin, //output y limits
500  float &ymax) {
501  inT16 stepindex; //current point
502  ICOORD pos; //current coords
503  ICOORD vec; //rotated step
504  C_OUTLINE *outline; //current outline
505  //outlines
506  C_OUTLINE_IT out_it = blob->out_list ();
507 
508  ymin = (float) MAX_INT32;
509  ymax = (float) -MAX_INT32;
510  for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
511  outline = out_it.data ();
512  pos = outline->start_pos (); //get coords
513  pos.rotate (rotation);
514  for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
515  //inside
516  if (pos.x () >= leftx && pos.x () <= rightx) {
517  UpdateRange(pos.y(), &ymin, &ymax);
518  }
519  vec = outline->step (stepindex);
520  vec.rotate (rotation);
521  pos += vec; //move to next
522  }
523  }
524 }
525 
526 
527 /**********************************************************************
528  * find_cblob_vlimits
529  *
530  * Scan the outlines of the cblob to locate the y min and max
531  * between the given x limits.
532  **********************************************************************/
533 
534 void find_cblob_vlimits( //get y limits
535  C_BLOB *blob, //blob to search
536  float leftx, //x limits
537  float rightx,
538  float &ymin, //output y limits
539  float &ymax) {
540  inT16 stepindex; //current point
541  ICOORD pos; //current coords
542  ICOORD vec; //rotated step
543  C_OUTLINE *outline; //current outline
544  //outlines
545  C_OUTLINE_IT out_it = blob->out_list ();
546 
547  ymin = (float) MAX_INT32;
548  ymax = (float) -MAX_INT32;
549  for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
550  outline = out_it.data ();
551  pos = outline->start_pos (); //get coords
552  for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
553  //inside
554  if (pos.x () >= leftx && pos.x () <= rightx) {
555  UpdateRange(pos.y(), &ymin, &ymax);
556  }
557  vec = outline->step (stepindex);
558  pos += vec; //move to next
559  }
560  }
561 }
562 
563 
564 /**********************************************************************
565  * find_cblob_hlimits
566  *
567  * Scan the outlines of the cblob to locate the x min and max
568  * between the given y limits.
569  **********************************************************************/
570 
571 void find_cblob_hlimits( //get x limits
572  C_BLOB *blob, //blob to search
573  float bottomy, //y limits
574  float topy,
575  float &xmin, //output x limits
576  float &xmax) {
577  inT16 stepindex; //current point
578  ICOORD pos; //current coords
579  ICOORD vec; //rotated step
580  C_OUTLINE *outline; //current outline
581  //outlines
582  C_OUTLINE_IT out_it = blob->out_list ();
583 
584  xmin = (float) MAX_INT32;
585  xmax = (float) -MAX_INT32;
586  for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
587  outline = out_it.data ();
588  pos = outline->start_pos (); //get coords
589  for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
590  //inside
591  if (pos.y () >= bottomy && pos.y () <= topy) {
592  UpdateRange(pos.x(), &xmin, &xmax);
593  }
594  vec = outline->step (stepindex);
595  pos += vec; //move to next
596  }
597  }
598 }
599 
600 /**********************************************************************
601  * crotate_cblob
602  *
603  * Rotate the copy by the given vector and return a C_BLOB.
604  **********************************************************************/
605 
606 C_BLOB *crotate_cblob( //rotate it
607  C_BLOB *blob, //blob to search
608  FCOORD rotation //for landscape
609  ) {
610  C_OUTLINE_LIST out_list; //output outlines
611  //input outlines
612  C_OUTLINE_IT in_it = blob->out_list ();
613  //output outlines
614  C_OUTLINE_IT out_it = &out_list;
615 
616  for (in_it.mark_cycle_pt (); !in_it.cycled_list (); in_it.forward ()) {
617  out_it.add_after_then_move (new C_OUTLINE (in_it.data (), rotation));
618  }
619  return new C_BLOB (&out_list);
620 }
621 
622 
623 /**********************************************************************
624  * box_next
625  *
626  * Compute the bounding box of this blob with merging of x overlaps
627  * but no pre-chopping.
628  * Then move the iterator on to the start of the next blob.
629  **********************************************************************/
630 
631 TBOX box_next( //get bounding box
632  BLOBNBOX_IT *it //iterator to blobds
633  ) {
634  BLOBNBOX *blob; //current blob
635  TBOX result; //total box
636 
637  blob = it->data ();
638  result = blob->bounding_box ();
639  do {
640  it->forward ();
641  blob = it->data ();
642  if (blob->cblob() == NULL)
643  //was pre-chopped
644  result += blob->bounding_box ();
645  }
646  //until next real blob
647  while ((blob->cblob() == NULL) || blob->joined_to_prev());
648  return result;
649 }
650 
651 
652 /**********************************************************************
653  * box_next_pre_chopped
654  *
655  * Compute the bounding box of this blob with merging of x overlaps
656  * but WITH pre-chopping.
657  * Then move the iterator on to the start of the next pre-chopped blob.
658  **********************************************************************/
659 
660 TBOX box_next_pre_chopped( //get bounding box
661  BLOBNBOX_IT *it //iterator to blobds
662  ) {
663  BLOBNBOX *blob; //current blob
664  TBOX result; //total box
665 
666  blob = it->data ();
667  result = blob->bounding_box ();
668  do {
669  it->forward ();
670  blob = it->data ();
671  }
672  //until next real blob
673  while (blob->joined_to_prev ());
674  return result;
675 }
676 
677 
678 /**********************************************************************
679  * TO_ROW::TO_ROW
680  *
681  * Constructor to make a row from a blob.
682  **********************************************************************/
683 
684 TO_ROW::TO_ROW ( //constructor
685 BLOBNBOX * blob, //first blob
686 float top, //corrected top
687 float bottom, //of row
688 float row_size //ideal
689 ) {
690  clear();
691  y_min = bottom;
692  y_max = top;
693  initial_y_min = bottom;
694 
695  float diff; //in size
696  BLOBNBOX_IT it = &blobs; //list of blobs
697 
698  it.add_to_end (blob);
699  diff = top - bottom - row_size;
700  if (diff > 0) {
701  y_max -= diff / 2;
702  y_min += diff / 2;
703  }
704  //very small object
705  else if ((top - bottom) * 3 < row_size) {
706  diff = row_size / 3 + bottom - top;
707  y_max += diff / 2;
708  y_min -= diff / 2;
709  }
710 }
711 
712 void TO_ROW::print() const {
713  tprintf("pitch=%d, fp=%g, fps=%g, fpns=%g, prs=%g, prns=%g,"
714  " spacing=%g xh=%g y_origin=%g xev=%d, asc=%g, desc=%g,"
715  " body=%g, minsp=%d maxnsp=%d, thr=%d kern=%g sp=%g\n",
716  pitch_decision, fixed_pitch, fp_space, fp_nonsp, pr_space, pr_nonsp,
717  spacing, xheight, y_origin, xheight_evidence, ascrise, descdrop,
718  body_size, min_space, max_nonspace, space_threshold, kern_size,
719  space_size);
720 }
721 
722 /**********************************************************************
723  * TO_ROW:add_blob
724  *
725  * Add the blob to the end of the row.
726  **********************************************************************/
727 
728 void TO_ROW::add_blob( //constructor
729  BLOBNBOX *blob, //first blob
730  float top, //corrected top
731  float bottom, //of row
732  float row_size //ideal
733  ) {
734  float allowed; //allowed expansion
735  float available; //expansion
736  BLOBNBOX_IT it = &blobs; //list of blobs
737 
738  it.add_to_end (blob);
739  allowed = row_size + y_min - y_max;
740  if (allowed > 0) {
741  available = top > y_max ? top - y_max : 0;
742  if (bottom < y_min)
743  //total available
744  available += y_min - bottom;
745  if (available > 0) {
746  available += available; //do it gradually
747  if (available < allowed)
748  available = allowed;
749  if (bottom < y_min)
750  y_min -= (y_min - bottom) * allowed / available;
751  if (top > y_max)
752  y_max += (top - y_max) * allowed / available;
753  }
754  }
755 }
756 
757 
758 /**********************************************************************
759  * TO_ROW:insert_blob
760  *
761  * Add the blob to the row in the correct position.
762  **********************************************************************/
763 
764 void TO_ROW::insert_blob( //constructor
765  BLOBNBOX *blob //first blob
766  ) {
767  BLOBNBOX_IT it = &blobs; //list of blobs
768 
769  if (it.empty ())
770  it.add_before_then_move (blob);
771  else {
772  it.mark_cycle_pt ();
773  while (!it.cycled_list ()
774  && it.data ()->bounding_box ().left () <=
775  blob->bounding_box ().left ())
776  it.forward ();
777  if (it.cycled_list ())
778  it.add_to_end (blob);
779  else
780  it.add_before_stay_put (blob);
781  }
782 }
783 
784 
785 /**********************************************************************
786  * TO_ROW::compute_vertical_projection
787  *
788  * Compute the vertical projection of a TO_ROW from its blobs.
789  **********************************************************************/
790 
791 void TO_ROW::compute_vertical_projection() { //project whole row
792  TBOX row_box; //bound of row
793  BLOBNBOX *blob; //current blob
794  TBOX blob_box; //bounding box
795  BLOBNBOX_IT blob_it = blob_list ();
796 
797  if (blob_it.empty ())
798  return;
799  row_box = blob_it.data ()->bounding_box ();
800  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ())
801  row_box += blob_it.data ()->bounding_box ();
802 
803  projection.set_range (row_box.left () - PROJECTION_MARGIN,
804  row_box.right () + PROJECTION_MARGIN);
805  projection_left = row_box.left () - PROJECTION_MARGIN;
806  projection_right = row_box.right () + PROJECTION_MARGIN;
807  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
808  blob = blob_it.data();
809  if (blob->cblob() != NULL)
810  vertical_cblob_projection(blob->cblob(), &projection);
811  }
812 }
813 
814 
815 /**********************************************************************
816  * TO_ROW::clear
817  *
818  * Zero out all scalar members.
819  **********************************************************************/
820 void TO_ROW::clear() {
821  all_caps = 0;
822  used_dm_model = 0;
823  projection_left = 0;
824  projection_right = 0;
825  pitch_decision = PITCH_DUNNO;
826  fixed_pitch = 0.0;
827  fp_space = 0.0;
828  fp_nonsp = 0.0;
829  pr_space = 0.0;
830  pr_nonsp = 0.0;
831  spacing = 0.0;
832  xheight = 0.0;
833  xheight_evidence = 0;
834  body_size = 0.0;
835  ascrise = 0.0;
836  descdrop = 0.0;
837  min_space = 0;
838  max_nonspace = 0;
839  space_threshold = 0;
840  kern_size = 0.0;
841  space_size = 0.0;
842  y_min = 0.0;
843  y_max = 0.0;
844  initial_y_min = 0.0;
845  m = 0.0;
846  c = 0.0;
847  error = 0.0;
848  para_c = 0.0;
849  para_error = 0.0;
850  y_origin = 0.0;
851  credibility = 0.0;
852  num_repeated_sets_ = -1;
853 }
854 
855 
856 /**********************************************************************
857  * vertical_cblob_projection
858  *
859  * Compute the vertical projection of a cblob from its outlines
860  * and add to the given STATS.
861  **********************************************************************/
862 
863 void vertical_cblob_projection( //project outlines
864  C_BLOB *blob, //blob to project
865  STATS *stats //output
866  ) {
867  //outlines of blob
868  C_OUTLINE_IT out_it = blob->out_list ();
869 
870  for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
871  vertical_coutline_projection (out_it.data (), stats);
872  }
873 }
874 
875 
876 /**********************************************************************
877  * vertical_coutline_projection
878  *
879  * Compute the vertical projection of a outline from its outlines
880  * and add to the given STATS.
881  **********************************************************************/
882 
883 void vertical_coutline_projection( //project outlines
884  C_OUTLINE *outline, //outline to project
885  STATS *stats //output
886  ) {
887  ICOORD pos; //current point
888  ICOORD step; //edge step
889  inT32 length; //of outline
890  inT16 stepindex; //current step
891  C_OUTLINE_IT out_it = outline->child ();
892 
893  pos = outline->start_pos ();
894  length = outline->pathlength ();
895  for (stepindex = 0; stepindex < length; stepindex++) {
896  step = outline->step (stepindex);
897  if (step.x () > 0) {
898  stats->add (pos.x (), -pos.y ());
899  } else if (step.x () < 0) {
900  stats->add (pos.x () - 1, pos.y ());
901  }
902  pos += step;
903  }
904 
905  for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
906  vertical_coutline_projection (out_it.data (), stats);
907  }
908 }
909 
910 
911 /**********************************************************************
912  * TO_BLOCK::TO_BLOCK
913  *
914  * Constructor to make a TO_BLOCK from a real block.
915  **********************************************************************/
916 
917 TO_BLOCK::TO_BLOCK( //make a block
918  BLOCK *src_block //real block
919  ) {
920  clear();
921  block = src_block;
922 }
923 
924 static void clear_blobnboxes(BLOBNBOX_LIST* boxes) {
925  BLOBNBOX_IT it = boxes;
926  // A BLOBNBOX generally doesn't own its blobs, so if they do, you
927  // have to delete them explicitly.
928  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
929  BLOBNBOX* box = it.data();
930  if (box->cblob() != NULL)
931  delete box->cblob();
932  }
933 }
934 
935 /**********************************************************************
936  * TO_BLOCK::clear
937  *
938  * Zero out all scalar members.
939  **********************************************************************/
941  block = NULL;
942  pitch_decision = PITCH_DUNNO;
943  line_spacing = 0.0;
944  line_size = 0.0;
945  max_blob_size = 0.0;
946  baseline_offset = 0.0;
947  xheight = 0.0;
948  fixed_pitch = 0.0;
949  kern_size = 0.0;
950  space_size = 0.0;
951  min_space = 0;
952  max_nonspace = 0;
953  fp_space = 0.0;
954  fp_nonsp = 0.0;
955  pr_space = 0.0;
956  pr_nonsp = 0.0;
957  key_row = NULL;
958 }
959 
960 
962  // Any residual BLOBNBOXes at this stage own their blobs, so delete them.
963  clear_blobnboxes(&blobs);
964  clear_blobnboxes(&underlines);
965  clear_blobnboxes(&noise_blobs);
966  clear_blobnboxes(&small_blobs);
967  clear_blobnboxes(&large_blobs);
968 }
969 
970 // Helper function to divide the input blobs over noise, small, medium
971 // and large lists. Blobs small in height and (small in width or large in width)
972 // go in the noise list. Dash (-) candidates go in the small list, and
973 // medium and large are by height.
974 // SIDE-EFFECT: reset all blobs to initial state by calling Init().
975 static void SizeFilterBlobs(int min_height, int max_height,
976  BLOBNBOX_LIST* src_list,
977  BLOBNBOX_LIST* noise_list,
978  BLOBNBOX_LIST* small_list,
979  BLOBNBOX_LIST* medium_list,
980  BLOBNBOX_LIST* large_list) {
981  BLOBNBOX_IT noise_it(noise_list);
982  BLOBNBOX_IT small_it(small_list);
983  BLOBNBOX_IT medium_it(medium_list);
984  BLOBNBOX_IT large_it(large_list);
985  for (BLOBNBOX_IT src_it(src_list); !src_it.empty(); src_it.forward()) {
986  BLOBNBOX* blob = src_it.extract();
987  blob->ReInit();
988  int width = blob->bounding_box().width();
989  int height = blob->bounding_box().height();
990  if (height < min_height &&
991  (width < min_height || width > max_height))
992  noise_it.add_after_then_move(blob);
993  else if (height > max_height)
994  large_it.add_after_then_move(blob);
995  else if (height < min_height)
996  small_it.add_after_then_move(blob);
997  else
998  medium_it.add_after_then_move(blob);
999  }
1000 }
1001 
1002 // Reorganize the blob lists with a different definition of small, medium
1003 // and large, compared to the original definition.
1004 // Height is still the primary filter key, but medium width blobs of small
1005 // height become small, and very wide blobs of small height stay noise, along
1006 // with small dot-shaped blobs.
1008  int min_height = IntCastRounded(kMinMediumSizeRatio * line_size);
1009  int max_height = IntCastRounded(kMaxMediumSizeRatio * line_size);
1010  BLOBNBOX_LIST noise_list;
1011  BLOBNBOX_LIST small_list;
1012  BLOBNBOX_LIST medium_list;
1013  BLOBNBOX_LIST large_list;
1014  SizeFilterBlobs(min_height, max_height, &blobs,
1015  &noise_list, &small_list, &medium_list, &large_list);
1016  SizeFilterBlobs(min_height, max_height, &large_blobs,
1017  &noise_list, &small_list, &medium_list, &large_list);
1018  SizeFilterBlobs(min_height, max_height, &small_blobs,
1019  &noise_list, &small_list, &medium_list, &large_list);
1020  SizeFilterBlobs(min_height, max_height, &noise_blobs,
1021  &noise_list, &small_list, &medium_list, &large_list);
1022  BLOBNBOX_IT blob_it(&blobs);
1023  blob_it.add_list_after(&medium_list);
1024  blob_it.set_to_list(&large_blobs);
1025  blob_it.add_list_after(&large_list);
1026  blob_it.set_to_list(&small_blobs);
1027  blob_it.add_list_after(&small_list);
1028  blob_it.set_to_list(&noise_blobs);
1029  blob_it.add_list_after(&noise_list);
1030 }
1031 
1032 // Deletes noise blobs from all lists where not owned by a ColPartition.
1034  BLOBNBOX::CleanNeighbours(&blobs);
1035  BLOBNBOX::CleanNeighbours(&small_blobs);
1036  BLOBNBOX::CleanNeighbours(&noise_blobs);
1037  BLOBNBOX::CleanNeighbours(&large_blobs);
1039  BLOBNBOX::DeleteNoiseBlobs(&small_blobs);
1040  BLOBNBOX::DeleteNoiseBlobs(&noise_blobs);
1041  BLOBNBOX::DeleteNoiseBlobs(&large_blobs);
1042 }
1043 
1044 // Computes and stores the edge offsets on each blob for use in feature
1045 // extraction, using greyscale if the supplied grey and thresholds pixes
1046 // are 8-bit or otherwise (if NULL or not 8 bit) the original binary
1047 // edge step outlines.
1048 // Thresholds must either be the same size as grey or an integer down-scale
1049 // of grey.
1050 // See coutln.h for an explanation of edge offsets.
1051 void TO_BLOCK::ComputeEdgeOffsets(Pix* thresholds, Pix* grey) {
1052  BLOBNBOX::ComputeEdgeOffsets(thresholds, grey, &blobs);
1053  BLOBNBOX::ComputeEdgeOffsets(thresholds, grey, &small_blobs);
1054  BLOBNBOX::ComputeEdgeOffsets(thresholds, grey, &noise_blobs);
1055 }
1056 
1057 #ifndef GRAPHICS_DISABLED
1058 // Draw the noise blobs from all lists in red.
1064 }
1065 
1066 // Draw the blobs on the various lists in the block in different colors.
1070  win);
1072  win);
1074 }
1075 
1076 /**********************************************************************
1077  * plot_blob_list
1078  *
1079  * Draw a list of blobs.
1080  **********************************************************************/
1081 
1082 void plot_blob_list(ScrollView* win, // window to draw in
1083  BLOBNBOX_LIST *list, // blob list
1084  ScrollView::Color body_colour, // colour to draw
1085  ScrollView::Color child_colour) { // colour of child
1086  BLOBNBOX_IT it = list;
1087  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1088  it.data()->plot(win, body_colour, child_colour);
1089  }
1090 }
1091 #endif // GRAPHICS_DISABLED
const double kDefiniteAspectRatio
Definition: blobbox.cpp:41
void NeighbourGaps(int gaps[BND_COUNT]) const
Definition: blobbox.cpp:176
TBOX box_next_pre_chopped(BLOBNBOX_IT *it)
Definition: blobbox.cpp:660
C_OUTLINE_LIST * out_list()
Definition: stepblob.h:64
void rotate(const FCOORD &vec)
Definition: ipoints.h:241
C_OUTLINE_LIST * child()
Definition: coutln.h:106
#define TRUE
Definition: capi.h:45
Definition: points.h:189
void ReSetAndReFilterBlobs()
Definition: blobbox.cpp:1007
int32_t inT32
Definition: host.h:38
bool IsDiacritic() const
Definition: blobbox.h:365
const double kMaxMediumSizeRatio
Definition: blobbox.cpp:47
bool good_stroke_neighbour(BlobNeighbourDir n) const
Definition: blobbox.h:358
void plot_graded_blobs(ScrollView *to_win)
Definition: blobbox.cpp:1067
void set_diacritic_box(const TBOX &diacritic_box)
Definition: blobbox.h:383
#define MAX_INT32
Definition: host.h:62
float horz_stroke_width() const
Definition: blobbox.h:322
void find_cblob_hlimits(C_BLOB *blob, float bottomy, float topy, float &xmin, float &xmax)
Definition: blobbox.cpp:571
TO_BLOCK()
Definition: blobbox.h:691
ScrollView::Color BoxColor() const
Definition: blobbox.cpp:476
void rotate_box(FCOORD rotation)
Definition: blobbox.cpp:66
#define MAX_INT16
Definition: host.h:61
float area_stroke_width() const
Definition: blobbox.h:334
inT32 area()
Definition: stepblob.cpp:270
inT16 x() const
access function
Definition: points.h:52
void print() const
Definition: blobbox.cpp:712
#define tprintf(...)
Definition: tprintf.h:31
void plot_noise_blobs(ScrollView *to_win)
Definition: blobbox.cpp:1059
bool ConfirmNoTabViolation(const BLOBNBOX &other) const
Definition: blobbox.cpp:287
int GoodTextBlob() const
Definition: blobbox.cpp:221
void set_horz_possible(bool value)
Definition: blobbox.h:295
bool joined_to_prev() const
Definition: blobbox.h:241
C_BLOB * cblob() const
Definition: blobbox.h:253
void rotate(FCOORD rotation)
Definition: blobbox.cpp:50
void ReInit()
Definition: blobbox.h:466
bool NearlyEqual(T x, T y, T tolerance)
Definition: host.h:87
int IntCastRounded(double x)
Definition: helpers.h:179
bool DeletableNoise() const
Definition: blobbox.h:188
BlobRegionType region_type() const
Definition: blobbox.h:268
int16_t inT16
Definition: host.h:36
static void ComputeEdgeOffsets(Pix *thresholds, Pix *grey, BLOBNBOX_LIST *blobs)
Definition: blobbox.cpp:380
#define ASSERT_HOST(x)
Definition: errcode.h:84
void ComputeEdgeOffsets(Pix *thresholds, Pix *grey)
Definition: blobbox.cpp:1051
inT16 left() const
Definition: rect.h:68
static ScrollView::Color TextlineColor(BlobRegionType region_type, BlobTextFlowType flow_type)
Definition: blobbox.cpp:439
int y_gap(const TBOX &box) const
Definition: rect.h:225
bool MatchingStrokeWidth(const BLOBNBOX &other, double fractional_tolerance, double constant_tolerance) const
Definition: blobbox.cpp:300
#define ELIST2IZE(CLASSNAME)
Definition: elst2.h:962
void MinMaxGapsClipped(int *h_min, int *h_max, int *v_min, int *v_max) const
Definition: blobbox.cpp:195
TBOX BoundsWithinLimits(int left, int right)
Definition: blobbox.cpp:328
TBOX box_next(BLOBNBOX_IT *it)
Definition: blobbox.cpp:631
BLOBNBOX()
Definition: blobbox.h:131
void vertical_cblob_projection(C_BLOB *blob, STATS *stats)
Definition: blobbox.cpp:863
void really_merge(BLOBNBOX *other)
Definition: blobbox.cpp:98
void set_vert_possible(bool value)
Definition: blobbox.h:289
TO_ROW()
Definition: blobbox.h:544
inT16 x
Definition: blobs.h:71
inT16 y() const
access_function
Definition: points.h:56
void reflect_box_in_y_axis()
Definition: blobbox.cpp:57
void find_cblob_vlimits(C_BLOB *blob, float leftx, float rightx, float &ymin, float &ymax)
Definition: blobbox.cpp:534
void DeleteUnownedNoise()
Definition: blobbox.cpp:1033
void ComputeEdgeOffsets(int threshold, Pix *pix)
Definition: stepblob.cpp:409
const double kCosSmallAngle
Definition: blobbox.cpp:39
BLOBNBOX * neighbour(BlobNeighbourDir n) const
Definition: blobbox.h:355
~TO_BLOCK()
Definition: blobbox.cpp:961
void vertical_coutline_projection(C_OUTLINE *outline, STATS *stats)
Definition: blobbox.cpp:883
float vert_stroke_width() const
Definition: blobbox.h:328
void merge(BLOBNBOX *nextblob)
Definition: blobbox.cpp:87
void add(inT32 value, inT32 count)
Definition: statistc.cpp:101
inT16 top() const
Definition: rect.h:54
#define MAX(x, y)
Definition: ndminx.h:24
void plot(ScrollView *window, ScrollView::Color blob_colour, ScrollView::Color child_colour)
Definition: stepblob.cpp:532
void compute_vertical_projection()
Definition: blobbox.cpp:791
inT16 y
Definition: blobs.h:72
static void DeleteNoiseBlobs(BLOBNBOX_LIST *blobs)
Definition: blobbox.cpp:367
Definition: rect.h:30
#define MIN(x, y)
Definition: ndminx.h:28
void chop(BLOBNBOX_IT *start_it, BLOBNBOX_IT *blob_it, FCOORD rotation, float xheight)
Definition: blobbox.cpp:115
ICOORD step(int index) const
Definition: coutln.h:142
void plot_blob_list(ScrollView *win, BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour)
Definition: blobbox.cpp:1082
TBOX bounding_box() const
Definition: stepblob.cpp:250
void CleanNeighbours()
Definition: blobbox.cpp:209
const ICOORD & start_pos() const
Definition: coutln.h:146
Definition: blobs.h:50
inT16 height() const
Definition: rect.h:104
inT16 right() const
Definition: rect.h:75
void insert_blob(BLOBNBOX *blob)
Definition: blobbox.cpp:764
inT16 width() const
Definition: rect.h:111
void set_right(int x)
Definition: rect.h:78
C_BLOB * crotate_cblob(C_BLOB *blob, FCOORD rotation)
Definition: blobbox.cpp:606
void set_left(int x)
Definition: rect.h:71
BlobTextFlowType
Definition: blobbox.h:99
Definition: statistc.h:33
void plot(ScrollView *window, ScrollView::Color blob_colour, ScrollView::Color child_colour)
Definition: blobbox.cpp:480
inT16 bottom() const
Definition: rect.h:61
inT16 EstimateBaselinePosition()
Definition: stepblob.cpp:427
#define ELISTIZE(CLASSNAME)
Definition: elst.h:961
#define PROJECTION_MARGIN
Definition: blobbox.cpp:31
const double kComplexShapePerimeterRatio
Definition: blobbox.cpp:43
inT32 perimeter()
Definition: stepblob.cpp:289
void add_blob(BLOBNBOX *blob, float top, float bottom, float row_size)
Definition: blobbox.cpp:728
inT32 pathlength() const
Definition: coutln.h:133
BlobRegionType
Definition: blobbox.h:57
static void PlotBlobs(BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour, ScrollView *win)
Definition: blobbox.cpp:414
const double kMinMediumSizeRatio
Definition: blobbox.cpp:45
BlobNeighbourDir
Definition: blobbox.h:72
void EstimateBaselinePosition()
Definition: blobbox.cpp:352
bool DefiniteIndividualFlow()
Definition: blobbox.cpp:247
float x() const
Definition: points.h:209
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
Definition: helpers.h:132
const TBOX & bounding_box() const
Definition: blobbox.h:215
int count(LIST var_list)
Definition: oldlist.cpp:103
void rotate(const FCOORD &vec)
Definition: rect.h:189
Definition: ocrblock.h:30
static void PlotNoiseBlobs(BLOBNBOX_LIST *list, ScrollView::Color body_colour, ScrollView::Color child_colour, ScrollView *win)
Definition: blobbox.cpp:427
void compute_bounding_box()
Definition: blobbox.h:225
void find_cblob_limits(C_BLOB *blob, float leftx, float rightx, FCOORD rotation, float &ymin, float &ymax)
Definition: blobbox.cpp:494
int NoisyNeighbours() const
Definition: blobbox.cpp:232
integer coordinate
Definition: points.h:30
int x_gap(const TBOX &box) const
Definition: rect.h:217
void clear()
Definition: blobbox.cpp:940