Coverage Report

Created: 2024-02-28 06:46

/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