/src/tesseract/src/wordrec/chop.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * File: chop.cpp (Formerly chop.c) |
4 | | * Author: Mark Seaman, OCR Technology |
5 | | * |
6 | | * (c) Copyright 1987, Hewlett-Packard Company. |
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 | | /*---------------------------------------------------------------------- |
20 | | I n c l u d e s |
21 | | ----------------------------------------------------------------------*/ |
22 | | |
23 | | #define _USE_MATH_DEFINES // for M_PI |
24 | | #include "chop.h" |
25 | | #include <cmath> // for M_PI |
26 | | #include "outlines.h" |
27 | | #include "plotedges.h" |
28 | | #include "wordrec.h" |
29 | | |
30 | | // Include automatically generated configuration file if running autoconf. |
31 | | #ifdef HAVE_CONFIG_H |
32 | | # include "config_auto.h" |
33 | | #endif |
34 | | |
35 | | namespace tesseract { |
36 | | |
37 | | // Show if the line is going in the positive or negative X direction. |
38 | 8.03M | static int direction(const EDGEPT *point) { |
39 | | //* direction to return |
40 | 8.03M | int dir = 0; |
41 | | //* prev point |
42 | 8.03M | const EDGEPT *prev = point->prev; |
43 | | //* next point |
44 | 8.03M | const EDGEPT *next = point->next; |
45 | | |
46 | 8.03M | if (((prev->pos.x <= point->pos.x) && (point->pos.x < next->pos.x)) || |
47 | 6.08M | ((prev->pos.x < point->pos.x) && (point->pos.x <= next->pos.x))) { |
48 | 2.77M | dir = 1; |
49 | 2.77M | } |
50 | 8.03M | if (((prev->pos.x >= point->pos.x) && (point->pos.x > next->pos.x)) || |
51 | 6.02M | ((prev->pos.x > point->pos.x) && (point->pos.x >= next->pos.x))) { |
52 | 2.64M | dir = -1; |
53 | 2.64M | } |
54 | | |
55 | 8.03M | return dir; |
56 | 8.03M | } |
57 | | |
58 | | /** |
59 | | * @name point_priority |
60 | | * |
61 | | * Assign a priority to and edge point that might be used as part of a |
62 | | * split. The argument should be of type EDGEPT. |
63 | | */ |
64 | 23.0M | PRIORITY Wordrec::point_priority(EDGEPT *point) { |
65 | 23.0M | return static_cast<PRIORITY>(angle_change(point->prev, point, point->next)); |
66 | 23.0M | } |
67 | | |
68 | | /** |
69 | | * @name add_point_to_list |
70 | | * |
71 | | * Add an edge point to a POINT_GROUP containing a list of other points. |
72 | | */ |
73 | 2.89M | void Wordrec::add_point_to_list(PointHeap *point_heap, EDGEPT *point) { |
74 | 2.89M | if (point_heap->size() < MAX_NUM_POINTS - 2) { |
75 | 2.45M | PointPair pair(point_priority(point), point); |
76 | 2.45M | point_heap->Push(&pair); |
77 | 2.45M | } |
78 | | |
79 | | #ifndef GRAPHICS_DISABLED |
80 | | if (chop_debug > 2) { |
81 | | mark_outline(point); |
82 | | } |
83 | | #endif |
84 | 2.89M | } |
85 | | |
86 | | // Returns true if the edgept supplied as input is an inside angle. This |
87 | | // is determined by the angular change of the vectors from point to point. |
88 | 3.33M | bool Wordrec::is_inside_angle(EDGEPT *pt) { |
89 | 3.33M | return angle_change(pt->prev, pt, pt->next) < chop_inside_angle; |
90 | 3.33M | } |
91 | | |
92 | | /** |
93 | | * @name angle_change |
94 | | * |
95 | | * Return the change in angle (degrees) of the line segments between |
96 | | * points one and two, and two and three. |
97 | | */ |
98 | 74.7M | int Wordrec::angle_change(EDGEPT *point1, EDGEPT *point2, EDGEPT *point3) { |
99 | 74.7M | VECTOR vector1; |
100 | 74.7M | VECTOR vector2; |
101 | | |
102 | 74.7M | int angle; |
103 | | |
104 | | /* Compute angle */ |
105 | 74.7M | vector1.x = point2->pos.x - point1->pos.x; |
106 | 74.7M | vector1.y = point2->pos.y - point1->pos.y; |
107 | 74.7M | vector2.x = point3->pos.x - point2->pos.x; |
108 | 74.7M | vector2.y = point3->pos.y - point2->pos.y; |
109 | | /* Use cross product */ |
110 | 74.7M | float length = std::sqrt(static_cast<float>(vector1.length2()) * vector2.length2()); |
111 | 74.7M | if (static_cast<int>(length) == 0) { |
112 | 179k | return (0); |
113 | 179k | } |
114 | 74.5M | auto f = vector1.cross(vector2) / length; |
115 | | // Avoid FP exception in std::asin caused by illegal values of f |
116 | | // (caused by rounding errors). |
117 | 74.5M | if (f <= -1.0f) { |
118 | 2.06M | angle = -90; |
119 | 72.5M | } else if (f >= 1.0f) { |
120 | 666k | angle = 90; |
121 | 71.8M | } else { |
122 | 71.8M | angle = static_cast<int>(floor(std::asin(f) / M_PI * 180.0 + 0.5)); |
123 | | // Use dot product. |
124 | 71.8M | if (vector1.dot(vector2) < 0) { |
125 | 43.1M | angle = 180 - angle; |
126 | 43.1M | } |
127 | | // Adjust angle. |
128 | 71.8M | if (angle > 180) { |
129 | 34.9M | angle -= 360; |
130 | 36.9M | } else if (angle <= -180) { |
131 | 0 | angle += 360; |
132 | 0 | } |
133 | 71.8M | } |
134 | 74.5M | return angle; |
135 | 74.7M | } |
136 | | |
137 | | /** |
138 | | * @name pick_close_point |
139 | | * |
140 | | * Choose the edge point that is closest to the critical point. This |
141 | | * point may not be exactly vertical from the critical point. |
142 | | */ |
143 | 27.6M | EDGEPT *Wordrec::pick_close_point(EDGEPT *critical_point, EDGEPT *vertical_point, int *best_dist) { |
144 | 27.6M | EDGEPT *best_point = nullptr; |
145 | 27.6M | int this_distance; |
146 | 27.6M | bool found_better; |
147 | | |
148 | 27.6M | do { |
149 | 27.6M | found_better = false; |
150 | | |
151 | 27.6M | this_distance = edgept_dist(critical_point, vertical_point); |
152 | 27.6M | if (this_distance <= *best_dist) { |
153 | 8.48M | if (!(same_point(critical_point->pos, vertical_point->pos) || |
154 | 8.45M | same_point(critical_point->pos, vertical_point->next->pos) || |
155 | 8.27M | (best_point && same_point(best_point->pos, vertical_point->pos)) || |
156 | 8.27M | is_exterior_point(critical_point, vertical_point))) { |
157 | 6.24M | *best_dist = this_distance; |
158 | 6.24M | best_point = vertical_point; |
159 | 6.24M | if (chop_vertical_creep) { |
160 | 0 | found_better = true; |
161 | 0 | } |
162 | 6.24M | } |
163 | 8.48M | } |
164 | 27.6M | vertical_point = vertical_point->next; |
165 | 27.6M | } while (found_better == true); |
166 | | |
167 | 27.6M | return (best_point); |
168 | 27.6M | } |
169 | | |
170 | | /** |
171 | | * @name prioritize_points |
172 | | * |
173 | | * Find a list of edge points from the outer outline of this blob. For |
174 | | * each of these points assign a priority. Sort these points using a |
175 | | * heap structure so that they can be visited in order. |
176 | | */ |
177 | 1.76M | void Wordrec::prioritize_points(TESSLINE *outline, PointHeap *points) { |
178 | 1.76M | EDGEPT *this_point; |
179 | 1.76M | EDGEPT *local_min = nullptr; |
180 | 1.76M | EDGEPT *local_max = nullptr; |
181 | | |
182 | 1.76M | this_point = outline->loop; |
183 | 1.76M | local_min = this_point; |
184 | 1.76M | local_max = this_point; |
185 | 11.6M | do { |
186 | 11.6M | if (this_point->vec.y < 0) { |
187 | | /* Look for minima */ |
188 | 4.89M | if (local_max != nullptr) { |
189 | 3.34M | new_max_point(local_max, points); |
190 | 3.34M | } else if (is_inside_angle(this_point)) { |
191 | 336k | add_point_to_list(points, this_point); |
192 | 336k | } |
193 | 4.89M | local_max = nullptr; |
194 | 4.89M | local_min = this_point->next; |
195 | 6.77M | } else if (this_point->vec.y > 0) { |
196 | | /* Look for maxima */ |
197 | 4.71M | if (local_min != nullptr) { |
198 | 2.93M | new_min_point(local_min, points); |
199 | 2.93M | } else if (is_inside_angle(this_point)) { |
200 | 289k | add_point_to_list(points, this_point); |
201 | 289k | } |
202 | 4.71M | local_min = nullptr; |
203 | 4.71M | local_max = this_point->next; |
204 | 4.71M | } else { |
205 | | /* Flat area */ |
206 | 2.05M | if (local_max != nullptr) { |
207 | 1.37M | if (local_max->prev->vec.y != 0) { |
208 | 1.15M | new_max_point(local_max, points); |
209 | 1.15M | } |
210 | 1.37M | local_max = this_point->next; |
211 | 1.37M | local_min = nullptr; |
212 | 1.37M | } else { |
213 | 685k | if (local_min->prev->vec.y != 0) { |
214 | 599k | new_min_point(local_min, points); |
215 | 599k | } |
216 | 685k | local_min = this_point->next; |
217 | 685k | local_max = nullptr; |
218 | 685k | } |
219 | 2.05M | } |
220 | | |
221 | | /* Next point */ |
222 | 11.6M | this_point = this_point->next; |
223 | 11.6M | } while (this_point != outline->loop); |
224 | 1.76M | } |
225 | | |
226 | | /** |
227 | | * @name new_min_point |
228 | | * |
229 | | * Found a new minimum point try to decide whether to save it or not. |
230 | | * Return the new value for the local minimum. If a point is saved then |
231 | | * the local minimum is reset to nullptr. |
232 | | */ |
233 | 3.53M | void Wordrec::new_min_point(EDGEPT *local_min, PointHeap *points) { |
234 | 3.53M | int16_t dir; |
235 | | |
236 | 3.53M | dir = direction(local_min); |
237 | | |
238 | 3.53M | if (dir < 0) { |
239 | 717k | add_point_to_list(points, local_min); |
240 | 717k | return; |
241 | 717k | } |
242 | | |
243 | 2.81M | if (dir == 0 && point_priority(local_min) < 0) { |
244 | 245k | add_point_to_list(points, local_min); |
245 | 245k | return; |
246 | 245k | } |
247 | 2.81M | } |
248 | | |
249 | | /** |
250 | | * @name new_max_point |
251 | | * |
252 | | * Found a new minimum point try to decide whether to save it or not. |
253 | | * Return the new value for the local minimum. If a point is saved then |
254 | | * the local minimum is reset to nullptr. |
255 | | */ |
256 | 4.49M | void Wordrec::new_max_point(EDGEPT *local_max, PointHeap *points) { |
257 | 4.49M | int16_t dir; |
258 | | |
259 | 4.49M | dir = direction(local_max); |
260 | | |
261 | 4.49M | if (dir > 0) { |
262 | 878k | add_point_to_list(points, local_max); |
263 | 878k | return; |
264 | 878k | } |
265 | | |
266 | 3.61M | if (dir == 0 && point_priority(local_max) < 0) { |
267 | 432k | add_point_to_list(points, local_max); |
268 | 432k | return; |
269 | 432k | } |
270 | 3.61M | } |
271 | | |
272 | | /** |
273 | | * @name vertical_projection_point |
274 | | * |
275 | | * For one point on the outline, find the corresponding point on the |
276 | | * other side of the outline that is a likely projection for a split |
277 | | * point. This is done by iterating through the edge points until the |
278 | | * X value of the point being looked at is greater than the X value of |
279 | | * the split point. Ensure that the point being returned is not right |
280 | | * next to the split point. Return the edge point in *best_point as |
281 | | * a result, and any points that were newly created are also saved on |
282 | | * the new_points list. |
283 | | */ |
284 | | void Wordrec::vertical_projection_point(EDGEPT *split_point, EDGEPT *target_point, |
285 | 13.8M | EDGEPT **best_point, EDGEPT_CLIST *new_points) { |
286 | 13.8M | EDGEPT *p; /* Iterator */ |
287 | 13.8M | EDGEPT *this_edgept; /* Iterator */ |
288 | 13.8M | EDGEPT_C_IT new_point_it(new_points); |
289 | 13.8M | int x = split_point->pos.x; /* X value of vertical */ |
290 | 13.8M | int best_dist = LARGE_DISTANCE; /* Best point found */ |
291 | | |
292 | 13.8M | if (*best_point != nullptr) { |
293 | 9.85M | best_dist = edgept_dist(split_point, *best_point); |
294 | 9.85M | } |
295 | | |
296 | 13.8M | p = target_point; |
297 | | /* Look at each edge point */ |
298 | 235M | do { |
299 | 235M | if (((p->pos.x <= x && x <= p->next->pos.x) || (p->next->pos.x <= x && x <= p->pos.x)) && |
300 | 36.2M | !same_point(split_point->pos, p->pos) && !same_point(split_point->pos, p->next->pos) && |
301 | 31.1M | !p->IsChopPt() && (*best_point == nullptr || !same_point((*best_point)->pos, p->pos))) { |
302 | 27.6M | if (near_point(split_point, p, p->next, &this_edgept)) { |
303 | 4.18M | new_point_it.add_before_then_move(this_edgept); |
304 | 4.18M | } |
305 | | |
306 | 27.6M | if (*best_point == nullptr) { |
307 | 3.99M | best_dist = edgept_dist(split_point, this_edgept); |
308 | 3.99M | } |
309 | | |
310 | 27.6M | this_edgept = pick_close_point(split_point, this_edgept, &best_dist); |
311 | 27.6M | if (this_edgept) { |
312 | 6.24M | *best_point = this_edgept; |
313 | 6.24M | } |
314 | 27.6M | } |
315 | | |
316 | 235M | p = p->next; |
317 | 235M | } while (p != target_point); |
318 | 13.8M | } |
319 | | |
320 | | } // namespace tesseract |