tesseract  4.0.0-1-g2a2b
blobs.cpp
Go to the documentation of this file.
1 /* -*-C-*-
2  ********************************************************************************
3  *
4  * File: blobs.cpp (Formerly blobs.c)
5  * Description: Blob definition
6  * Author: Mark Seaman, OCR Technology
7  * Created: Fri Oct 27 15:39:52 1989
8  * Modified: Thu Mar 28 15:33:26 1991 (Mark Seaman) marks@hpgrlt
9  * Language: C
10  * Package: N/A
11  * Status: Experimental (Do Not Distribute)
12  *
13  * (c) Copyright 1989, Hewlett-Packard Company.
14  ** Licensed under the Apache License, Version 2.0 (the "License");
15  ** you may not use this file except in compliance with the License.
16  ** You may obtain a copy of the License at
17  ** http://www.apache.org/licenses/LICENSE-2.0
18  ** Unless required by applicable law or agreed to in writing, software
19  ** distributed under the License is distributed on an "AS IS" BASIS,
20  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21  ** See the License for the specific language governing permissions and
22  ** limitations under the License.
23  *
24  *********************************************************************************/
25 
26 /*----------------------------------------------------------------------
27  I n c l u d e s
28 ----------------------------------------------------------------------*/
29 // Include automatically generated configuration file if running autoconf.
30 #ifdef HAVE_CONFIG_H
31 #include "config_auto.h"
32 #endif
33 
34 #include "blobs.h"
35 #include "ccstruct.h"
36 #include "clst.h"
37 #include "emalloc.h"
38 #include "helpers.h"
39 #include "linlsq.h"
40 #include "normalis.h"
41 #include "ocrblock.h"
42 #include "ocrrow.h"
43 #include "points.h"
44 #include "polyaprx.h"
45 #include "structures.h"
46 #include "werd.h"
47 
48 #include <algorithm>
49 
51 
52 // A Vector representing the "vertical" direction when measuring the
53 // divisiblity of blobs into multiple blobs just by separating outlines.
54 // See divisible_blob below for the use.
56 // A vector representing the "vertical" direction for italic text for use
57 // when separating outlines. Using it actually deteriorates final accuracy,
58 // so it is only used for ApplyBoxes chopping to get a better segmentation.
60 
61 /*----------------------------------------------------------------------
62  F u n c t i o n s
63 ----------------------------------------------------------------------*/
64 
66 
67 // Returns true when the two line segments cross each other.
68 // (Moved from outlines.cpp).
69 // Finds where the projected lines would cross and then checks to see if the
70 // point of intersection lies on both of the line segments. If it does
71 // then these two segments cross.
72 /* static */
73 bool TPOINT::IsCrossed(const TPOINT& a0, const TPOINT& a1, const TPOINT& b0,
74  const TPOINT& b1) {
75  int b0a1xb0b1, b0b1xb0a0;
76  int a1b1xa1a0, a1a0xa1b0;
77 
78  TPOINT b0a1, b0a0, a1b1, b0b1, a1a0;
79 
80  b0a1.x = a1.x - b0.x;
81  b0a0.x = a0.x - b0.x;
82  a1b1.x = b1.x - a1.x;
83  b0b1.x = b1.x - b0.x;
84  a1a0.x = a0.x - a1.x;
85  b0a1.y = a1.y - b0.y;
86  b0a0.y = a0.y - b0.y;
87  a1b1.y = b1.y - a1.y;
88  b0b1.y = b1.y - b0.y;
89  a1a0.y = a0.y - a1.y;
90 
91  b0a1xb0b1 = CROSS(b0a1, b0b1);
92  b0b1xb0a0 = CROSS(b0b1, b0a0);
93  a1b1xa1a0 = CROSS(a1b1, a1a0);
94  // For clarity, we want CROSS(a1a0,a1b0) here but we have b0a1 instead of a1b0
95  // so use -CROSS(a1b0,b0a1) instead, which is the same.
96  a1a0xa1b0 = -CROSS(a1a0, b0a1);
97 
98  return ((b0a1xb0b1 > 0 && b0b1xb0a0 > 0) ||
99  (b0a1xb0b1 < 0 && b0b1xb0a0 < 0)) &&
100  ((a1b1xa1a0 > 0 && a1a0xa1b0 > 0) || (a1b1xa1a0 < 0 && a1a0xa1b0 < 0));
101 }
102 
103 // Consume the circular list of EDGEPTs to make a TESSLINE.
105  TESSLINE* result = new TESSLINE;
106  result->loop = outline;
107  if (outline->src_outline != nullptr) {
108  // ASSUMPTION: This function is only ever called from ApproximateOutline
109  // and therefore either all points have a src_outline or all do not.
110  // Just as SetupFromPos sets the vectors from the vertices, setup the
111  // step_count members to indicate the (positive) number of original
112  // C_OUTLINE steps to the next vertex.
113  EDGEPT* pt = outline;
114  do {
115  pt->step_count = pt->next->start_step - pt->start_step;
116  if (pt->step_count < 0) pt->step_count += pt->src_outline->pathlength();
117  pt = pt->next;
118  } while (pt != outline);
119  }
120  result->SetupFromPos();
121  return result;
122 }
123 
124 // Copies the data and the outline, but leaves next untouched.
125 void TESSLINE::CopyFrom(const TESSLINE& src) {
126  Clear();
127  topleft = src.topleft;
128  botright = src.botright;
129  start = src.start;
130  is_hole = src.is_hole;
131  if (src.loop != nullptr) {
132  EDGEPT* prevpt = nullptr;
133  EDGEPT* newpt = nullptr;
134  EDGEPT* srcpt = src.loop;
135  do {
136  newpt = new EDGEPT(*srcpt);
137  if (prevpt == nullptr) {
138  loop = newpt;
139  } else {
140  newpt->prev = prevpt;
141  prevpt->next = newpt;
142  }
143  prevpt = newpt;
144  srcpt = srcpt->next;
145  } while (srcpt != src.loop);
146  loop->prev = newpt;
147  newpt->next = loop;
148  }
149 }
150 
151 // Deletes owned data.
153  if (loop == nullptr) return;
154 
155  EDGEPT* this_edge = loop;
156  do {
157  EDGEPT* next_edge = this_edge->next;
158  delete this_edge;
159  this_edge = next_edge;
160  } while (this_edge != loop);
161  loop = nullptr;
162 }
163 
164 // Normalize in-place using the DENORM.
165 void TESSLINE::Normalize(const DENORM& denorm) {
166  EDGEPT* pt = loop;
167  do {
168  denorm.LocalNormTransform(pt->pos, &pt->pos);
169  pt = pt->next;
170  } while (pt != loop);
171  SetupFromPos();
172 }
173 
174 // Rotates by the given rotation in place.
175 void TESSLINE::Rotate(const FCOORD rot) {
176  EDGEPT* pt = loop;
177  do {
178  int tmp = static_cast<int>(
179  floor(pt->pos.x * rot.x() - pt->pos.y * rot.y() + 0.5));
180  pt->pos.y = static_cast<int>(
181  floor(pt->pos.y * rot.x() + pt->pos.x * rot.y() + 0.5));
182  pt->pos.x = tmp;
183  pt = pt->next;
184  } while (pt != loop);
185  SetupFromPos();
186 }
187 
188 // Moves by the given vec in place.
189 void TESSLINE::Move(const ICOORD vec) {
190  EDGEPT* pt = loop;
191  do {
192  pt->pos.x += vec.x();
193  pt->pos.y += vec.y();
194  pt = pt->next;
195  } while (pt != loop);
196  SetupFromPos();
197 }
198 
199 // Scales by the given factor in place.
200 void TESSLINE::Scale(float factor) {
201  EDGEPT* pt = loop;
202  do {
203  pt->pos.x = static_cast<int>(floor(pt->pos.x * factor + 0.5));
204  pt->pos.y = static_cast<int>(floor(pt->pos.y * factor + 0.5));
205  pt = pt->next;
206  } while (pt != loop);
207  SetupFromPos();
208 }
209 
210 // Sets up the start and vec members of the loop from the pos members.
212  EDGEPT* pt = loop;
213  do {
214  pt->vec.x = pt->next->pos.x - pt->pos.x;
215  pt->vec.y = pt->next->pos.y - pt->pos.y;
216  pt = pt->next;
217  } while (pt != loop);
218  start = pt->pos;
220 }
221 
222 // Recomputes the bounding box from the points in the loop.
224  int minx = INT32_MAX;
225  int miny = INT32_MAX;
226  int maxx = -INT32_MAX;
227  int maxy = -INT32_MAX;
228 
229  // Find boundaries.
230  start = loop->pos;
231  EDGEPT* this_edge = loop;
232  do {
233  if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
234  if (this_edge->pos.x < minx) minx = this_edge->pos.x;
235  if (this_edge->pos.y < miny) miny = this_edge->pos.y;
236  if (this_edge->pos.x > maxx) maxx = this_edge->pos.x;
237  if (this_edge->pos.y > maxy) maxy = this_edge->pos.y;
238  }
239  this_edge = this_edge->next;
240  } while (this_edge != loop);
241  // Reset bounds.
242  topleft.x = minx;
243  topleft.y = maxy;
244  botright.x = maxx;
245  botright.y = miny;
246 }
247 
248 // Computes the min and max cross product of the outline points with the
249 // given vec and returns the results in min_xp and max_xp. Geometrically
250 // this is the left and right edge of the outline perpendicular to the
251 // given direction, but to get the distance units correct, you would
252 // have to divide by the modulus of vec.
253 void TESSLINE::MinMaxCrossProduct(const TPOINT vec, int* min_xp,
254  int* max_xp) const {
255  *min_xp = INT32_MAX;
256  *max_xp = INT32_MIN;
257  EDGEPT* this_edge = loop;
258  do {
259  if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
260  int product = CROSS(this_edge->pos, vec);
261  UpdateRange(product, min_xp, max_xp);
262  }
263  this_edge = this_edge->next;
264  } while (this_edge != loop);
265 }
266 
268  return TBOX(topleft.x, botright.y, botright.x, topleft.y);
269 }
270 
271 #ifndef GRAPHICS_DISABLED
273  ScrollView::Color child_color) {
274  if (is_hole)
275  window->Pen(child_color);
276  else
277  window->Pen(color);
278  window->SetCursor(start.x, start.y);
279  EDGEPT* pt = loop;
280  do {
281  bool prev_hidden = pt->IsHidden();
282  pt = pt->next;
283  if (prev_hidden)
284  window->SetCursor(pt->pos.x, pt->pos.y);
285  else
286  window->DrawTo(pt->pos.x, pt->pos.y);
287  } while (pt != loop);
288 }
289 #endif // GRAPHICS_DISABLED
290 
291 // Returns the first non-hidden EDGEPT that has a different src_outline to
292 // its predecessor, or, if all the same, the lowest indexed point.
294  EDGEPT* best_start = loop;
295  int best_step = loop->start_step;
296  // Iterate the polygon.
297  EDGEPT* pt = loop;
298  do {
299  if (pt->IsHidden()) continue;
300  if (pt->prev->IsHidden() || pt->prev->src_outline != pt->src_outline)
301  return pt; // Qualifies as the best.
302  if (pt->start_step < best_step) {
303  best_step = pt->start_step;
304  best_start = pt;
305  }
306  } while ((pt = pt->next) != loop);
307  return best_start;
308 }
309 
310 // Iterate the given list of outlines, converting to TESSLINE by polygonal
311 // approximation and recursively any children, returning the current tail
312 // of the resulting list of TESSLINEs.
313 static TESSLINE** ApproximateOutlineList(bool allow_detailed_fx,
314  C_OUTLINE_LIST* outlines,
315  bool children, TESSLINE** tail) {
316  C_OUTLINE_IT ol_it(outlines);
317  for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
318  C_OUTLINE* outline = ol_it.data();
319  if (outline->pathlength() > 0) {
320  TESSLINE* tessline = ApproximateOutline(allow_detailed_fx, outline);
321  tessline->is_hole = children;
322  *tail = tessline;
323  tail = &tessline->next;
324  }
325  if (!outline->child()->empty()) {
326  tail = ApproximateOutlineList(allow_detailed_fx, outline->child(), true,
327  tail);
328  }
329  }
330  return tail;
331 }
332 
333 // Factory to build a TBLOB from a C_BLOB with polygonal approximation along
334 // the way. If allow_detailed_fx is true, the EDGEPTs in the returned TBLOB
335 // contain pointers to the input C_OUTLINEs that enable higher-resolution
336 // feature extraction that does not use the polygonal approximation.
337 TBLOB* TBLOB::PolygonalCopy(bool allow_detailed_fx, C_BLOB* src) {
338  TBLOB* tblob = new TBLOB;
339  ApproximateOutlineList(allow_detailed_fx, src->out_list(), false,
340  &tblob->outlines);
341  return tblob;
342 }
343 
344 // Factory builds a blob with no outlines, but copies the other member data.
346  TBLOB* blob = new TBLOB;
347  blob->denorm_ = src.denorm_;
348  return blob;
349 }
350 
351 // Normalizes the blob for classification only if needed.
352 // (Normally this means a non-zero classify rotation.)
353 // If no Normalization is needed, then nullptr is returned, and the input blob
354 // can be used directly. Otherwise a new TBLOB is returned which must be
355 // deleted after use.
357  TBLOB* rotated_blob = nullptr;
358  // If necessary, copy the blob and rotate it. The rotation is always
359  // +/- 90 degrees, as 180 was already taken care of.
360  if (denorm_.block() != nullptr &&
361  denorm_.block()->classify_rotation().y() != 0.0) {
362  TBOX box = bounding_box();
363  int x_middle = (box.left() + box.right()) / 2;
364  int y_middle = (box.top() + box.bottom()) / 2;
365  rotated_blob = new TBLOB(*this);
366  const FCOORD& rotation = denorm_.block()->classify_rotation();
367  // Move the rotated blob back to the same y-position so that we
368  // can still distinguish similar glyphs with differeny y-position.
369  float target_y =
371  (rotation.y() > 0 ? x_middle - box.left() : box.right() - x_middle);
372  rotated_blob->Normalize(nullptr, &rotation, &denorm_, x_middle, y_middle,
373  1.0f, 1.0f, 0.0f, target_y, denorm_.inverse(),
374  denorm_.pix());
375  }
376  return rotated_blob;
377 }
378 
379 // Copies the data and the outline, but leaves next untouched.
380 void TBLOB::CopyFrom(const TBLOB& src) {
381  Clear();
382  TESSLINE* prev_outline = nullptr;
383  for (TESSLINE* srcline = src.outlines; srcline != nullptr;
384  srcline = srcline->next) {
385  TESSLINE* new_outline = new TESSLINE(*srcline);
386  if (outlines == nullptr)
387  outlines = new_outline;
388  else
389  prev_outline->next = new_outline;
390  prev_outline = new_outline;
391  }
392  denorm_ = src.denorm_;
393 }
394 
395 // Deletes owned data.
396 void TBLOB::Clear() {
397  for (TESSLINE* next_outline = nullptr; outlines != nullptr;
398  outlines = next_outline) {
399  next_outline = outlines->next;
400  delete outlines;
401  }
402 }
403 
404 // Sets up the built-in DENORM and normalizes the blob in-place.
405 // For parameters see DENORM::SetupNormalization, plus the inverse flag for
406 // this blob and the Pix for the full image.
407 void TBLOB::Normalize(const BLOCK* block, const FCOORD* rotation,
408  const DENORM* predecessor, float x_origin, float y_origin,
409  float x_scale, float y_scale, float final_xshift,
410  float final_yshift, bool inverse, Pix* pix) {
411  denorm_.SetupNormalization(block, rotation, predecessor, x_origin, y_origin,
412  x_scale, y_scale, final_xshift, final_yshift);
413  denorm_.set_inverse(inverse);
414  denorm_.set_pix(pix);
415  // TODO(rays) outline->Normalize is more accurate, but breaks tests due
416  // the changes it makes. Reinstate this code with a retraining.
417  // The reason this change is troublesome is that it normalizes for the
418  // baseline value computed independently at each x-coord. If the baseline
419  // is not horizontal, this introduces shear into the normalized blob, which
420  // is useful on the rare occasions that the baseline is really curved, but
421  // the baselines need to be stabilized the rest of the time.
422 #if 0
423  for (TESSLINE* outline = outlines; outline != nullptr; outline = outline->next) {
424  outline->Normalize(denorm_);
425  }
426 #else
427  denorm_.LocalNormBlob(this);
428 #endif
429 }
430 
431 // Rotates by the given rotation in place.
432 void TBLOB::Rotate(const FCOORD rotation) {
433  for (TESSLINE* outline = outlines; outline != nullptr;
434  outline = outline->next) {
435  outline->Rotate(rotation);
436  }
437 }
438 
439 // Moves by the given vec in place.
440 void TBLOB::Move(const ICOORD vec) {
441  for (TESSLINE* outline = outlines; outline != nullptr;
442  outline = outline->next) {
443  outline->Move(vec);
444  }
445 }
446 
447 // Scales by the given factor in place.
448 void TBLOB::Scale(float factor) {
449  for (TESSLINE* outline = outlines; outline != nullptr;
450  outline = outline->next) {
451  outline->Scale(factor);
452  }
453 }
454 
455 // Recomputes the bounding boxes of the outlines.
457  for (TESSLINE* outline = outlines; outline != nullptr;
458  outline = outline->next) {
459  outline->ComputeBoundingBox();
460  }
461 }
462 
463 // Returns the number of outlines.
464 int TBLOB::NumOutlines() const {
465  int result = 0;
466  for (TESSLINE* outline = outlines; outline != nullptr;
467  outline = outline->next)
468  ++result;
469  return result;
470 }
471 
472 /**********************************************************************
473  * TBLOB::bounding_box()
474  *
475  * Compute the bounding_box of a compound blob, defined to be the
476  * bounding box of the union of all top-level outlines in the blob.
477  **********************************************************************/
479  if (outlines == nullptr) return TBOX(0, 0, 0, 0);
480  TESSLINE* outline = outlines;
481  TBOX box = outline->bounding_box();
482  for (outline = outline->next; outline != nullptr; outline = outline->next) {
483  box += outline->bounding_box();
484  }
485  return box;
486 }
487 
488 // Finds and deletes any duplicate outlines in this blob, without deleting
489 // their EDGEPTs.
491  for (TESSLINE* outline = outlines; outline != nullptr;
492  outline = outline->next) {
493  TESSLINE* last_outline = outline;
494  for (TESSLINE* other_outline = outline->next; other_outline != nullptr;
495  last_outline = other_outline, other_outline = other_outline->next) {
496  if (outline->SameBox(*other_outline)) {
497  last_outline->next = other_outline->next;
498  // This doesn't leak - the outlines share the EDGEPTs.
499  other_outline->loop = nullptr;
500  delete other_outline;
501  other_outline = last_outline;
502  // If it is part of a cut, then it can't be a hole any more.
503  outline->is_hole = false;
504  }
505  }
506  }
507 }
508 
509 // Swaps the outlines of *this and next if needed to keep the centers in
510 // increasing x.
512  TBOX box = bounding_box();
513  TBOX next_box = next->bounding_box();
514  if (box.x_middle() > next_box.x_middle()) {
515  Swap(&outlines, &next->outlines);
516  }
517 }
518 
519 #ifndef GRAPHICS_DISABLED
521  ScrollView::Color child_color) {
522  for (TESSLINE* outline = outlines; outline != nullptr;
523  outline = outline->next)
524  outline->plot(window, color, child_color);
525 }
526 #endif // GRAPHICS_DISABLED
527 
528 // Computes the center of mass and second moments for the old baseline and
529 // 2nd moment normalizations. Returns the outline length.
530 // The input denorm should be the normalizations that have been applied from
531 // the image to the current state of this TBLOB.
532 int TBLOB::ComputeMoments(FCOORD* center, FCOORD* second_moments) const {
533  // Compute 1st and 2nd moments of the original outline.
534  LLSQ accumulator;
535  TBOX box = bounding_box();
536  // Iterate the outlines, accumulating edges relative the box.botleft().
537  CollectEdges(box, nullptr, &accumulator, nullptr, nullptr);
538  *center = accumulator.mean_point() + box.botleft();
539  // The 2nd moments are just the standard deviation of the point positions.
540  double x2nd = sqrt(accumulator.x_variance());
541  double y2nd = sqrt(accumulator.y_variance());
542  if (x2nd < 1.0) x2nd = 1.0;
543  if (y2nd < 1.0) y2nd = 1.0;
544  second_moments->set_x(x2nd);
545  second_moments->set_y(y2nd);
546  return accumulator.count();
547 }
548 
549 // Computes the precise bounding box of the coords that are generated by
550 // GetEdgeCoords. This may be different from the bounding box of the polygon.
551 void TBLOB::GetPreciseBoundingBox(TBOX* precise_box) const {
552  TBOX box = bounding_box();
553  *precise_box = TBOX();
554  CollectEdges(box, precise_box, nullptr, nullptr, nullptr);
555  precise_box->move(box.botleft());
556 }
557 
558 // Adds edges to the given vectors.
559 // For all the edge steps in all the outlines, or polygonal approximation
560 // where there are no edge steps, collects the steps into x_coords/y_coords.
561 // x_coords is a collection of the x-coords of vertical edges for each
562 // y-coord starting at box.bottom().
563 // y_coords is a collection of the y-coords of horizontal edges for each
564 // x-coord starting at box.left().
565 // Eg x_coords[0] is a collection of the x-coords of edges at y=bottom.
566 // Eg x_coords[1] is a collection of the x-coords of edges at y=bottom + 1.
567 void TBLOB::GetEdgeCoords(const TBOX& box,
568  GenericVector<GenericVector<int> >* x_coords,
569  GenericVector<GenericVector<int> >* y_coords) const {
570  GenericVector<int> empty;
571  x_coords->init_to_size(box.height(), empty);
572  y_coords->init_to_size(box.width(), empty);
573  CollectEdges(box, nullptr, nullptr, x_coords, y_coords);
574  // Sort the output vectors.
575  for (int i = 0; i < x_coords->size(); ++i) (*x_coords)[i].sort();
576  for (int i = 0; i < y_coords->size(); ++i) (*y_coords)[i].sort();
577 }
578 
579 // Accumulates the segment between pt1 and pt2 in the LLSQ, quantizing over
580 // the integer coordinate grid to properly weight long vectors.
581 static void SegmentLLSQ(const FCOORD& pt1, const FCOORD& pt2,
582  LLSQ* accumulator) {
583  FCOORD step(pt2);
584  step -= pt1;
585  int xstart = IntCastRounded(std::min(pt1.x(), pt2.x()));
586  int xend = IntCastRounded(std::max(pt1.x(), pt2.x()));
587  int ystart = IntCastRounded(std::min(pt1.y(), pt2.y()));
588  int yend = IntCastRounded(std::max(pt1.y(), pt2.y()));
589  if (xstart == xend && ystart == yend) return; // Nothing to do.
590  double weight = step.length() / (xend - xstart + yend - ystart);
591  // Compute and save the y-position at the middle of each x-step.
592  for (int x = xstart; x < xend; ++x) {
593  double y = pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x();
594  accumulator->add(x + 0.5, y, weight);
595  }
596  // Compute and save the x-position at the middle of each y-step.
597  for (int y = ystart; y < yend; ++y) {
598  double x = pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y();
599  accumulator->add(x, y + 0.5, weight);
600  }
601 }
602 
603 // Adds any edges from a single segment of outline between pt1 and pt2 to
604 // the x_coords, y_coords vectors. pt1 and pt2 should be relative to the
605 // bottom-left of the bounding box, hence indices to x_coords, y_coords
606 // are clipped to ([0,x_limit], [0,y_limit]).
607 // See GetEdgeCoords above for a description of x_coords, y_coords.
608 static void SegmentCoords(const FCOORD& pt1, const FCOORD& pt2, int x_limit,
609  int y_limit,
610  GenericVector<GenericVector<int> >* x_coords,
611  GenericVector<GenericVector<int> >* y_coords) {
612  FCOORD step(pt2);
613  step -= pt1;
614  int start =
615  ClipToRange(IntCastRounded(std::min(pt1.x(), pt2.x())), 0, x_limit);
616  int end = ClipToRange(IntCastRounded(std::max(pt1.x(), pt2.x())), 0, x_limit);
617  for (int x = start; x < end; ++x) {
618  int y = IntCastRounded(pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x());
619  (*y_coords)[x].push_back(y);
620  }
621  start = ClipToRange(IntCastRounded(std::min(pt1.y(), pt2.y())), 0, y_limit);
622  end = ClipToRange(IntCastRounded(std::max(pt1.y(), pt2.y())), 0, y_limit);
623  for (int y = start; y < end; ++y) {
624  int x = IntCastRounded(pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y());
625  (*x_coords)[y].push_back(x);
626  }
627 }
628 
629 // Adds any edges from a single segment of outline between pt1 and pt2 to
630 // the bbox such that it guarantees to contain anything produced by
631 // SegmentCoords.
632 static void SegmentBBox(const FCOORD& pt1, const FCOORD& pt2, TBOX* bbox) {
633  FCOORD step(pt2);
634  step -= pt1;
635  int x1 = IntCastRounded(std::min(pt1.x(), pt2.x()));
636  int x2 = IntCastRounded(std::max(pt1.x(), pt2.x()));
637  if (x2 > x1) {
638  int y1 =
639  IntCastRounded(pt1.y() + step.y() * (x1 + 0.5 - pt1.x()) / step.x());
640  int y2 =
641  IntCastRounded(pt1.y() + step.y() * (x2 - 0.5 - pt1.x()) / step.x());
642  TBOX point(x1, std::min(y1, y2), x2, std::max(y1, y2));
643  *bbox += point;
644  }
645  int y1 = IntCastRounded(std::min(pt1.y(), pt2.y()));
646  int y2 = IntCastRounded(std::max(pt1.y(), pt2.y()));
647  if (y2 > y1) {
648  int x1 =
649  IntCastRounded(pt1.x() + step.x() * (y1 + 0.5 - pt1.y()) / step.y());
650  int x2 =
651  IntCastRounded(pt1.x() + step.x() * (y2 - 0.5 - pt1.y()) / step.y());
652  TBOX point(std::min(x1, x2), y1, std::max(x1, x2), y2);
653  *bbox += point;
654  }
655 }
656 
657 // Collects edges into the given bounding box, LLSQ accumulator and/or x_coords,
658 // y_coords vectors.
659 // For a description of x_coords/y_coords, see GetEdgeCoords above.
660 // Startpt to lastpt, inclusive, MUST have the same src_outline member,
661 // which may be nullptr. The vector from lastpt to its next is included in
662 // the accumulation. Hidden edges should be excluded by the caller.
663 // The input denorm should be the normalizations that have been applied from
664 // the image to the current state of the TBLOB from which startpt, lastpt come.
665 // box is the bounding box of the blob from which the EDGEPTs are taken and
666 // indices into x_coords, y_coords are offset by box.botleft().
667 static void CollectEdgesOfRun(const EDGEPT* startpt, const EDGEPT* lastpt,
668  const DENORM& denorm, const TBOX& box,
669  TBOX* bounding_box, LLSQ* accumulator,
670  GenericVector<GenericVector<int> >* x_coords,
671  GenericVector<GenericVector<int> >* y_coords) {
672  const C_OUTLINE* outline = startpt->src_outline;
673  int x_limit = box.width() - 1;
674  int y_limit = box.height() - 1;
675  if (outline != nullptr) {
676  // Use higher-resolution edge points stored on the outline.
677  // The outline coordinates may not match the binary image because of the
678  // rotation for vertical text lines, but the root_denorm IS the matching
679  // start of the DENORM chain.
680  const DENORM* root_denorm = denorm.RootDenorm();
681  int step_length = outline->pathlength();
682  int start_index = startpt->start_step;
683  // Note that if this run straddles the wrap-around point of the outline,
684  // that lastpt->start_step may have a lower index than startpt->start_step,
685  // and we want to use an end_index that allows us to use a positive
686  // increment, so we add step_length if necessary, but that may be beyond the
687  // bounds of the outline steps/ due to wrap-around, so we use % step_length
688  // everywhere, except for start_index.
689  int end_index = lastpt->start_step + lastpt->step_count;
690  if (end_index <= start_index) end_index += step_length;
691  // pos is the integer coordinates of the binary image steps.
692  ICOORD pos = outline->position_at_index(start_index);
693  FCOORD origin(box.left(), box.bottom());
694  // f_pos is a floating-point version of pos that offers improved edge
695  // positioning using greyscale information or smoothing of edge steps.
696  FCOORD f_pos = outline->sub_pixel_pos_at_index(pos, start_index);
697  // pos_normed is f_pos after the appropriate normalization, and relative
698  // to origin.
699  // prev_normed is the previous value of pos_normed.
700  FCOORD prev_normed;
701  denorm.NormTransform(root_denorm, f_pos, &prev_normed);
702  prev_normed -= origin;
703  for (int index = start_index; index < end_index; ++index) {
704  ICOORD step = outline->step(index % step_length);
705  // Only use the point if its edge strength is positive. This excludes
706  // points that don't provide useful information, eg
707  // ___________
708  // |___________
709  // The vertical step provides only noisy, damaging information, as even
710  // with a greyscale image, the positioning of the edge there may be a
711  // fictitious extrapolation, so previous processing has eliminated it.
712  if (outline->edge_strength_at_index(index % step_length) > 0) {
713  FCOORD f_pos =
714  outline->sub_pixel_pos_at_index(pos, index % step_length);
715  FCOORD pos_normed;
716  denorm.NormTransform(root_denorm, f_pos, &pos_normed);
717  pos_normed -= origin;
718  // Accumulate the information that is selected by the caller.
719  if (bounding_box != nullptr) {
720  SegmentBBox(pos_normed, prev_normed, bounding_box);
721  }
722  if (accumulator != nullptr) {
723  SegmentLLSQ(pos_normed, prev_normed, accumulator);
724  }
725  if (x_coords != nullptr && y_coords != nullptr) {
726  SegmentCoords(pos_normed, prev_normed, x_limit, y_limit, x_coords,
727  y_coords);
728  }
729  prev_normed = pos_normed;
730  }
731  pos += step;
732  }
733  } else {
734  // There is no outline, so we are forced to use the polygonal approximation.
735  const EDGEPT* endpt = lastpt->next;
736  const EDGEPT* pt = startpt;
737  do {
738  FCOORD next_pos(pt->next->pos.x - box.left(),
739  pt->next->pos.y - box.bottom());
740  FCOORD pos(pt->pos.x - box.left(), pt->pos.y - box.bottom());
741  if (bounding_box != nullptr) {
742  SegmentBBox(next_pos, pos, bounding_box);
743  }
744  if (accumulator != nullptr) {
745  SegmentLLSQ(next_pos, pos, accumulator);
746  }
747  if (x_coords != nullptr && y_coords != nullptr) {
748  SegmentCoords(next_pos, pos, x_limit, y_limit, x_coords, y_coords);
749  }
750  } while ((pt = pt->next) != endpt);
751  }
752 }
753 
754 // For all the edge steps in all the outlines, or polygonal approximation
755 // where there are no edge steps, collects the steps into the bounding_box,
756 // llsq and/or the x_coords/y_coords. Both are used in different kinds of
757 // normalization.
758 // For a description of x_coords, y_coords, see GetEdgeCoords above.
759 void TBLOB::CollectEdges(const TBOX& box, TBOX* bounding_box, LLSQ* llsq,
760  GenericVector<GenericVector<int> >* x_coords,
761  GenericVector<GenericVector<int> >* y_coords) const {
762  // Iterate the outlines.
763  for (const TESSLINE* ol = outlines; ol != nullptr; ol = ol->next) {
764  // Iterate the polygon.
765  EDGEPT* loop_pt = ol->FindBestStartPt();
766  EDGEPT* pt = loop_pt;
767  if (pt == nullptr) continue;
768  do {
769  if (pt->IsHidden()) continue;
770  // Find a run of equal src_outline.
771  EDGEPT* last_pt = pt;
772  do {
773  last_pt = last_pt->next;
774  } while (last_pt != loop_pt && !last_pt->IsHidden() &&
775  last_pt->src_outline == pt->src_outline);
776  last_pt = last_pt->prev;
777  CollectEdgesOfRun(pt, last_pt, denorm_, box, bounding_box, llsq, x_coords,
778  y_coords);
779  pt = last_pt;
780  } while ((pt = pt->next) != loop_pt);
781  }
782 }
783 
784 // Factory to build a TWERD from a (C_BLOB) WERD, with polygonal
785 // approximation along the way.
786 TWERD* TWERD::PolygonalCopy(bool allow_detailed_fx, WERD* src) {
787  TWERD* tessword = new TWERD;
788  tessword->latin_script = src->flag(W_SCRIPT_IS_LATIN);
789  C_BLOB_IT b_it(src->cblob_list());
790  for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
791  C_BLOB* blob = b_it.data();
792  TBLOB* tblob = TBLOB::PolygonalCopy(allow_detailed_fx, blob);
793  tessword->blobs.push_back(tblob);
794  }
795  return tessword;
796 }
797 
798 // Baseline normalizes the blobs in-place, recording the normalization in the
799 // DENORMs in the blobs.
800 void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix,
801  bool inverse, float x_height, float baseline_shift,
802  bool numeric_mode, tesseract::OcrEngineMode hint,
803  const TBOX* norm_box, DENORM* word_denorm) {
804  TBOX word_box = bounding_box();
805  if (norm_box != nullptr) word_box = *norm_box;
806  float word_middle = (word_box.left() + word_box.right()) / 2.0f;
807  float input_y_offset = 0.0f;
808  float final_y_offset = static_cast<float>(kBlnBaselineOffset);
809  float scale = kBlnXHeight / x_height;
810  if (row == nullptr) {
811  word_middle = word_box.left();
812  input_y_offset = word_box.bottom();
813  final_y_offset = 0.0f;
814  } else {
815  input_y_offset = row->base_line(word_middle) + baseline_shift;
816  }
817  for (int b = 0; b < blobs.size(); ++b) {
818  TBLOB* blob = blobs[b];
819  TBOX blob_box = blob->bounding_box();
820  float mid_x = (blob_box.left() + blob_box.right()) / 2.0f;
821  float baseline = input_y_offset;
822  float blob_scale = scale;
823  if (numeric_mode) {
824  baseline = blob_box.bottom();
825  blob_scale = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()),
826  scale, scale * 1.5f);
827  } else if (row != nullptr) {
828  baseline = row->base_line(mid_x) + baseline_shift;
829  }
830  // The image will be 8-bit grey if the input was grey or color. Note that in
831  // a grey image 0 is black and 255 is white. If the input was binary, then
832  // the pix will be binary and 0 is white, with 1 being black.
833  // To tell the difference pixGetDepth() will return 8 or 1.
834  // The inverse flag will be true iff the word has been determined to be
835  // white on black, and is independent of whether the pix is 8 bit or 1 bit.
836  blob->Normalize(block, nullptr, nullptr, word_middle, baseline, blob_scale,
837  blob_scale, 0.0f, final_y_offset, inverse, pix);
838  }
839  if (word_denorm != nullptr) {
840  word_denorm->SetupNormalization(block, nullptr, nullptr, word_middle,
841  input_y_offset, scale, scale, 0.0f,
842  final_y_offset);
843  word_denorm->set_inverse(inverse);
844  word_denorm->set_pix(pix);
845  }
846 }
847 
848 // Copies the data and the blobs, but leaves next untouched.
849 void TWERD::CopyFrom(const TWERD& src) {
850  Clear();
852  for (int b = 0; b < src.blobs.size(); ++b) {
853  TBLOB* new_blob = new TBLOB(*src.blobs[b]);
854  blobs.push_back(new_blob);
855  }
856 }
857 
858 // Deletes owned data.
859 void TWERD::Clear() {
861  blobs.clear();
862 }
863 
864 // Recomputes the bounding boxes of the blobs.
866  for (int b = 0; b < blobs.size(); ++b) {
867  blobs[b]->ComputeBoundingBoxes();
868  }
869 }
870 
872  TBOX result;
873  for (int b = 0; b < blobs.size(); ++b) {
874  TBOX box = blobs[b]->bounding_box();
875  result += box;
876  }
877  return result;
878 }
879 
880 // Merges the blobs from start to end, not including end, and deletes
881 // the blobs between start and end.
882 void TWERD::MergeBlobs(int start, int end) {
883  if (start >= blobs.size() - 1) return; // Nothing to do.
884  TESSLINE* outline = blobs[start]->outlines;
885  for (int i = start + 1; i < end && i < blobs.size(); ++i) {
886  TBLOB* next_blob = blobs[i];
887  // Take the outlines from the next blob.
888  if (outline == nullptr) {
889  blobs[start]->outlines = next_blob->outlines;
890  outline = blobs[start]->outlines;
891  } else {
892  while (outline->next != nullptr) outline = outline->next;
893  outline->next = next_blob->outlines;
894  next_blob->outlines = nullptr;
895  }
896  // Delete the next blob and move on.
897  delete next_blob;
898  blobs[i] = nullptr;
899  }
900  // Remove dead blobs from the vector.
901  for (int i = start + 1; i < end && start + 1 < blobs.size(); ++i) {
902  blobs.remove(start + 1);
903  }
904 }
905 
906 #ifndef GRAPHICS_DISABLED
907 void TWERD::plot(ScrollView* window) {
909  for (int b = 0; b < blobs.size(); ++b) {
910  blobs[b]->plot(window, color, ScrollView::BROWN);
911  color = WERD::NextColor(color);
912  }
913 }
914 #endif // GRAPHICS_DISABLED
915 
916 /**********************************************************************
917  * divisible_blob
918  *
919  * Returns true if the blob contains multiple outlines than can be
920  * separated using divide_blobs. Sets the location to be used in the
921  * call to divide_blobs.
922  **********************************************************************/
923 bool divisible_blob(TBLOB* blob, bool italic_blob, TPOINT* location) {
924  if (blob->outlines == nullptr || blob->outlines->next == nullptr)
925  return false; // Need at least 2 outlines for it to be possible.
926  int max_gap = 0;
927  TPOINT vertical =
929  for (TESSLINE* outline1 = blob->outlines; outline1 != nullptr;
930  outline1 = outline1->next) {
931  if (outline1->is_hole) continue; // Holes do not count as separable.
932  TPOINT mid_pt1(
933  static_cast<int16_t>((outline1->topleft.x + outline1->botright.x) / 2),
934  static_cast<int16_t>((outline1->topleft.y + outline1->botright.y) / 2));
935  int mid_prod1 = CROSS(mid_pt1, vertical);
936  int min_prod1, max_prod1;
937  outline1->MinMaxCrossProduct(vertical, &min_prod1, &max_prod1);
938  for (TESSLINE* outline2 = outline1->next; outline2 != nullptr;
939  outline2 = outline2->next) {
940  if (outline2->is_hole) continue; // Holes do not count as separable.
941  TPOINT mid_pt2(static_cast<int16_t>(
942  (outline2->topleft.x + outline2->botright.x) / 2),
943  static_cast<int16_t>(
944  (outline2->topleft.y + outline2->botright.y) / 2));
945  int mid_prod2 = CROSS(mid_pt2, vertical);
946  int min_prod2, max_prod2;
947  outline2->MinMaxCrossProduct(vertical, &min_prod2, &max_prod2);
948  int mid_gap = abs(mid_prod2 - mid_prod1);
949  int overlap =
950  std::min(max_prod1, max_prod2) - std::max(min_prod1, min_prod2);
951  if (mid_gap - overlap / 4 > max_gap) {
952  max_gap = mid_gap - overlap / 4;
953  *location = mid_pt1;
954  *location += mid_pt2;
955  *location /= 2;
956  }
957  }
958  }
959  // Use the y component of the vertical vector as an approximation to its
960  // length.
961  return max_gap > vertical.y;
962 }
963 
964 /**********************************************************************
965  * divide_blobs
966  *
967  * Create two blobs by grouping the outlines in the appropriate blob.
968  * The outlines that are beyond the location point are moved to the
969  * other blob. The ones whose x location is less than that point are
970  * retained in the original blob.
971  **********************************************************************/
972 void divide_blobs(TBLOB* blob, TBLOB* other_blob, bool italic_blob,
973  const TPOINT& location) {
974  TPOINT vertical =
976  TESSLINE* outline1 = nullptr;
977  TESSLINE* outline2 = nullptr;
978 
979  TESSLINE* outline = blob->outlines;
980  blob->outlines = nullptr;
981  int location_prod = CROSS(location, vertical);
982 
983  while (outline != nullptr) {
984  TPOINT mid_pt(
985  static_cast<int16_t>((outline->topleft.x + outline->botright.x) / 2),
986  static_cast<int16_t>((outline->topleft.y + outline->botright.y) / 2));
987  int mid_prod = CROSS(mid_pt, vertical);
988  if (mid_prod < location_prod) {
989  // Outline is in left blob.
990  if (outline1)
991  outline1->next = outline;
992  else
993  blob->outlines = outline;
994  outline1 = outline;
995  } else {
996  // Outline is in right blob.
997  if (outline2)
998  outline2->next = outline;
999  else
1000  other_blob->outlines = outline;
1001  outline2 = outline;
1002  }
1003  outline = outline->next;
1004  }
1005 
1006  if (outline1) outline1->next = nullptr;
1007  if (outline2) outline2->next = nullptr;
1008 }
void BLNormalize(const BLOCK *block, const ROW *row, Pix *pix, bool inverse, float x_height, float baseline_shift, bool numeric_mode, tesseract::OcrEngineMode hint, const TBOX *norm_box, DENORM *word_denorm)
Definition: blobs.cpp:800
void Clear()
Definition: blobs.cpp:396
bool inverse() const
Definition: normalis.h:252
int step_count
Definition: blobs.h:181
int ComputeMoments(FCOORD *center, FCOORD *second_moments) const
Definition: blobs.cpp:532
void NormTransform(const DENORM *first_norm, const TPOINT &pt, TPOINT *transformed) const
Definition: normalis.cpp:335
void DrawTo(int x, int y)
Definition: scrollview.cpp:527
TESSLINE * next
Definition: blobs.h:265
void SetupNormalization(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift)
Definition: normalis.cpp:96
static TBLOB * PolygonalCopy(bool allow_detailed_fx, C_BLOB *src)
Definition: blobs.cpp:337
void LocalNormBlob(TBLOB *blob) const
Definition: normalis.cpp:412
int size() const
Definition: genericvector.h:71
void Swap(T *p1, T *p2)
Definition: helpers.h:98
Definition: blobs.h:402
void MinMaxCrossProduct(const TPOINT vec, int *min_xp, int *max_xp) const
Definition: blobs.cpp:253
const DENORM * RootDenorm() const
Definition: normalis.h:258
TPOINT pos
Definition: blobs.h:170
void CopyFrom(const TBLOB &src)
Definition: blobs.cpp:380
FCOORD sub_pixel_pos_at_index(const ICOORD &pos, int index) const
Definition: coutln.h:163
void Scale(float factor)
Definition: blobs.cpp:200
TBOX bounding_box() const
Definition: blobs.cpp:267
TPOINT botright
Definition: blobs.h:261
static ScrollView::Color NextColor(ScrollView::Color colour)
Definition: werd.cpp:305
float base_line(float xpos) const
Definition: ocrrow.h:59
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:520
int16_t y() const
access_function
Definition: points.h:57
void remove(int index)
void ComputeBoundingBox()
Definition: blobs.cpp:223
int32_t count() const
Definition: linlsq.h:43
TBOX bounding_box() const
Definition: blobs.cpp:871
const BLOCK * block() const
Definition: normalis.h:273
Definition: rect.h:34
#define CROSS(a, b)
Definition: vecfuncs.h:52
void Rotate(const FCOORD rotation)
Definition: blobs.cpp:175
const int kBlnXHeight
Definition: normalis.h:24
const TPOINT kDivisibleVerticalUpright(0, 1)
const int kBlnBaselineOffset
Definition: normalis.h:25
bool IsHidden() const
Definition: blobs.h:160
void Move(const ICOORD vec)
Definition: blobs.cpp:189
void set_x(float xin)
rewrite function
Definition: points.h:215
void SetCursor(int x, int y)
Definition: scrollview.cpp:521
int start_step
Definition: blobs.h:180
TPOINT start
Definition: blobs.h:262
bool latin_script
Definition: blobs.h:444
TBLOB()
Definition: blobs.h:269
void move(const ICOORD vec)
Definition: rect.h:157
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT *location)
Definition: blobs.cpp:923
void Normalize(const DENORM &denorm)
Definition: blobs.cpp:165
int16_t width() const
Definition: rect.h:115
void add(double x, double y)
Definition: linlsq.cpp:48
FCOORD mean_point() const
Definition: linlsq.cpp:166
int16_t left() const
Definition: rect.h:72
bool flag(WERD_FLAGS mask) const
Definition: werd.h:126
void EliminateDuplicateOutlines()
Definition: blobs.cpp:490
void LocalNormTransform(const TPOINT &pt, TPOINT *transformed) const
Definition: normalis.cpp:306
int16_t top() const
Definition: rect.h:58
TWERD()
Definition: blobs.h:403
EDGEPT * loop
Definition: blobs.h:264
void Normalize(const BLOCK *block, const FCOORD *rotation, const DENORM *predecessor, float x_origin, float y_origin, float x_scale, float y_scale, float final_xshift, float final_yshift, bool inverse, Pix *pix)
Definition: blobs.cpp:407
integer coordinate
Definition: points.h:32
int16_t x() const
access function
Definition: points.h:53
ICOORD position_at_index(int index) const
Definition: coutln.h:153
void init_to_size(int size, const T &t)
void CopyFrom(const TWERD &src)
Definition: blobs.cpp:849
int edge_strength_at_index(int index) const
Definition: coutln.h:187
int IntCastRounded(double x)
Definition: helpers.h:168
VECTOR vec
Definition: blobs.h:171
FCOORD classify_rotation() const
Definition: ocrblock.h:142
void plot(ScrollView *window)
Definition: blobs.cpp:907
void Rotate(const FCOORD rotation)
Definition: blobs.cpp:432
void set_pix(Pix *pix)
Definition: normalis.h:249
Definition: werd.h:59
Definition: linlsq.h:28
Definition: blobs.h:83
int32_t pathlength() const
Definition: coutln.h:135
EDGEPT * prev
Definition: blobs.h:177
const TPOINT kDivisibleVerticalItalic(1, 5)
Definition: ocrrow.h:36
TBOX bounding_box() const
Definition: blobs.cpp:478
bool is_hole
Definition: blobs.h:263
TBLOB * ClassifyNormalizeIfNeeded() const
Definition: blobs.cpp:356
int NumOutlines() const
Definition: blobs.cpp:464
Definition: ocrblock.h:30
C_BLOB_LIST * cblob_list()
Definition: werd.h:98
EDGEPT * FindBestStartPt() const
Definition: blobs.cpp:293
void plot(ScrollView *window, ScrollView::Color colour) const
Definition: coutln.cpp:943
C_OUTLINE_LIST * child()
Definition: coutln.h:108
int push_back(T object)
const ICOORD & botleft() const
Definition: rect.h:92
void set_inverse(bool value)
Definition: normalis.h:255
GenericVector< TBLOB * > blobs
Definition: blobs.h:443
int16_t x
Definition: blobs.h:78
void CopyFrom(const TESSLINE &src)
Definition: blobs.cpp:125
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:272
void GetPreciseBoundingBox(TBOX *precise_box) const
Definition: blobs.cpp:551
void ComputeBoundingBoxes()
Definition: blobs.cpp:456
double y_variance() const
Definition: linlsq.h:87
C_OUTLINE * src_outline
Definition: blobs.h:178
static TBLOB * ShallowCopy(const TBLOB &src)
Definition: blobs.cpp:345
void MergeBlobs(int start, int end)
Definition: blobs.cpp:882
C_OUTLINE_LIST * out_list()
Definition: stepblob.h:70
void delete_data_pointers()
void Clear()
Definition: blobs.cpp:152
Definition: points.h:189
int x_middle() const
Definition: rect.h:85
void Clear()
Definition: blobs.cpp:859
double x_variance() const
Definition: linlsq.h:81
int16_t right() const
Definition: rect.h:79
float x() const
Definition: points.h:208
T ClipToRange(const T &x, const T &lower_bound, const T &upper_bound)
Definition: helpers.h:111
Pix * pix() const
Definition: normalis.h:246
CLISTIZE(BLOCK_RES) ELISTIZE(ROW_RES) ELISTIZE(WERD_RES) static const double kStopperAmbiguityThresholdGain
Definition: blobs.h:268
void CorrectBlobOrder(TBLOB *next)
Definition: blobs.cpp:511
static TWERD * PolygonalCopy(bool allow_detailed_fx, WERD *src)
Definition: blobs.cpp:786
void ComputeBoundingBoxes()
Definition: blobs.cpp:865
Definition: blobs.h:57
void GetEdgeCoords(const TBOX &box, GenericVector< GenericVector< int > > *x_coords, GenericVector< GenericVector< int > > *y_coords) const
Definition: blobs.cpp:567
int16_t y
Definition: blobs.h:79
void Pen(Color color)
Definition: scrollview.cpp:722
int16_t bottom() const
Definition: rect.h:65
void set_y(float yin)
rewrite function
Definition: points.h:219
TPOINT topleft
Definition: blobs.h:260
TESSLINE * outlines
Definition: blobs.h:384
static TESSLINE * BuildFromOutlineList(EDGEPT *outline)
Definition: blobs.cpp:104
EDGEPT * next
Definition: blobs.h:176
TESSLINE()
Definition: blobs.h:188
void SetupFromPos()
Definition: blobs.cpp:211
ICOORD step(int index) const
Definition: coutln.h:144
int16_t height() const
Definition: rect.h:108
void Move(const ICOORD vec)
Definition: blobs.cpp:440
void Scale(float factor)
Definition: blobs.cpp:448
TESSLINE * ApproximateOutline(bool allow_detailed_fx, C_OUTLINE *c_outline)
Definition: polyaprx.cpp:65
float y() const
Definition: points.h:211
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob, const TPOINT &location)
Definition: blobs.cpp:972
void UpdateRange(const T1 &x, T2 *lower_bound, T2 *upper_bound)
Definition: helpers.h:121