Coverage Report

Created: 2024-07-27 06:27

/src/leptonica/src/runlength.c
Line
Count
Source (jump to first uncovered line)
1
/*====================================================================*
2
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
3
 -
4
 -  Redistribution and use in source and binary forms, with or without
5
 -  modification, are permitted provided that the following conditions
6
 -  are met:
7
 -  1. Redistributions of source code must retain the above copyright
8
 -     notice, this list of conditions and the following disclaimer.
9
 -  2. Redistributions in binary form must reproduce the above
10
 -     copyright notice, this list of conditions and the following
11
 -     disclaimer in the documentation and/or other materials
12
 -     provided with the distribution.
13
 -
14
 -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
 -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
 -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
 -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18
 -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19
 -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20
 -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21
 -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22
 -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
 -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
 -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 *====================================================================*/
26
27
/*!
28
 * \file runlength.c
29
 * <pre>
30
 *
31
 *     Label pixels by membership in runs
32
 *           PIX         *pixStrokeWidthTransform()
33
 *           static PIX  *pixFindMinRunsOrthogonal()
34
 *           PIX         *pixRunlengthTransform()
35
 *
36
 *     Find runs along horizontal and vertical lines
37
 *           l_int32      pixFindHorizontalRuns()
38
 *           l_int32      pixFindVerticalRuns()
39
 *
40
 *     Find max runs along horizontal and vertical lines
41
 *           l_int32      pixFindMaxRuns()
42
 *           l_int32      pixFindMaxHorizontalRunOnLine()
43
 *           l_int32      pixFindMaxVerticalRunOnLine()
44
 *
45
 *     Compute runlength-to-membership transform on a line
46
 *           l_int32      runlengthMembershipOnLine()
47
 *
48
 *     Make byte position LUT
49
 *           l_int32      makeMSBitLocTab()
50
 *
51
 *  Here we're handling runs of either black or white pixels on 1 bpp
52
 *  images.  The directions of the runs in the stroke width transform
53
 *  are selectable from given sets of angles.  Most of the other runs
54
 *  are oriented either horizontally along the raster lines or
55
 *  vertically along pixel columns.
56
 * </pre>
57
 */
58
59
#ifdef HAVE_CONFIG_H
60
#include <config_auto.h>
61
#endif  /* HAVE_CONFIG_H */
62
63
#include <string.h>
64
#include <math.h>
65
#include "allheaders.h"
66
67
static PIX *pixFindMinRunsOrthogonal(PIX *pixs, l_float32 angle, l_int32 depth);
68
69
/*-----------------------------------------------------------------------*
70
 *                   Label pixels by membership in runs                  *
71
 *-----------------------------------------------------------------------*/
72
/*!
73
 * \brief   pixStrokeWidthTransform()
74
 *
75
 * \param[in]     pixs      1 bpp
76
 * \param[in]     color     0 for white runs, 1 for black runs
77
 * \param[in]     depth     of pixd: 8 or 16 bpp
78
 * \param[in]     nangles   2, 4, 6 or 8
79
 * \return   pixd   8 or 16 bpp, or NULL on error
80
 *
81
 * <pre>
82
 * Notes:
83
 *      (1) The dest Pix is 8 or 16 bpp, with the pixel values
84
 *          equal to the stroke width in which it is a member.
85
 *          The values are clipped to the max pixel value if necessary.
86
 *      (2) %color determines if we're labelling white or black strokes.
87
 *      (3) A pixel that is not a member of the chosen color gets
88
 *          value 0; it belongs to a width of length 0 of the
89
 *          chosen color.
90
 *      (4) This chooses, for each dest pixel, the minimum of sets
91
 *          of runlengths through each pixel.  Here are the sets:
92
 *            nangles    increment          set
93
 *            -------    ---------    --------------------------------
94
 *               2          90       {0, 90}
95
 *               4          45       {0, 45, 90, 135}
96
 *               6          30       {0, 30, 60, 90, 120, 150}
97
 *               8          22.5     {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5}
98
 *      (5) Runtime scales linearly with (%nangles - 2).
99
 * </pre>
100
 */
101
PIX *
102
pixStrokeWidthTransform(PIX     *pixs,
103
                        l_int32  color,
104
                        l_int32  depth,
105
                        l_int32  nangles)
106
0
{
107
0
l_float32  angle, pi;
108
0
PIX       *pixh, *pixv, *pixt, *pixg1, *pixg2, *pixg3, *pixg4;
109
110
0
    if (!pixs || pixGetDepth(pixs) != 1)
111
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
112
0
    if (depth != 8 && depth != 16)
113
0
        return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", __func__, NULL);
114
0
    if (nangles != 2 && nangles != 4 && nangles != 6 && nangles != 8)
115
0
        return (PIX *)ERROR_PTR("nangles not in {2,4,6,8}", __func__, NULL);
116
117
        /* Use fg runs for evaluation */
118
0
    if (color == 0)
119
0
        pixt = pixInvert(NULL, pixs);
120
0
    else
121
0
        pixt = pixClone(pixs);
122
123
        /* Find min length at 0 and 90 degrees */
124
0
    pixh = pixRunlengthTransform(pixt, 1, L_HORIZONTAL_RUNS, depth);
125
0
    pixv = pixRunlengthTransform(pixt, 1, L_VERTICAL_RUNS, depth);
126
0
    pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN);
127
0
    pixDestroy(&pixh);
128
0
    pixDestroy(&pixv);
129
130
0
    pixg2 = pixg3 = pixg4 = NULL;
131
0
    pi = 3.1415926535f;
132
0
    if (nangles == 4 || nangles == 8) {
133
            /* Find min length at +45 and -45 degrees */
134
0
        angle = pi / 4.0f;
135
0
        pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth);
136
0
    }
137
138
0
    if (nangles == 6) {
139
            /* Find min length at +30 and -60 degrees */
140
0
        angle = pi / 6.0f;
141
0
        pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth);
142
143
            /* Find min length at +60 and -30 degrees */
144
0
        angle = pi / 3.0f;
145
0
        pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth);
146
0
    }
147
148
0
    if (nangles == 8) {
149
            /* Find min length at +22.5 and -67.5 degrees */
150
0
        angle = pi / 8.0f;
151
0
        pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth);
152
153
            /* Find min length at +67.5 and -22.5 degrees */
154
0
        angle = 3.0 * pi / 8.0f;
155
0
        pixg4 = pixFindMinRunsOrthogonal(pixt, angle, depth);
156
0
    }
157
0
    pixDestroy(&pixt);
158
159
0
    if (nangles > 2)
160
0
        pixMinOrMax(pixg1, pixg1, pixg2, L_CHOOSE_MIN);
161
0
    if (nangles > 4)
162
0
        pixMinOrMax(pixg1, pixg1, pixg3, L_CHOOSE_MIN);
163
0
    if (nangles > 6)
164
0
        pixMinOrMax(pixg1, pixg1, pixg4, L_CHOOSE_MIN);
165
0
    pixDestroy(&pixg2);
166
0
    pixDestroy(&pixg3);
167
0
    pixDestroy(&pixg4);
168
0
    return pixg1;
169
0
}
170
171
172
/*!
173
 * \brief   pixFindMinRunsOrthogonal()
174
 *
175
 * \param[in]     pixs     1 bpp
176
 * \param[in]     angle    in radians
177
 * \param[in]     depth    of pixd: 8 or 16 bpp
178
 * \return   pixd 8 or 16 bpp, or NULL on error
179
 *
180
 * <pre>
181
 * Notes:
182
 *      (1) This computes, for each fg pixel in pixs, the minimum of
183
 *          the runlengths going through that pixel in two orthogonal
184
 *          directions: at %angle and at (90 + %angle).
185
 *      (2) We use rotation by shear because the forward and backward
186
 *          rotations by the same angle are exact inverse operations.
187
 *          As a result, the nonzero pixels in pixd correspond exactly
188
 *          to the fg pixels in pixs.  This is not the case with
189
 *          sampled rotation, due to spatial quantization.  Nevertheless,
190
 *          the result suffers from lack of exact correspondence
191
 *          between original and rotated pixels, also due to spatial
192
 *          quantization, causing some boundary pixels to be
193
 *          shifted from bg to fg or v.v.
194
 * </pre>
195
 */
196
static PIX *
197
pixFindMinRunsOrthogonal(PIX       *pixs,
198
                         l_float32  angle,
199
                         l_int32    depth)
200
0
{
201
0
l_int32  w, h, diag, xoff, yoff;
202
0
PIX     *pixb, *pixr, *pixh, *pixv, *pixg1, *pixg2, *pixd;
203
0
BOX     *box;
204
205
0
    if (!pixs || pixGetDepth(pixs) != 1)
206
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
207
208
        /* Rasterop into the center of a sufficiently large image
209
         * so we don't lose pixels for any rotation angle. */
210
0
    pixGetDimensions(pixs, &w, &h, NULL);
211
0
    diag = (l_int32)(sqrt((l_float64)(w * w + h * h)) + 2.5);
212
0
    xoff = (diag - w) / 2;
213
0
    yoff = (diag - h) / 2;
214
0
    pixb = pixCreate(diag, diag, 1);
215
0
    pixRasterop(pixb, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
216
217
        /* Rotate about the 'center', get the min of orthogonal transforms,
218
         * rotate back, and crop the part corresponding to pixs.  */
219
0
    pixr = pixRotateShear(pixb, diag / 2, diag / 2, angle, L_BRING_IN_WHITE);
220
0
    pixh = pixRunlengthTransform(pixr, 1, L_HORIZONTAL_RUNS, depth);
221
0
    pixv = pixRunlengthTransform(pixr, 1, L_VERTICAL_RUNS, depth);
222
0
    pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN);
223
0
    pixg2 = pixRotateShear(pixg1, diag / 2, diag / 2, -angle, L_BRING_IN_WHITE);
224
0
    box = boxCreate(xoff, yoff, w, h);
225
0
    pixd = pixClipRectangle(pixg2, box, NULL);
226
227
0
    pixDestroy(&pixb);
228
0
    pixDestroy(&pixr);
229
0
    pixDestroy(&pixh);
230
0
    pixDestroy(&pixv);
231
0
    pixDestroy(&pixg1);
232
0
    pixDestroy(&pixg2);
233
0
    boxDestroy(&box);
234
0
    return pixd;
235
0
}
236
237
238
/*!
239
 * \brief   pixRunlengthTransform()
240
 *
241
 * \param[in]     pixs        1 bpp
242
 * \param[in]     color       0 for white runs, 1 for black runs
243
 * \param[in]     direction   L_HORIZONTAL_RUNS, L_VERTICAL_RUNS
244
 * \param[in]     depth       8 or 16 bpp
245
 * \return   pixd   8 or 16 bpp, or NULL on error
246
 *
247
 * <pre>
248
 * Notes:
249
 *      (1) The dest Pix is 8 or 16 bpp, with the pixel values
250
 *          equal to the runlength in which it is a member.
251
 *          The length is clipped to the max pixel value if necessary.
252
 *      (2) %color determines if we're labelling white or black runs.
253
 *      (3) A pixel that is not a member of the chosen color gets
254
 *          value 0; it belongs to a run of length 0 of the
255
 *          chosen color.
256
 *      (4) To convert for maximum dynamic range, either linear or
257
 *          log, use pixMaxDynamicRange().
258
 * </pre>
259
 */
260
PIX *
261
pixRunlengthTransform(PIX     *pixs,
262
                      l_int32  color,
263
                      l_int32  direction,
264
                      l_int32  depth)
265
0
{
266
0
l_int32    i, j, w, h, wpld, bufsize, maxsize, n;
267
0
l_int32   *start, *end, *buffer;
268
0
l_uint32  *datad, *lined;
269
0
PIX       *pixt, *pixd;
270
271
0
    if (!pixs)
272
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
273
0
    if (pixGetDepth(pixs) != 1)
274
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
275
0
    if (depth != 8 && depth != 16)
276
0
        return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", __func__, NULL);
277
278
0
    pixGetDimensions(pixs, &w, &h, NULL);
279
0
    if (direction == L_HORIZONTAL_RUNS)
280
0
        maxsize = 1 + w / 2;
281
0
    else if (direction == L_VERTICAL_RUNS)
282
0
        maxsize = 1 + h / 2;
283
0
    else
284
0
        return (PIX *)ERROR_PTR("invalid direction", __func__, NULL);
285
0
    bufsize = L_MAX(w, h);
286
0
    if (bufsize > 1000000) {
287
0
        L_ERROR("largest image dimension = %d; too big\n", __func__, bufsize);
288
0
        return NULL;
289
0
    }
290
291
0
    if ((pixd = pixCreate(w, h, depth)) == NULL)
292
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
293
0
    datad = pixGetData(pixd);
294
0
    wpld = pixGetWpl(pixd);
295
296
0
    start = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32));
297
0
    end = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32));
298
0
    buffer = (l_int32 *)LEPT_CALLOC(bufsize, sizeof(l_int32));
299
300
        /* Use fg runs for evaluation */
301
0
    if (color == 0)
302
0
        pixt = pixInvert(NULL, pixs);
303
0
    else
304
0
        pixt = pixClone(pixs);
305
306
0
    if (direction == L_HORIZONTAL_RUNS) {
307
0
        for (i = 0; i < h; i++) {
308
0
            pixFindHorizontalRuns(pixt, i, start, end, &n);
309
0
            runlengthMembershipOnLine(buffer, w, depth, start, end, n);
310
0
            lined = datad + i * wpld;
311
0
            if (depth == 8) {
312
0
                for (j = 0; j < w; j++)
313
0
                    SET_DATA_BYTE(lined, j, buffer[j]);
314
0
            } else {  /* depth == 16 */
315
0
                for (j = 0; j < w; j++)
316
0
                    SET_DATA_TWO_BYTES(lined, j, buffer[j]);
317
0
            }
318
0
        }
319
0
    } else {  /* L_VERTICAL_RUNS */
320
0
        for (j = 0; j < w; j++) {
321
0
            pixFindVerticalRuns(pixt, j, start, end, &n);
322
0
            runlengthMembershipOnLine(buffer, h, depth, start, end, n);
323
0
            if (depth == 8) {
324
0
                for (i = 0; i < h; i++) {
325
0
                    lined = datad + i * wpld;
326
0
                    SET_DATA_BYTE(lined, j, buffer[i]);
327
0
                }
328
0
            } else {  /* depth == 16 */
329
0
                for (i = 0; i < h; i++) {
330
0
                    lined = datad + i * wpld;
331
0
                    SET_DATA_TWO_BYTES(lined, j, buffer[i]);
332
0
                }
333
0
            }
334
0
        }
335
0
    }
336
337
0
    pixDestroy(&pixt);
338
0
    LEPT_FREE(start);
339
0
    LEPT_FREE(end);
340
0
    LEPT_FREE(buffer);
341
0
    return pixd;
342
0
}
343
344
345
/*-----------------------------------------------------------------------*
346
 *               Find runs along horizontal and vertical lines           *
347
 *-----------------------------------------------------------------------*/
348
/*!
349
 * \brief   pixFindHorizontalRuns()
350
 *
351
 * \param[in]    pix      1 bpp
352
 * \param[in]    y        line to traverse
353
 * \param[in]    xstart   returns array of start positions for fg runs
354
 * \param[in]    xend     returns array of end positions for fg runs
355
 * \param[out]   pn       the number of runs found
356
 * \return  0 if OK; 1 on error
357
 *
358
 * <pre>
359
 * Notes:
360
 *      (1) This finds foreground horizontal runs on a single scanline.
361
 *      (2) To find background runs, use pixInvert() before applying
362
 *          this function.
363
 *      (3) %xstart and %xend arrays are input.  They should be
364
 *          of size w/2 + 1 to insure that they can hold
365
 *          the maximum number of runs in the raster line.
366
 * </pre>
367
 */
368
l_ok
369
pixFindHorizontalRuns(PIX      *pix,
370
                      l_int32   y,
371
                      l_int32  *xstart,
372
                      l_int32  *xend,
373
                      l_int32  *pn)
374
0
{
375
0
l_int32    inrun;  /* boolean */
376
0
l_int32    index, w, h, d, j, wpl, val;
377
0
l_uint32  *line;
378
379
0
    if (!pn)
380
0
        return ERROR_INT("&n not defined", __func__, 1);
381
0
    *pn = 0;
382
0
    if (!pix)
383
0
        return ERROR_INT("pix not defined", __func__, 1);
384
0
    pixGetDimensions(pix, &w, &h, &d);
385
0
    if (d != 1)
386
0
        return ERROR_INT("pix not 1 bpp", __func__, 1);
387
0
    if (y < 0 || y >= h)
388
0
        return ERROR_INT("y not in [0 ... h - 1]", __func__, 1);
389
0
    if (!xstart)
390
0
        return ERROR_INT("xstart not defined", __func__, 1);
391
0
    if (!xend)
392
0
        return ERROR_INT("xend not defined", __func__, 1);
393
394
0
    wpl = pixGetWpl(pix);
395
0
    line = pixGetData(pix) + y * wpl;
396
397
0
    inrun = FALSE;
398
0
    index = 0;
399
0
    for (j = 0; j < w; j++) {
400
0
        val = GET_DATA_BIT(line, j);
401
0
        if (!inrun) {
402
0
            if (val) {
403
0
                xstart[index] = j;
404
0
                inrun = TRUE;
405
0
            }
406
0
        } else {
407
0
            if (!val) {
408
0
                xend[index++] = j - 1;
409
0
                inrun = FALSE;
410
0
            }
411
0
        }
412
0
    }
413
414
        /* Finish last run if necessary */
415
0
    if (inrun)
416
0
        xend[index++] = w - 1;
417
418
0
    *pn = index;
419
0
    return 0;
420
0
}
421
422
423
/*!
424
 * \brief   pixFindVerticalRuns()
425
 *
426
 * \param[in]    pix      1 bpp
427
 * \param[in]    x        line to traverse
428
 * \param[in]    ystart   returns array of start positions for fg runs
429
 * \param[in]    yend     returns array of end positions for fg runs
430
 * \param[out]   pn       the number of runs found
431
 * \return  0 if OK; 1 on error
432
 *
433
 * <pre>
434
 * Notes:
435
 *      (1) This finds foreground vertical runs on a single scanline.
436
 *      (2) To find background runs, use pixInvert() before applying
437
 *          this function.
438
 *      (3) %ystart and %yend arrays are input.  They should be
439
 *          of size h/2 + 1 to insure that they can hold
440
 *          the maximum number of runs in the raster line.
441
 * </pre>
442
 */
443
l_ok
444
pixFindVerticalRuns(PIX      *pix,
445
                    l_int32   x,
446
                    l_int32  *ystart,
447
                    l_int32  *yend,
448
                    l_int32  *pn)
449
0
{
450
0
l_int32    inrun;  /* boolean */
451
0
l_int32    index, w, h, d, i, wpl, val;
452
0
l_uint32  *data, *line;
453
454
0
    if (!pn)
455
0
        return ERROR_INT("&n not defined", __func__, 1);
456
0
    *pn = 0;
457
0
    if (!pix)
458
0
        return ERROR_INT("pix not defined", __func__, 1);
459
0
    pixGetDimensions(pix, &w, &h, &d);
460
0
    if (d != 1)
461
0
        return ERROR_INT("pix not 1 bpp", __func__, 1);
462
0
    if (x < 0 || x >= w)
463
0
        return ERROR_INT("x not in [0 ... w - 1]", __func__, 1);
464
0
    if (!ystart)
465
0
        return ERROR_INT("ystart not defined", __func__, 1);
466
0
    if (!yend)
467
0
        return ERROR_INT("yend not defined", __func__, 1);
468
469
0
    wpl = pixGetWpl(pix);
470
0
    data = pixGetData(pix);
471
472
0
    inrun = FALSE;
473
0
    index = 0;
474
0
    for (i = 0; i < h; i++) {
475
0
        line = data + i * wpl;
476
0
        val = GET_DATA_BIT(line, x);
477
0
        if (!inrun) {
478
0
            if (val) {
479
0
                ystart[index] = i;
480
0
                inrun = TRUE;
481
0
            }
482
0
        } else {
483
0
            if (!val) {
484
0
                yend[index++] = i - 1;
485
0
                inrun = FALSE;
486
0
            }
487
0
        }
488
0
    }
489
490
        /* Finish last run if necessary */
491
0
    if (inrun)
492
0
        yend[index++] = h - 1;
493
494
0
    *pn = index;
495
0
    return 0;
496
0
}
497
498
499
/*-----------------------------------------------------------------------*
500
 *            Find max runs along horizontal and vertical lines          *
501
 *-----------------------------------------------------------------------*/
502
/*!
503
 * \brief   pixFindMaxRuns()
504
 *
505
 * \param[in]    pix         1 bpp
506
 * \param[in]    direction   L_HORIZONTAL_RUNS or L_VERTICAL_RUNS
507
 * \param[out]   pnastart    [optional] start locations of longest runs
508
 * \return  na of lengths of runs, or NULL on error
509
 *
510
 * <pre>
511
 * Notes:
512
 *      (1) This finds the longest foreground runs by row or column
513
 *      (2) To find background runs, use pixInvert() before applying
514
 *          this function.
515
 * </pre>
516
 */
517
NUMA *
518
pixFindMaxRuns(PIX     *pix,
519
               l_int32  direction,
520
               NUMA   **pnastart)
521
0
{
522
0
l_int32  w, h, i, start, size;
523
0
NUMA    *nasize;
524
525
0
    if (pnastart) *pnastart = NULL;
526
0
    if (direction != L_HORIZONTAL_RUNS && direction != L_VERTICAL_RUNS)
527
0
        return (NUMA *)ERROR_PTR("direction invalid", __func__, NULL);
528
0
    if (!pix || pixGetDepth(pix) != 1)
529
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
530
531
0
    pixGetDimensions(pix, &w, &h, NULL);
532
0
    nasize = numaCreate(w);
533
0
    if (pnastart) *pnastart = numaCreate(w);
534
0
    if (direction == L_HORIZONTAL_RUNS) {
535
0
        for (i = 0; i < h; i++) {
536
0
            pixFindMaxHorizontalRunOnLine(pix, i, &start, &size);
537
0
            numaAddNumber(nasize, size);
538
0
            if (pnastart) numaAddNumber(*pnastart, start);
539
0
        }
540
0
    } else {  /* vertical scans */
541
0
        for (i = 0; i < w; i++) {
542
0
            pixFindMaxVerticalRunOnLine(pix, i, &start, &size);
543
0
            numaAddNumber(nasize, size);
544
0
            if (pnastart) numaAddNumber(*pnastart, start);
545
0
        }
546
0
    }
547
548
0
    return nasize;
549
0
}
550
551
552
/*!
553
 * \brief   pixFindMaxHorizontalRunOnLine()
554
 *
555
 * \param[in]    pix       1 bpp
556
 * \param[in]    y         line to traverse
557
 * \param[out]   pxstart   [optional] start position
558
 * \param[out]   psize     the size of the run
559
 * \return  0 if OK; 1 on error
560
 *
561
 * <pre>
562
 * Notes:
563
 *      (1) This finds the longest foreground horizontal run on a scanline.
564
 *      (2) To find background runs, use pixInvert() before applying
565
 *          this function.
566
 * </pre>
567
 */
568
l_ok
569
pixFindMaxHorizontalRunOnLine(PIX      *pix,
570
                              l_int32   y,
571
                              l_int32  *pxstart,
572
                              l_int32  *psize)
573
0
{
574
0
l_int32    inrun;  /* boolean */
575
0
l_int32    w, h, j, wpl, val, maxstart, maxsize, length, start;
576
0
l_uint32  *line;
577
578
0
    if (pxstart) *pxstart = 0;
579
0
    if (!psize)
580
0
        return ERROR_INT("&size not defined", __func__, 1);
581
0
    *psize = 0;
582
0
    if (!pix || pixGetDepth(pix) != 1)
583
0
        return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
584
0
    pixGetDimensions(pix, &w, &h, NULL);
585
0
    if (y < 0 || y >= h)
586
0
        return ERROR_INT("y not in [0 ... h - 1]", __func__, 1);
587
588
0
    wpl = pixGetWpl(pix);
589
0
    line = pixGetData(pix) + y * wpl;
590
0
    inrun = FALSE;
591
0
    start = 0;
592
0
    maxstart = 0;
593
0
    maxsize = 0;
594
0
    for (j = 0; j < w; j++) {
595
0
        val = GET_DATA_BIT(line, j);
596
0
        if (!inrun) {
597
0
            if (val) {
598
0
                start = j;
599
0
                inrun = TRUE;
600
0
            }
601
0
        } else if (!val) {  /* run just ended */
602
0
            length = j - start;
603
0
            if (length > maxsize) {
604
0
                maxsize = length;
605
0
                maxstart = start;
606
0
            }
607
0
            inrun = FALSE;
608
0
        }
609
0
    }
610
611
0
    if (inrun) {  /* a run has continued to the end of the row */
612
0
        length = j - start;
613
0
        if (length > maxsize) {
614
0
            maxsize = length;
615
0
            maxstart = start;
616
0
        }
617
0
    }
618
0
    if (pxstart) *pxstart = maxstart;
619
0
    *psize = maxsize;
620
0
    return 0;
621
0
}
622
623
624
/*!
625
 * \brief   pixFindMaxVerticalRunOnLine()
626
 *
627
 * \param[in]    pix       1 bpp
628
 * \param[in]    x         column to traverse
629
 * \param[out]   pystart   [optional] start position
630
 * \param[out]   psize     the size of the run
631
 * \return  0 if OK; 1 on error
632
 *
633
 * <pre>
634
 * Notes:
635
 *      (1) This finds the longest foreground vertical run on a scanline.
636
 *      (2) To find background runs, use pixInvert() before applying
637
 *          this function.
638
 * </pre>
639
 */
640
l_ok
641
pixFindMaxVerticalRunOnLine(PIX      *pix,
642
                            l_int32   x,
643
                            l_int32  *pystart,
644
                            l_int32  *psize)
645
0
{
646
0
l_int32    inrun;  /* boolean */
647
0
l_int32    w, h, i, wpl, val, maxstart, maxsize, length, start;
648
0
l_uint32  *data, *line;
649
650
0
    if (pystart) *pystart = 0;
651
0
    if (!psize)
652
0
        return ERROR_INT("&size not defined", __func__, 1);
653
0
    *psize = 0;
654
0
    if (!pix || pixGetDepth(pix) != 1)
655
0
        return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
656
0
    pixGetDimensions(pix, &w, &h, NULL);
657
0
    if (x < 0 || x >= w)
658
0
        return ERROR_INT("x not in [0 ... w - 1]", __func__, 1);
659
660
0
    wpl = pixGetWpl(pix);
661
0
    data = pixGetData(pix);
662
0
    inrun = FALSE;
663
0
    start = 0;
664
0
    maxstart = 0;
665
0
    maxsize = 0;
666
0
    for (i = 0; i < h; i++) {
667
0
        line = data + i * wpl;
668
0
        val = GET_DATA_BIT(line, x);
669
0
        if (!inrun) {
670
0
            if (val) {
671
0
                start = i;
672
0
                inrun = TRUE;
673
0
            }
674
0
        } else if (!val) {  /* run just ended */
675
0
            length = i - start;
676
0
            if (length > maxsize) {
677
0
                maxsize = length;
678
0
                maxstart = start;
679
0
            }
680
0
            inrun = FALSE;
681
0
        }
682
0
    }
683
684
0
    if (inrun) {  /* a run has continued to the end of the column */
685
0
        length = i - start;
686
0
        if (length > maxsize) {
687
0
            maxsize = length;
688
0
            maxstart = start;
689
0
        }
690
0
    }
691
0
    if (pystart) *pystart = maxstart;
692
0
    *psize = maxsize;
693
0
    return 0;
694
0
}
695
696
697
/*-----------------------------------------------------------------------*
698
 *            Compute runlength-to-membership transform on a line        *
699
 *-----------------------------------------------------------------------*/
700
/*!
701
 * \brief   runlengthMembershipOnLine()
702
 *
703
 * \param[in]     buffer   into which full line of data is placed
704
 * \param[in]     size     full size of line; w or h
705
 * \param[in]     depth    8 or 16 bpp
706
 * \param[in]     start    array of start positions for fg runs
707
 * \param[in]     end      array of end positions for fg runs
708
 * \param[in]     n        the number of runs
709
 * \return   0 if OK; 1 on error
710
 *
711
 * <pre>
712
 * Notes:
713
 *      (1) Converts a set of runlengths into a buffer of
714
 *          runlength membership values.
715
 *      (2) Initialization of the array gives pixels that are
716
 *          not within a run the value 0.
717
 * </pre>
718
 */
719
l_ok
720
runlengthMembershipOnLine(l_int32  *buffer,
721
                          l_int32   size,
722
                          l_int32   depth,
723
                          l_int32  *start,
724
                          l_int32  *end,
725
                          l_int32   n)
726
0
{
727
0
l_int32  i, j, first, last, diff, max;
728
729
0
    if (!buffer)
730
0
        return ERROR_INT("buffer not defined", __func__, 1);
731
0
    if (!start)
732
0
        return ERROR_INT("start not defined", __func__, 1);
733
0
    if (!end)
734
0
        return ERROR_INT("end not defined", __func__, 1);
735
736
0
    if (depth == 8)
737
0
        max = 0xff;
738
0
    else  /* depth == 16 */
739
0
        max = 0xffff;
740
741
0
    memset(buffer, 0, 4 * size);
742
0
    for (i = 0; i < n; i++) {
743
0
        first = start[i];
744
0
        last = end[i];
745
0
        diff = last - first + 1;
746
0
        diff = L_MIN(diff, max);
747
0
        for (j = first; j <= last; j++)
748
0
            buffer[j] = diff;
749
0
    }
750
751
0
    return 0;
752
0
}
753
754
755
/*-----------------------------------------------------------------------*
756
 *                       Make byte position LUT                          *
757
 *-----------------------------------------------------------------------*/
758
/*!
759
 * \brief   makeMSBitLocTab()
760
 *
761
 * \param[in]    bitval   either 0 or 1
762
 * \return  table:  for an input byte, the MS bit location, starting at 0
763
 *                  with the MSBit in the byte, or NULL on error.
764
 *
765
 * <pre>
766
 * Notes:
767
 *      (1) If %bitval == 1, it finds the leftmost ON pixel in a byte;
768
 *          otherwise if %bitval == 0, it finds the leftmost OFF pixel.
769
 *      (2) If there are no pixels of the indicated color in the byte,
770
 *          this returns 8.
771
 * </pre>
772
 */
773
l_int32 *
774
makeMSBitLocTab(l_int32  bitval)
775
0
{
776
0
l_int32   i, j;
777
0
l_int32  *tab;
778
0
l_uint8   byte, mask;
779
780
0
    tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
781
0
    for (i = 0; i < 256; i++) {
782
0
        byte = (l_uint8)i;
783
0
        if (bitval == 0)
784
0
            byte = ~byte;
785
0
        tab[i] = 8;
786
0
        mask = 0x80;
787
0
        for (j = 0; j < 8; j++) {
788
0
            if (byte & mask) {
789
0
                tab[i] = j;
790
0
                break;
791
0
            }
792
0
            mask >>= 1;
793
0
        }
794
0
    }
795
0
    return tab;
796
0
}