31 #include <allheaders.h>
37 static INT_VAR(textord_tabfind_show_images,
false,
"Show image blobs");
66 return pixCreate(pixGetWidth(pix), pixGetHeight(pix), 1);
70 Image pixr = pixReduceRankBinaryCascade(pix, 1, 0, 0, 0);
71 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
72 pixa_debug->
AddPix(pixr,
"CascadeReduced");
82 return pixCreate(pixGetWidth(pix), pixGetHeight(pix), 1);
86 Pixa *pixadb = (textord_tabfind_show_images && pixa_debug !=
nullptr) ? pixaCreate(0) :
nullptr;
87 Image pixht2 = pixGenerateHalftoneMask(pixr,
nullptr, &ht_found, pixadb);
89 Image pixdb = pixaDisplayTiledInColumns(pixadb, 3, 1.0, 20, 2);
90 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
91 pixa_debug->
AddPix(pixdb,
"HalftoneMask");
97 if (!ht_found && pixht2 !=
nullptr) {
100 if (pixht2 ==
nullptr) {
101 return pixCreate(pixGetWidth(pix), pixGetHeight(pix), 1);
105 Image pixht = pixExpandReplicate(pixht2, 2);
106 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
107 pixa_debug->
AddPix(pixht,
"HalftoneReplicated");
112 Image pixt = pixSeedfillBinary(
nullptr, pixht, pix, 8);
117 Image pixfinemask = pixReduceRankBinaryCascade(pixht, 1, 1, 3, 3);
118 pixDilateBrick(pixfinemask, pixfinemask, 5, 5);
119 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
120 pixa_debug->
AddPix(pixfinemask,
"FineMask");
122 Image pixreduced = pixReduceRankBinaryCascade(pixht, 1, 1, 1, 1);
123 Image pixreduced2 = pixReduceRankBinaryCascade(pixreduced, 3, 3, 3, 0);
125 pixDilateBrick(pixreduced2, pixreduced2, 5, 5);
126 Image pixcoarsemask = pixExpandReplicate(pixreduced2, 8);
128 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
129 pixa_debug->
AddPix(pixcoarsemask,
"CoarseMask");
132 pixcoarsemask &= pixfinemask;
135 pixDilateBrick(pixcoarsemask, pixcoarsemask, 3, 3);
136 Image pixmask = pixExpandReplicate(pixcoarsemask, 16);
138 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
139 pixa_debug->
AddPix(pixmask,
"MaskDilated");
144 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
145 pixa_debug->
AddPix(pixht,
"FinalMask");
148 Image result = pixCreate(pixGetWidth(pix), pixGetHeight(pix), 1);
166 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
167 pixa_debug->
AddPix(pix,
"Conncompimage");
170 *boxa = pixConnComp(pix, pixa, 8);
175 if (*boxa !=
nullptr && *pixa !=
nullptr) {
176 npixes = pixaGetCount(*pixa);
178 for (
int i = 0; i < npixes; ++i) {
179 int x_start, x_end, y_start, y_end;
180 Image img_pix = pixaGetPix(*pixa, i, L_CLONE);
181 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
182 pixa_debug->
AddPix(img_pix,
"A component");
186 Image simple_pix = pixCreate(x_end - x_start, y_end - y_start, 1);
187 pixSetAll(simple_pix);
190 pixaReplacePix(*pixa, i, simple_pix,
nullptr);
191 img_pix = pixaGetPix(*pixa, i, L_CLONE);
193 l_int32 x, y, width, height;
194 boxaGetBoxGeometry(*boxa, i, &x, &y, &width, &height);
195 Box *simple_box = boxCreate(x + x_start, y + y_start, x_end - x_start, y_end - y_start);
196 boxaReplaceBox(*boxa, i, simple_box);
209 static bool HScanForEdge(uint32_t *data,
int wpl,
int x_start,
int x_end,
int min_count,
210 int mid_width,
int max_count,
int y_end,
int y_step,
int *y_start) {
212 for (
int y = *y_start; y != y_end; y += y_step) {
216 uint32_t *line = data + wpl * y;
217 for (
int x = x_start; x < x_end; ++x) {
218 if (GET_DATA_BIT(line, x)) {
222 if (mid_rows == 0 && pix_count < min_count) {
228 if (pix_count > max_count) {
232 if (mid_rows > mid_width) {
246 static bool VScanForEdge(uint32_t *data,
int wpl,
int y_start,
int y_end,
int min_count,
247 int mid_width,
int max_count,
int x_end,
int x_step,
int *x_start) {
249 for (
int x = *x_start; x != x_end; x += x_step) {
251 uint32_t *line = data + y_start * wpl;
252 for (
int y = y_start; y < y_end; ++y, line += wpl) {
253 if (GET_DATA_BIT(line, x)) {
257 if (mid_cols == 0 && pix_count < min_count) {
263 if (pix_count > max_count) {
267 if (mid_cols > mid_width) {
284 double max_skew_gradient,
int *x_start,
int *y_start,
285 int *x_end,
int *y_end) {
288 *x_end = pixGetWidth(pix);
290 *y_end = pixGetHeight(pix);
292 uint32_t *data = pixGetData(pix);
293 int wpl = pixGetWpl(pix);
294 bool any_cut =
false;
295 bool left_done =
false;
296 bool right_done =
false;
297 bool top_done =
false;
298 bool bottom_done =
false;
302 int width = *x_end - *x_start;
303 int min_count =
static_cast<int>(width * min_fraction);
304 int max_count =
static_cast<int>(width * max_fraction);
305 int edge_width =
static_cast<int>(width * max_skew_gradient);
306 if (HScanForEdge(data, wpl, *x_start, *x_end, min_count, edge_width, max_count, *y_end, 1,
313 if (HScanForEdge(data, wpl, *x_start, *x_end, min_count, edge_width, max_count, *y_start, -1,
322 int height = *y_end - *y_start;
323 min_count =
static_cast<int>(height * min_fraction);
324 max_count =
static_cast<int>(height * max_fraction);
325 edge_width =
static_cast<int>(height * max_skew_gradient);
326 if (VScanForEdge(data, wpl, *y_start, *y_end, min_count, edge_width, max_count, *x_end, 1,
333 if (VScanForEdge(data, wpl, *y_start, *y_end, min_count, edge_width, max_count, *x_start, -1,
344 return left_done && right_done && top_done && bottom_done;
352 Box *input_box = boxCreate(*x_start, *y_start, *x_end - *x_start, *y_end - *y_start);
353 Box *output_box =
nullptr;
354 pixClipBoxToForeground(pix, input_box,
nullptr, &output_box);
355 bool result = output_box !=
nullptr;
357 l_int32 x, y, width, height;
358 boxGetGeometry(output_box, &x, &y, &width, &height);
363 boxDestroy(&output_box);
365 boxDestroy(&input_box);
373 const uint8_t *point) {
377 line_vector[i] =
static_cast<int>(line2[i]) -
static_cast<int>(line1[i]);
378 point_vector[i] =
static_cast<int>(point[i]) -
static_cast<int>(line1[i]);
380 line_vector[L_ALPHA_CHANNEL] = 0;
383 cross[COLOR_RED] = line_vector[COLOR_GREEN] * point_vector[COLOR_BLUE] -
384 line_vector[COLOR_BLUE] * point_vector[COLOR_GREEN];
385 cross[COLOR_GREEN] = line_vector[COLOR_BLUE] * point_vector[COLOR_RED] -
386 line_vector[COLOR_RED] * point_vector[COLOR_BLUE];
387 cross[COLOR_BLUE] = line_vector[COLOR_RED] * point_vector[COLOR_GREEN] -
388 line_vector[COLOR_GREEN] * point_vector[COLOR_RED];
389 cross[L_ALPHA_CHANNEL] = 0;
391 double cross_sq = 0.0;
392 double line_sq = 0.0;
394 cross_sq +=
static_cast<double>(cross[j]) * cross[j];
395 line_sq +=
static_cast<double>(line_vector[j]) * line_vector[j];
397 if (line_sq == 0.0) {
400 return cross_sq / line_sq;
406 composeRGBPixel(r, g, b, &result);
414 }
else if (pixel >= 255.0) {
417 return static_cast<uint8_t
>(pixel);
431 Image color_map2,
Image rms_map, uint8_t *color1,
433 ASSERT_HOST(pix !=
nullptr && pixGetDepth(pix) == 32);
436 int width = pixGetWidth(pix);
437 int height = pixGetHeight(pix);
438 int left_pad = std::max(rect.
left() - 2 * factor, 0) / factor;
439 int top_pad = (rect.
top() + 2 * factor + (factor - 1)) / factor;
440 top_pad = std::min(height, top_pad);
441 int right_pad = (rect.
right() + 2 * factor + (factor - 1)) / factor;
442 right_pad = std::min(width, right_pad);
443 int bottom_pad = std::max(rect.
bottom() - 2 * factor, 0) / factor;
444 int width_pad = right_pad - left_pad;
445 int height_pad = top_pad - bottom_pad;
446 if (width_pad < 1 || height_pad < 1 || width_pad + height_pad < 4) {
450 Box *scaled_box = boxCreate(left_pad, height - top_pad, width_pad, height_pad);
451 Image scaled = pixClipRectangle(pix, scaled_box,
nullptr);
454 STATS red_stats(0, 256);
455 STATS green_stats(0, 256);
456 STATS blue_stats(0, 256);
457 uint32_t *data = pixGetData(scaled);
459 for (
int y = 0; y < height_pad; ++y) {
460 for (
int x = 0; x < width_pad; ++x, ++data) {
461 int r = GET_DATA_BYTE(data, COLOR_RED);
462 int g = GET_DATA_BYTE(data, COLOR_GREEN);
463 int b = GET_DATA_BYTE(data, COLOR_BLUE);
465 green_stats.
add(g, 1);
466 blue_stats.
add(b, 1);
473 int best_l8 =
static_cast<int>(red_stats.
ile(0.125f));
474 int best_u8 =
static_cast<int>(ceil(red_stats.
ile(0.875f)));
475 int best_i8r = best_u8 - best_l8;
476 int x_color = COLOR_RED;
477 int y1_color = COLOR_GREEN;
478 int y2_color = COLOR_BLUE;
479 int l8 =
static_cast<int>(green_stats.
ile(0.125f));
480 int u8 =
static_cast<int>(ceil(green_stats.
ile(0.875f)));
481 if (u8 - l8 > best_i8r) {
485 x_color = COLOR_GREEN;
486 y1_color = COLOR_RED;
488 l8 =
static_cast<int>(blue_stats.
ile(0.125f));
489 u8 =
static_cast<int>(ceil(blue_stats.
ile(0.875f)));
490 if (u8 - l8 > best_i8r) {
494 x_color = COLOR_BLUE;
495 y1_color = COLOR_GREEN;
496 y2_color = COLOR_RED;
501 uint32_t *data = pixGetData(scaled);
502 for (
int im_y = 0; im_y < height_pad; ++im_y) {
503 for (
int im_x = 0; im_x < width_pad; ++im_x, ++data) {
504 int x = GET_DATA_BYTE(data, x_color);
505 int y1 = GET_DATA_BYTE(data, y1_color);
506 int y2 = GET_DATA_BYTE(data, y2_color);
511 double m1 = line1.
m();
512 double c1 = line1.
c(m1);
513 double m2 = line2.
m();
514 double c2 = line2.
c(m2);
515 double rms = line1.
rms(m1, c1) + line2.
rms(m2, c2);
519 color1[y1_color] =
ClipToByte(m1 * best_l8 + c1 + 0.5);
520 color1[y2_color] =
ClipToByte(m2 * best_l8 + c2 + 0.5);
523 color2[y1_color] =
ClipToByte(m1 * best_u8 + c1 + 0.5);
524 color2[y2_color] =
ClipToByte(m2 * best_u8 + c2 + 0.5);
531 color1[L_ALPHA_CHANNEL] = 0;
532 memcpy(color2, color1, 4);
534 if (color_map1 !=
nullptr) {
535 pixSetInRectArbitrary(color_map1, scaled_box,
536 ComposeRGB(color1[COLOR_RED], color1[COLOR_GREEN], color1[COLOR_BLUE]));
537 pixSetInRectArbitrary(color_map2, scaled_box,
538 ComposeRGB(color2[COLOR_RED], color2[COLOR_GREEN], color2[COLOR_BLUE]));
539 pixSetInRectArbitrary(rms_map, scaled_box, color1[L_ALPHA_CHANNEL]);
542 boxDestroy(&scaled_box);
589 TBOX search_box(box1);
592 if (box1.
x_gap(box2) <= 0) {
598 if (box1.
y_gap(box2) <= 0) {
617 TBOX rotated_im_box(im_box);
618 rotated_im_box.
rotate(rotation);
620 pixRasterop(rect_pix, 0, 0, box.
width(), box.
height(), PIX_SRC, pix,
621 box.
left() - rotated_im_box.
left(), rotated_im_box.
top() - box.
top());
623 pixCountPixels(rect_pix, &result,
nullptr);
632 static void AttemptToShrinkBox(
const FCOORD &rotation,
const FCOORD &rerotation,
const TBOX &im_box,
634 TBOX rotated_box(*slice);
635 rotated_box.rotate(rerotation);
636 TBOX rotated_im_box(im_box);
637 rotated_im_box.rotate(rerotation);
638 int left = rotated_box.left() - rotated_im_box.left();
639 int right = rotated_box.right() - rotated_im_box.left();
640 int top = rotated_im_box.top() - rotated_box.top();
641 int bottom = rotated_im_box.top() - rotated_box.bottom();
643 top = rotated_im_box.top() - top;
644 bottom = rotated_im_box.top() - bottom;
645 left += rotated_im_box.left();
646 right += rotated_im_box.left();
647 rotated_box.set_to_given_coords(left, bottom, right, top);
648 rotated_box.rotate(rotation);
649 slice->
set_left(rotated_box.left());
677 static void CutChunkFromParts(
const TBOX &box,
const TBOX &im_box,
const FCOORD &rotation,
678 const FCOORD &rerotation, Image pix, ColPartition_LIST *part_list) {
680 ColPartition_IT part_it(part_list);
682 ColPartition *part = part_it.data();
683 TBOX part_box = part->bounding_box();
684 if (part_box.overlap(box)) {
690 if (box.top() < part_box.top()) {
691 TBOX slice(part_box);
694 AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
695 part_it.add_before_stay_put(
700 if (box.left() > part_box.left()) {
701 TBOX slice(part_box);
703 if (box.top() < part_box.top()) {
706 if (box.bottom() > part_box.bottom()) {
710 AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
711 part_it.add_before_stay_put(
716 if (box.right() < part_box.right()) {
717 TBOX slice(part_box);
719 if (box.top() < part_box.top()) {
722 if (box.bottom() > part_box.bottom()) {
726 AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
727 part_it.add_before_stay_put(
732 if (box.bottom() > part_box.bottom()) {
733 TBOX slice(part_box);
736 AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
737 part_it.add_before_stay_put(
742 delete part_it.extract();
745 }
while (!part_it.at_first());
755 static void DivideImageIntoParts(
const TBOX &im_box,
const FCOORD &rotation,
756 const FCOORD &rerotation, Image pix,
759 ColPartition *pix_part =
761 ColPartition_IT part_it(part_list);
762 part_it.add_after_then_move(pix_part);
764 rectsearch->StartRectSearch(im_box);
766 while ((part = rectsearch->NextRectSearch()) !=
nullptr) {
767 TBOX part_box = part->bounding_box();
768 if (part_box.contains(im_box) && part->flow() >=
BTFT_CHAIN) {
770 for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
771 ColPartition *pix_part = part_it.extract();
772 pix_part->DeleteBoxes();
777 TBOX overlap_box = part_box.intersection(im_box);
780 if (black_area * 2 < part_box.area() || !im_box.contains(part_box)) {
783 int padding = part->blob_type() ==
BRT_VERT_TEXT ? part_box.width() : part_box.height();
784 part_box.set_top(part_box.top() + padding / 2);
785 part_box.set_bottom(part_box.bottom() - padding / 2);
786 CutChunkFromParts(part_box, im_box, rotation, rerotation, pix, part_list);
792 if (part_list->empty()) {
800 static int ExpandImageLeft(
const TBOX &box,
int left_limit, ColPartitionGrid *part_grid) {
804 search.StartSideSearch(box.left(), box.bottom(), box.top());
805 while ((part =
search.NextSideSearch(
true)) !=
nullptr) {
807 const TBOX &part_box(part->bounding_box());
808 if (part_box.y_gap(box) < 0) {
809 if (part_box.right() > left_limit && part_box.right() < box.left()) {
810 left_limit = part_box.right();
816 if (part !=
nullptr) {
818 TBOX search_box(left_limit, box.bottom(), box.left(), box.top());
819 search.StartRectSearch(search_box);
820 while ((part =
search.NextRectSearch()) !=
nullptr) {
822 const TBOX &part_box(part->bounding_box());
823 if (part_box.y_gap(box) < 0) {
824 if (part_box.right() > left_limit && part_box.right() < box.left()) {
825 left_limit = part_box.right();
836 static int ExpandImageRight(
const TBOX &box,
int right_limit, ColPartitionGrid *part_grid) {
840 search.StartSideSearch(box.right(), box.bottom(), box.top());
841 while ((part =
search.NextSideSearch(
false)) !=
nullptr) {
843 const TBOX &part_box(part->bounding_box());
844 if (part_box.y_gap(box) < 0) {
845 if (part_box.left() < right_limit && part_box.left() > box.right()) {
846 right_limit = part_box.left();
852 if (part !=
nullptr) {
854 TBOX search_box(box.left(), box.bottom(), right_limit, box.top());
855 search.StartRectSearch(search_box);
856 while ((part =
search.NextRectSearch()) !=
nullptr) {
858 const TBOX &part_box(part->bounding_box());
859 if (part_box.y_gap(box) < 0) {
860 if (part_box.left() < right_limit && part_box.left() > box.right()) {
861 right_limit = part_box.left();
872 static int ExpandImageBottom(
const TBOX &box,
int bottom_limit, ColPartitionGrid *part_grid) {
876 search.StartVerticalSearch(box.left(), box.right(), box.bottom());
877 while ((part =
search.NextVerticalSearch(
true)) !=
nullptr) {
879 const TBOX &part_box(part->bounding_box());
880 if (part_box.x_gap(box) < 0) {
881 if (part_box.top() > bottom_limit && part_box.top() < box.bottom()) {
882 bottom_limit = part_box.top();
888 if (part !=
nullptr) {
890 TBOX search_box(box.left(), bottom_limit, box.right(), box.bottom());
891 search.StartRectSearch(search_box);
892 while ((part =
search.NextRectSearch()) !=
nullptr) {
894 const TBOX &part_box(part->bounding_box());
895 if (part_box.x_gap(box) < 0) {
896 if (part_box.top() > bottom_limit && part_box.top() < box.bottom()) {
897 bottom_limit = part_box.top();
908 static int ExpandImageTop(
const TBOX &box,
int top_limit, ColPartitionGrid *part_grid) {
912 search.StartVerticalSearch(box.left(), box.right(), box.top());
913 while ((part =
search.NextVerticalSearch(
false)) !=
nullptr) {
915 const TBOX &part_box(part->bounding_box());
916 if (part_box.x_gap(box) < 0) {
917 if (part_box.bottom() < top_limit && part_box.bottom() > box.top()) {
918 top_limit = part_box.bottom();
924 if (part !=
nullptr) {
926 TBOX search_box(box.left(), box.top(), box.right(), top_limit);
927 search.StartRectSearch(search_box);
928 while ((part =
search.NextRectSearch()) !=
nullptr) {
930 const TBOX &part_box(part->bounding_box());
931 if (part_box.x_gap(box) < 0) {
932 if (part_box.bottom() < top_limit && part_box.bottom() > box.top()) {
933 top_limit = part_box.bottom();
947 ColPartitionGrid *part_grid,
TBOX *expanded_box) {
948 *expanded_box = im_box;
951 expanded_box->set_left(ExpandImageLeft(im_box, limit_box.left(), part_grid));
954 expanded_box->set_right(ExpandImageRight(im_box, limit_box.right(), part_grid));
957 expanded_box->set_top(ExpandImageTop(im_box, limit_box.top(), part_grid));
960 expanded_box->set_bottom(ExpandImageBottom(im_box, limit_box.bottom(), part_grid));
965 return expanded_box->area() - im_box.area();
972 static void MaximalImageBoundingBox(ColPartitionGrid *part_grid,
TBOX *im_box) {
974 memset(dunnit, 0,
sizeof(dunnit));
975 TBOX limit_box(part_grid->bleft().x(), part_grid->bleft().y(), part_grid->tright().x(),
976 part_grid->tright().y());
977 TBOX text_box(*im_box);
978 for (
int iteration = 0; iteration <
BND_COUNT; ++iteration) {
983 for (
int dir = 0; dir <
BND_COUNT; ++dir) {
987 int area_delta = ExpandImageDir(bnd, text_box, limit_box, part_grid, &expanded_boxes[bnd]);
988 if (best_delta < 0 || area_delta < best_delta) {
989 best_delta = area_delta;
995 dunnit[best_dir] =
true;
996 text_box = expanded_boxes[best_dir];
1005 static void DeletePartition(ColPartition *part) {
1009 part->DeleteBoxes();
1014 part->SetBlobTypes();
1015 part->DisownBoxes();
1033 ColPartitionGrid *part_grid, ColPartition **part_ptr) {
1034 ColPartition *image_part = *part_ptr;
1035 TBOX im_part_box = image_part->bounding_box();
1036 if (textord_tabfind_show_images > 1) {
1037 tprintf(
"Searching for merge with image part:");
1038 im_part_box.print();
1040 max_image_box.print();
1042 rectsearch->StartRectSearch(max_image_box);
1044 ColPartition *best_part =
nullptr;
1046 while ((part = rectsearch->NextRectSearch()) !=
nullptr) {
1047 if (textord_tabfind_show_images > 1) {
1048 tprintf(
"Considering merge with part:");
1050 if (im_part_box.contains(part->bounding_box())) {
1052 }
else if (!max_image_box.contains(part->bounding_box())) {
1053 tprintf(
"Not within text box\n");
1064 TBOX box = part->bounding_box();
1065 if (max_image_box.contains(box) && part->blob_type() !=
BRT_NOISE) {
1066 if (im_part_box.contains(box)) {
1068 rectsearch->RemoveBBox();
1069 DeletePartition(part);
1072 int x_dist = std::max(0, box.x_gap(im_part_box));
1073 int y_dist = std::max(0, box.y_gap(im_part_box));
1074 int dist = x_dist * x_dist + y_dist * y_dist;
1075 if (dist > box.area() || dist > im_part_box.area()) {
1078 if (best_part ==
nullptr || dist < best_dist) {
1085 if (best_part !=
nullptr) {
1087 TBOX box = best_part->bounding_box();
1088 if (textord_tabfind_show_images > 1) {
1089 tprintf(
"Merging image part:");
1090 im_part_box.print();
1096 DeletePartition(image_part);
1097 part_grid->RemoveBBox(best_part);
1098 DeletePartition(best_part);
1099 rectsearch->RepositionIterator();
1107 static int IntersectArea(
const TBOX &box, ColPartition_LIST *part_list) {
1108 int intersect_area = 0;
1109 ColPartition_IT part_it(part_list);
1111 for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
1112 ColPartition *image_part = part_it.data();
1113 TBOX intersect = box.intersection(image_part->bounding_box());
1114 intersect_area += intersect.area();
1116 return intersect_area;
1124 static bool TestWeakIntersectedPart(
const TBOX &im_box, ColPartition_LIST *part_list,
1125 ColPartition *part) {
1128 const TBOX &part_box = part->bounding_box();
1129 if (im_box.contains(part_box)) {
1130 int area = part_box.area();
1131 int intersect_area = IntersectArea(part_box, part_list);
1132 if (area < 2 * intersect_area) {
1145 static void EliminateWeakParts(
const TBOX &im_box, ColPartitionGrid *part_grid,
1146 ColPartition_LIST *big_parts, ColPartition_LIST *part_list) {
1149 rectsearch.StartRectSearch(im_box);
1150 while ((part = rectsearch.NextRectSearch()) !=
nullptr) {
1151 if (TestWeakIntersectedPart(im_box, part_list, part)) {
1154 rectsearch.RemoveBBox();
1155 DeletePartition(part);
1161 part->SetBlobTypes();
1165 ColPartition_IT big_it(big_parts);
1166 for (big_it.mark_cycle_pt(); !big_it.cycled_list(); big_it.forward()) {
1167 part = big_it.data();
1168 if (TestWeakIntersectedPart(im_box, part_list, part)) {
1170 DeletePartition(big_it.extract());
1179 static bool ScanForOverlappingText(ColPartitionGrid *part_grid,
TBOX *box) {
1181 TBOX padded_box(*box);
1183 rectsearch.StartRectSearch(padded_box);
1185 bool any_text_in_padded_rect =
false;
1186 while ((part = rectsearch.NextRectSearch()) !=
nullptr) {
1189 any_text_in_padded_rect =
true;
1190 const TBOX &part_box = part->bounding_box();
1191 if (box->overlap(part_box)) {
1196 if (!any_text_in_padded_rect) {
1206 static void MarkAndDeleteImageParts(
const FCOORD &rerotate, ColPartitionGrid *part_grid,
1207 ColPartition_LIST *image_parts, Image image_pix) {
1208 if (image_pix ==
nullptr) {
1211 int imageheight = pixGetHeight(image_pix);
1212 ColPartition_IT part_it(image_parts);
1213 for (; !part_it.empty(); part_it.forward()) {
1214 ColPartition *part = part_it.extract();
1215 TBOX part_box = part->bounding_box();
1217 if (!ScanForOverlappingText(part_grid, &part_box) || type ==
BRT_RECTIMAGE ||
1221 part_box.rotate(rerotate);
1222 int left = part_box.left();
1223 int top = part_box.top();
1224 pixRasterop(image_pix, left, imageheight - top, part_box.width(), part_box.height(), PIX_SET,
1227 DeletePartition(part);
1241 ColPartition_LIST parts_list;
1242 ColPartition_IT part_it(&parts_list);
1249 part_it.add_after_then_move(part);
1254 MarkAndDeleteImageParts(rerotation, part_grid, &parts_list, image_mask);
1261 if (part_grid !=
nullptr) {
1265 gsearch.StartFullSearch();
1267 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
1271 const TBOX &part_box = part->bounding_box();
1274 gsearch.RemoveBBox();
1275 DeletePartition(part);
1294 ColPartition_LIST *big_parts) {
1295 int imageheight = pixGetHeight(image_pix);
1301 if (boxa !=
nullptr && pixa !=
nullptr) {
1302 nboxes = boxaGetCount(boxa);
1304 for (
int i = 0; i < nboxes; ++i) {
1305 l_int32 x, y, width, height;
1306 boxaGetBoxGeometry(boxa, i, &x, &y, &width, &height);
1307 Image pix = pixaGetPix(pixa, i, L_CLONE);
1308 TBOX im_box(x, imageheight - y - height, x + width, imageheight - y);
1312 ColPartition_LIST part_list;
1313 DivideImageIntoParts(im_box, rotation, rerotation, pix, &rectsearch, &part_list);
1314 if (textord_tabfind_show_images && pixa_debug !=
nullptr) {
1315 pixa_debug->
AddPix(pix,
"ImageComponent");
1316 tprintf(
"Component has %d parts\n", part_list.length());
1319 if (!part_list.empty()) {
1320 ColPartition_IT part_it(&part_list);
1321 if (part_list.singleton()) {
1326 TBOX text_box(im_box);
1327 MaximalImageBoundingBox(part_grid, &text_box);
1328 while (ExpandImageIntoParts(text_box, &rectsearch, part_grid, &part)) {
1331 part_it.set_to_list(&part_list);
1332 part_it.add_after_then_move(part);
1335 EliminateWeakParts(im_box, part_grid, big_parts, &part_list);
1337 for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
1340 part_grid->
InsertBBox(
true,
true, image_part);
1341 if (!part_it.at_last()) {
1351 DeleteSmallImages(part_grid);
1352 #ifndef GRAPHICS_DISABLED
1353 if (textord_tabfind_show_images) {
#define INT_VAR(name, val, comment)
const double kMinRectangularFraction
const double kRMSFitScaling
void tprintf(const char *format,...)
GridSearch< ColPartition, ColPartition_CLIST, ColPartition_C_IT > ColPartitionGridSearch
const int kMinColorDifference
LIST search(LIST list, void *key, int_compare is_equal)
const double kMaxRectangularGradient
const int kMinImageFindSize
const double kMaxRectangularFraction
void AddPix(const Image pix, const char *caption)
void add(double x, double y)
double rms(double m, double c) const
int y_gap(const TBOX &box) const
TDimension height() const
void rotate(const FCOORD &vec)
int x_gap(const TBOX &box) const
TDimension bottom() const
void add(int32_t value, int32_t count)
double ile(double frac) const
void SetUniqueMode(bool mode)
void DisplayBoxes(ScrollView *window)
void InsertBBox(bool h_spread, bool v_spread, BBC *bbox)
ScrollView * MakeWindow(int x, int y, const char *window_name)
static ColPartition * FakePartition(const TBOX &box, PolyBlockType block_type, BlobRegionType blob_type, BlobTextFlowType flow)
BlobRegionType blob_type() const
const TBOX & bounding_box() const
void AddPartner(bool upper, ColPartition *partner)
static bool BlankImageInBetween(const TBOX &box1, const TBOX &box2, const TBOX &im_box, const FCOORD &rotation, Image pix)
static bool pixNearlyRectangular(Image pix, double min_fraction, double max_fraction, double max_skew_gradient, int *x_start, int *y_start, int *x_end, int *y_end)
static void ConnCompAndRectangularize(Image pix, DebugPixa *pixa_debug, Boxa **boxa, Pixa **pixa)
static bool BoundsWithinRect(Image pix, int *x_start, int *y_start, int *x_end, int *y_end)
static void ComputeRectangleColors(const TBOX &rect, Image pix, int factor, Image color_map1, Image color_map2, Image rms_map, uint8_t *color1, uint8_t *color2)
static uint32_t ComposeRGB(uint32_t r, uint32_t g, uint32_t b)
static void FindImagePartitions(Image image_pix, const FCOORD &rotation, const FCOORD &rerotation, TO_BLOCK *block, TabFind *tab_grid, DebugPixa *pixa_debug, ColPartitionGrid *part_grid, ColPartition_LIST *big_parts)
static uint8_t ClipToByte(double pixel)
static void TransferImagePartsToImageMask(const FCOORD &rerotation, ColPartitionGrid *part_grid, Image image_mask)
static Image FindImages(Image pix, DebugPixa *pixa_debug)
static int CountPixelsInRotatedBox(TBOX box, const TBOX &im_box, const FCOORD &rotation, Image pix)
static double ColorDistanceFromLine(const uint8_t *line1, const uint8_t *line2, const uint8_t *point)