/src/tesseract/src/ccstruct/coutln.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /********************************************************************** |
2 | | * File: coutln.cpp (Formerly coutline.c) |
3 | | * Description: Code for the C_OUTLINE class. |
4 | | * Author: Ray Smith |
5 | | * |
6 | | * (C) Copyright 1991, Hewlett-Packard Ltd. |
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 | | // Include automatically generated configuration file if running autoconf. |
20 | | #ifdef HAVE_CONFIG_H |
21 | | # include "config_auto.h" |
22 | | #endif |
23 | | |
24 | | #include "coutln.h" |
25 | | |
26 | | #include "arrayaccess.h" // for GET_DATA_BYTE |
27 | | #include "blobs.h" // for TPOINT |
28 | | #include "crakedge.h" // for CRACKEDGE |
29 | | #include "environ.h" // for l_uint32 |
30 | | #include "errcode.h" // for ASSERT_HOST |
31 | | #include "normalis.h" // for DENORM |
32 | | |
33 | | #include "helpers.h" // for ClipToRange, IntCastRounded, Modulo |
34 | | |
35 | | #include <allheaders.h> // for pixSetPixel, pixGetData, pixRasterop, pixGe... |
36 | | #include "pix.h" // for Pix (ptr only), PIX_DST, PIX_NOT |
37 | | |
38 | | #include <algorithm> // for max, min |
39 | | #include <cmath> // for abs |
40 | | #include <cstdlib> // for abs |
41 | | #include <cstring> // for memset, memcpy, memmove |
42 | | |
43 | | namespace tesseract { |
44 | | |
45 | | ICOORD C_OUTLINE::step_coords[4] = {ICOORD(-1, 0), ICOORD(0, -1), ICOORD(1, 0), ICOORD(0, 1)}; |
46 | | |
47 | | /** |
48 | | * @name C_OUTLINE::C_OUTLINE |
49 | | * |
50 | | * Constructor to build a C_OUTLINE from a CRACKEDGE LOOP. |
51 | | * @param startpt outline to convert |
52 | | * @param bot_left bounding box |
53 | | * @param top_right bounding box |
54 | | * @param length length of loop |
55 | | */ |
56 | | |
57 | | C_OUTLINE::C_OUTLINE(CRACKEDGE *startpt, ICOORD bot_left, ICOORD top_right, int16_t length) |
58 | 3.43M | : box(bot_left, top_right), start(startpt->pos), offsets(nullptr) { |
59 | 3.43M | int16_t stepindex; // index to step |
60 | 3.43M | CRACKEDGE *edgept; // current point |
61 | | |
62 | 3.43M | stepcount = length; // no of steps |
63 | 3.43M | if (length == 0) { |
64 | 262k | return; |
65 | 262k | } |
66 | | // get memory |
67 | 3.17M | steps.resize(step_mem()); |
68 | 3.17M | edgept = startpt; |
69 | | |
70 | 97.4M | for (stepindex = 0; stepindex < length; stepindex++) { |
71 | | // set compact step |
72 | 94.2M | set_step(stepindex, edgept->stepdir); |
73 | 94.2M | edgept = edgept->next; |
74 | 94.2M | } |
75 | 3.17M | } |
76 | | |
77 | | /** |
78 | | * @name C_OUTLINE::C_OUTLINE |
79 | | * |
80 | | * Constructor to build a C_OUTLINE from a C_OUTLINE_FRAG. |
81 | | */ |
82 | | C_OUTLINE::C_OUTLINE( |
83 | | // constructor |
84 | | // steps to copy |
85 | | ICOORD startpt, DIR128 *new_steps, |
86 | | int16_t length // length of loop |
87 | | ) |
88 | 175k | : start(startpt), offsets(nullptr) { |
89 | 175k | int8_t dirdiff; // direction difference |
90 | 175k | DIR128 prevdir; // previous direction |
91 | 175k | DIR128 dir; // current direction |
92 | 175k | DIR128 lastdir; // dir of last step |
93 | 175k | TBOX new_box; // easy bounding |
94 | 175k | int16_t stepindex; // index to step |
95 | 175k | int16_t srcindex; // source steps |
96 | 175k | ICOORD pos; // current position |
97 | | |
98 | 175k | pos = startpt; |
99 | 175k | stepcount = length; // No. of steps. |
100 | 175k | ASSERT_HOST(length >= 0); |
101 | 175k | steps.resize(step_mem()); // Get memory. |
102 | | |
103 | 175k | lastdir = new_steps[length - 1]; |
104 | 175k | prevdir = lastdir; |
105 | 23.7M | for (stepindex = 0, srcindex = 0; srcindex < length; stepindex++, srcindex++) { |
106 | 23.5M | new_box = TBOX(pos, pos); |
107 | 23.5M | box += new_box; |
108 | | // copy steps |
109 | 23.5M | dir = new_steps[srcindex]; |
110 | 23.5M | set_step(stepindex, dir); |
111 | 23.5M | dirdiff = dir - prevdir; |
112 | 23.5M | pos += step(stepindex); |
113 | 23.5M | if ((dirdiff == 64 || dirdiff == -64) && stepindex > 0) { |
114 | 0 | stepindex -= 2; // cancel there-and-back |
115 | 0 | prevdir = stepindex >= 0 ? step_dir(stepindex) : lastdir; |
116 | 23.5M | } else { |
117 | 23.5M | prevdir = dir; |
118 | 23.5M | } |
119 | 23.5M | } |
120 | 175k | ASSERT_HOST(pos.x() == startpt.x() && pos.y() == startpt.y()); |
121 | 175k | do { |
122 | 175k | dirdiff = step_dir(stepindex - 1) - step_dir(0); |
123 | 175k | if (dirdiff == 64 || dirdiff == -64) { |
124 | 0 | start += step(0); |
125 | 0 | stepindex -= 2; // cancel there-and-back |
126 | 0 | for (int i = 0; i < stepindex; ++i) { |
127 | 0 | set_step(i, step_dir(i + 1)); |
128 | 0 | } |
129 | 0 | } |
130 | 175k | } while (stepindex > 1 && (dirdiff == 64 || dirdiff == -64)); |
131 | 175k | stepcount = stepindex; |
132 | 175k | ASSERT_HOST(stepcount >= 4); |
133 | 175k | } |
134 | | |
135 | | /** |
136 | | * @name C_OUTLINE::C_OUTLINE |
137 | | * |
138 | | * Constructor to build a C_OUTLINE from a rotation of a C_OUTLINE. |
139 | | * @param srcline outline to rotate |
140 | | * @param rotation rotate to coord |
141 | | */ |
142 | | |
143 | 67.2k | C_OUTLINE::C_OUTLINE(C_OUTLINE *srcline, FCOORD rotation) : offsets(nullptr) { |
144 | 67.2k | TBOX new_box; // easy bounding |
145 | 67.2k | int16_t stepindex; // index to step |
146 | 67.2k | int16_t dirdiff; // direction change |
147 | 67.2k | ICOORD pos; // current position |
148 | 67.2k | ICOORD prevpos; // previous dest point |
149 | | |
150 | 67.2k | ICOORD destpos; // destination point |
151 | 67.2k | int16_t destindex = INT16_MAX; // index to step |
152 | 67.2k | DIR128 dir; // coded direction |
153 | 67.2k | uint8_t new_step; |
154 | | |
155 | 67.2k | stepcount = srcline->stepcount * 2; |
156 | 67.2k | if (stepcount == 0) { |
157 | 0 | box = srcline->box; |
158 | 0 | box.rotate(rotation); |
159 | 0 | return; |
160 | 0 | } |
161 | | // get memory |
162 | 67.2k | steps.resize(step_mem()); |
163 | | |
164 | 67.2k | for (int iteration = 0; iteration < 2; ++iteration) { |
165 | 67.2k | DIR128 round1 = iteration == 0 ? 32 : 0; |
166 | 67.2k | DIR128 round2 = iteration != 0 ? 32 : 0; |
167 | 67.2k | pos = srcline->start; |
168 | 67.2k | prevpos = pos; |
169 | 67.2k | prevpos.rotate(rotation); |
170 | 67.2k | start = prevpos; |
171 | 67.2k | box = TBOX(start, start); |
172 | 67.2k | destindex = 0; |
173 | 18.9M | for (stepindex = 0; stepindex < srcline->stepcount; stepindex++) { |
174 | 18.9M | pos += srcline->step(stepindex); |
175 | 18.9M | destpos = pos; |
176 | 18.9M | destpos.rotate(rotation); |
177 | | // tprintf("%i %i %i %i ", destpos.x(), destpos.y(), pos.x(), pos.y()); |
178 | 37.8M | while (destpos.x() != prevpos.x() || destpos.y() != prevpos.y()) { |
179 | 18.9M | dir = DIR128(FCOORD(destpos - prevpos)); |
180 | 18.9M | dir += 64; // turn to step style |
181 | 18.9M | new_step = dir.get_dir(); |
182 | | // tprintf(" %i\n", new_step); |
183 | 18.9M | if (new_step & 31) { |
184 | 267k | set_step(destindex++, dir + round1); |
185 | 267k | prevpos += step(destindex - 1); |
186 | 267k | if (destindex < 2 || |
187 | 267k | ((dirdiff = step_dir(destindex - 1) - step_dir(destindex - 2)) != -64 && |
188 | 267k | dirdiff != 64)) { |
189 | 233k | set_step(destindex++, dir + round2); |
190 | 233k | prevpos += step(destindex - 1); |
191 | 233k | } else { |
192 | 34.3k | prevpos -= step(destindex - 1); |
193 | 34.3k | destindex--; |
194 | 34.3k | prevpos -= step(destindex - 1); |
195 | 34.3k | set_step(destindex - 1, dir + round2); |
196 | 34.3k | prevpos += step(destindex - 1); |
197 | 34.3k | } |
198 | 18.6M | } else { |
199 | 18.6M | set_step(destindex++, dir); |
200 | 18.6M | prevpos += step(destindex - 1); |
201 | 18.6M | } |
202 | 18.9M | while (destindex >= 2 && |
203 | 18.9M | ((dirdiff = step_dir(destindex - 1) - step_dir(destindex - 2)) == -64 || |
204 | 18.8M | dirdiff == 64)) { |
205 | 33.1k | prevpos -= step(destindex - 1); |
206 | 33.1k | prevpos -= step(destindex - 2); |
207 | 33.1k | destindex -= 2; // Forget u turn |
208 | 33.1k | } |
209 | | // ASSERT_HOST(prevpos.x() == destpos.x() && prevpos.y() == |
210 | | // destpos.y()); |
211 | 18.9M | new_box = TBOX(destpos, destpos); |
212 | 18.9M | box += new_box; |
213 | 18.9M | } |
214 | 18.9M | } |
215 | 67.2k | ASSERT_HOST(destpos.x() == start.x() && destpos.y() == start.y()); |
216 | 68.8k | while (destindex > 1) { |
217 | 68.8k | dirdiff = step_dir(destindex - 1) - step_dir(0); |
218 | 68.8k | if (dirdiff != 64 && dirdiff != -64) { |
219 | 67.2k | break; |
220 | 67.2k | } |
221 | 1.59k | start += step(0); |
222 | 1.59k | destindex -= 2; |
223 | 626k | for (int i = 0; i < destindex; ++i) { |
224 | 625k | set_step(i, step_dir(i + 1)); |
225 | 625k | } |
226 | 1.59k | } |
227 | 67.2k | if (destindex >= 4) { |
228 | 67.2k | break; |
229 | 67.2k | } |
230 | 67.2k | } |
231 | 67.2k | ASSERT_HOST(destindex <= stepcount); |
232 | 67.2k | stepcount = destindex; |
233 | 67.2k | destpos = start; |
234 | 19.0M | for (stepindex = 0; stepindex < stepcount; stepindex++) { |
235 | 19.0M | destpos += step(stepindex); |
236 | 19.0M | } |
237 | 67.2k | ASSERT_HOST(destpos.x() == start.x() && destpos.y() == start.y()); |
238 | 67.2k | } |
239 | | |
240 | | // Build a fake outline, given just a bounding box and append to the list. |
241 | 262k | void C_OUTLINE::FakeOutline(const TBOX &box, C_OUTLINE_LIST *outlines) { |
242 | 262k | C_OUTLINE_IT ol_it(outlines); |
243 | | // Make a C_OUTLINE from the bounds. This is a bit of a hack, |
244 | | // as there is no outline, just a bounding box, but it works nicely. |
245 | 262k | CRACKEDGE start; |
246 | 262k | start.pos = box.topleft(); |
247 | 262k | auto *outline = new C_OUTLINE(&start, box.topleft(), box.botright(), 0); |
248 | 262k | ol_it.add_to_end(outline); |
249 | 262k | } |
250 | | |
251 | | /** |
252 | | * @name C_OUTLINE::area |
253 | | * |
254 | | * Compute the area of the outline. |
255 | | */ |
256 | | |
257 | 3.24M | int32_t C_OUTLINE::area() const { |
258 | 3.24M | int stepindex; // current step |
259 | 3.24M | int32_t total_steps; // steps to do |
260 | 3.24M | int32_t total; // total area |
261 | 3.24M | ICOORD pos; // position of point |
262 | 3.24M | ICOORD next_step; // step to next pix |
263 | | // We aren't going to modify the list, or its contents, but there is |
264 | | // no const iterator. |
265 | 3.24M | C_OUTLINE_IT it(const_cast<C_OUTLINE_LIST *>(&children)); |
266 | | |
267 | 3.24M | pos = start_pos(); |
268 | 3.24M | total_steps = pathlength(); |
269 | 3.24M | total = 0; |
270 | 102M | for (stepindex = 0; stepindex < total_steps; stepindex++) { |
271 | | // all intersected |
272 | 99.6M | next_step = step(stepindex); |
273 | 99.6M | if (next_step.x() < 0) { |
274 | 22.4M | total += pos.y(); |
275 | 77.2M | } else if (next_step.x() > 0) { |
276 | 22.4M | total -= pos.y(); |
277 | 22.4M | } |
278 | 99.6M | pos += next_step; |
279 | 99.6M | } |
280 | 3.48M | for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { |
281 | 241k | total += it.data()->area(); // add areas of children |
282 | 241k | } |
283 | | |
284 | 3.24M | return total; |
285 | 3.24M | } |
286 | | |
287 | | /** |
288 | | * @name C_OUTLINE::perimeter |
289 | | * |
290 | | * Compute the perimeter of the outline and its first level children. |
291 | | */ |
292 | | |
293 | 2.63M | int32_t C_OUTLINE::perimeter() const { |
294 | 2.63M | int32_t total_steps; // Return value. |
295 | | // We aren't going to modify the list, or its contents, but there is |
296 | | // no const iterator. |
297 | 2.63M | C_OUTLINE_IT it(const_cast<C_OUTLINE_LIST *>(&children)); |
298 | | |
299 | 2.63M | total_steps = pathlength(); |
300 | 2.82M | for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { |
301 | 191k | total_steps += it.data()->pathlength(); // Add perimeters of children. |
302 | 191k | } |
303 | | |
304 | 2.63M | return total_steps; |
305 | 2.63M | } |
306 | | |
307 | | /** |
308 | | * @name C_OUTLINE::outer_area |
309 | | * |
310 | | * Compute the area of the outline. |
311 | | */ |
312 | | |
313 | 3.52M | int32_t C_OUTLINE::outer_area() const { |
314 | 3.52M | int stepindex; // current step |
315 | 3.52M | int32_t total_steps; // steps to do |
316 | 3.52M | int32_t total; // total area |
317 | 3.52M | ICOORD pos; // position of point |
318 | 3.52M | ICOORD next_step; // step to next pix |
319 | | |
320 | 3.52M | pos = start_pos(); |
321 | 3.52M | total_steps = pathlength(); |
322 | 3.52M | if (total_steps == 0) { |
323 | 0 | return box.area(); |
324 | 0 | } |
325 | 3.52M | total = 0; |
326 | 125M | for (stepindex = 0; stepindex < total_steps; stepindex++) { |
327 | | // all intersected |
328 | 121M | next_step = step(stepindex); |
329 | 121M | if (next_step.x() < 0) { |
330 | 28.3M | total += pos.y(); |
331 | 93.1M | } else if (next_step.x() > 0) { |
332 | 28.3M | total -= pos.y(); |
333 | 28.3M | } |
334 | 121M | pos += next_step; |
335 | 121M | } |
336 | | |
337 | 3.52M | return total; |
338 | 3.52M | } |
339 | | |
340 | | /** |
341 | | * @name C_OUTLINE::count_transitions |
342 | | * |
343 | | * Compute the number of x and y maxes and mins in the outline. |
344 | | * @param threshold winding number on size |
345 | | */ |
346 | | |
347 | 2.68M | int32_t C_OUTLINE::count_transitions(int32_t threshold) { |
348 | 2.68M | bool first_was_max_x; // what was first |
349 | 2.68M | bool first_was_max_y; |
350 | 2.68M | bool looking_for_max_x; // what is next |
351 | 2.68M | bool looking_for_min_x; |
352 | 2.68M | bool looking_for_max_y; // what is next |
353 | 2.68M | bool looking_for_min_y; |
354 | 2.68M | int stepindex; // current step |
355 | 2.68M | int32_t total_steps; // steps to do |
356 | | // current limits |
357 | 2.68M | int32_t max_x, min_x, max_y, min_y; |
358 | 2.68M | int32_t initial_x, initial_y; // initial limits |
359 | 2.68M | int32_t total; // total changes |
360 | 2.68M | ICOORD pos; // position of point |
361 | 2.68M | ICOORD next_step; // step to next pix |
362 | | |
363 | 2.68M | pos = start_pos(); |
364 | 2.68M | total_steps = pathlength(); |
365 | 2.68M | total = 0; |
366 | 2.68M | max_x = min_x = pos.x(); |
367 | 2.68M | max_y = min_y = pos.y(); |
368 | 2.68M | looking_for_max_x = true; |
369 | 2.68M | looking_for_min_x = true; |
370 | 2.68M | looking_for_max_y = true; |
371 | 2.68M | looking_for_min_y = true; |
372 | 2.68M | first_was_max_x = false; |
373 | 2.68M | first_was_max_y = false; |
374 | 2.68M | initial_x = pos.x(); |
375 | 2.68M | initial_y = pos.y(); // stop uninit warning |
376 | 66.9M | for (stepindex = 0; stepindex < total_steps; stepindex++) { |
377 | | // all intersected |
378 | 64.2M | next_step = step(stepindex); |
379 | 64.2M | pos += next_step; |
380 | 64.2M | if (next_step.x() < 0) { |
381 | 11.8M | if (looking_for_max_x && pos.x() < min_x) { |
382 | 5.05M | min_x = pos.x(); |
383 | 5.05M | } |
384 | 11.8M | if (looking_for_min_x && max_x - pos.x() > threshold) { |
385 | 3.04M | if (looking_for_max_x) { |
386 | 414k | initial_x = max_x; |
387 | 414k | first_was_max_x = false; |
388 | 414k | } |
389 | 3.04M | total++; |
390 | 3.04M | looking_for_max_x = true; |
391 | 3.04M | looking_for_min_x = false; |
392 | 3.04M | min_x = pos.x(); // reset min |
393 | 3.04M | } |
394 | 52.4M | } else if (next_step.x() > 0) { |
395 | 11.8M | if (looking_for_min_x && pos.x() > max_x) { |
396 | 7.88M | max_x = pos.x(); |
397 | 7.88M | } |
398 | 11.8M | if (looking_for_max_x && pos.x() - min_x > threshold) { |
399 | 2.83M | if (looking_for_min_x) { |
400 | 1.53M | initial_x = min_x; // remember first min |
401 | 1.53M | first_was_max_x = true; |
402 | 1.53M | } |
403 | 2.83M | total++; |
404 | 2.83M | looking_for_max_x = false; |
405 | 2.83M | looking_for_min_x = true; |
406 | 2.83M | max_x = pos.x(); |
407 | 2.83M | } |
408 | 40.6M | } else if (next_step.y() < 0) { |
409 | 20.3M | if (looking_for_max_y && pos.y() < min_y) { |
410 | 18.2M | min_y = pos.y(); |
411 | 18.2M | } |
412 | 20.3M | if (looking_for_min_y && max_y - pos.y() > threshold) { |
413 | 3.11M | if (looking_for_max_y) { |
414 | 2.49M | initial_y = max_y; // remember first max |
415 | 2.49M | first_was_max_y = false; |
416 | 2.49M | } |
417 | 3.11M | total++; |
418 | 3.11M | looking_for_max_y = true; |
419 | 3.11M | looking_for_min_y = false; |
420 | 3.11M | min_y = pos.y(); // reset min |
421 | 3.11M | } |
422 | 20.3M | } else { |
423 | 20.3M | if (looking_for_min_y && pos.y() > max_y) { |
424 | 13.1M | max_y = pos.y(); |
425 | 13.1M | } |
426 | 20.3M | if (looking_for_max_y && pos.y() - min_y > threshold) { |
427 | 3.12M | if (looking_for_min_y) { |
428 | 12.7k | initial_y = min_y; // remember first min |
429 | 12.7k | first_was_max_y = true; |
430 | 12.7k | } |
431 | 3.12M | total++; |
432 | 3.12M | looking_for_max_y = false; |
433 | 3.12M | looking_for_min_y = true; |
434 | 3.12M | max_y = pos.y(); |
435 | 3.12M | } |
436 | 20.3M | } |
437 | 64.2M | } |
438 | 2.68M | if (first_was_max_x && looking_for_min_x) { |
439 | 127k | if (max_x - initial_x > threshold) { |
440 | 127k | total++; |
441 | 127k | } else { |
442 | 0 | total--; |
443 | 0 | } |
444 | 2.55M | } else if (!first_was_max_x && looking_for_max_x) { |
445 | 1.06M | if (initial_x - min_x > threshold) { |
446 | 0 | total++; |
447 | 1.06M | } else { |
448 | 1.06M | total--; |
449 | 1.06M | } |
450 | 1.06M | } |
451 | 2.68M | if (first_was_max_y && looking_for_min_y) { |
452 | 8.29k | if (max_y - initial_y > threshold) { |
453 | 330 | total++; |
454 | 7.96k | } else { |
455 | 7.96k | total--; |
456 | 7.96k | } |
457 | 2.67M | } else if (!first_was_max_y && looking_for_max_y) { |
458 | 185k | if (initial_y - min_y > threshold) { |
459 | 438 | total++; |
460 | 184k | } else { |
461 | 184k | total--; |
462 | 184k | } |
463 | 185k | } |
464 | | |
465 | 2.68M | return total; |
466 | 2.68M | } |
467 | | |
468 | | /** |
469 | | * @name C_OUTLINE::operator< |
470 | | * |
471 | | * @return true if the left operand is inside the right one. |
472 | | * @param other other outline |
473 | | */ |
474 | | |
475 | 39.4M | bool C_OUTLINE::operator<(const C_OUTLINE &other) const { |
476 | 39.4M | int16_t count = 0; // winding count |
477 | 39.4M | ICOORD pos; // position of point |
478 | 39.4M | int32_t stepindex; // index to cstep |
479 | | |
480 | 39.4M | if (!box.overlap(other.box)) { |
481 | 35.0M | return false; // can't be contained |
482 | 35.0M | } |
483 | 4.34M | if (stepcount == 0) { |
484 | 0 | return other.box.contains(this->box); |
485 | 0 | } |
486 | | |
487 | 4.34M | pos = start; |
488 | 5.01M | for (stepindex = 0; stepindex < stepcount && (count = other.winding_number(pos)) == INTERSECTING; |
489 | 4.34M | stepindex++) { |
490 | 666k | pos += step(stepindex); // try all points |
491 | 666k | } |
492 | 4.34M | if (count == INTERSECTING) { |
493 | | // all intersected |
494 | 0 | pos = other.start; |
495 | 0 | for (stepindex = 0; |
496 | 0 | stepindex < other.stepcount && (count = winding_number(pos)) == INTERSECTING; |
497 | 0 | stepindex++) { |
498 | | // try other way round |
499 | 0 | pos += other.step(stepindex); |
500 | 0 | } |
501 | 0 | return count == INTERSECTING || count == 0; |
502 | 0 | } |
503 | 4.34M | return count != 0; |
504 | 4.34M | } |
505 | | |
506 | | /** |
507 | | * @name C_OUTLINE::winding_number |
508 | | * |
509 | | * @return the winding number of the outline around the given point. |
510 | | * @param point point to wind around |
511 | | */ |
512 | | |
513 | 5.01M | int16_t C_OUTLINE::winding_number(ICOORD point) const { |
514 | 5.01M | int16_t stepindex; // index to cstep |
515 | 5.01M | int16_t count; // winding count |
516 | 5.01M | ICOORD vec; // to current point |
517 | 5.01M | ICOORD stepvec; // step vector |
518 | 5.01M | int32_t cross; // cross product |
519 | | |
520 | 5.01M | vec = start - point; // vector to it |
521 | 5.01M | count = 0; |
522 | 1.70G | for (stepindex = 0; stepindex < stepcount; stepindex++) { |
523 | 1.70G | stepvec = step(stepindex); // get the step |
524 | | // crossing the line |
525 | 1.70G | if (vec.y() <= 0 && vec.y() + stepvec.y() > 0) { |
526 | 10.9M | cross = vec * stepvec; // cross product |
527 | 10.9M | if (cross > 0) { |
528 | 6.50M | count++; // crossing right half |
529 | 6.50M | } else if (cross == 0) { |
530 | 115k | return INTERSECTING; // going through point |
531 | 115k | } |
532 | 1.69G | } else if (vec.y() > 0 && vec.y() + stepvec.y() <= 0) { |
533 | 11.4M | cross = vec * stepvec; |
534 | 11.4M | if (cross < 0) { |
535 | 5.60M | count--; // crossing back |
536 | 5.83M | } else if (cross == 0) { |
537 | 550k | return INTERSECTING; // illegal |
538 | 550k | } |
539 | 11.4M | } |
540 | 1.70G | vec += stepvec; // sum vectors |
541 | 1.70G | } |
542 | 4.34M | return count; // winding number |
543 | 5.01M | } |
544 | | |
545 | | /** |
546 | | * C_OUTLINE::turn_direction |
547 | | * |
548 | | * @return the sum direction delta of the outline. |
549 | | */ |
550 | | |
551 | 3.44M | int16_t C_OUTLINE::turn_direction() const { // winding number |
552 | 3.44M | DIR128 prevdir; // previous direction |
553 | 3.44M | DIR128 dir; // current direction |
554 | 3.44M | int16_t stepindex; // index to cstep |
555 | 3.44M | int8_t dirdiff; // direction difference |
556 | 3.44M | int16_t count; // winding count |
557 | | |
558 | 3.44M | if (stepcount == 0) { |
559 | 262k | return 128; |
560 | 262k | } |
561 | 3.18M | count = 0; |
562 | 3.18M | prevdir = step_dir(stepcount - 1); |
563 | 120M | for (stepindex = 0; stepindex < stepcount; stepindex++) { |
564 | 117M | dir = step_dir(stepindex); |
565 | 117M | dirdiff = dir - prevdir; |
566 | 117M | ASSERT_HOST(dirdiff == 0 || dirdiff == 32 || dirdiff == -32); |
567 | 117M | count += dirdiff; |
568 | 117M | prevdir = dir; |
569 | 117M | } |
570 | 3.18M | ASSERT_HOST(count == 128 || count == -128); |
571 | 3.18M | return count; // winding number |
572 | 3.44M | } |
573 | | |
574 | | /** |
575 | | * @name C_OUTLINE::reverse |
576 | | * |
577 | | * Reverse the direction of an outline. |
578 | | */ |
579 | | |
580 | 986k | void C_OUTLINE::reverse() { // reverse drection |
581 | 986k | DIR128 halfturn = MODULUS / 2; // amount to shift |
582 | 986k | DIR128 stepdir; // direction of step |
583 | 986k | int16_t stepindex; // index to cstep |
584 | 986k | int16_t farindex; // index to other side |
585 | 986k | int16_t halfsteps; // half of stepcount |
586 | | |
587 | 986k | halfsteps = (stepcount + 1) / 2; |
588 | 11.6M | for (stepindex = 0; stepindex < halfsteps; stepindex++) { |
589 | 10.6M | farindex = stepcount - stepindex - 1; |
590 | 10.6M | stepdir = step_dir(stepindex); |
591 | 10.6M | set_step(stepindex, step_dir(farindex) + halfturn); |
592 | 10.6M | set_step(farindex, stepdir + halfturn); |
593 | 10.6M | } |
594 | 986k | } |
595 | | |
596 | | /** |
597 | | * @name C_OUTLINE::move |
598 | | * |
599 | | * Move C_OUTLINE by vector |
600 | | * @param vec vector to reposition OUTLINE by |
601 | | */ |
602 | | |
603 | 0 | void C_OUTLINE::move(const ICOORD vec) { |
604 | 0 | C_OUTLINE_IT it(&children); // iterator |
605 | |
|
606 | 0 | box.move(vec); |
607 | 0 | start += vec; |
608 | |
|
609 | 0 | for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { |
610 | 0 | it.data()->move(vec); // move child outlines |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | | /** |
615 | | * Returns true if *this and its children are legally nested. |
616 | | * The outer area of a child should have the opposite sign to the |
617 | | * parent. If not, it means we have discarded an outline in between |
618 | | * (probably due to excessive length). |
619 | | */ |
620 | 3.17M | bool C_OUTLINE::IsLegallyNested() const { |
621 | 3.17M | if (stepcount == 0) { |
622 | 0 | return true; |
623 | 0 | } |
624 | 3.17M | int64_t parent_area = outer_area(); |
625 | | // We aren't going to modify the list, or its contents, but there is |
626 | | // no const iterator. |
627 | 3.17M | C_OUTLINE_IT child_it(const_cast<C_OUTLINE_LIST *>(&children)); |
628 | 3.39M | for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { |
629 | 218k | const C_OUTLINE *child = child_it.data(); |
630 | 218k | if (child->outer_area() * parent_area > 0 || !child->IsLegallyNested()) { |
631 | 0 | return false; |
632 | 0 | } |
633 | 218k | } |
634 | 3.17M | return true; |
635 | 3.17M | } |
636 | | |
637 | | /** |
638 | | * If this outline is smaller than the given min_size, delete this and |
639 | | * remove from its list, via *it, after checking that *it points to this. |
640 | | * Otherwise, if any children of this are too small, delete them. |
641 | | * On entry, *it must be an iterator pointing to this. If this gets deleted |
642 | | * then this is extracted from *it, so an iteration can continue. |
643 | | * @param min_size minimum size for outline |
644 | | * @param it outline iterator |
645 | | */ |
646 | 2.68M | void C_OUTLINE::RemoveSmallRecursive(int min_size, C_OUTLINE_IT *it) { |
647 | 2.68M | if (box.width() < min_size || box.height() < min_size) { |
648 | 525k | ASSERT_HOST(this == it->data()); |
649 | 525k | delete it->extract(); // Too small so get rid of it and any children. |
650 | 2.15M | } else if (!children.empty()) { |
651 | | // Search the children of this, deleting any that are too small. |
652 | 70.2k | C_OUTLINE_IT child_it(&children); |
653 | 231k | for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { |
654 | 161k | C_OUTLINE *child = child_it.data(); |
655 | 161k | child->RemoveSmallRecursive(min_size, &child_it); |
656 | 161k | } |
657 | 70.2k | } |
658 | 2.68M | } |
659 | | |
660 | | // Factored out helpers below are used only by ComputeEdgeOffsets to operate |
661 | | // on data from an 8-bit Pix, and assume that any input x and/or y are already |
662 | | // constrained to be legal Pix coordinates. |
663 | | |
664 | | /** |
665 | | * Helper computes the local 2-D gradient (dx, dy) from the 2x2 cell centered |
666 | | * on the given (x,y). If the cell would go outside the image, it is padded |
667 | | * with white. |
668 | | */ |
669 | | static void ComputeGradient(const l_uint32 *data, int wpl, int x, int y, int width, int height, |
670 | 0 | ICOORD *gradient) { |
671 | 0 | const l_uint32 *line = data + y * wpl; |
672 | 0 | int pix_x_y = x < width && y < height ? GET_DATA_BYTE(line, x) : 255; |
673 | 0 | int pix_x_prevy = x < width && y > 0 ? GET_DATA_BYTE(line - wpl, x) : 255; |
674 | 0 | int pix_prevx_prevy = x > 0 && y > 0 ? GET_DATA_BYTE(line - wpl, x - 1) : 255; |
675 | 0 | int pix_prevx_y = x > 0 && y < height ? GET_DATA_BYTE(line, x - 1) : 255; |
676 | 0 | gradient->set_x(pix_x_y + pix_x_prevy - (pix_prevx_y + pix_prevx_prevy)); |
677 | 0 | gradient->set_y(pix_x_prevy + pix_prevx_prevy - (pix_x_y + pix_prevx_y)); |
678 | 0 | } |
679 | | |
680 | | /** |
681 | | * Helper evaluates a vertical difference, (x,y) - (x,y-1), returning true if |
682 | | * the difference, matches diff_sign and updating the best_diff, best_sum, |
683 | | * best_y if a new max. |
684 | | */ |
685 | | static bool EvaluateVerticalDiff(const l_uint32 *data, int wpl, int diff_sign, int x, int y, |
686 | 0 | int height, int *best_diff, int *best_sum, int *best_y) { |
687 | 0 | if (y <= 0 || y >= height) { |
688 | 0 | return false; |
689 | 0 | } |
690 | 0 | const l_uint32 *line = data + y * wpl; |
691 | 0 | int pixel1 = GET_DATA_BYTE(line - wpl, x); |
692 | 0 | int pixel2 = GET_DATA_BYTE(line, x); |
693 | 0 | int diff = (pixel2 - pixel1) * diff_sign; |
694 | 0 | if (diff > *best_diff) { |
695 | 0 | *best_diff = diff; |
696 | 0 | *best_sum = pixel1 + pixel2; |
697 | 0 | *best_y = y; |
698 | 0 | } |
699 | 0 | return diff > 0; |
700 | 0 | } |
701 | | |
702 | | /** |
703 | | * Helper evaluates a horizontal difference, (x,y) - (x-1,y), where y is implied |
704 | | * by the input image line, returning true if the difference matches diff_sign |
705 | | * and updating the best_diff, best_sum, best_x if a new max. |
706 | | */ |
707 | | static bool EvaluateHorizontalDiff(const l_uint32 *line, int diff_sign, int x, int width, |
708 | 0 | int *best_diff, int *best_sum, int *best_x) { |
709 | 0 | if (x <= 0 || x >= width) { |
710 | 0 | return false; |
711 | 0 | } |
712 | 0 | int pixel1 = GET_DATA_BYTE(line, x - 1); |
713 | 0 | int pixel2 = GET_DATA_BYTE(line, x); |
714 | 0 | int diff = (pixel2 - pixel1) * diff_sign; |
715 | 0 | if (diff > *best_diff) { |
716 | 0 | *best_diff = diff; |
717 | 0 | *best_sum = pixel1 + pixel2; |
718 | 0 | *best_x = x; |
719 | 0 | } |
720 | 0 | return diff > 0; |
721 | 0 | } |
722 | | |
723 | | /** |
724 | | * Adds sub-pixel resolution EdgeOffsets for the outline if the supplied |
725 | | * pix is 8-bit. Does nothing otherwise. |
726 | | * Operation: Consider the following near-horizontal line: |
727 | | * @verbatim |
728 | | * _________ |
729 | | * |________ |
730 | | * |________ |
731 | | * @endverbatim |
732 | | * At *every* position along this line, the gradient direction will be close |
733 | | * to vertical. Extrapoaltion/interpolation of the position of the threshold |
734 | | * that was used to binarize the image gives a more precise vertical position |
735 | | * for each horizontal step, and the conflict in step direction and gradient |
736 | | * direction can be used to ignore the vertical steps. |
737 | | */ |
738 | 0 | void C_OUTLINE::ComputeEdgeOffsets(int threshold, Image pix) { |
739 | 0 | if (pixGetDepth(pix) != 8) { |
740 | 0 | return; |
741 | 0 | } |
742 | 0 | const l_uint32 *data = pixGetData(pix); |
743 | 0 | int wpl = pixGetWpl(pix); |
744 | 0 | int width = pixGetWidth(pix); |
745 | 0 | int height = pixGetHeight(pix); |
746 | 0 | bool negative = flag(COUT_INVERSE); |
747 | 0 | delete[] offsets; |
748 | 0 | offsets = new EdgeOffset[stepcount]; |
749 | 0 | ICOORD pos = start; |
750 | 0 | ICOORD prev_gradient; |
751 | 0 | ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &prev_gradient); |
752 | 0 | for (int s = 0; s < stepcount; ++s) { |
753 | 0 | ICOORD step_vec = step(s); |
754 | 0 | TPOINT pt1(pos); |
755 | 0 | pos += step_vec; |
756 | 0 | TPOINT pt2(pos); |
757 | 0 | ICOORD next_gradient; |
758 | 0 | ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &next_gradient); |
759 | | // Use the sum of the prev and next as the working gradient. |
760 | 0 | ICOORD gradient = prev_gradient + next_gradient; |
761 | | // best_diff will be manipulated to be always positive. |
762 | 0 | int best_diff = 0; |
763 | | // offset will be the extrapolation of the location of the greyscale |
764 | | // threshold from the edge with the largest difference, relative to the |
765 | | // location of the binary edge. |
766 | 0 | int offset = 0; |
767 | 0 | if (pt1.y == pt2.y && abs(gradient.y()) * 2 >= abs(gradient.x())) { |
768 | | // Horizontal step. diff_sign == 1 indicates black above. |
769 | 0 | int diff_sign = (pt1.x > pt2.x) == negative ? 1 : -1; |
770 | 0 | int x = std::min(pt1.x, pt2.x); |
771 | 0 | int y = height - pt1.y; |
772 | 0 | int best_sum = 0; |
773 | 0 | int best_y = y; |
774 | 0 | EvaluateVerticalDiff(data, wpl, diff_sign, x, y, height, &best_diff, &best_sum, &best_y); |
775 | | // Find the strongest edge. |
776 | 0 | int test_y = y; |
777 | 0 | do { |
778 | 0 | ++test_y; |
779 | 0 | } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, |
780 | 0 | &best_y)); |
781 | 0 | test_y = y; |
782 | 0 | do { |
783 | 0 | --test_y; |
784 | 0 | } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, |
785 | 0 | &best_y)); |
786 | 0 | offset = diff_sign * (best_sum / 2 - threshold) + (y - best_y) * best_diff; |
787 | 0 | } else if (pt1.x == pt2.x && abs(gradient.x()) * 2 >= abs(gradient.y())) { |
788 | | // Vertical step. diff_sign == 1 indicates black on the left. |
789 | 0 | int diff_sign = (pt1.y > pt2.y) == negative ? 1 : -1; |
790 | 0 | int x = pt1.x; |
791 | 0 | int y = height - std::max(pt1.y, pt2.y); |
792 | 0 | const l_uint32 *line = pixGetData(pix) + y * wpl; |
793 | 0 | int best_sum = 0; |
794 | 0 | int best_x = x; |
795 | 0 | EvaluateHorizontalDiff(line, diff_sign, x, width, &best_diff, &best_sum, &best_x); |
796 | | // Find the strongest edge. |
797 | 0 | int test_x = x; |
798 | 0 | do { |
799 | 0 | ++test_x; |
800 | 0 | } while ( |
801 | 0 | EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); |
802 | 0 | test_x = x; |
803 | 0 | do { |
804 | 0 | --test_x; |
805 | 0 | } while ( |
806 | 0 | EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); |
807 | 0 | offset = diff_sign * (threshold - best_sum / 2) + (best_x - x) * best_diff; |
808 | 0 | } |
809 | 0 | offsets[s].offset_numerator = ClipToRange<int>(offset, -INT8_MAX, INT8_MAX); |
810 | 0 | offsets[s].pixel_diff = ClipToRange<int>(best_diff, 0, UINT8_MAX); |
811 | 0 | if (negative) { |
812 | 0 | gradient = -gradient; |
813 | 0 | } |
814 | | // Compute gradient angle quantized to 256 directions, rotated by 64 (pi/2) |
815 | | // to convert from gradient direction to edge direction. |
816 | 0 | offsets[s].direction = Modulo(FCOORD::binary_angle_plus_pi(gradient.angle()) + 64, 256); |
817 | 0 | prev_gradient = next_gradient; |
818 | 0 | } |
819 | 0 | } |
820 | | |
821 | | /** |
822 | | * Adds sub-pixel resolution EdgeOffsets for the outline using only |
823 | | * a binary image source. |
824 | | * |
825 | | * Runs a sliding window of 5 edge steps over the outline, maintaining a count |
826 | | * of the number of steps in each of the 4 directions in the window, and a |
827 | | * sum of the x or y position of each step (as appropriate to its direction.) |
828 | | * Ignores single-count steps EXCEPT the sharp U-turn and smoothes out the |
829 | | * perpendicular direction. Eg |
830 | | * @verbatim |
831 | | * ___ ___ Chain code from the left: |
832 | | * |___ ___ ___| 222122212223221232223000 |
833 | | * |___| |_| Corresponding counts of each direction: |
834 | | * 0 00000000000000000123 |
835 | | * 1 11121111001111100000 |
836 | | * 2 44434443443333343321 |
837 | | * 3 00000001111111112111 |
838 | | * Count of direction at center 41434143413313143313 |
839 | | * Step gets used? YNYYYNYYYNYYNYNYYYyY (y= U-turn exception) |
840 | | * Path redrawn showing only the used points: |
841 | | * ___ ___ |
842 | | * ___ ___ ___| |
843 | | * ___ _ |
844 | | * @endverbatim |
845 | | * Sub-pixel edge position cannot be shown well with ASCII-art, but each |
846 | | * horizontal step's y position is the mean of the y positions of the steps |
847 | | * in the same direction in the sliding window, which makes a much smoother |
848 | | * outline, without losing important detail. |
849 | | */ |
850 | 3.06M | void C_OUTLINE::ComputeBinaryOffsets() { |
851 | 3.06M | delete[] offsets; |
852 | 3.06M | offsets = new EdgeOffset[stepcount]; |
853 | | // Count of the number of steps in each direction in the sliding window. |
854 | 3.06M | int dir_counts[4]; |
855 | | // Sum of the positions (y for a horizontal step, x for vertical) in each |
856 | | // direction in the sliding window. |
857 | 3.06M | int pos_totals[4]; |
858 | 3.06M | memset(dir_counts, 0, sizeof(dir_counts)); |
859 | 3.06M | memset(pos_totals, 0, sizeof(pos_totals)); |
860 | 3.06M | ICOORD pos = start; |
861 | 3.06M | ICOORD tail_pos = pos; |
862 | | // tail_pos is the trailing position, with the next point to be lost from |
863 | | // the window. |
864 | 3.06M | tail_pos -= step(stepcount - 1); |
865 | 3.06M | tail_pos -= step(stepcount - 2); |
866 | | // head_pos is the leading position, with the next point to be added to the |
867 | | // window. |
868 | 3.06M | ICOORD head_pos = tail_pos; |
869 | | // Set up the initial window with 4 points in [-2, 2) |
870 | 15.3M | for (int s = -2; s < 2; ++s) { |
871 | 12.2M | increment_step(s, 1, &head_pos, dir_counts, pos_totals); |
872 | 12.2M | } |
873 | 82.8M | for (int s = 0; s < stepcount; pos += step(s++)) { |
874 | | // At step s, s in the middle of [s-2, s+2]. |
875 | 79.8M | increment_step(s + 2, 1, &head_pos, dir_counts, pos_totals); |
876 | 79.8M | int dir_index = chain_code(s); |
877 | 79.8M | ICOORD step_vec = step(s); |
878 | 79.8M | int best_diff = 0; |
879 | 79.8M | int offset = 0; |
880 | | // Use only steps that have a count of >=2 OR the strong U-turn with a |
881 | | // single d and 2 at d-1 and 2 at d+1 (mod 4). |
882 | 79.8M | if (dir_counts[dir_index] >= 2 || |
883 | 79.8M | (dir_counts[dir_index] == 1 && dir_counts[Modulo(dir_index - 1, 4)] == 2 && |
884 | 73.4M | dir_counts[Modulo(dir_index + 1, 4)] == 2)) { |
885 | | // Valid step direction. |
886 | 73.4M | best_diff = dir_counts[dir_index]; |
887 | 73.4M | int edge_pos = step_vec.x() == 0 ? pos.x() : pos.y(); |
888 | | // The offset proposes that the actual step should be positioned at |
889 | | // the mean position of the steps in the window of the same direction. |
890 | | // See ASCII art above. |
891 | 73.4M | offset = pos_totals[dir_index] - best_diff * edge_pos; |
892 | 73.4M | } |
893 | 79.8M | offsets[s].offset_numerator = ClipToRange<int>(offset, -INT8_MAX, INT8_MAX); |
894 | 79.8M | offsets[s].pixel_diff = ClipToRange<int>(best_diff, 0, UINT8_MAX); |
895 | | // The direction is just the vector from start to end of the window. |
896 | 79.8M | FCOORD direction(head_pos.x() - tail_pos.x(), head_pos.y() - tail_pos.y()); |
897 | 79.8M | offsets[s].direction = direction.to_direction(); |
898 | 79.8M | increment_step(s - 2, -1, &tail_pos, dir_counts, pos_totals); |
899 | 79.8M | } |
900 | 3.06M | } |
901 | | |
902 | | /** |
903 | | * Renders the outline to the given pix, with left and top being |
904 | | * the coords of the upper-left corner of the pix. |
905 | | */ |
906 | 0 | void C_OUTLINE::render(int left, int top, Image pix) const { |
907 | 0 | ICOORD pos = start; |
908 | 0 | for (int stepindex = 0; stepindex < stepcount; ++stepindex) { |
909 | 0 | ICOORD next_step = step(stepindex); |
910 | 0 | if (next_step.y() < 0) { |
911 | 0 | pixRasterop(pix, 0, top - pos.y(), pos.x() - left, 1, PIX_NOT(PIX_DST), nullptr, 0, 0); |
912 | 0 | } else if (next_step.y() > 0) { |
913 | 0 | pixRasterop(pix, 0, top - pos.y() - 1, pos.x() - left, 1, PIX_NOT(PIX_DST), nullptr, 0, 0); |
914 | 0 | } |
915 | 0 | pos += next_step; |
916 | 0 | } |
917 | 0 | } |
918 | | |
919 | | /** |
920 | | * Renders just the outline to the given pix (no fill), with left and top |
921 | | * being the coords of the upper-left corner of the pix. |
922 | | * @param left coord |
923 | | * @param top coord |
924 | | * @param pix the pix to outline |
925 | | */ |
926 | 0 | void C_OUTLINE::render_outline(int left, int top, Image pix) const { |
927 | 0 | ICOORD pos = start; |
928 | 0 | for (int stepindex = 0; stepindex < stepcount; ++stepindex) { |
929 | 0 | ICOORD next_step = step(stepindex); |
930 | 0 | if (next_step.y() < 0) { |
931 | 0 | pixSetPixel(pix, pos.x() - left, top - pos.y(), 1); |
932 | 0 | } else if (next_step.y() > 0) { |
933 | 0 | pixSetPixel(pix, pos.x() - left - 1, top - pos.y() - 1, 1); |
934 | 0 | } else if (next_step.x() < 0) { |
935 | 0 | pixSetPixel(pix, pos.x() - left - 1, top - pos.y(), 1); |
936 | 0 | } else if (next_step.x() > 0) { |
937 | 0 | pixSetPixel(pix, pos.x() - left, top - pos.y() - 1, 1); |
938 | 0 | } |
939 | 0 | pos += next_step; |
940 | 0 | } |
941 | 0 | } |
942 | | |
943 | | /** |
944 | | * @name C_OUTLINE::plot |
945 | | * |
946 | | * Draw the outline in the given colour. |
947 | | * @param window window to draw in |
948 | | * @param colour colour to draw in |
949 | | */ |
950 | | |
951 | | #ifndef GRAPHICS_DISABLED |
952 | | void C_OUTLINE::plot(ScrollView *window, ScrollView::Color colour) const { |
953 | | int16_t stepindex; // index to cstep |
954 | | ICOORD pos; // current position |
955 | | DIR128 stepdir; // direction of step |
956 | | |
957 | | pos = start; // current position |
958 | | window->Pen(colour); |
959 | | if (stepcount == 0) { |
960 | | window->Rectangle(box.left(), box.top(), box.right(), box.bottom()); |
961 | | return; |
962 | | } |
963 | | window->SetCursor(pos.x(), pos.y()); |
964 | | |
965 | | stepindex = 0; |
966 | | while (stepindex < stepcount) { |
967 | | pos += step(stepindex); // step to next |
968 | | stepdir = step_dir(stepindex); |
969 | | stepindex++; // count steps |
970 | | // merge straight lines |
971 | | while (stepindex < stepcount && stepdir.get_dir() == step_dir(stepindex).get_dir()) { |
972 | | pos += step(stepindex); |
973 | | stepindex++; |
974 | | } |
975 | | window->DrawTo(pos.x(), pos.y()); |
976 | | } |
977 | | } |
978 | | |
979 | | /** |
980 | | * Draws the outline in the given colour, normalized using the given denorm, |
981 | | * making use of sub-pixel accurate information if available. |
982 | | */ |
983 | | void C_OUTLINE::plot_normed(const DENORM &denorm, ScrollView::Color colour, |
984 | | ScrollView *window) const { |
985 | | window->Pen(colour); |
986 | | if (stepcount == 0) { |
987 | | window->Rectangle(box.left(), box.top(), box.right(), box.bottom()); |
988 | | return; |
989 | | } |
990 | | const DENORM *root_denorm = denorm.RootDenorm(); |
991 | | ICOORD pos = start; // current position |
992 | | FCOORD f_pos = sub_pixel_pos_at_index(pos, 0); |
993 | | FCOORD pos_normed; |
994 | | denorm.NormTransform(root_denorm, f_pos, &pos_normed); |
995 | | window->SetCursor(IntCastRounded(pos_normed.x()), IntCastRounded(pos_normed.y())); |
996 | | for (int s = 0; s < stepcount; pos += step(s++)) { |
997 | | int edge_weight = edge_strength_at_index(s); |
998 | | if (edge_weight == 0) { |
999 | | // This point has conflicting gradient and step direction, so ignore it. |
1000 | | continue; |
1001 | | } |
1002 | | FCOORD f_pos = sub_pixel_pos_at_index(pos, s); |
1003 | | FCOORD pos_normed; |
1004 | | denorm.NormTransform(root_denorm, f_pos, &pos_normed); |
1005 | | window->DrawTo(IntCastRounded(pos_normed.x()), IntCastRounded(pos_normed.y())); |
1006 | | } |
1007 | | } |
1008 | | #endif |
1009 | | |
1010 | | /** |
1011 | | * @name C_OUTLINE::operator= |
1012 | | * |
1013 | | * Assignment - deep copy data |
1014 | | * @param source assign from this |
1015 | | */ |
1016 | | |
1017 | 1.13M | C_OUTLINE &C_OUTLINE::operator=(const C_OUTLINE &source) { |
1018 | 1.13M | box = source.box; |
1019 | 1.13M | start = source.start; |
1020 | 1.13M | if (!children.empty()) { |
1021 | 0 | children.clear(); |
1022 | 0 | } |
1023 | 1.13M | children.deep_copy(&source.children, &deep_copy); |
1024 | 1.13M | delete[] offsets; |
1025 | 1.13M | offsets = nullptr; |
1026 | 1.13M | stepcount = source.stepcount; |
1027 | 1.13M | if (stepcount > 0) { |
1028 | 1.13M | steps.resize(step_mem()); |
1029 | 1.13M | memmove(&steps[0], &source.steps[0], step_mem()); |
1030 | 1.13M | if (source.offsets != nullptr) { |
1031 | 1.09M | offsets = new EdgeOffset[stepcount]; |
1032 | 1.09M | memcpy(offsets, source.offsets, stepcount * sizeof(*offsets)); |
1033 | 1.09M | } |
1034 | 1.13M | } |
1035 | 1.13M | return *this; |
1036 | 1.13M | } |
1037 | | |
1038 | | /** |
1039 | | * Helper for ComputeBinaryOffsets. Increments pos, dir_counts, pos_totals |
1040 | | * by the step, increment, and vertical step ? x : y position * increment |
1041 | | * at step s Mod stepcount respectively. Used to add or subtract the |
1042 | | * direction and position to/from accumulators of a small neighbourhood. |
1043 | | */ |
1044 | | void C_OUTLINE::increment_step(int s, int increment, ICOORD *pos, int *dir_counts, |
1045 | 171M | int *pos_totals) const { |
1046 | 171M | int step_index = Modulo(s, stepcount); |
1047 | 171M | int dir_index = chain_code(step_index); |
1048 | 171M | dir_counts[dir_index] += increment; |
1049 | 171M | ICOORD step_vec = step(step_index); |
1050 | 171M | if (step_vec.x() == 0) { |
1051 | 103M | pos_totals[dir_index] += pos->x() * increment; |
1052 | 103M | } else { |
1053 | 68.2M | pos_totals[dir_index] += pos->y() * increment; |
1054 | 68.2M | } |
1055 | 171M | *pos += step_vec; |
1056 | 171M | } |
1057 | | |
1058 | 0 | ICOORD C_OUTLINE::chain_step(int chaindir) { |
1059 | 0 | return step_coords[chaindir % 4]; |
1060 | 0 | } |
1061 | | |
1062 | | } // namespace tesseract |