#ifdef HAVE_CONFIG_H
# include "config_auto.h"
#endif
#include "imagefind.h"
#include "colpartitiongrid.h"
#include "linlsq.h"
#include "params.h"
#include "statistc.h"
#include <allheaders.h>
#include <algorithm>
namespace tesseract {
static INT_VAR(textord_tabfind_show_images, false, "Show image blobs");
const double kMinRectangularFraction = 0.125;
const double kMaxRectangularFraction = 0.75;
const double kMaxRectangularGradient = 0.1;
const int kMinImageFindSize = 100;
const int kNoisePadding = 4;
static bool HScanForEdge(uint32_t *data, int wpl, int x_start, int x_end, int min_count,
int mid_width, int max_count, int y_end, int y_step, int *y_start) {
int mid_rows = 0;
for (int y = *y_start; y != y_end; y += y_step) {
int pix_count = 0;
uint32_t *line = data + wpl * y;
for (int x = x_start; x < x_end; ++x) {
if (GET_DATA_BIT(line, x)) {
++pix_count;
}
}
if (mid_rows == 0 && pix_count < min_count) {
continue;
}
if (mid_rows == 0) {
*y_start = y;
}
if (pix_count > max_count) {
return true;
}
++mid_rows;
if (mid_rows > mid_width) {
break;
}
}
return false;
}
static bool VScanForEdge(uint32_t *data, int wpl, int y_start, int y_end, int min_count,
int mid_width, int max_count, int x_end, int x_step, int *x_start) {
int mid_cols = 0;
for (int x = *x_start; x != x_end; x += x_step) {
int pix_count = 0;
uint32_t *line = data + y_start * wpl;
for (int y = y_start; y < y_end; ++y, line += wpl) {
if (GET_DATA_BIT(line, x)) {
++pix_count;
}
}
if (mid_cols == 0 && pix_count < min_count) {
continue;
}
if (mid_cols == 0) {
*x_start = x;
}
if (pix_count > max_count) {
return true;
}
++mid_cols;
if (mid_cols > mid_width) {
break;
}
}
return false;
}
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) {
ASSERT_HOST(pix != nullptr);
*x_start = 0;
*x_end = pixGetWidth(pix);
*y_start = 0;
*y_end = pixGetHeight(pix);
uint32_t *data = pixGetData(pix);
int wpl = pixGetWpl(pix);
bool any_cut = false;
bool left_done = false;
bool right_done = false;
bool top_done = false;
bool bottom_done = false;
do {
any_cut = false;
int width = *x_end - *x_start;
int min_count = static_cast<int>(width * min_fraction);
int max_count = static_cast<int>(width * max_fraction);
int edge_width = static_cast<int>(width * max_skew_gradient);
if (HScanForEdge(data, wpl, *x_start, *x_end, min_count, edge_width, max_count, *y_end, 1,
y_start) &&
!top_done) {
top_done = true;
any_cut = true;
}
--(*y_end);
if (HScanForEdge(data, wpl, *x_start, *x_end, min_count, edge_width, max_count, *y_start, -1,
y_end) &&
!bottom_done) {
bottom_done = true;
any_cut = true;
}
++(*y_end);
int height = *y_end - *y_start;
min_count = static_cast<int>(height * min_fraction);
max_count = static_cast<int>(height * max_fraction);
edge_width = static_cast<int>(height * max_skew_gradient);
if (VScanForEdge(data, wpl, *y_start, *y_end, min_count, edge_width, max_count, *x_end, 1,
x_start) &&
!left_done) {
left_done = true;
any_cut = true;
}
--(*x_end);
if (VScanForEdge(data, wpl, *y_start, *y_end, min_count, edge_width, max_count, *x_start, -1,
x_end) &&
!right_done) {
right_done = true;
any_cut = true;
}
++(*x_end);
} while (any_cut);
return left_done && right_done && top_done && bottom_done;
}
static void ConnCompAndRectangularize(Image pix, DebugPixa *pixa_debug, Boxa **boxa,
Pixa **pixa) {
*boxa = nullptr;
*pixa = nullptr;
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pix, "Conncompimage");
}
*boxa = pixConnComp(pix, pixa, 8);
int npixes = 0;
if (*boxa != nullptr && *pixa != nullptr) {
npixes = pixaGetCount(*pixa);
}
for (int i = 0; i < npixes; ++i) {
int x_start, x_end, y_start, y_end;
Image img_pix = pixaGetPix(*pixa, i, L_CLONE);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(img_pix, "A component");
}
if (pixNearlyRectangular(img_pix, kMinRectangularFraction, kMaxRectangularFraction,
kMaxRectangularGradient, &x_start, &y_start, &x_end, &y_end)) {
Image simple_pix = pixCreate(x_end - x_start, y_end - y_start, 1);
pixSetAll(simple_pix);
img_pix.destroy();
pixaReplacePix(*pixa, i, simple_pix, nullptr);
img_pix = pixaGetPix(*pixa, i, L_CLONE);
l_int32 x, y, width, height;
boxaGetBoxGeometry(*boxa, i, &x, &y, &width, &height);
Box *simple_box = boxCreate(x + x_start, y + y_start, x_end - x_start, y_end - y_start);
boxaReplaceBox(*boxa, i, simple_box);
}
img_pix.destroy();
}
}
Image ImageFind::FindImages(Image pix, DebugPixa *pixa_debug) {
auto width = pixGetWidth(pix);
auto height = pixGetHeight(pix);
if (width / 2 < kMinImageFindSize || height / 2 < kMinImageFindSize) {
return pixCreate(width, height, 1);
}
Image pixr = pixReduceRankBinaryCascade(pix, 1, 0, 0, 0);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixr, "CascadeReduced");
}
l_int32 ht_found = 0;
Pixa *pixadb = (textord_tabfind_show_images && pixa_debug != nullptr) ? pixaCreate(0) : nullptr;
Image pixht2 = pixGenerateHalftoneMask(pixr, nullptr, &ht_found, pixadb);
if (pixadb) {
Image pixdb = pixaDisplayTiledInColumns(pixadb, 3, 1.0, 20, 2);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixdb, "HalftoneMask");
}
pixdb.destroy();
pixaDestroy(&pixadb);
}
pixr.destroy();
if (!ht_found && pixht2 != nullptr) {
pixht2.destroy();
}
if (pixht2 == nullptr) {
return pixCreate(width, height, 1);
}
Image pixht = pixExpandReplicate(pixht2, 2);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixht, "HalftoneReplicated");
}
pixht2.destroy();
Image pixt = pixSeedfillBinary(nullptr, pixht, pix, 8);
pixht |= pixt;
pixt.destroy();
Image pixfinemask = pixReduceRankBinaryCascade(pixht, 1, 1, 3, 3);
pixDilateBrick(pixfinemask, pixfinemask, 5, 5);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixfinemask, "FineMask");
}
Image pixreduced = pixReduceRankBinaryCascade(pixht, 1, 1, 1, 1);
Image pixreduced2 = pixReduceRankBinaryCascade(pixreduced, 3, 3, 3, 0);
pixreduced.destroy();
pixDilateBrick(pixreduced2, pixreduced2, 5, 5);
Image pixcoarsemask = pixExpandReplicate(pixreduced2, 8);
pixreduced2.destroy();
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixcoarsemask, "CoarseMask");
}
pixcoarsemask &= pixfinemask;
pixfinemask.destroy();
pixDilateBrick(pixcoarsemask, pixcoarsemask, 3, 3);
Image pixmask = pixExpandReplicate(pixcoarsemask, 16);
pixcoarsemask.destroy();
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixmask, "MaskDilated");
}
pixht &= pixmask;
pixmask.destroy();
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pixht, "FinalMask");
}
Image result = pixCreate(width, height, 1);
result |= pixht;
pixht.destroy();
return result;
}
bool ImageFind::BoundsWithinRect(Image pix, int *x_start, int *y_start, int *x_end, int *y_end) {
Box *input_box = boxCreate(*x_start, *y_start, *x_end - *x_start, *y_end - *y_start);
Box *output_box = nullptr;
pixClipBoxToForeground(pix, input_box, nullptr, &output_box);
bool result = output_box != nullptr;
if (result) {
l_int32 x, y, width, height;
boxGetGeometry(output_box, &x, &y, &width, &height);
*x_start = x;
*y_start = y;
*x_end = x + width;
*y_end = y + height;
boxDestroy(&output_box);
}
boxDestroy(&input_box);
return result;
}
double ImageFind::ColorDistanceFromLine(const uint8_t *line1, const uint8_t *line2,
const uint8_t *point) {
int line_vector[kRGBRMSColors];
int point_vector[kRGBRMSColors];
for (int i = 0; i < kRGBRMSColors; ++i) {
line_vector[i] = static_cast<int>(line2[i]) - static_cast<int>(line1[i]);
point_vector[i] = static_cast<int>(point[i]) - static_cast<int>(line1[i]);
}
line_vector[L_ALPHA_CHANNEL] = 0;
int cross[kRGBRMSColors];
cross[COLOR_RED] = line_vector[COLOR_GREEN] * point_vector[COLOR_BLUE] -
line_vector[COLOR_BLUE] * point_vector[COLOR_GREEN];
cross[COLOR_GREEN] = line_vector[COLOR_BLUE] * point_vector[COLOR_RED] -
line_vector[COLOR_RED] * point_vector[COLOR_BLUE];
cross[COLOR_BLUE] = line_vector[COLOR_RED] * point_vector[COLOR_GREEN] -
line_vector[COLOR_GREEN] * point_vector[COLOR_RED];
cross[L_ALPHA_CHANNEL] = 0;
double cross_sq = 0.0;
double line_sq = 0.0;
for (int j = 0; j < kRGBRMSColors; ++j) {
cross_sq += static_cast<double>(cross[j]) * cross[j];
line_sq += static_cast<double>(line_vector[j]) * line_vector[j];
}
if (line_sq == 0.0) {
return 0.0;
}
return cross_sq / line_sq;
}
bool ImageFind::BlankImageInBetween(const TBOX &box1, const TBOX &box2, const TBOX &im_box,
const FCOORD &rotation, Image pix) {
TBOX search_box(box1);
search_box += box2;
if (box1.x_gap(box2) >= box1.y_gap(box2)) {
if (box1.x_gap(box2) <= 0) {
return true;
}
search_box.set_left(std::min(box1.right(), box2.right()));
search_box.set_right(std::max(box1.left(), box2.left()));
} else {
if (box1.y_gap(box2) <= 0) {
return true;
}
search_box.set_top(std::max(box1.bottom(), box2.bottom()));
search_box.set_bottom(std::min(box1.top(), box2.top()));
}
return CountPixelsInRotatedBox(search_box, im_box, rotation, pix) == 0;
}
int ImageFind::CountPixelsInRotatedBox(TBOX box, const TBOX &im_box, const FCOORD &rotation,
Image pix) {
box &= im_box;
if (box.null_box()) {
return 0;
}
box.rotate(rotation);
TBOX rotated_im_box(im_box);
rotated_im_box.rotate(rotation);
Image rect_pix = pixCreate(box.width(), box.height(), 1);
pixRasterop(rect_pix, 0, 0, box.width(), box.height(), PIX_SRC, pix,
box.left() - rotated_im_box.left(), rotated_im_box.top() - box.top());
l_int32 result;
pixCountPixels(rect_pix, &result, nullptr);
rect_pix.destroy();
return result;
}
static void AttemptToShrinkBox(const FCOORD &rotation, const FCOORD &rerotation, const TBOX &im_box,
Image pix, TBOX *slice) {
TBOX rotated_box(*slice);
rotated_box.rotate(rerotation);
TBOX rotated_im_box(im_box);
rotated_im_box.rotate(rerotation);
int left = rotated_box.left() - rotated_im_box.left();
int right = rotated_box.right() - rotated_im_box.left();
int top = rotated_im_box.top() - rotated_box.top();
int bottom = rotated_im_box.top() - rotated_box.bottom();
ImageFind::BoundsWithinRect(pix, &left, &top, &right, &bottom);
top = rotated_im_box.top() - top;
bottom = rotated_im_box.top() - bottom;
left += rotated_im_box.left();
right += rotated_im_box.left();
rotated_box.set_to_given_coords(left, bottom, right, top);
rotated_box.rotate(rotation);
slice->set_left(rotated_box.left());
slice->set_right(rotated_box.right());
}
static void CutChunkFromParts(const TBOX &box, const TBOX &im_box, const FCOORD &rotation,
const FCOORD &rerotation, Image pix, ColPartition_LIST *part_list) {
ASSERT_HOST(!part_list->empty());
ColPartition_IT part_it(part_list);
do {
ColPartition *part = part_it.data();
TBOX part_box = part->bounding_box();
if (part_box.overlap(box)) {
if (box.top() < part_box.top()) {
TBOX slice(part_box);
slice.set_bottom(box.top());
if (ImageFind::CountPixelsInRotatedBox(slice, im_box, rerotation, pix) > 0) {
AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
part_it.add_before_stay_put(
ColPartition::FakePartition(slice, PT_UNKNOWN, BRT_POLYIMAGE, BTFT_NONTEXT));
}
}
if (box.left() > part_box.left()) {
TBOX slice(part_box);
slice.set_right(box.left());
if (box.top() < part_box.top()) {
slice.set_top(box.top());
}
if (box.bottom() > part_box.bottom()) {
slice.set_bottom(box.bottom());
}
if (ImageFind::CountPixelsInRotatedBox(slice, im_box, rerotation, pix) > 0) {
AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
part_it.add_before_stay_put(
ColPartition::FakePartition(slice, PT_UNKNOWN, BRT_POLYIMAGE, BTFT_NONTEXT));
}
}
if (box.right() < part_box.right()) {
TBOX slice(part_box);
slice.set_left(box.right());
if (box.top() < part_box.top()) {
slice.set_top(box.top());
}
if (box.bottom() > part_box.bottom()) {
slice.set_bottom(box.bottom());
}
if (ImageFind::CountPixelsInRotatedBox(slice, im_box, rerotation, pix) > 0) {
AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
part_it.add_before_stay_put(
ColPartition::FakePartition(slice, PT_UNKNOWN, BRT_POLYIMAGE, BTFT_NONTEXT));
}
}
if (box.bottom() > part_box.bottom()) {
TBOX slice(part_box);
slice.set_top(box.bottom());
if (ImageFind::CountPixelsInRotatedBox(slice, im_box, rerotation, pix) > 0) {
AttemptToShrinkBox(rotation, rerotation, im_box, pix, &slice);
part_it.add_before_stay_put(
ColPartition::FakePartition(slice, PT_UNKNOWN, BRT_POLYIMAGE, BTFT_NONTEXT));
}
}
part->DeleteBoxes();
delete part_it.extract();
}
part_it.forward();
} while (!part_it.at_first());
}
static void DivideImageIntoParts(const TBOX &im_box, const FCOORD &rotation,
const FCOORD &rerotation, Image pix,
ColPartitionGridSearch *rectsearch, ColPartition_LIST *part_list) {
ColPartition *pix_part =
ColPartition::FakePartition(im_box, PT_UNKNOWN, BRT_RECTIMAGE, BTFT_NONTEXT);
ColPartition_IT part_it(part_list);
part_it.add_after_then_move(pix_part);
rectsearch->StartRectSearch(im_box);
ColPartition *part;
while ((part = rectsearch->NextRectSearch()) != nullptr) {
TBOX part_box = part->bounding_box();
if (part_box.contains(im_box) && part->flow() >= BTFT_CHAIN) {
for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
ColPartition *pix_part = part_it.extract();
pix_part->DeleteBoxes();
delete pix_part;
}
} else if (part->flow() == BTFT_STRONG_CHAIN) {
TBOX overlap_box = part_box.intersection(im_box);
int black_area = ImageFind::CountPixelsInRotatedBox(overlap_box, im_box, rerotation, pix);
if (black_area * 2 < part_box.area() || !im_box.contains(part_box)) {
int padding = part->blob_type() == BRT_VERT_TEXT ? part_box.width() : part_box.height();
part_box.set_top(part_box.top() + padding / 2);
part_box.set_bottom(part_box.bottom() - padding / 2);
CutChunkFromParts(part_box, im_box, rotation, rerotation, pix, part_list);
} else {
part->set_flow(BTFT_TEXT_ON_IMAGE);
}
}
if (part_list->empty()) {
break;
}
}
}
static int ExpandImageLeft(const TBOX &box, int left_limit, ColPartitionGrid *part_grid) {
ColPartitionGridSearch search(part_grid);
ColPartition *part;
search.StartSideSearch(box.left(), box.bottom(), box.top());
while ((part = search.NextSideSearch(true)) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.y_gap(box) < 0) {
if (part_box.right() > left_limit && part_box.right() < box.left()) {
left_limit = part_box.right();
}
break;
}
}
}
if (part != nullptr) {
TBOX search_box(left_limit, box.bottom(), box.left(), box.top());
search.StartRectSearch(search_box);
while ((part = search.NextRectSearch()) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.y_gap(box) < 0) {
if (part_box.right() > left_limit && part_box.right() < box.left()) {
left_limit = part_box.right();
}
}
}
}
}
return left_limit;
}
static int ExpandImageRight(const TBOX &box, int right_limit, ColPartitionGrid *part_grid) {
ColPartitionGridSearch search(part_grid);
ColPartition *part;
search.StartSideSearch(box.right(), box.bottom(), box.top());
while ((part = search.NextSideSearch(false)) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.y_gap(box) < 0) {
if (part_box.left() < right_limit && part_box.left() > box.right()) {
right_limit = part_box.left();
}
break;
}
}
}
if (part != nullptr) {
TBOX search_box(box.left(), box.bottom(), right_limit, box.top());
search.StartRectSearch(search_box);
while ((part = search.NextRectSearch()) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.y_gap(box) < 0) {
if (part_box.left() < right_limit && part_box.left() > box.right()) {
right_limit = part_box.left();
}
}
}
}
}
return right_limit;
}
static int ExpandImageBottom(const TBOX &box, int bottom_limit, ColPartitionGrid *part_grid) {
ColPartitionGridSearch search(part_grid);
ColPartition *part;
search.StartVerticalSearch(box.left(), box.right(), box.bottom());
while ((part = search.NextVerticalSearch(true)) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.x_gap(box) < 0) {
if (part_box.top() > bottom_limit && part_box.top() < box.bottom()) {
bottom_limit = part_box.top();
}
break;
}
}
}
if (part != nullptr) {
TBOX search_box(box.left(), bottom_limit, box.right(), box.bottom());
search.StartRectSearch(search_box);
while ((part = search.NextRectSearch()) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.x_gap(box) < 0) {
if (part_box.top() > bottom_limit && part_box.top() < box.bottom()) {
bottom_limit = part_box.top();
}
}
}
}
}
return bottom_limit;
}
static int ExpandImageTop(const TBOX &box, int top_limit, ColPartitionGrid *part_grid) {
ColPartitionGridSearch search(part_grid);
ColPartition *part;
search.StartVerticalSearch(box.left(), box.right(), box.top());
while ((part = search.NextVerticalSearch(false)) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.x_gap(box) < 0) {
if (part_box.bottom() < top_limit && part_box.bottom() > box.top()) {
top_limit = part_box.bottom();
}
break;
}
}
}
if (part != nullptr) {
TBOX search_box(box.left(), box.top(), box.right(), top_limit);
search.StartRectSearch(search_box);
while ((part = search.NextRectSearch()) != nullptr) {
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_CHAIN) {
const TBOX &part_box(part->bounding_box());
if (part_box.x_gap(box) < 0) {
if (part_box.bottom() < top_limit && part_box.bottom() > box.top()) {
top_limit = part_box.bottom();
}
}
}
}
}
return top_limit;
}
static int ExpandImageDir(BlobNeighbourDir dir, const TBOX &im_box, const TBOX &limit_box,
ColPartitionGrid *part_grid, TBOX *expanded_box) {
*expanded_box = im_box;
switch (dir) {
case BND_LEFT:
expanded_box->set_left(ExpandImageLeft(im_box, limit_box.left(), part_grid));
break;
case BND_RIGHT:
expanded_box->set_right(ExpandImageRight(im_box, limit_box.right(), part_grid));
break;
case BND_ABOVE:
expanded_box->set_top(ExpandImageTop(im_box, limit_box.top(), part_grid));
break;
case BND_BELOW:
expanded_box->set_bottom(ExpandImageBottom(im_box, limit_box.bottom(), part_grid));
break;
default:
return 0;
}
return expanded_box->area() - im_box.area();
}
static void MaximalImageBoundingBox(ColPartitionGrid *part_grid, TBOX *im_box) {
bool dunnit[BND_COUNT];
memset(dunnit, 0, sizeof(dunnit));
TBOX limit_box(part_grid->bleft().x(), part_grid->bleft().y(), part_grid->tright().x(),
part_grid->tright().y());
TBOX text_box(*im_box);
for (int iteration = 0; iteration < BND_COUNT; ++iteration) {
int best_delta = -1;
BlobNeighbourDir best_dir = BND_LEFT;
TBOX expanded_boxes[BND_COUNT];
for (int dir = 0; dir < BND_COUNT; ++dir) {
auto bnd = static_cast<BlobNeighbourDir>(dir);
if (!dunnit[bnd]) {
TBOX expanded_box;
int area_delta = ExpandImageDir(bnd, text_box, limit_box, part_grid, &expanded_boxes[bnd]);
if (best_delta < 0 || area_delta < best_delta) {
best_delta = area_delta;
best_dir = bnd;
}
}
}
dunnit[best_dir] = true;
text_box = expanded_boxes[best_dir];
}
*im_box = text_box;
}
static void DeletePartition(ColPartition *part) {
BlobRegionType type = part->blob_type();
if (type == BRT_RECTIMAGE || type == BRT_POLYIMAGE) {
part->DeleteBoxes();
} else {
part->set_flow(BTFT_NONTEXT);
part->set_blob_type(BRT_NOISE);
part->SetBlobTypes();
part->DisownBoxes();
}
delete part;
}
static bool ExpandImageIntoParts(const TBOX &max_image_box, ColPartitionGridSearch *rectsearch,
ColPartitionGrid *part_grid, ColPartition **part_ptr) {
ColPartition *image_part = *part_ptr;
TBOX im_part_box = image_part->bounding_box();
if (textord_tabfind_show_images > 1) {
tprintf("Searching for merge with image part:");
im_part_box.print();
tprintf("Text box=");
max_image_box.print();
}
rectsearch->StartRectSearch(max_image_box);
ColPartition *part;
ColPartition *best_part = nullptr;
int best_dist = 0;
while ((part = rectsearch->NextRectSearch()) != nullptr) {
if (textord_tabfind_show_images > 1) {
tprintf("Considering merge with part:");
part->Print();
if (im_part_box.contains(part->bounding_box())) {
tprintf("Fully contained\n");
} else if (!max_image_box.contains(part->bounding_box())) {
tprintf("Not within text box\n");
} else if (part->flow() == BTFT_STRONG_CHAIN) {
tprintf("Too strong text\n");
} else {
tprintf("Real candidate\n");
}
}
if (part->flow() == BTFT_STRONG_CHAIN || part->flow() == BTFT_TEXT_ON_IMAGE ||
part->blob_type() == BRT_POLYIMAGE) {
continue;
}
TBOX box = part->bounding_box();
if (max_image_box.contains(box) && part->blob_type() != BRT_NOISE) {
if (im_part_box.contains(box)) {
rectsearch->RemoveBBox();
DeletePartition(part);
continue;
}
int x_dist = std::max(0, box.x_gap(im_part_box));
int y_dist = std::max(0, box.y_gap(im_part_box));
int dist = x_dist * x_dist + y_dist * y_dist;
if (dist > box.area() || dist > im_part_box.area()) {
continue;
}
if (best_part == nullptr || dist < best_dist) {
best_part = part;
best_dist = dist;
}
}
}
if (best_part != nullptr) {
TBOX box = best_part->bounding_box();
if (textord_tabfind_show_images > 1) {
tprintf("Merging image part:");
im_part_box.print();
tprintf("with part:");
box.print();
}
im_part_box += box;
*part_ptr = ColPartition::FakePartition(im_part_box, PT_UNKNOWN, BRT_RECTIMAGE, BTFT_NONTEXT);
DeletePartition(image_part);
part_grid->RemoveBBox(best_part);
DeletePartition(best_part);
rectsearch->RepositionIterator();
return true;
}
return false;
}
static int IntersectArea(const TBOX &box, ColPartition_LIST *part_list) {
int intersect_area = 0;
ColPartition_IT part_it(part_list);
for (part_it.mark_cycle_pt(); !part_it.cycled_list(); part_it.forward()) {
ColPartition *image_part = part_it.data();
TBOX intersect = box.intersection(image_part->bounding_box());
intersect_area += intersect.area();
}
return intersect_area;
}
static bool TestWeakIntersectedPart(const TBOX &im_box, ColPartition_LIST *part_list,
ColPartition *part) {
if (part->flow() < BTFT_STRONG_CHAIN) {
const TBOX &part_box = part->bounding_box();
if (im_box.contains(part_box)) {
int area = part_box.area();
int intersect_area = IntersectArea(part_box, part_list);
if (area < 2 * intersect_area) {
return true;
}
}
}
return false;
}
static void EliminateWeakParts(const TBOX &im_box, ColPartitionGrid *part_grid,
ColPartition_LIST *big_parts, ColPartition_LIST *part_list) {
ColPartitionGridSearch rectsearch(part_grid);
ColPartition *part;
rectsearch.StartRectSearch(im_box);
while ((part = rectsearch.NextRectSearch()) != nullptr) {
if (TestWeakIntersectedPart(im_box, part_list, part)) {
BlobRegionType type = part->blob_type();
if (type == BRT_POLYIMAGE || type == BRT_RECTIMAGE) {
rectsearch.RemoveBBox();
DeletePartition(part);
} else {
part->set_flow(BTFT_NONTEXT);
part->set_blob_type(BRT_NOISE);
part->SetBlobTypes();
}
}
}
ColPartition_IT big_it(big_parts);
for (big_it.mark_cycle_pt(); !big_it.cycled_list(); big_it.forward()) {
part = big_it.data();
if (TestWeakIntersectedPart(im_box, part_list, part)) {
DeletePartition(big_it.extract());
}
}
}
static bool ScanForOverlappingText(ColPartitionGrid *part_grid, TBOX *box) {
ColPartitionGridSearch rectsearch(part_grid);
TBOX padded_box(*box);
padded_box.pad(kNoisePadding, kNoisePadding);
rectsearch.StartRectSearch(padded_box);
ColPartition *part;
bool any_text_in_padded_rect = false;
while ((part = rectsearch.NextRectSearch()) != nullptr) {
if (part->flow() == BTFT_CHAIN || part->flow() == BTFT_STRONG_CHAIN) {
any_text_in_padded_rect = true;
const TBOX &part_box = part->bounding_box();
if (box->overlap(part_box)) {
return true;
}
}
}
if (!any_text_in_padded_rect) {
*box = padded_box;
}
return false;
}
static void MarkAndDeleteImageParts(const FCOORD &rerotate, ColPartitionGrid *part_grid,
ColPartition_LIST *image_parts, Image image_pix) {
if (image_pix == nullptr) {
return;
}
int imageheight = pixGetHeight(image_pix);
ColPartition_IT part_it(image_parts);
for (; !part_it.empty(); part_it.forward()) {
ColPartition *part = part_it.extract();
TBOX part_box = part->bounding_box();
BlobRegionType type = part->blob_type();
if (!ScanForOverlappingText(part_grid, &part_box) || type == BRT_RECTIMAGE ||
type == BRT_POLYIMAGE) {
part_box.rotate(rerotate);
int left = part_box.left();
int top = part_box.top();
pixRasterop(image_pix, left, imageheight - top, part_box.width(), part_box.height(), PIX_SET,
nullptr, 0, 0);
}
DeletePartition(part);
}
}
void ImageFind::TransferImagePartsToImageMask(const FCOORD &rerotation, ColPartitionGrid *part_grid,
Image image_mask) {
ColPartition_LIST parts_list;
ColPartition_IT part_it(&parts_list);
ColPartitionGridSearch gsearch(part_grid);
gsearch.StartFullSearch();
ColPartition *part;
while ((part = gsearch.NextFullSearch()) != nullptr) {
BlobRegionType type = part->blob_type();
if (type == BRT_NOISE || type == BRT_RECTIMAGE || type == BRT_POLYIMAGE) {
part_it.add_after_then_move(part);
gsearch.RemoveBBox();
}
}
MarkAndDeleteImageParts(rerotation, part_grid, &parts_list, image_mask);
}
static void DeleteSmallImages(ColPartitionGrid *part_grid) {
if (part_grid != nullptr) {
return;
}
ColPartitionGridSearch gsearch(part_grid);
gsearch.StartFullSearch();
ColPartition *part;
while ((part = gsearch.NextFullSearch()) != nullptr) {
if (part->blob_type() == BRT_RECTIMAGE) {
const TBOX &part_box = part->bounding_box();
if (part_box.width() < kMinImageFindSize || part_box.height() < kMinImageFindSize) {
gsearch.RemoveBBox();
DeletePartition(part);
}
}
}
}
void ImageFind::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) {
int imageheight = pixGetHeight(image_pix);
Boxa *boxa;
Pixa *pixa;
ConnCompAndRectangularize(image_pix, pixa_debug, &boxa, &pixa);
int nboxes = 0;
if (boxa != nullptr && pixa != nullptr) {
nboxes = boxaGetCount(boxa);
}
for (int i = 0; i < nboxes; ++i) {
l_int32 x, y, width, height;
boxaGetBoxGeometry(boxa, i, &x, &y, &width, &height);
Image pix = pixaGetPix(pixa, i, L_CLONE);
TBOX im_box(x, imageheight - y - height, x + width, imageheight - y);
im_box.rotate(rotation);
ColPartitionGridSearch rectsearch(part_grid);
rectsearch.SetUniqueMode(true);
ColPartition_LIST part_list;
DivideImageIntoParts(im_box, rotation, rerotation, pix, &rectsearch, &part_list);
if (textord_tabfind_show_images && pixa_debug != nullptr) {
pixa_debug->AddPix(pix, "ImageComponent");
tprintf("Component has %d parts\n", part_list.length());
}
pix.destroy();
if (!part_list.empty()) {
ColPartition_IT part_it(&part_list);
if (part_list.singleton()) {
ColPartition *part = part_it.extract();
TBOX text_box(im_box);
MaximalImageBoundingBox(part_grid, &text_box);
while (ExpandImageIntoParts(text_box, &rectsearch, part_grid, &part)) {
;
}
part_it.set_to_list(&part_list);
part_it.add_after_then_move(part);
im_box = part->bounding_box();
}
EliminateWeakParts(im_box, part_grid, big_parts, &part_list);
for (part_it.move_to_first(); !part_it.empty(); part_it.forward()) {
ColPartition *image_part = part_it.extract();
im_box = image_part->bounding_box();
part_grid->InsertBBox(true, true, image_part);
if (!part_it.at_last()) {
ColPartition *neighbour = part_it.data_relative(1);
image_part->AddPartner(false, neighbour);
neighbour->AddPartner(true, image_part);
}
}
}
}
boxaDestroy(&boxa);
pixaDestroy(&pixa);
DeleteSmallImages(part_grid);
#ifndef GRAPHICS_DISABLED
if (textord_tabfind_show_images) {
ScrollView *images_win_ = part_grid->MakeWindow(1000, 400, "With Images");
part_grid->DisplayBoxes(images_win_);
}
#endif
}
}