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