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