| /////////////////////////////////////////////////////////////////////// |
| // File: strokewidth.cpp |
| // Description: Subclass of BBGrid to find uniformity of strokewidth. |
| // Author: Ray Smith |
| // Created: Mon Mar 31 16:17:01 PST 2008 |
| // |
| // (C) Copyright 2008, Google Inc. |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| /////////////////////////////////////////////////////////////////////// |
| |
| #include "strokewidth.h" |
| #include "blobbox.h" |
| #include "tabfind.h" |
| #include "tordmain.h" // For SetBlobStrokeWidth. |
| |
| namespace tesseract { |
| |
| // Allowed proportional change in stroke width to be the same font. |
| const double kStrokeWidthFractionTolerance = 0.125; |
| // Allowed constant change in stroke width to be the same font. |
| // Really 1.5 pixels. |
| const double kStrokeWidthTolerance = 1.5; |
| // Maximum height in inches of the largest possible text. |
| const double kMaxTextSize = 2.0; |
| |
| StrokeWidth::StrokeWidth(int gridsize, |
| const ICOORD& bleft, const ICOORD& tright) |
| : BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>(gridsize, bleft, tright) { |
| } |
| |
| StrokeWidth::~StrokeWidth() { |
| } |
| |
| // Puts the block blobs (normal and large) into the grid. |
| void StrokeWidth::InsertBlobs(TO_BLOCK* block, TabFind* line_grid) { |
| // Insert the blobs into this grid using the separator lines in line_grid. |
| line_grid->InsertBlobList(true, false, false, &block->blobs, false, this); |
| line_grid->InsertBlobList(true, false, true, &block->large_blobs, |
| false, this); |
| } |
| |
| // Moves the large blobs that have good stroke-width neighbours to the normal |
| // blobs list. |
| void StrokeWidth::MoveGoodLargeBlobs(int resolution, TO_BLOCK* block) { |
| BLOBNBOX_IT large_it = &block->large_blobs; |
| BLOBNBOX_IT blob_it = &block->blobs; |
| int max_height = static_cast<int>(resolution * kMaxTextSize); |
| int b_count = 0; |
| for (large_it.mark_cycle_pt(); !large_it.cycled_list(); large_it.forward()) { |
| BLOBNBOX* large_blob = large_it.data(); |
| if (large_blob->bounding_box().height() <= max_height && |
| GoodTextBlob(large_blob)) { |
| blob_it.add_to_end(large_it.extract()); |
| ++b_count; |
| } |
| } |
| if (textord_debug_tabfind) { |
| tprintf("Moved %d large blobs to normal list\n", |
| b_count); |
| } |
| } |
| |
| // Displays the blobs green or red according to whether they are good or not. |
| ScrollView* StrokeWidth::DisplayGoodBlobs(const char* window_name, |
| ScrollView* window) { |
| #ifndef GRAPHICS_DISABLED |
| if (window == NULL) |
| window = MakeWindow(0, 0, window_name); |
| // For every blob in the grid, display it. |
| window->Brush(ScrollView::NONE); |
| |
| // For every bbox in the grid, display it. |
| GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> gsearch(this); |
| gsearch.StartFullSearch(); |
| BLOBNBOX* bbox; |
| while ((bbox = gsearch.NextFullSearch()) != NULL) { |
| TBOX box = bbox->bounding_box(); |
| int left_x = box.left(); |
| int right_x = box.right(); |
| int top_y = box.top(); |
| int bottom_y = box.bottom(); |
| if (textord_debug_printable || GoodTextBlob(bbox)) |
| window->Pen(ScrollView::GREEN); |
| else |
| window->Pen(ScrollView::RED); |
| window->Rectangle(left_x, bottom_y, right_x, top_y); |
| } |
| window->Update(); |
| #endif |
| return window; |
| } |
| |
| // Handles a click event in a display window. |
| void StrokeWidth::HandleClick(int x, int y) { |
| BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>::HandleClick(x, y); |
| // Run a radial search for blobs that overlap. |
| GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(this); |
| radsearch.StartRadSearch(x, y, 1); |
| BLOBNBOX* neighbour; |
| FCOORD click(x, y); |
| while ((neighbour = radsearch.NextRadSearch()) != NULL) { |
| TBOX nbox = neighbour->bounding_box(); |
| if (nbox.contains(click) && neighbour->cblob() != NULL) { |
| SetBlobStrokeWidth(true, neighbour); |
| tprintf("Box (%d,%d)->(%d,%d): h-width=%.1f, v-width=%.1f p-width=%1.f\n", |
| nbox.left(), nbox.bottom(), nbox.right(), nbox.top(), |
| neighbour->horz_stroke_width(), neighbour->vert_stroke_width(), |
| 2.0 * neighbour->cblob()->area()/neighbour->cblob()->perimeter()); |
| } |
| } |
| } |
| |
| // Returns true if there is at least one side neighbour that has a similar |
| // stroke width and is not on the other side of a rule line. |
| bool StrokeWidth::GoodTextBlob(BLOBNBOX* blob) { |
| double h_width = blob->horz_stroke_width(); |
| double v_width = blob->vert_stroke_width(); |
| // The perimeter-based width is used as a backup in case there is |
| // no information in the blob. |
| double p_width = 2.0f * blob->cblob()->area(); |
| p_width /= blob->cblob()->perimeter(); |
| double h_tolerance = h_width * kStrokeWidthFractionTolerance |
| + kStrokeWidthTolerance; |
| double v_tolerance = v_width * kStrokeWidthFractionTolerance |
| + kStrokeWidthTolerance; |
| double p_tolerance = p_width * kStrokeWidthFractionTolerance |
| + kStrokeWidthTolerance; |
| |
| // Run a radial search for neighbours that overlap. |
| TBOX box = blob->bounding_box(); |
| int radius = box.height() / gridsize_ + 2; |
| GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(this); |
| radsearch.StartRadSearch((box.left() + box.right()) / 2, box.bottom(), |
| radius); |
| int top = box.top(); |
| int bottom = box.bottom(); |
| int min_overlap = (top - bottom) / 2; |
| BLOBNBOX* neighbour; |
| while ((neighbour = radsearch.NextRadSearch()) != NULL) { |
| TBOX nbox = neighbour->bounding_box(); |
| if (neighbour == blob) { |
| continue; |
| } |
| // In finding a suitable neighbour, do not cross rule lines. |
| if (nbox.right() > blob->right_rule() || nbox.left() < blob->left_rule()) { |
| continue; // Can't use it. |
| } |
| int overlap = MIN(nbox.top(), top) - MAX(nbox.bottom(), bottom); |
| if (overlap >= min_overlap && |
| !TabFind::DifferentSizes(box.height(), nbox.height())) { |
| double n_h_width = neighbour->horz_stroke_width(); |
| double n_v_width = neighbour->vert_stroke_width(); |
| double n_p_width = 2.0f * neighbour->cblob()->area(); |
| n_p_width /= neighbour->cblob()->perimeter(); |
| bool h_zero = h_width == 0.0f || n_h_width == 0.0f; |
| bool v_zero = v_width == 0.0f || n_v_width == 0.0f; |
| bool h_ok = !h_zero && NearlyEqual(h_width, n_h_width, h_tolerance); |
| bool v_ok = !v_zero && NearlyEqual(v_width, n_v_width, v_tolerance); |
| bool p_ok = h_zero && v_zero && |
| NearlyEqual(p_width, n_p_width, p_tolerance); |
| // For a match, at least one of the horizontal and vertical widths |
| // must match, and the other one must either match or be zero. |
| // Only if both are zero will we look at the perimeter metric. |
| if (p_ok || ((v_ok || h_ok) && (h_ok || h_zero) && (v_ok || v_zero))) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| } // namespace tesseract. |
| |