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