1 ///////////////////////////////////////////////////////////////////////
2 // File: strokewidth.cpp
3 // Description: Subclass of BBGrid to find uniformity of strokewidth.
4 // Author: Ray Smith
5 // Created: Mon Mar 31 16:17:01 PST 2008
6 //
7 // (C) Copyright 2008, Google Inc.
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 ///////////////////////////////////////////////////////////////////////
19
20 #include "strokewidth.h"
21 #include "blobbox.h"
22 #include "tabfind.h"
23 #include "tordmain.h" // For SetBlobStrokeWidth.
24
25 namespace tesseract {
26
27 // Allowed proportional change in stroke width to be the same font.
28 const double kStrokeWidthFractionTolerance = 0.125;
29 // Allowed constant change in stroke width to be the same font.
30 // Really 1.5 pixels.
31 const double kStrokeWidthTolerance = 1.5;
32 // Maximum height in inches of the largest possible text.
33 const double kMaxTextSize = 2.0;
34
StrokeWidth(int gridsize,const ICOORD & bleft,const ICOORD & tright)35 StrokeWidth::StrokeWidth(int gridsize,
36 const ICOORD& bleft, const ICOORD& tright)
37 : BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>(gridsize, bleft, tright) {
38 }
39
~StrokeWidth()40 StrokeWidth::~StrokeWidth() {
41 }
42
43 // Puts the block blobs (normal and large) into the grid.
InsertBlobs(TO_BLOCK * block,TabFind * line_grid)44 void StrokeWidth::InsertBlobs(TO_BLOCK* block, TabFind* line_grid) {
45 // Insert the blobs into this grid using the separator lines in line_grid.
46 line_grid->InsertBlobList(true, false, false, &block->blobs, false, this);
47 line_grid->InsertBlobList(true, false, true, &block->large_blobs,
48 false, this);
49 }
50
51 // Moves the large blobs that have good stroke-width neighbours to the normal
52 // blobs list.
MoveGoodLargeBlobs(int resolution,TO_BLOCK * block)53 void StrokeWidth::MoveGoodLargeBlobs(int resolution, TO_BLOCK* block) {
54 BLOBNBOX_IT large_it = &block->large_blobs;
55 BLOBNBOX_IT blob_it = &block->blobs;
56 int max_height = static_cast<int>(resolution * kMaxTextSize);
57 int b_count = 0;
58 for (large_it.mark_cycle_pt(); !large_it.cycled_list(); large_it.forward()) {
59 BLOBNBOX* large_blob = large_it.data();
60 if (large_blob->bounding_box().height() <= max_height &&
61 GoodTextBlob(large_blob)) {
62 blob_it.add_to_end(large_it.extract());
63 ++b_count;
64 }
65 }
66 if (textord_debug_tabfind) {
67 tprintf("Moved %d large blobs to normal list\n",
68 b_count);
69 }
70 }
71
72 // Displays the blobs green or red according to whether they are good or not.
DisplayGoodBlobs(const char * window_name,ScrollView * window)73 ScrollView* StrokeWidth::DisplayGoodBlobs(const char* window_name,
74 ScrollView* window) {
75 #ifndef GRAPHICS_DISABLED
76 if (window == NULL)
77 window = MakeWindow(0, 0, window_name);
78 // For every blob in the grid, display it.
79 window->Brush(ScrollView::NONE);
80
81 // For every bbox in the grid, display it.
82 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> gsearch(this);
83 gsearch.StartFullSearch();
84 BLOBNBOX* bbox;
85 while ((bbox = gsearch.NextFullSearch()) != NULL) {
86 TBOX box = bbox->bounding_box();
87 int left_x = box.left();
88 int right_x = box.right();
89 int top_y = box.top();
90 int bottom_y = box.bottom();
91 if (textord_debug_printable || GoodTextBlob(bbox))
92 window->Pen(ScrollView::GREEN);
93 else
94 window->Pen(ScrollView::RED);
95 window->Rectangle(left_x, bottom_y, right_x, top_y);
96 }
97 window->Update();
98 #endif
99 return window;
100 }
101
102 // Handles a click event in a display window.
HandleClick(int x,int y)103 void StrokeWidth::HandleClick(int x, int y) {
104 BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>::HandleClick(x, y);
105 // Run a radial search for blobs that overlap.
106 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(this);
107 radsearch.StartRadSearch(x, y, 1);
108 BLOBNBOX* neighbour;
109 FCOORD click(x, y);
110 while ((neighbour = radsearch.NextRadSearch()) != NULL) {
111 TBOX nbox = neighbour->bounding_box();
112 if (nbox.contains(click) && neighbour->cblob() != NULL) {
113 SetBlobStrokeWidth(true, neighbour);
114 tprintf("Box (%d,%d)->(%d,%d): h-width=%.1f, v-width=%.1f p-width=%1.f\n",
115 nbox.left(), nbox.bottom(), nbox.right(), nbox.top(),
116 neighbour->horz_stroke_width(), neighbour->vert_stroke_width(),
117 2.0 * neighbour->cblob()->area()/neighbour->cblob()->perimeter());
118 }
119 }
120 }
121
122 // Returns true if there is at least one side neighbour that has a similar
123 // stroke width and is not on the other side of a rule line.
GoodTextBlob(BLOBNBOX * blob)124 bool StrokeWidth::GoodTextBlob(BLOBNBOX* blob) {
125 double h_width = blob->horz_stroke_width();
126 double v_width = blob->vert_stroke_width();
127 // The perimeter-based width is used as a backup in case there is
128 // no information in the blob.
129 double p_width = 2.0f * blob->cblob()->area();
130 p_width /= blob->cblob()->perimeter();
131 double h_tolerance = h_width * kStrokeWidthFractionTolerance
132 + kStrokeWidthTolerance;
133 double v_tolerance = v_width * kStrokeWidthFractionTolerance
134 + kStrokeWidthTolerance;
135 double p_tolerance = p_width * kStrokeWidthFractionTolerance
136 + kStrokeWidthTolerance;
137
138 // Run a radial search for neighbours that overlap.
139 TBOX box = blob->bounding_box();
140 int radius = box.height() / gridsize_ + 2;
141 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(this);
142 radsearch.StartRadSearch((box.left() + box.right()) / 2, box.bottom(),
143 radius);
144 int top = box.top();
145 int bottom = box.bottom();
146 int min_overlap = (top - bottom) / 2;
147 BLOBNBOX* neighbour;
148 while ((neighbour = radsearch.NextRadSearch()) != NULL) {
149 TBOX nbox = neighbour->bounding_box();
150 if (neighbour == blob) {
151 continue;
152 }
153 // In finding a suitable neighbour, do not cross rule lines.
154 if (nbox.right() > blob->right_rule() || nbox.left() < blob->left_rule()) {
155 continue; // Can't use it.
156 }
157 int overlap = MIN(nbox.top(), top) - MAX(nbox.bottom(), bottom);
158 if (overlap >= min_overlap &&
159 !TabFind::DifferentSizes(box.height(), nbox.height())) {
160 double n_h_width = neighbour->horz_stroke_width();
161 double n_v_width = neighbour->vert_stroke_width();
162 double n_p_width = 2.0f * neighbour->cblob()->area();
163 n_p_width /= neighbour->cblob()->perimeter();
164 bool h_zero = h_width == 0.0f || n_h_width == 0.0f;
165 bool v_zero = v_width == 0.0f || n_v_width == 0.0f;
166 bool h_ok = !h_zero && NearlyEqual(h_width, n_h_width, h_tolerance);
167 bool v_ok = !v_zero && NearlyEqual(v_width, n_v_width, v_tolerance);
168 bool p_ok = h_zero && v_zero &&
169 NearlyEqual(p_width, n_p_width, p_tolerance);
170 // For a match, at least one of the horizontal and vertical widths
171 // must match, and the other one must either match or be zero.
172 // Only if both are zero will we look at the perimeter metric.
173 if (p_ok || ((v_ok || h_ok) && (h_ok || h_zero) && (v_ok || v_zero))) {
174 return true;
175 }
176 }
177 }
178 return false;
179 }
180
181 } // namespace tesseract.
182
183