Coverage Report

Created: 2025-07-23 06:34

/src/leptonica/src/pix5.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 pix5.c
29
 * <pre>
30
 *
31
 *    This file has these operations:
32
 *
33
 *      (1) Measurement of 1 bpp image properties
34
 *      (2) Extract rectangular regions
35
 *      (3) Clip to foreground
36
 *      (4) Extract pixel averages, reversals and variance along lines
37
 *      (5) Rank row and column transforms
38
 *
39
 *    Measurement of properties
40
 *           l_int32     pixaFindDimensions()
41
 *           l_int32     pixFindAreaPerimRatio()
42
 *           NUMA       *pixaFindPerimToAreaRatio()
43
 *           l_int32     pixFindPerimToAreaRatio()
44
 *           NUMA       *pixaFindPerimSizeRatio()
45
 *           l_int32     pixFindPerimSizeRatio()
46
 *           NUMA       *pixaFindAreaFraction()
47
 *           l_int32     pixFindAreaFraction()
48
 *           NUMA       *pixaFindAreaFractionMasked()
49
 *           l_int32     pixFindAreaFractionMasked()
50
 *           NUMA       *pixaFindWidthHeightRatio()
51
 *           NUMA       *pixaFindWidthHeightProduct()
52
 *           l_int32     pixFindOverlapFraction()
53
 *           BOXA       *pixFindRectangleComps()
54
 *           l_int32     pixConformsToRectangle()
55
 *
56
 *    Extract rectangular regions
57
 *           PIX        *pixExtractRectangularRegions()
58
 *           PIXA       *pixClipRectangles()
59
 *           PIX        *pixClipRectangle()
60
 *           PIX        *pixClipRectangleWithBorder()
61
 *           PIX        *pixClipMasked()
62
 *           l_int32     pixCropToMatch()
63
 *           PIX        *pixCropToSize()
64
 *           PIX        *pixResizeToMatch()
65
 *
66
 *    Select a connected component by size
67
 *           PIX        *pixSelectComponentBySize()
68
 *           PIX        *pixFilterComponentBySize()
69
 *
70
 *    Make special masks
71
 *           PIX        *pixMakeSymmetricMask()
72
 *           PIX        *pixMakeFrameMask()
73
 *
74
 *    Generate a covering of rectangles over connected components
75
 *           PIX        * pixMakeCoveringOfRectangles()
76
 *
77
 *    Fraction of Fg pixels under a mask
78
 *           l_int32     pixFractionFgInMask()
79
 *
80
 *    Clip to foreground
81
 *           PIX        *pixClipToForeground()
82
 *           l_int32     pixTestClipToForeground()
83
 *           l_int32     pixClipBoxToForeground()
84
 *           l_int32     pixScanForForeground()
85
 *           l_int32     pixClipBoxToEdges()
86
 *           l_int32     pixScanForEdge()
87
 *
88
 *    Extract pixel averages and reversals along lines
89
 *           NUMA       *pixExtractOnLine()
90
 *           l_float32   pixAverageOnLine()
91
 *           NUMA       *pixAverageIntensityProfile()
92
 *           NUMA       *pixReversalProfile()
93
 *
94
 *    Extract windowed variance along a line
95
 *           NUMA       *pixWindowedVarianceOnLine()
96
 *
97
 *    Extract min/max of pixel values near lines
98
 *           l_int32     pixMinMaxNearLine()
99
 *
100
 *    Rank row and column transforms
101
 *           PIX        *pixRankRowTransform()
102
 *           PIX        *pixRankColumnTransform()
103
 * </pre>
104
 */
105
106
#ifdef HAVE_CONFIG_H
107
#include <config_auto.h>
108
#endif  /* HAVE_CONFIG_H */
109
110
#include <string.h>
111
#include <math.h>
112
#include "allheaders.h"
113
114
static const l_uint32 rmask32[] = {0x0,
115
    0x00000001, 0x00000003, 0x00000007, 0x0000000f,
116
    0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
117
    0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
118
    0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
119
    0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
120
    0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
121
    0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
122
    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
123
124
#ifndef  NO_CONSOLE_IO
125
#define  DEBUG_EDGES         0
126
#endif  /* ~NO_CONSOLE_IO */
127
128
129
/*-------------------------------------------------------------*
130
 *                 Measurement of properties                   *
131
 *-------------------------------------------------------------*/
132
/*!
133
 * \brief   pixaFindDimensions()
134
 *
135
 * \param[in]    pixa
136
 * \param[out]   pnaw [optional] numa of pix widths
137
 * \param[out]   pnah [optional] numa of pix heights
138
 * \return  0 if OK, 1 on error
139
 */
140
l_ok
141
pixaFindDimensions(PIXA   *pixa,
142
                   NUMA  **pnaw,
143
                   NUMA  **pnah)
144
1.60k
{
145
1.60k
l_int32  i, n, w, h;
146
1.60k
PIX     *pixt;
147
148
1.60k
    if (pnaw) *pnaw = NULL;
149
1.60k
    if (pnah) *pnah = NULL;
150
1.60k
    if (!pnaw && !pnah)
151
0
        return ERROR_INT("no output requested", __func__, 1);
152
1.60k
    if (!pixa)
153
0
        return ERROR_INT("pixa not defined", __func__, 1);
154
155
1.60k
    n = pixaGetCount(pixa);
156
1.60k
    if (pnaw) *pnaw = numaCreate(n);
157
1.60k
    if (pnah) *pnah = numaCreate(n);
158
168k
    for (i = 0; i < n; i++) {
159
166k
        pixt = pixaGetPix(pixa, i, L_CLONE);
160
166k
        pixGetDimensions(pixt, &w, &h, NULL);
161
166k
        if (pnaw)
162
166k
            numaAddNumber(*pnaw, w);
163
166k
        if (pnah)
164
166k
            numaAddNumber(*pnah, h);
165
166k
        pixDestroy(&pixt);
166
166k
    }
167
1.60k
    return 0;
168
1.60k
}
169
170
171
/*!
172
 * \brief   pixFindAreaPerimRatio()
173
 *
174
 * \param[in]    pixs    1 bpp
175
 * \param[in]    tab     [optional] pixel sum table, can be NULL
176
 * \param[out]   pfract  area/perimeter ratio
177
 * \return  0 if OK, 1 on error
178
 *
179
 * <pre>
180
 * Notes:
181
 *      (1) The area is the number of fg pixels that are not on the
182
 *          boundary (i.e., are not 8-connected to a bg pixel), and the
183
 *          perimeter is the number of fg boundary pixels.  Returns
184
 *          0.0 if there are no fg pixels.
185
 *      (2) This function is retained because clients are using it.
186
 * </pre>
187
 */
188
l_ok
189
pixFindAreaPerimRatio(PIX        *pixs,
190
                      l_int32    *tab,
191
                      l_float32  *pfract)
192
0
{
193
0
l_int32  *tab8;
194
0
l_int32   nfg, nbound;
195
0
PIX      *pixt;
196
197
0
    if (!pfract)
198
0
        return ERROR_INT("&fract not defined", __func__, 1);
199
0
    *pfract = 0.0;
200
0
    if (!pixs || pixGetDepth(pixs) != 1)
201
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
202
203
0
    if (!tab)
204
0
        tab8 = makePixelSumTab8();
205
0
    else
206
0
        tab8 = tab;
207
208
0
    pixt = pixErodeBrick(NULL, pixs, 3, 3);
209
0
    pixCountPixels(pixt, &nfg, tab8);
210
0
    if (nfg == 0) {
211
0
        pixDestroy(&pixt);
212
0
        if (!tab) LEPT_FREE(tab8);
213
0
        return 0;
214
0
    }
215
0
    pixXor(pixt, pixt, pixs);
216
0
    pixCountPixels(pixt, &nbound, tab8);
217
0
    *pfract = (l_float32)nfg / (l_float32)nbound;
218
0
    pixDestroy(&pixt);
219
220
0
    if (!tab) LEPT_FREE(tab8);
221
0
    return 0;
222
0
}
223
224
225
/*!
226
 * \brief   pixaFindPerimToAreaRatio()
227
 *
228
 * \param[in]    pixa   of 1 bpp pix
229
 * \return  na   of perimeter/arear ratio for each pix, or NULL on error
230
 *
231
 * <pre>
232
 * Notes:
233
 *      (1) This is typically used for a pixa consisting of
234
 *          1 bpp connected components.
235
 * </pre>
236
 */
237
NUMA *
238
pixaFindPerimToAreaRatio(PIXA  *pixa)
239
0
{
240
0
l_int32    i, n;
241
0
l_int32   *tab;
242
0
l_float32  fract;
243
0
NUMA      *na;
244
0
PIX       *pixt;
245
246
0
    if (!pixa)
247
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
248
249
0
    n = pixaGetCount(pixa);
250
0
    na = numaCreate(n);
251
0
    tab = makePixelSumTab8();
252
0
    for (i = 0; i < n; i++) {
253
0
        pixt = pixaGetPix(pixa, i, L_CLONE);
254
0
        pixFindPerimToAreaRatio(pixt, tab, &fract);
255
0
        numaAddNumber(na, fract);
256
0
        pixDestroy(&pixt);
257
0
    }
258
0
    LEPT_FREE(tab);
259
0
    return na;
260
0
}
261
262
263
/*!
264
 * \brief   pixFindPerimToAreaRatio()
265
 *
266
 * \param[in]    pixs    1 bpp
267
 * \param[in]    tab     [optional] pixel sum table, can be NULL
268
 * \param[out]   pfract  perimeter/area ratio
269
 * \return  0 if OK, 1 on error
270
 *
271
 * <pre>
272
 * Notes:
273
 *      (1) The perimeter is the number of fg boundary pixels, and the
274
 *          area is the number of fg pixels.  This returns 0.0 if
275
 *          there are no fg pixels.
276
 *      (2) Unlike pixFindAreaPerimRatio(), this uses the full set of
277
 *          fg pixels for the area, and the ratio is taken in the opposite
278
 *          order.
279
 *      (3) This is typically used for a single connected component.
280
 *          This always has a value <= 1.0, and if the average distance
281
 *          of a fg pixel from the nearest bg pixel is d, this has
282
 *          a value ~1/d.
283
 * </pre>
284
 */
285
l_ok
286
pixFindPerimToAreaRatio(PIX        *pixs,
287
                        l_int32    *tab,
288
                        l_float32  *pfract)
289
0
{
290
0
l_int32  *tab8;
291
0
l_int32   nfg, nbound;
292
0
PIX      *pixt;
293
294
0
    if (!pfract)
295
0
        return ERROR_INT("&fract not defined", __func__, 1);
296
0
    *pfract = 0.0;
297
0
    if (!pixs || pixGetDepth(pixs) != 1)
298
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
299
300
0
    if (!tab)
301
0
        tab8 = makePixelSumTab8();
302
0
    else
303
0
        tab8 = tab;
304
305
0
    pixCountPixels(pixs, &nfg, tab8);
306
0
    if (nfg == 0) {
307
0
        if (!tab) LEPT_FREE(tab8);
308
0
        return 0;
309
0
    }
310
0
    pixt = pixErodeBrick(NULL, pixs, 3, 3);
311
0
    pixXor(pixt, pixt, pixs);
312
0
    pixCountPixels(pixt, &nbound, tab8);
313
0
    *pfract = (l_float32)nbound / (l_float32)nfg;
314
0
    pixDestroy(&pixt);
315
316
0
    if (!tab) LEPT_FREE(tab8);
317
0
    return 0;
318
0
}
319
320
321
/*!
322
 * \brief   pixaFindPerimSizeRatio()
323
 *
324
 * \param[in]    pixa   of 1 bpp pix
325
 * \return  na   of fg perimeter/(2*(w+h)) ratio for each pix,
326
 *                  or NULL on error
327
 *
328
 * <pre>
329
 * Notes:
330
 *      (1) This is typically used for a pixa consisting of
331
 *          1 bpp connected components.
332
 *      (2) This has a minimum value for a circle of pi/4; a value for
333
 *          a rectangle component of approx. 1.0; and a value much larger
334
 *          than 1.0 for a component with a highly irregular boundary.
335
 * </pre>
336
 */
337
NUMA *
338
pixaFindPerimSizeRatio(PIXA  *pixa)
339
0
{
340
0
l_int32    i, n;
341
0
l_int32   *tab;
342
0
l_float32  ratio;
343
0
NUMA      *na;
344
0
PIX       *pixt;
345
346
0
    if (!pixa)
347
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
348
349
0
    n = pixaGetCount(pixa);
350
0
    na = numaCreate(n);
351
0
    tab = makePixelSumTab8();
352
0
    for (i = 0; i < n; i++) {
353
0
        pixt = pixaGetPix(pixa, i, L_CLONE);
354
0
        pixFindPerimSizeRatio(pixt, tab, &ratio);
355
0
        numaAddNumber(na, ratio);
356
0
        pixDestroy(&pixt);
357
0
    }
358
0
    LEPT_FREE(tab);
359
0
    return na;
360
0
}
361
362
363
/*!
364
 * \brief   pixFindPerimSizeRatio()
365
 *
366
 * \param[in]    pixs    1 bpp
367
 * \param[in]    tab     [optional] pixel sum table, can be NULL
368
 * \param[out]   pratio  perimeter/size ratio
369
 * \return  0 if OK, 1 on error
370
 *
371
 * <pre>
372
 * Notes:
373
 *      (1) We take the 'size' as twice the sum of the width and
374
 *          height of pixs, and the perimeter is the number of fg
375
 *          boundary pixels.  We use the fg pixels of the boundary
376
 *          because the pix may be clipped to the boundary, so an
377
 *          erosion is required to count all boundary pixels.
378
 *      (2) This has a large value for dendritic, fractal-like components
379
 *          with highly irregular boundaries.
380
 *      (3) This is typically used for a single connected component.
381
 *          It has a value of about 1.0 for rectangular components with
382
 *          relatively smooth boundaries.
383
 * </pre>
384
 */
385
l_ok
386
pixFindPerimSizeRatio(PIX        *pixs,
387
                      l_int32    *tab,
388
                      l_float32  *pratio)
389
0
{
390
0
l_int32  *tab8;
391
0
l_int32   w, h, nbound;
392
0
PIX      *pixt;
393
394
0
    if (!pratio)
395
0
        return ERROR_INT("&ratio not defined", __func__, 1);
396
0
    *pratio = 0.0;
397
0
    if (!pixs || pixGetDepth(pixs) != 1)
398
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
399
400
0
    if (!tab)
401
0
        tab8 = makePixelSumTab8();
402
0
    else
403
0
        tab8 = tab;
404
405
0
    pixt = pixErodeBrick(NULL, pixs, 3, 3);
406
0
    pixXor(pixt, pixt, pixs);
407
0
    pixCountPixels(pixt, &nbound, tab8);
408
0
    pixGetDimensions(pixs, &w, &h, NULL);
409
0
    *pratio = (0.5f * nbound) / (l_float32)(w + h);
410
0
    pixDestroy(&pixt);
411
412
0
    if (!tab) LEPT_FREE(tab8);
413
0
    return 0;
414
0
}
415
416
417
/*!
418
 * \brief   pixaFindAreaFraction()
419
 *
420
 * \param[in]    pixa   of 1 bpp pix
421
 * \return  na  of area fractions for each pix, or NULL on error
422
 *
423
 * <pre>
424
 * Notes:
425
 *      (1) This is typically used for a pixa consisting of
426
 *          1 bpp connected components.
427
 * </pre>
428
 */
429
NUMA *
430
pixaFindAreaFraction(PIXA  *pixa)
431
1.60k
{
432
1.60k
l_int32    i, n;
433
1.60k
l_int32   *tab;
434
1.60k
l_float32  fract;
435
1.60k
NUMA      *na;
436
1.60k
PIX       *pixt;
437
438
1.60k
    if (!pixa)
439
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
440
441
1.60k
    n = pixaGetCount(pixa);
442
1.60k
    na = numaCreate(n);
443
1.60k
    tab = makePixelSumTab8();
444
168k
    for (i = 0; i < n; i++) {
445
166k
        pixt = pixaGetPix(pixa, i, L_CLONE);
446
166k
        pixFindAreaFraction(pixt, tab, &fract);
447
166k
        numaAddNumber(na, fract);
448
166k
        pixDestroy(&pixt);
449
166k
    }
450
1.60k
    LEPT_FREE(tab);
451
1.60k
    return na;
452
1.60k
}
453
454
455
/*!
456
 * \brief   pixFindAreaFraction()
457
 *
458
 * \param[in]    pixs    1 bpp
459
 * \param[in]    tab     [optional] pixel sum table, can be NULL
460
 * \param[out]   pfract  fg area/size ratio
461
 * \return  0 if OK, 1 on error
462
 *
463
 * <pre>
464
 * Notes:
465
 *      (1) This finds the ratio of the number of fg pixels to the
466
 *          size of the pix (w * h).  It is typically used for a
467
 *          single connected component.
468
 * </pre>
469
 */
470
l_ok
471
pixFindAreaFraction(PIX        *pixs,
472
                    l_int32    *tab,
473
                    l_float32  *pfract)
474
166k
{
475
166k
l_int32   w, h, sum;
476
166k
l_int32  *tab8;
477
478
166k
    if (!pfract)
479
0
        return ERROR_INT("&fract not defined", __func__, 1);
480
166k
    *pfract = 0.0;
481
166k
    if (!pixs || pixGetDepth(pixs) != 1)
482
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
483
484
166k
    if (!tab)
485
0
        tab8 = makePixelSumTab8();
486
166k
    else
487
166k
        tab8 = tab;
488
166k
    pixGetDimensions(pixs, &w, &h, NULL);
489
166k
    pixCountPixels(pixs, &sum, tab8);
490
166k
    *pfract = (l_float32)sum / (l_float32)(w * h);
491
492
166k
    if (!tab) LEPT_FREE(tab8);
493
166k
    return 0;
494
166k
}
495
496
497
/*!
498
 * \brief   pixaFindAreaFractionMasked()
499
 *
500
 * \param[in]    pixa    of 1 bpp pix
501
 * \param[in]    pixm    mask image
502
 * \param[in]    debug   1 for output, 0 to suppress
503
 * \return  na of ratio masked/total fractions for each pix,
504
 *                  or NULL on error
505
 *
506
 * <pre>
507
 * Notes:
508
 *      (1) This is typically used for a pixa consisting of
509
 *          1 bpp connected components, which has an associated
510
 *          boxa giving the location of the components relative
511
 *          to the mask origin.
512
 *      (2) The debug flag displays in green and red the masked and
513
 *          unmasked parts of the image from which pixa was derived.
514
 * </pre>
515
 */
516
NUMA *
517
pixaFindAreaFractionMasked(PIXA    *pixa,
518
                           PIX     *pixm,
519
                           l_int32  debug)
520
0
{
521
0
l_int32    i, n, full;
522
0
l_int32   *tab;
523
0
l_float32  fract;
524
0
BOX       *box;
525
0
NUMA      *na;
526
0
PIX       *pix;
527
528
0
    if (!pixa)
529
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
530
0
    if (!pixm || pixGetDepth(pixm) != 1)
531
0
        return (NUMA *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
532
533
0
    n = pixaGetCount(pixa);
534
0
    na = numaCreate(n);
535
0
    tab = makePixelSumTab8();
536
0
    pixaIsFull(pixa, NULL, &full);  /* check boxa */
537
0
    box = NULL;
538
0
    for (i = 0; i < n; i++) {
539
0
        pix = pixaGetPix(pixa, i, L_CLONE);
540
0
        if (full)
541
0
            box = pixaGetBox(pixa, i, L_CLONE);
542
0
        pixFindAreaFractionMasked(pix, box, pixm, tab, &fract);
543
0
        numaAddNumber(na, fract);
544
0
        boxDestroy(&box);
545
0
        pixDestroy(&pix);
546
0
    }
547
0
    LEPT_FREE(tab);
548
549
0
    if (debug) {
550
0
        l_int32  w, h;
551
0
        PIX     *pix1, *pix2;
552
0
        pixGetDimensions(pixm, &w, &h, NULL);
553
0
        pix1 = pixaDisplay(pixa, w, h);  /* recover original image */
554
0
        pix2 = pixCreate(w, h, 8);  /* make an 8 bpp white image ... */
555
0
        pixSetColormap(pix2, pixcmapCreate(8));  /* that's cmapped ... */
556
0
        pixSetBlackOrWhite(pix2, L_SET_WHITE);  /* and init to white */
557
0
        pixSetMaskedCmap(pix2, pix1, 0, 0, 255, 0, 0);  /* color all fg red */
558
0
        pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, 0, 0);
559
0
        pixSetMaskedCmap(pix2, pix1, 0, 0, 0, 255, 0);  /* turn masked green */
560
0
        pixDisplay(pix2, 100, 100);
561
0
        pixDestroy(&pix1);
562
0
        pixDestroy(&pix2);
563
0
    }
564
565
0
    return na;
566
0
}
567
568
569
/*!
570
 * \brief   pixFindAreaFractionMasked()
571
 *
572
 * \param[in]    pixs    1 bpp, typically a single component
573
 * \param[in]    box     [optional] for pixs relative to pixm
574
 * \param[in]    pixm    1 bpp mask, typically over the entire image from
575
 *                       which the component pixs was extracted
576
 * \param[in]    tab     [optional] pixel sum table, can be NULL
577
 * \param[out]   pfract  fg area/size ratio
578
 * \return  0 if OK, 1 on error
579
 *
580
 * <pre>
581
 * Notes:
582
 *      (1) This finds the ratio of the number of masked fg pixels
583
 *          in pixs to the total number of fg pixels in pixs.
584
 *          It is typically used for a single connected component.
585
 *          If there are no fg pixels, this returns a ratio of 0.0.
586
 *      (2) The box gives the location of the pix relative to that
587
 *          of the UL corner of the mask.  Therefore, the rasterop
588
 *          is performed with the pix translated to its location
589
 *          (x, y) in the mask before ANDing.
590
 *          If box == NULL, the UL corners of pixs and pixm are aligned.
591
 * </pre>
592
 */
593
l_ok
594
pixFindAreaFractionMasked(PIX        *pixs,
595
                          BOX        *box,
596
                          PIX        *pixm,
597
                          l_int32    *tab,
598
                          l_float32  *pfract)
599
0
{
600
0
l_int32   x, y, w, h, sum, masksum;
601
0
l_int32  *tab8;
602
0
PIX      *pix1;
603
604
0
    if (!pfract)
605
0
        return ERROR_INT("&fract not defined", __func__, 1);
606
0
    *pfract = 0.0;
607
0
    if (!pixs || pixGetDepth(pixs) != 1)
608
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
609
0
    if (!pixm || pixGetDepth(pixm) != 1)
610
0
        return ERROR_INT("pixm not defined or not 1 bpp", __func__, 1);
611
612
0
    if (!tab)
613
0
        tab8 = makePixelSumTab8();
614
0
    else
615
0
        tab8 = tab;
616
0
    x = y = 0;
617
0
    if (box)
618
0
        boxGetGeometry(box, &x, &y, NULL, NULL);
619
0
    pixGetDimensions(pixs, &w, &h, NULL);
620
621
0
    pix1 = pixCopy(NULL, pixs);
622
0
    pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, x, y);
623
0
    pixCountPixels(pixs, &sum, tab8);
624
0
    if (sum == 0) {
625
0
        pixDestroy(&pix1);
626
0
        if (!tab) LEPT_FREE(tab8);
627
0
        return 0;
628
0
    }
629
0
    pixCountPixels(pix1, &masksum, tab8);
630
0
    *pfract = (l_float32)masksum / (l_float32)sum;
631
632
0
    if (!tab) LEPT_FREE(tab8);
633
0
    pixDestroy(&pix1);
634
0
    return 0;
635
0
}
636
637
638
/*!
639
 * \brief   pixaFindWidthHeightRatio()
640
 *
641
 * \param[in]    pixa   of 1 bpp pix
642
 * \return  na of width/height ratios for each pix, or NULL on error
643
 *
644
 * <pre>
645
 * Notes:
646
 *      (1) This is typically used for a pixa consisting of
647
 *          1 bpp connected components.
648
 * </pre>
649
 */
650
NUMA *
651
pixaFindWidthHeightRatio(PIXA  *pixa)
652
1.60k
{
653
1.60k
l_int32  i, n, w, h;
654
1.60k
NUMA    *na;
655
1.60k
PIX     *pixt;
656
657
1.60k
    if (!pixa)
658
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
659
660
1.60k
    n = pixaGetCount(pixa);
661
1.60k
    na = numaCreate(n);
662
168k
    for (i = 0; i < n; i++) {
663
166k
        pixt = pixaGetPix(pixa, i, L_CLONE);
664
166k
        pixGetDimensions(pixt, &w, &h, NULL);
665
166k
        numaAddNumber(na, (l_float32)w / (l_float32)h);
666
166k
        pixDestroy(&pixt);
667
166k
    }
668
1.60k
    return na;
669
1.60k
}
670
671
672
/*!
673
 * \brief   pixaFindWidthHeightProduct()
674
 *
675
 * \param[in]    pixa   of 1 bpp pix
676
 * \return  na of width*height products for each pix, or NULL on error
677
 *
678
 * <pre>
679
 * Notes:
680
 *      (1) This is typically used for a pixa consisting of
681
 *          1 bpp connected components.
682
 * </pre>
683
 */
684
NUMA *
685
pixaFindWidthHeightProduct(PIXA  *pixa)
686
0
{
687
0
l_int32  i, n, w, h;
688
0
NUMA    *na;
689
0
PIX     *pixt;
690
691
0
    if (!pixa)
692
0
        return (NUMA *)ERROR_PTR("pixa not defined", __func__, NULL);
693
694
0
    n = pixaGetCount(pixa);
695
0
    na = numaCreate(n);
696
0
    for (i = 0; i < n; i++) {
697
0
        pixt = pixaGetPix(pixa, i, L_CLONE);
698
0
        pixGetDimensions(pixt, &w, &h, NULL);
699
0
        numaAddNumber(na, w * h);
700
0
        pixDestroy(&pixt);
701
0
    }
702
0
    return na;
703
0
}
704
705
706
/*!
707
 * \brief   pixFindOverlapFraction()
708
 *
709
 * \param[in]    pixs1, pixs2   1 bpp
710
 * \param[in]    x2, y2         location in pixs1 of UL corner of pixs2
711
 * \param[in]    tab            [optional] pixel sum table, can be null
712
 * \param[out]   pratio         ratio fg intersection to fg union
713
 * \param[out]   pnoverlap      [optional] number of overlapping pixels
714
 * \return  0 if OK, 1 on error
715
 *
716
 * <pre>
717
 * Notes:
718
 *      (1) The UL corner of pixs2 is placed at (x2, y2) in pixs1.
719
 *      (2) This measure is similar to the correlation.
720
 * </pre>
721
 */
722
l_ok
723
pixFindOverlapFraction(PIX        *pixs1,
724
                       PIX        *pixs2,
725
                       l_int32     x2,
726
                       l_int32     y2,
727
                       l_int32    *tab,
728
                       l_float32  *pratio,
729
                       l_int32    *pnoverlap)
730
0
{
731
0
l_int32  *tab8;
732
0
l_int32   w, h, nintersect, nunion;
733
0
PIX      *pixt;
734
735
0
    if (pnoverlap) *pnoverlap = 0;
736
0
    if (!pratio)
737
0
        return ERROR_INT("&ratio not defined", __func__, 1);
738
0
    *pratio = 0.0;
739
0
    if (!pixs1 || pixGetDepth(pixs1) != 1)
740
0
        return ERROR_INT("pixs1 not defined or not 1 bpp", __func__, 1);
741
0
    if (!pixs2 || pixGetDepth(pixs2) != 1)
742
0
        return ERROR_INT("pixs2 not defined or not 1 bpp", __func__, 1);
743
744
0
    if (!tab)
745
0
        tab8 = makePixelSumTab8();
746
0
    else
747
0
        tab8 = tab;
748
749
0
    pixGetDimensions(pixs2, &w, &h, NULL);
750
0
    pixt = pixCopy(NULL, pixs1);
751
0
    pixRasterop(pixt, x2, y2, w, h, PIX_MASK, pixs2, 0, 0);  /* AND */
752
0
    pixCountPixels(pixt, &nintersect, tab8);
753
0
    if (pnoverlap)
754
0
        *pnoverlap = nintersect;
755
0
    pixCopy(pixt, pixs1);
756
0
    pixRasterop(pixt, x2, y2, w, h, PIX_PAINT, pixs2, 0, 0);  /* OR */
757
0
    pixCountPixels(pixt, &nunion, tab8);
758
0
    if (!tab) LEPT_FREE(tab8);
759
0
    pixDestroy(&pixt);
760
761
0
    if (nunion > 0)
762
0
        *pratio = (l_float32)nintersect / (l_float32)nunion;
763
0
    return 0;
764
0
}
765
766
767
/*!
768
 * \brief   pixFindRectangleComps()
769
 *
770
 * \param[in]    pixs        1 bpp
771
 * \param[in]    dist        max distance allowed between bounding box
772
 *                           and nearest foreground pixel within it
773
 * \param[in]    minw, minh  minimum size in each direction as a requirement
774
 *                           for a conforming rectangle
775
 * \return  boxa of components that conform, or NULL on error
776
 *
777
 * <pre>
778
 * Notes:
779
 *      (1) This applies the function pixConformsToRectangle() to
780
 *          each 8-c.c. in pixs, and returns a boxa containing the
781
 *          regions of all components that are conforming.
782
 *      (2) Conforming components must satisfy both the size constraint
783
 *          given by %minsize and the slop in conforming to a rectangle
784
 *          determined by %dist.
785
 * </pre>
786
 */
787
BOXA *
788
pixFindRectangleComps(PIX     *pixs,
789
                      l_int32  dist,
790
                      l_int32  minw,
791
                      l_int32  minh)
792
0
{
793
0
l_int32  w, h, i, n, conforms;
794
0
BOX     *box;
795
0
BOXA    *boxa, *boxad;
796
0
PIX     *pix;
797
0
PIXA    *pixa;
798
799
0
    if (!pixs || pixGetDepth(pixs) != 1)
800
0
        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
801
0
    if (dist < 0)
802
0
        return (BOXA *)ERROR_PTR("dist must be >= 0", __func__, NULL);
803
0
    if (minw <= 2 * dist && minh <= 2 * dist)
804
0
        return (BOXA *)ERROR_PTR("invalid parameters", __func__, NULL);
805
806
0
    boxa = pixConnComp(pixs, &pixa, 8);
807
0
    boxad = boxaCreate(0);
808
0
    n = pixaGetCount(pixa);
809
0
    for (i = 0; i < n; i++) {
810
0
        pix = pixaGetPix(pixa, i, L_CLONE);
811
0
        pixGetDimensions(pix, &w, &h, NULL);
812
0
        if (w < minw || h < minh) {
813
0
            pixDestroy(&pix);
814
0
            continue;
815
0
        }
816
0
        pixConformsToRectangle(pix, NULL, dist, &conforms);
817
0
        if (conforms) {
818
0
            box = boxaGetBox(boxa, i, L_COPY);
819
0
            boxaAddBox(boxad, box, L_INSERT);
820
0
        }
821
0
        pixDestroy(&pix);
822
0
    }
823
0
    boxaDestroy(&boxa);
824
0
    pixaDestroy(&pixa);
825
0
    return boxad;
826
0
}
827
828
829
/*!
830
 * \brief   pixConformsToRectangle()
831
 *
832
 * \param[in]    pixs       1 bpp
833
 * \param[in]    box        [optional] if null, use the entire pixs
834
 * \param[in]    dist       max distance allowed between bounding box and
835
 *                          nearest foreground pixel within it
836
 * \param[out]   pconforms  0 (false) if not conforming;
837
 *                          1 (true) if conforming
838
 * \return  0 if OK, 1 on error
839
 *
840
 * <pre>
841
 * Notes:
842
 *      (1) There are several ways to test if a connected component has
843
 *          an essentially rectangular boundary, such as:
844
 *           a. Fraction of fill into the bounding box
845
 *           b. Max-min distance of fg pixel from periphery of bounding box
846
 *           c. Max depth of bg intrusions into component within bounding box
847
 *          The weakness of (a) is that it is highly sensitive to holes
848
 *          within the c.c.  The weakness of (b) is that it can have
849
 *          arbitrarily large intrusions into the c.c.  Method (c) tests
850
 *          the integrity of the outer boundary of the c.c., with respect
851
 *          to the enclosing bounding box, so we use it.
852
 *      (2) This tests if the connected component within the box conforms
853
 *          to the box at all points on the periphery within %dist.
854
 *          Inside, at a distance from the box boundary that is greater
855
 *          than %dist, we don't care about the pixels in the c.c.
856
 *      (3) We can think of the conforming condition as follows:
857
 *          No pixel inside a distance %dist from the boundary
858
 *          can connect to the boundary through a path through the bg.
859
 *          To implement this, we need to do a flood fill.  We can go
860
 *          either from inside toward the boundary, or the other direction.
861
 *          It's easiest to fill from the boundary, and then verify that
862
 *          there are no filled pixels farther than %dist from the boundary.
863
 * </pre>
864
 */
865
l_ok
866
pixConformsToRectangle(PIX      *pixs,
867
                       BOX      *box,
868
                       l_int32   dist,
869
                       l_int32  *pconforms)
870
0
{
871
0
l_int32  w, h, empty;
872
0
PIX     *pix1, *pix2;
873
874
0
    if (!pconforms)
875
0
        return ERROR_INT("&conforms not defined", __func__, 1);
876
0
    *pconforms = 0;
877
0
    if (!pixs || pixGetDepth(pixs) != 1)
878
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
879
0
    if (dist < 0)
880
0
        return ERROR_INT("dist must be >= 0", __func__, 1);
881
0
    pixGetDimensions(pixs, &w, &h, NULL);
882
0
    if (w <= 2 * dist || h <= 2 * dist) {
883
0
        L_WARNING("automatic conformation: distance too large\n", __func__);
884
0
        *pconforms = 1;
885
0
        return 0;
886
0
    }
887
888
        /* Extract the region, if necessary */
889
0
    if (box)
890
0
        pix1 = pixClipRectangle(pixs, box, NULL);
891
0
    else
892
0
        pix1 = pixCopy(NULL, pixs);
893
894
        /* Invert and fill from the boundary into the interior.
895
         * Because we're considering the connected component in an
896
         * 8-connected sense, we do the background filling as 4 c.c. */
897
0
    pixInvert(pix1, pix1);
898
0
    pix2 = pixExtractBorderConnComps(pix1, 4);
899
900
        /* Mask out all pixels within a distance %dist from the box
901
         * boundary.  Any remaining pixels are from filling that goes
902
         * more than %dist from the boundary.  If no pixels remain,
903
         * the component conforms to the bounding rectangle within
904
         * a distance %dist. */
905
0
    pixSetOrClearBorder(pix2, dist, dist, dist, dist, PIX_CLR);
906
0
    pixZero(pix2, &empty);
907
0
    pixDestroy(&pix1);
908
0
    pixDestroy(&pix2);
909
0
    *pconforms = (empty) ? 1 : 0;
910
0
    return 0;
911
0
}
912
913
914
/*-----------------------------------------------------------------------*
915
 *                      Extract rectangular regions                      *
916
 *-----------------------------------------------------------------------*/
917
/*!
918
 * \brief   pixExtractRectangularRegions()
919
 *
920
 * \param[in]    pixs
921
 * \param[in]    boxa  regions to extract
922
 * \return  pix  with extracted regions, or NULL on error
923
 *
924
 * <pre>
925
 * Notes:
926
 *     (1) The returned pix has the rectangular regions clipped from
927
 *         the input pixs.
928
 *     (2) We could equally well do this operation using a mask of 1's over
929
 *         the regions determined by the boxa:
930
 *           pix1 = pixCreateTemplate(pixs);
931
 *           pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
932
 *           pixAnd(pix1, pix1, pixs);
933
 * </pre>
934
 */
935
PIX *
936
pixExtractRectangularRegions(PIX   *pixs,
937
                             BOXA  *boxa)
938
0
{
939
0
l_int32  w, h;
940
0
PIX     *pix1;
941
0
PIXA    *pixa1;
942
943
0
    if (!pixs)
944
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
945
0
    if (!boxa)
946
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
947
948
0
    if ((pixa1 = pixClipRectangles(pixs, boxa)) == NULL)
949
0
        return (PIX *)ERROR_PTR("pixa1 not made", __func__, NULL);
950
0
    pixGetDimensions(pixs, &w, &h, NULL);
951
0
    pix1 = pixaDisplay(pixa1, w, h);
952
0
    pixaDestroy(&pixa1);
953
0
    return pix1;
954
0
}
955
956
957
/*!
958
 * \brief   pixClipRectangles()
959
 *
960
 * \param[in]    pixs
961
 * \param[in]    boxa  requested clipping regions
962
 * \return  pixa consisting of requested regions, or NULL on error
963
 *
964
 * <pre>
965
 * Notes:
966
 *     (1) The boxa in the returned pixa has the regions clipped from
967
 *         the input pixs.
968
 * </pre>
969
 */
970
PIXA *
971
pixClipRectangles(PIX   *pixs,
972
                  BOXA  *boxa)
973
5.38k
{
974
5.38k
l_int32  i, n;
975
5.38k
BOX     *box, *boxc;
976
5.38k
PIX     *pix;
977
5.38k
PIXA    *pixa;
978
979
5.38k
    if (!pixs)
980
0
        return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL);
981
5.38k
    if (!boxa)
982
632
        return (PIXA *)ERROR_PTR("boxa not defined", __func__, NULL);
983
984
4.75k
    n = boxaGetCount(boxa);
985
4.75k
    pixa = pixaCreate(n);
986
331k
    for (i = 0; i < n; i++) {
987
327k
        box = boxaGetBox(boxa, i, L_CLONE);
988
327k
        pix = pixClipRectangle(pixs, box, &boxc);
989
327k
        pixaAddPix(pixa, pix, L_INSERT);
990
327k
        pixaAddBox(pixa, boxc, L_INSERT);
991
327k
        boxDestroy(&box);
992
327k
    }
993
994
4.75k
    return pixa;
995
5.38k
}
996
997
998
/*!
999
 * \brief   pixClipRectangle()
1000
 *
1001
 * \param[in]    pixs
1002
 * \param[in]    box    requested clipping region; const
1003
 * \param[out]   pboxc  [optional] actual box of clipped region
1004
 * \return  clipped pix, or NULL on error or if rectangle
1005
 *              doesn't intersect pixs
1006
 *
1007
 * <pre>
1008
 * Notes:
1009
 *
1010
 *  This should be simple, but there are choices to be made.
1011
 *  The box is defined relative to the pix coordinates.  However,
1012
 *  if the box is not contained within the pix, we have two choices:
1013
 *
1014
 *      (1) clip the box to the pix
1015
 *      (2) make a new pix equal to the full box dimensions,
1016
 *          but let rasterop do the clipping and positioning
1017
 *          of the src with respect to the dest
1018
 *
1019
 *  Choice (2) immediately brings up the problem of what pixel values
1020
 *  to use that were not taken from the src.  For example, on a grayscale
1021
 *  image, do you want the pixels not taken from the src to be black
1022
 *  or white or something else?  To implement choice 2, one needs to
1023
 *  specify the color of these extra pixels.
1024
 *
1025
 *  So we adopt (1), and clip the box first, if necessary,
1026
 *  before making the dest pix and doing the rasterop.  But there
1027
 *  is another issue to consider.  If you want to paste the
1028
 *  clipped pix back into pixs, it must be properly aligned, and
1029
 *  it is necessary to use the clipped box for alignment.
1030
 *  Accordingly, this function has a third (optional) argument, which is
1031
 *  the input box clipped to the src pix.
1032
 * </pre>
1033
 */
1034
PIX *
1035
pixClipRectangle(PIX   *pixs,
1036
                 BOX   *box,
1037
                 BOX  **pboxc)
1038
26.2M
{
1039
26.2M
l_int32  w, h, d, bx, by, bw, bh;
1040
26.2M
BOX     *boxc;
1041
26.2M
PIX     *pixd;
1042
1043
26.2M
    if (pboxc) *pboxc = NULL;
1044
26.2M
    if (!pixs)
1045
188
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1046
26.2M
    if (!box)
1047
5.71k
        return (PIX *)ERROR_PTR("box not defined", __func__, NULL);
1048
1049
        /* Clip the input box to the pix */
1050
26.2M
    pixGetDimensions(pixs, &w, &h, &d);
1051
26.2M
    if ((boxc = boxClipToRectangle(box, w, h)) == NULL) {
1052
706
        L_WARNING("box doesn't overlap pix\n", __func__);
1053
706
        return NULL;
1054
706
    }
1055
26.2M
    boxGetGeometry(boxc, &bx, &by, &bw, &bh);
1056
1057
        /* Extract the block */
1058
26.2M
    if ((pixd = pixCreate(bw, bh, d)) == NULL) {
1059
0
        boxDestroy(&boxc);
1060
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1061
0
    }
1062
26.2M
    pixCopyResolution(pixd, pixs);
1063
26.2M
    pixCopyColormap(pixd, pixs);
1064
26.2M
    pixCopyText(pixd, pixs);
1065
26.2M
    pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
1066
1067
26.2M
    if (pboxc)
1068
327k
        *pboxc = boxc;
1069
25.9M
    else
1070
25.9M
        boxDestroy(&boxc);
1071
1072
26.2M
    return pixd;
1073
26.2M
}
1074
1075
1076
/*!
1077
 * \brief   pixClipRectangleWithBorder()
1078
 *
1079
 * \param[in]    pixs
1080
 * \param[in]    box       requested clipping region; const
1081
 * \param[in]    maxbord   maximum amount of border to include
1082
 * \param[out]   pboxn     box in coordinates of returned pix
1083
 * \return  under-clipped pix, or NULL on error or if rectangle
1084
 *              doesn't intersect pixs
1085
 *
1086
 * <pre>
1087
 * Notes:
1088
 *      (1) This underclips by an amount determined by the minimum of
1089
 *          %maxbord and the amount of border that can be included
1090
 *          equally on all 4 sides.
1091
 *      (2) If part of the rectangle lies outside the pix, no border
1092
 *          is included on any side.
1093
 * </pre>
1094
 */
1095
PIX *
1096
pixClipRectangleWithBorder(PIX     *pixs,
1097
                           BOX     *box,
1098
                           l_int32  maxbord,
1099
                           BOX    **pboxn)
1100
0
{
1101
0
l_int32  w, h, bx, by, bw, bh, bord;
1102
0
BOX     *box1;
1103
0
PIX     *pix1;
1104
1105
0
    if (!pboxn)
1106
0
        return (PIX *)ERROR_PTR("&boxn not defined", __func__, NULL);
1107
0
    *pboxn = NULL;
1108
0
    if (!pixs)
1109
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1110
0
    if (!box)
1111
0
        return (PIX *)ERROR_PTR("box not defined", __func__, NULL);
1112
1113
        /* Determine the border width */
1114
0
    pixGetDimensions(pixs, &w, &h, NULL);
1115
0
    boxGetGeometry(box, &bx, &by, &bw, &bh);
1116
0
    bord = L_MIN(bx, by);
1117
0
    bord = L_MIN(bord, w - bx - bw);
1118
0
    bord = L_MIN(bord, h - by - bh);
1119
0
    bord = L_MIN(bord, maxbord);
1120
1121
0
    if (bord <= 0) {  /* standard clipping */
1122
0
        pix1 = pixClipRectangle(pixs, box, NULL);
1123
0
        pixGetDimensions(pix1, &w, &h, NULL);
1124
0
        *pboxn = boxCreate(0, 0, w, h);
1125
0
        return pix1;
1126
0
    }
1127
1128
        /* There is a positive border */
1129
0
    box1 = boxAdjustSides(NULL, box, -bord, bord, -bord, bord);
1130
0
    pix1 = pixClipRectangle(pixs, box1, NULL);
1131
0
    boxDestroy(&box1);
1132
0
    *pboxn = boxCreate(bord, bord, bw, bh);
1133
0
    return pix1;
1134
0
}
1135
1136
1137
/*!
1138
 * \brief   pixClipMasked()
1139
 *
1140
 * \param[in]    pixs    1, 2, 4, 8, 16, 32 bpp; colormap ok
1141
 * \param[in]    pixm    clipping mask, 1 bpp
1142
 * \param[in]    x, y    origin of clipping mask relative to pixs
1143
 * \param[in]    outval  val to use for pixels that are outside the mask
1144
 * \return  pixd, clipped pix or NULL on error or if pixm doesn't
1145
 *              intersect pixs
1146
 *
1147
 * <pre>
1148
 * Notes:
1149
 *      (1) If pixs has a colormap, it is preserved in pixd.
1150
 *      (2) The depth of pixd is the same as that of pixs.
1151
 *      (3) If the depth of pixs is 1, use %outval = 0 for white background
1152
 *          and 1 for black; otherwise, use the max value for white
1153
 *          and 0 for black.  If pixs has a colormap, the max value for
1154
 *          %outval is 0xffffffff; otherwise, it is 2^d - 1.
1155
 *      (4) When using 1 bpp pixs, this is a simple clip and
1156
 *          blend operation.  For example, if both pix1 and pix2 are
1157
 *          black text on white background, and you want to OR the
1158
 *          fg on the two images, let pixm be the inverse of pix2.
1159
 *          Then the operation takes all of pix1 that's in the bg of
1160
 *          pix2, and for the remainder (which are the pixels
1161
 *          corresponding to the fg of the pix2), paint them black
1162
 *          (1) in pix1.  The function call looks like
1163
 *             pixClipMasked(pix2, pixInvert(pix1, pix1), x, y, 1);
1164
 * </pre>
1165
 */
1166
PIX *
1167
pixClipMasked(PIX      *pixs,
1168
              PIX      *pixm,
1169
              l_int32   x,
1170
              l_int32   y,
1171
              l_uint32  outval)
1172
0
{
1173
0
l_int32   wm, hm, index, rval, gval, bval;
1174
0
l_uint32  pixel;
1175
0
BOX      *box;
1176
0
PIX      *pixmi, *pixd;
1177
0
PIXCMAP  *cmap;
1178
1179
0
    if (!pixs)
1180
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1181
0
    if (!pixm || pixGetDepth(pixm) != 1)
1182
0
        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
1183
1184
        /* Clip out the region specified by pixm and (x,y) */
1185
0
    pixGetDimensions(pixm, &wm, &hm, NULL);
1186
0
    box = boxCreate(x, y, wm, hm);
1187
0
    pixd = pixClipRectangle(pixs, box, NULL);
1188
1189
        /* Paint 'outval' (or something close to it if cmapped) through
1190
         * the pixels not masked by pixm */
1191
0
    cmap = pixGetColormap(pixd);
1192
0
    pixmi = pixInvert(NULL, pixm);
1193
0
    if (cmap) {
1194
0
        extractRGBValues(outval, &rval, &gval, &bval);
1195
0
        pixcmapGetNearestIndex(cmap, rval, gval, bval, &index);
1196
0
        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
1197
0
        composeRGBPixel(rval, gval, bval, &pixel);
1198
0
        pixPaintThroughMask(pixd, pixmi, 0, 0, pixel);
1199
0
    } else {
1200
0
        pixPaintThroughMask(pixd, pixmi, 0, 0, outval);
1201
0
    }
1202
1203
0
    boxDestroy(&box);
1204
0
    pixDestroy(&pixmi);
1205
0
    return pixd;
1206
0
}
1207
1208
1209
/*!
1210
 * \brief   pixCropToMatch()
1211
 *
1212
 * \param[in]    pixs1   any depth, colormap OK
1213
 * \param[in]    pixs2   any depth, colormap OK
1214
 * \param[out]   ppixd1  may be a clone
1215
 * \param[out]   ppixd2  may be a clone
1216
 * \return  0 if OK, 1 on error
1217
 *
1218
 * <pre>
1219
 * Notes:
1220
 *      (1) This resizes pixs1 and/or pixs2 by cropping at the right
1221
 *          and bottom, so that they're the same size.
1222
 *      (2) If a pix doesn't need to be cropped, a clone is returned.
1223
 *      (3) Note: the images are implicitly aligned to the UL corner.
1224
 * </pre>
1225
 */
1226
l_ok
1227
pixCropToMatch(PIX   *pixs1,
1228
               PIX   *pixs2,
1229
               PIX  **ppixd1,
1230
               PIX  **ppixd2)
1231
0
{
1232
0
l_int32  w1, h1, w2, h2, w, h;
1233
1234
0
    if (!ppixd1 || !ppixd2)
1235
0
        return ERROR_INT("&pixd1 and &pixd2 not both defined", __func__, 1);
1236
0
    *ppixd1 = *ppixd2 = NULL;
1237
0
    if (!pixs1 || !pixs2)
1238
0
        return ERROR_INT("pixs1 and pixs2 not defined", __func__, 1);
1239
1240
0
    pixGetDimensions(pixs1, &w1, &h1, NULL);
1241
0
    pixGetDimensions(pixs2, &w2, &h2, NULL);
1242
0
    w = L_MIN(w1, w2);
1243
0
    h = L_MIN(h1, h2);
1244
1245
0
    *ppixd1 = pixCropToSize(pixs1, w, h);
1246
0
    *ppixd2 = pixCropToSize(pixs2, w, h);
1247
0
    if (*ppixd1 == NULL || *ppixd2 == NULL)
1248
0
        return ERROR_INT("cropped image failure", __func__, 1);
1249
0
    return 0;
1250
0
}
1251
1252
1253
/*!
1254
 * \brief   pixCropToSize()
1255
 *
1256
 * \param[in]    pixs   any depth, colormap OK
1257
 * \param[in]    w, h   max dimensions of cropped image
1258
 * \return  pixd cropped if necessary or NULL on error.
1259
 *
1260
 * <pre>
1261
 * Notes:
1262
 *      (1) If either w or h is smaller than the corresponding dimension
1263
 *          of pixs, this returns a cropped image; otherwise it returns
1264
 *          a clone of pixs.
1265
 * </pre>
1266
 */
1267
PIX *
1268
pixCropToSize(PIX     *pixs,
1269
              l_int32  w,
1270
              l_int32  h)
1271
0
{
1272
0
l_int32  ws, hs, wd, hd, d;
1273
0
PIX     *pixd;
1274
1275
0
    if (!pixs)
1276
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1277
1278
0
    pixGetDimensions(pixs, &ws, &hs, &d);
1279
0
    if (ws <= w && hs <= h)  /* no cropping necessary */
1280
0
        return pixClone(pixs);
1281
1282
0
    wd = L_MIN(ws, w);
1283
0
    hd = L_MIN(hs, h);
1284
0
    if ((pixd = pixCreate(wd, hd, d)) == NULL)
1285
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1286
0
    pixCopyResolution(pixd, pixs);
1287
0
    pixCopyColormap(pixd, pixs);
1288
0
    pixCopyText(pixd, pixs);
1289
0
    pixCopyInputFormat(pixd, pixs);
1290
0
    pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixs, 0, 0);
1291
0
    return pixd;
1292
0
}
1293
1294
1295
/*!
1296
 * \brief   pixResizeToMatch()
1297
 *
1298
 * \param[in]    pixs   1, 2, 4, 8, 16, 32 bpp; colormap ok
1299
 * \param[in]    pixt   can be null; we use only the size
1300
 * \param[in]    w, h   ignored if pixt is defined
1301
 * \return  pixd resized to match or NULL on error
1302
 *
1303
 * <pre>
1304
 * Notes:
1305
 *      (1) This resizes pixs to make pixd, without scaling, by either
1306
 *          cropping or extending separately in both width and height.
1307
 *          Extension is done by replicating the last row or column.
1308
 *          This is useful in a situation where, due to scaling
1309
 *          operations, two images that are expected to be the
1310
 *          same size can differ slightly in each dimension.
1311
 *      (2) You can use either an existing pixt or specify
1312
 *          both %w and %h.  If pixt is defined, the values
1313
 *          in %w and %h are ignored.
1314
 *      (3) If pixt is larger than pixs (or if w and/or d is larger
1315
 *          than the dimension of pixs, replicate the outer row and
1316
 *          column of pixels in pixs into pixd.
1317
 * </pre>
1318
 */
1319
PIX *
1320
pixResizeToMatch(PIX     *pixs,
1321
                 PIX     *pixt,
1322
                 l_int32  w,
1323
                 l_int32  h)
1324
0
{
1325
0
l_int32  i, j, ws, hs, d;
1326
0
PIX     *pixd;
1327
1328
0
    if (!pixs)
1329
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1330
0
    if (!pixt && (w <= 0 || h <= 0))
1331
0
        return (PIX *)ERROR_PTR("both w and h not > 0", __func__, NULL);
1332
1333
0
    if (pixt)  /* redefine w, h */
1334
0
        pixGetDimensions(pixt, &w, &h, NULL);
1335
0
    pixGetDimensions(pixs, &ws, &hs, &d);
1336
0
    if (ws == w && hs == h)
1337
0
        return pixCopy(NULL, pixs);
1338
1339
0
    if ((pixd = pixCreate(w, h, d)) == NULL)
1340
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1341
0
    pixCopyResolution(pixd, pixs);
1342
0
    pixCopyColormap(pixd, pixs);
1343
0
    pixCopyText(pixd, pixs);
1344
0
    pixCopyInputFormat(pixd, pixs);
1345
0
    pixRasterop(pixd, 0, 0, ws, hs, PIX_SRC, pixs, 0, 0);
1346
0
    if (ws >= w && hs >= h)
1347
0
        return pixd;
1348
1349
        /* Replicate the last column and then the last row */
1350
0
    if (ws < w) {
1351
0
        for (j = ws; j < w; j++)
1352
0
            pixRasterop(pixd, j, 0, 1, h, PIX_SRC, pixd, ws - 1, 0);
1353
0
    }
1354
0
    if (hs < h) {
1355
0
        for (i = hs; i < h; i++)
1356
0
            pixRasterop(pixd, 0, i, w, 1, PIX_SRC, pixd, 0, hs - 1);
1357
0
    }
1358
1359
0
    return pixd;
1360
0
}
1361
1362
1363
/*---------------------------------------------------------------------*
1364
 *                Select a connected component by size                 *
1365
 *---------------------------------------------------------------------*/
1366
/*!
1367
 * \brief   pixSelectComponentBySize()
1368
 *
1369
 * \param[in]    pixs          1 bpp
1370
 * \param[in]    rankorder     in decreasing size: 0 for largest.
1371
 * \param[in]    type          L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
1372
 *                             L_SELECT_BY_MAX_DIMENSION,
1373
 *                             L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
1374
 * \param[in]    connectivity  4 or 8
1375
 * \param[out]   pbox          [optional] location of returned component
1376
 * \return  pix of rank order connected component, or NULL on error.
1377
 *
1378
 * <pre>
1379
 * Notes:
1380
 *      (1) This selects the Nth largest connected component, based on
1381
 *          the selection type and connectivity.
1382
 *      (2) Note that %rankorder is an integer.  Use %rankorder = 0 for
1383
 *          the largest component and %rankorder = -1 for the smallest.
1384
 *          If %rankorder >= number of components, select the smallest.
1385
 */
1386
PIX *
1387
pixSelectComponentBySize(PIX     *pixs,
1388
                         l_int32  rankorder,
1389
                         l_int32  type,
1390
                         l_int32  connectivity,
1391
                         BOX    **pbox)
1392
0
{
1393
0
l_int32  n, empty, sorttype, index;
1394
0
BOXA    *boxa1;
1395
0
NUMA    *naindex;
1396
0
PIX     *pixd;
1397
0
PIXA    *pixa1, *pixa2;
1398
1399
0
    if (pbox) *pbox = NULL;
1400
0
    if (!pixs || pixGetDepth(pixs) != 1)
1401
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1402
0
    if (type == L_SELECT_BY_WIDTH)
1403
0
        sorttype = L_SORT_BY_WIDTH;
1404
0
    else if (type == L_SELECT_BY_HEIGHT)
1405
0
        sorttype = L_SORT_BY_HEIGHT;
1406
0
    else if (type == L_SELECT_BY_MAX_DIMENSION)
1407
0
        sorttype = L_SORT_BY_MAX_DIMENSION;
1408
0
    else if (type == L_SELECT_BY_AREA)
1409
0
        sorttype = L_SORT_BY_AREA;
1410
0
    else if (type == L_SELECT_BY_PERIMETER)
1411
0
        sorttype = L_SORT_BY_PERIMETER;
1412
0
    else
1413
0
        return (PIX *)ERROR_PTR("invalid selection type", __func__, NULL);
1414
0
    if (connectivity != 4 && connectivity != 8)
1415
0
        return (PIX *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL);
1416
0
    pixZero(pixs, &empty);
1417
0
    if (empty)
1418
0
        return (PIX *)ERROR_PTR("no foreground pixels", __func__, NULL);
1419
1420
0
    boxa1 = pixConnComp(pixs, &pixa1, connectivity);
1421
0
    n = boxaGetCount(boxa1);
1422
0
    if (rankorder < 0 || rankorder >= n)
1423
0
        rankorder = n - 1;  /* smallest */
1424
0
    pixa2 = pixaSort(pixa1, sorttype, L_SORT_DECREASING, &naindex, L_CLONE);
1425
0
    pixd = pixaGetPix(pixa2, rankorder, L_COPY);
1426
0
    if (pbox) {
1427
0
        numaGetIValue(naindex, rankorder, &index);
1428
0
        *pbox = boxaGetBox(boxa1, index, L_COPY);
1429
0
    }
1430
1431
0
    numaDestroy(&naindex);
1432
0
    boxaDestroy(&boxa1);
1433
0
    pixaDestroy(&pixa1);
1434
0
    pixaDestroy(&pixa2);
1435
0
    return pixd;
1436
0
}
1437
1438
1439
/*!
1440
 * \brief   pixFilterComponentBySize()
1441
 *
1442
 * \param[in]    pixs          1 bpp
1443
 * \param[in]    rankorder     in decreasing size: 0 for largest.
1444
 * \param[in]    type          L_SELECT_BY_WIDTH, L_SELECT_BY_HEIGHT,
1445
 *                             L_SELECT_BY_MAX_DIMENSION,
1446
 *                             L_SELECT_BY_AREA, L_SELECT_BY_PERIMETER
1447
 * \param[in]    connectivity  4 or 8
1448
 * \param[out]   pbox          [optional] location of returned component
1449
 * \return  pix with all other components removed, or NULL on error.
1450
 *
1451
 * <pre>
1452
 * Notes:
1453
 *      (1) See notes in pixSelectComponentBySize().
1454
 *      (2) This returns a copy of %pixs, with all components removed
1455
 *          except for the selected one.
1456
 */
1457
PIX *
1458
pixFilterComponentBySize(PIX     *pixs,
1459
                         l_int32  rankorder,
1460
                         l_int32  type,
1461
                         l_int32  connectivity,
1462
                         BOX    **pbox)
1463
0
{
1464
0
l_int32  x, y, w, h;
1465
0
BOX     *box;
1466
0
PIX     *pix1, *pix2;
1467
1468
0
    if (!pixs || pixGetDepth(pixs) != 1)
1469
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1470
1471
0
    pix1 = pixSelectComponentBySize(pixs, rankorder, type, connectivity, &box);
1472
0
    if (!pix1) {
1473
0
        boxDestroy(&box);
1474
0
        return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1475
0
    }
1476
1477
        /* Put the selected component in a new pix at the same
1478
         * location as it had in %pixs */
1479
0
    boxGetGeometry(box, &x, &y, &w, &h);
1480
0
    pix2 = pixCreateTemplate(pixs);
1481
0
    pixRasterop(pix2, x, y, w, h, PIX_SRC, pix1, 0, 0);
1482
0
    if (pbox)
1483
0
        *pbox = box;
1484
0
    else
1485
0
        boxDestroy(&box);
1486
0
    pixDestroy(&pix1);
1487
0
    return pix2;
1488
0
}
1489
1490
1491
/*---------------------------------------------------------------------*
1492
 *                         Make special masks                          *
1493
 *---------------------------------------------------------------------*/
1494
/*!
1495
 * \brief   pixMakeSymmetricMask()
1496
 *
1497
 * \param[in]    w, h    dimensions of output 1 bpp pix
1498
 * \param[in]    hf      horizontal fraction of half-width
1499
 * \param[in]    vf      vertical fraction of half-height
1500
 * \param[in]    type    L_USE_INNER, L_USE_OUTER
1501
 * \return  pixd 1 bpp, or NULL on error.
1502
 *
1503
 * <pre>
1504
 * Notes:
1505
 *      (1) This is a convenience function for generating masks with
1506
 *          horizontal and vertical reflection symmetry, over either
1507
 *          the inner or outer parts of an image.
1508
 *      (2) Using L_USE_INNER to generate a mask over the inner part
1509
 *          of the image, the mask is a solid rectangle, and the fractions
1510
 *          describe the distance between the boundary of the image and
1511
 *          the rectangle boundary.  For example, with hf == vf == 0.0,
1512
 *          the mask covers the full image.
1513
 *      (3) Using L_USE_OUTER to generate a mask over an outer frame
1514
 *          of the image, the mask touches the boundary of the image,
1515
 *          and the fractions describe the location of the inner
1516
 *          boundary of the frame.  For example, with hf == vf == 1.0,
1517
 *          the inner boundary is at the center of the image, so the
1518
 *          mask covers the full image.
1519
 *      (4) More examples:
1520
 *           * mask covering the inner 70%: hf = vf = 0.3, type = L_USE_INNER
1521
 *           * frame covering the outer 30%: hf = vf = 0.3, type = L_USE_OUTER
1522
 * </pre>
1523
 */
1524
PIX *
1525
pixMakeSymmetricMask(l_int32    w,
1526
                     l_int32    h,
1527
                     l_float32  hf,
1528
                     l_float32  vf,
1529
                     l_int32    type)
1530
765
{
1531
765
    if (w <= 0 || h <= 0)
1532
0
        return (PIX *)ERROR_PTR("mask size 0", __func__, NULL);
1533
765
    if (hf < 0.0 || hf > 1.0)
1534
0
        return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL);
1535
765
    if (vf < 0.0 || vf > 1.0)
1536
0
        return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL);
1537
1538
765
    if (type == L_USE_INNER)
1539
765
        return pixMakeFrameMask(w, h, hf, 1.0, vf, 1.0);
1540
0
    else if (type == L_USE_OUTER)
1541
0
        return pixMakeFrameMask(w, h, 0.0, hf, 0.0, vf);
1542
0
    else
1543
0
        return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
1544
765
}
1545
1546
1547
/*!
1548
 * \brief   pixMakeFrameMask()
1549
 *
1550
 * \param[in]    w, h  dimensions of output 1 bpp pix
1551
 * \param[in]    hf1   horizontal fraction of half-width at outer frame bdry
1552
 * \param[in]    hf2   horizontal fraction of half-width at inner frame bdry
1553
 * \param[in]    vf1   vertical fraction of half-width at outer frame bdry
1554
 * \param[in]    vf2   vertical fraction of half-width at inner frame bdry
1555
 * \return  pixd 1 bpp, or NULL on error.
1556
 *
1557
 * <pre>
1558
 * Notes:
1559
 *      (1) This makes an arbitrary 1-component mask with a centered fg
1560
 *          frame, which can have both an inner and an outer boundary.
1561
 *          All input fractional distances are measured from the image
1562
 *          border to the frame boundary, in units of the image half-width
1563
 *          for hf1 and hf2 and the image half-height for vf1 and vf2.
1564
 *          The distances to the outer frame boundary are given by hf1
1565
 *          and vf1; to the inner frame boundary, by hf2 and vf2.
1566
 *          Input fractions are thus in [0.0 ... 1.0], with hf1 <= hf2
1567
 *          and vf1 <= vf2.  Horizontal and vertical frame widths are
1568
 *          thus independently specified.
1569
 *      (2) Special cases:
1570
 *           * full fg mask: hf1 = vf1 = 0.0, hf2 = vf2 = 1.0.
1571
 *           * empty fg (zero width) mask: set  hf1 = hf2  and vf1 = vf2.
1572
 *           * fg rectangle with no hole: set hf2 = vf2 = 1.0.
1573
 *           * frame touching outer boundary: set hf1 = vf1 = 0.0.
1574
 *      (3) The vertical thickness of the horizontal mask parts
1575
 *          is 0.5 * (vf2 - vf1) * h.  The horizontal thickness of the
1576
 *          vertical mask parts is 0.5 * (hf2 - hf1) * w.
1577
 * </pre>
1578
 */
1579
PIX *
1580
pixMakeFrameMask(l_int32    w,
1581
                 l_int32    h,
1582
                 l_float32  hf1,
1583
                 l_float32  hf2,
1584
                 l_float32  vf1,
1585
                 l_float32  vf2)
1586
765
{
1587
765
l_int32  h1, h2, v1, v2;
1588
765
PIX     *pixd;
1589
1590
765
    if (w <= 0 || h <= 0)
1591
0
        return (PIX *)ERROR_PTR("mask size 0", __func__, NULL);
1592
765
    if (hf1 < 0.0 || hf1 > 1.0 || hf2 < 0.0 || hf2 > 1.0)
1593
0
        return (PIX *)ERROR_PTR("invalid horiz fractions", __func__, NULL);
1594
765
    if (vf1 < 0.0 || vf1 > 1.0 || vf2 < 0.0 || vf2 > 1.0)
1595
0
        return (PIX *)ERROR_PTR("invalid vert fractions", __func__, NULL);
1596
765
    if (hf1 > hf2 || vf1 > vf2)
1597
0
        return (PIX *)ERROR_PTR("invalid relative sizes", __func__, NULL);
1598
1599
765
    pixd = pixCreate(w, h, 1);
1600
1601
        /* Special cases */
1602
765
    if (hf1 == 0.0 && vf1 == 0.0 && hf2 == 1.0 && vf2 == 1.0) {  /* full */
1603
0
        pixSetAll(pixd);
1604
0
        return pixd;
1605
0
    }
1606
765
    if (hf1 == hf2 && vf1 == vf2) {  /* empty */
1607
0
        return pixd;
1608
0
    }
1609
1610
        /* General case */
1611
765
    h1 = 0.5f * hf1 * w;
1612
765
    h2 = 0.5f * hf2 * w;
1613
765
    v1 = 0.5f * vf1 * h;
1614
765
    v2 = 0.5f * vf2 * h;
1615
765
    pixRasterop(pixd, h1, v1, w - 2 * h1, h - 2 * v1, PIX_SET, NULL, 0, 0);
1616
765
    if (hf2 < 1.0 && vf2 < 1.0)
1617
0
        pixRasterop(pixd, h2, v2, w - 2 * h2, h - 2 * v2, PIX_CLR, NULL, 0, 0);
1618
765
    return pixd;
1619
765
}
1620
1621
1622
/*---------------------------------------------------------------------*
1623
 *     Generate a covering of rectangles over connected components     *
1624
 *---------------------------------------------------------------------*/
1625
/*!
1626
 * \brief   pixMakeCoveringOfRectangles()
1627
 *
1628
 * \param[in]   pixs       1 bpp
1629
 * \param[in]   maxiters   max iterations: use 0 to iterate to completion
1630
 * \return  pixd, or NULL on error
1631
 *
1632
 * <pre>
1633
 * Notes:
1634
 *      (1) This iteratively finds the bounding boxes of the connected
1635
 *          components and generates a mask from them.  Two iterations
1636
 *          should suffice for most situations.
1637
 *      (2) Returns an empty pix if %pixs is empty.
1638
 *      (3) If there are many small components in proximity, it may
1639
 *          be useful to merge them with a morphological closing before
1640
 *          calling this one.
1641
 * </pre>
1642
 */
1643
PIX *
1644
pixMakeCoveringOfRectangles(PIX     *pixs,
1645
                            l_int32  maxiters)
1646
0
{
1647
0
l_int32  empty, same, niters;
1648
0
BOXA    *boxa;
1649
0
PIX     *pix1, *pix2;
1650
1651
0
    if (!pixs || pixGetDepth(pixs) != 1)
1652
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1653
0
    if (maxiters < 0)
1654
0
        return (PIX *)ERROR_PTR("maxiters must be >= 0", __func__, NULL);
1655
0
    if (maxiters == 0) maxiters = 50;  /* ridiculously large number */
1656
1657
0
    pixZero(pixs, &empty);
1658
0
    pix1 = pixCreateTemplate(pixs);
1659
0
    if (empty) return pix1;
1660
1661
        /* Do first iteration */
1662
0
    boxa = pixConnCompBB(pixs, 8);
1663
0
    pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
1664
0
    boxaDestroy(&boxa);
1665
0
    if (maxiters == 1) return pix1;
1666
1667
0
    niters = 1;
1668
0
    while (niters < maxiters) {  /* continue to add pixels to pix1 */
1669
0
        niters++;
1670
0
        boxa = pixConnCompBB(pix1, 8);
1671
0
        pix2 = pixCopy(NULL, pix1);
1672
0
        pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS);
1673
0
        boxaDestroy(&boxa);
1674
0
        pixEqual(pix1, pix2, &same);
1675
0
        pixDestroy(&pix2);
1676
0
        if (same) {
1677
0
            L_INFO("%d iterations\n", __func__, niters - 1);
1678
0
            return pix1;
1679
0
        }
1680
0
    }
1681
0
    L_INFO("maxiters = %d reached\n", __func__, niters);
1682
0
    return pix1;
1683
0
}
1684
1685
1686
/*---------------------------------------------------------------------*
1687
 *                 Fraction of Fg pixels under a mask                  *
1688
 *---------------------------------------------------------------------*/
1689
/*!
1690
 * \brief   pixFractionFgInMask()
1691
 *
1692
 * \param[in]    pix1    1 bpp
1693
 * \param[in]    pix2    1 bpp
1694
 * \param[out]   pfract  fraction of fg pixels in 1 that are
1695
 *                       aligned with the fg of 2
1696
 * \return  0 if OK, 1 on error.
1697
 *
1698
 * <pre>
1699
 * Notes:
1700
 *      (1) This gives the fraction of fg pixels in pix1 that are in
1701
 *          the intersection (i.e., under the fg) of pix2:
1702
 *          |1 & 2|/|1|, where |...| means the number of fg pixels.
1703
 *          Note that this is different from the situation where
1704
 *          pix1 and pix2 are reversed.
1705
 *      (2) Both pix1 and pix2 are registered to the UL corners.  A warning
1706
 *          is issued if pix1 and pix2 have different sizes.
1707
 *      (3) This can also be used to find the fraction of fg pixels in pix1
1708
 *          that are NOT under the fg of pix2: 1.0 - |1 & 2|/|1|
1709
 *      (4) If pix1 or pix2 are empty, this returns %fract = 0.0.
1710
 *      (5) For example, pix2 could be a frame around the outside of the
1711
 *          image, made from pixMakeFrameMask().
1712
 * </pre>
1713
 */
1714
l_ok
1715
pixFractionFgInMask(PIX        *pix1,
1716
                    PIX        *pix2,
1717
                    l_float32  *pfract)
1718
0
{
1719
0
l_int32  w1, h1, w2, h2, empty, count1, count3;
1720
0
PIX     *pix3;
1721
1722
0
    if (!pfract)
1723
0
        return ERROR_INT("&fract not defined", __func__, 1);
1724
0
    *pfract = 0.0;
1725
0
    if (!pix1 || pixGetDepth(pix1) != 1)
1726
0
        return ERROR_INT("pix1 not defined or not 1 bpp", __func__, 1);
1727
0
    if (!pix2 || pixGetDepth(pix2) != 1)
1728
0
        return ERROR_INT("pix2 not defined or not 1 bpp", __func__, 1);
1729
1730
0
    pixGetDimensions(pix1, &w1, &h1, NULL);
1731
0
    pixGetDimensions(pix2, &w2, &h2, NULL);
1732
0
    if (w1 != w2 || h1 != h2) {
1733
0
        L_INFO("sizes unequal: (w1,w2) = (%d,%d), (h1,h2) = (%d,%d)\n",
1734
0
               __func__, w1, w2, h1, h2);
1735
0
    }
1736
0
    pixZero(pix1, &empty);
1737
0
    if (empty) return 0;
1738
0
    pixZero(pix2, &empty);
1739
0
    if (empty) return 0;
1740
1741
0
    pix3 = pixCopy(NULL, pix1);
1742
0
    pixAnd(pix3, pix3, pix2);
1743
0
    pixCountPixels(pix1, &count1, NULL);  /* |1| */
1744
0
    pixCountPixels(pix3, &count3, NULL);  /* |1 & 2| */
1745
0
    *pfract = (l_float32)count3 / (l_float32)count1;
1746
0
    pixDestroy(&pix3);
1747
0
    return 0;
1748
0
}
1749
1750
1751
/*---------------------------------------------------------------------*
1752
 *                           Clip to Foreground                        *
1753
 *---------------------------------------------------------------------*/
1754
/*!
1755
 * \brief   pixClipToForeground()
1756
 *
1757
 * \param[in]    pixs   1 bpp
1758
 * \param[out]   ppixd  [optional] clipped pix returned
1759
 * \param[out]   pbox   [optional] bounding box
1760
 * \return  0 if OK; 1 on error or if there are no fg pixels
1761
 *
1762
 * <pre>
1763
 * Notes:
1764
 *      (1) At least one of {&pixd, &box} must be specified.
1765
 *      (2) If there are no fg pixels, the returned ptrs are null.
1766
 * </pre>
1767
 */
1768
l_ok
1769
pixClipToForeground(PIX   *pixs,
1770
                    PIX  **ppixd,
1771
                    BOX  **pbox)
1772
231k
{
1773
231k
l_int32    w, h, wpl, nfullwords, extra, i, j;
1774
231k
l_int32    minx, miny, maxx, maxy;
1775
231k
l_uint32   result, mask;
1776
231k
l_uint32  *data, *line;
1777
231k
BOX       *box;
1778
1779
231k
    if (ppixd) *ppixd = NULL;
1780
231k
    if (pbox) *pbox = NULL;
1781
231k
    if (!ppixd && !pbox)
1782
0
        return ERROR_INT("no output requested", __func__, 1);
1783
231k
    if (!pixs || (pixGetDepth(pixs) != 1))
1784
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1785
1786
231k
    pixGetDimensions(pixs, &w, &h, NULL);
1787
231k
    nfullwords = w / 32;
1788
231k
    extra = w & 31;
1789
231k
    mask = ~rmask32[32 - extra];
1790
231k
    wpl = pixGetWpl(pixs);
1791
231k
    data = pixGetData(pixs);
1792
1793
231k
    result = 0;
1794
2.02M
    for (i = 0, miny = 0; i < h; i++, miny++) {
1795
2.00M
        line = data + i * wpl;
1796
13.6M
        for (j = 0; j < nfullwords; j++)
1797
11.6M
            result |= line[j];
1798
2.00M
        if (extra)
1799
1.97M
            result |= (line[j] & mask);
1800
2.00M
        if (result)
1801
216k
            break;
1802
2.00M
    }
1803
231k
    if (miny == h)  /* no ON pixels */
1804
15.0k
        return 1;
1805
1806
216k
    result = 0;
1807
1.30M
    for (i = h - 1, maxy = h - 1; i >= 0; i--, maxy--) {
1808
1.30M
        line = data + i * wpl;
1809
8.18M
        for (j = 0; j < nfullwords; j++)
1810
6.88M
            result |= line[j];
1811
1.30M
        if (extra)
1812
1.28M
            result |= (line[j] & mask);
1813
1.30M
        if (result)
1814
216k
            break;
1815
1.30M
    }
1816
1817
216k
    minx = 0;
1818
19.1M
    for (j = 0, minx = 0; j < w; j++, minx++) {
1819
1.32G
        for (i = 0; i < h; i++) {
1820
1.30G
            line = data + i * wpl;
1821
1.30G
            if (GET_DATA_BIT(line, j))
1822
216k
                goto minx_found;
1823
1.30G
        }
1824
19.1M
    }
1825
1826
216k
minx_found:
1827
3.44M
    for (j = w - 1, maxx = w - 1; j >= 0; j--, maxx--) {
1828
224M
        for (i = 0; i < h; i++) {
1829
220M
            line = data + i * wpl;
1830
220M
            if (GET_DATA_BIT(line, j))
1831
216k
                goto maxx_found;
1832
220M
        }
1833
3.44M
    }
1834
1835
216k
maxx_found:
1836
216k
    box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1);
1837
1838
216k
    if (ppixd)
1839
214k
        *ppixd = pixClipRectangle(pixs, box, NULL);
1840
216k
    if (pbox)
1841
33.1k
        *pbox = box;
1842
183k
    else
1843
183k
        boxDestroy(&box);
1844
1845
216k
    return 0;
1846
216k
}
1847
1848
1849
/*!
1850
 * \brief   pixTestClipToForeground()
1851
 *
1852
 * \param[in]    pixs      1 bpp
1853
 * \param[out]   pcanclip  1 if fg does not extend to all four edges
1854
 * \return  0 if OK; 1 on error
1855
 *
1856
 * <pre>
1857
 * Notes:
1858
 *      (1) This is a lightweight test to determine if a 1 bpp image
1859
 *          can be further cropped without loss of fg pixels.
1860
 *          If it cannot, canclip is set to 0.
1861
 *      (2) It does not test for the existence of any fg pixels.
1862
 *          If there are no fg pixels, it will return %canclip = 1.
1863
 *          Check the output of the subsequent call to pixClipToForeground().
1864
 * </pre>
1865
 */
1866
l_ok
1867
pixTestClipToForeground(PIX      *pixs,
1868
                        l_int32  *pcanclip)
1869
137k
{
1870
137k
l_int32    i, j, w, h, wpl, found;
1871
137k
l_uint32  *data, *line;
1872
1873
137k
    if (!pcanclip)
1874
0
        return ERROR_INT("&canclip not defined", __func__, 1);
1875
137k
    *pcanclip = 0;
1876
137k
    if (!pixs || (pixGetDepth(pixs) != 1))
1877
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1878
1879
        /* Check top and bottom raster lines */
1880
137k
    pixGetDimensions(pixs, &w, &h, NULL);
1881
137k
    data = pixGetData(pixs);
1882
137k
    wpl = pixGetWpl(pixs);
1883
137k
    found = FALSE;
1884
6.88M
    for (j = 0; found == FALSE && j < w; j++)
1885
6.74M
        found = GET_DATA_BIT(data, j);
1886
137k
    if (!found) {
1887
56.8k
        *pcanclip = 1;
1888
56.8k
        return 0;
1889
56.8k
    }
1890
1891
80.3k
    line = data + (h - 1) * wpl;
1892
80.3k
    found = FALSE;
1893
648k
    for (j = 0; found == FALSE && j < w; j++)
1894
568k
        found = GET_DATA_BIT(data, j);
1895
80.3k
    if (!found) {
1896
0
        *pcanclip = 1;
1897
0
        return 0;
1898
0
    }
1899
1900
        /* Check left and right edges */
1901
80.3k
    found = FALSE;
1902
830k
    for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
1903
749k
        found = GET_DATA_BIT(line, 0);
1904
80.3k
    if (!found) {
1905
3.79k
        *pcanclip = 1;
1906
3.79k
        return 0;
1907
3.79k
    }
1908
1909
76.5k
    found = FALSE;
1910
721k
    for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
1911
645k
        found = GET_DATA_BIT(line, w - 1);
1912
76.5k
    if (!found)
1913
103
        *pcanclip = 1;
1914
1915
76.5k
    return 0;  /* fg pixels found on all edges */
1916
80.3k
}
1917
1918
1919
/*!
1920
 * \brief   pixClipBoxToForeground()
1921
 *
1922
 * \param[in]    pixs   1 bpp
1923
 * \param[in]    boxs   [optional] use full image if null
1924
 * \param[out]   ppixd  [optional] clipped pix returned
1925
 * \param[out]   pboxd  [optional] bounding box
1926
 * \return  0 if OK; 1 on error or if there are no fg pixels
1927
 *
1928
 * <pre>
1929
 * Notes:
1930
 *      (1) At least one of {&pixd, &boxd} must be specified.
1931
 *      (2) If there are no fg pixels, the returned ptrs are null.
1932
 *      (3) Do not use &pixs for the 3rd arg or &boxs for the 4th arg;
1933
 *          this will leak memory.
1934
 * </pre>
1935
 */
1936
l_ok
1937
pixClipBoxToForeground(PIX   *pixs,
1938
                       BOX   *boxs,
1939
                       PIX  **ppixd,
1940
                       BOX  **pboxd)
1941
0
{
1942
0
l_int32  w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
1943
0
BOX     *boxt, *boxd;
1944
1945
0
    if (ppixd) *ppixd = NULL;
1946
0
    if (pboxd) *pboxd = NULL;
1947
0
    if (!ppixd && !pboxd)
1948
0
        return ERROR_INT("no output requested", __func__, 1);
1949
0
    if (!pixs || (pixGetDepth(pixs) != 1))
1950
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1951
1952
0
    if (!boxs)
1953
0
        return pixClipToForeground(pixs, ppixd, pboxd);
1954
1955
0
    pixGetDimensions(pixs, &w, &h, NULL);
1956
0
    boxGetGeometry(boxs, &bx, &by, &bw, &bh);
1957
0
    cbw = L_MIN(bw, w - bx);
1958
0
    cbh = L_MIN(bh, h - by);
1959
0
    if (cbw < 0 || cbh < 0)
1960
0
        return ERROR_INT("box not within image", __func__, 1);
1961
0
    boxt = boxCreate(bx, by, cbw, cbh);
1962
1963
0
    if (pixScanForForeground(pixs, boxt, L_FROM_LEFT, &left)) {
1964
0
        boxDestroy(&boxt);
1965
0
        return 1;
1966
0
    }
1967
0
    pixScanForForeground(pixs, boxt, L_FROM_RIGHT, &right);
1968
0
    pixScanForForeground(pixs, boxt, L_FROM_TOP, &top);
1969
0
    pixScanForForeground(pixs, boxt, L_FROM_BOT, &bottom);
1970
1971
0
    boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
1972
0
    if (ppixd)
1973
0
        *ppixd = pixClipRectangle(pixs, boxd, NULL);
1974
0
    if (pboxd)
1975
0
        *pboxd = boxd;
1976
0
    else
1977
0
        boxDestroy(&boxd);
1978
1979
0
    boxDestroy(&boxt);
1980
0
    return 0;
1981
0
}
1982
1983
1984
/*!
1985
 * \brief   pixScanForForeground()
1986
 *
1987
 * \param[in]    pixs      1 bpp
1988
 * \param[in]    box       [optional] within which the search is conducted
1989
 * \param[in]    scanflag  direction of scan; e.g., L_FROM_LEFT
1990
 * \param[out]   ploc      location in scan direction of first black pixel
1991
 * \return  0 if OK; 1 on error or if no fg pixels are found
1992
 *
1993
 * <pre>
1994
 * Notes:
1995
 *      (1) If there are no fg pixels, the position is set to 0.
1996
 *          Caller must check the return value!
1997
 *      (2) Use %box == NULL to scan from edge of pixs
1998
 * </pre>
1999
 */
2000
l_ok
2001
pixScanForForeground(PIX      *pixs,
2002
                     BOX      *box,
2003
                     l_int32   scanflag,
2004
                     l_int32  *ploc)
2005
0
{
2006
0
l_int32    bx, by, bw, bh, x, xstart, xend, y, ystart, yend, wpl;
2007
0
l_uint32  *data, *line;
2008
0
BOX       *boxt;
2009
2010
0
    if (!ploc)
2011
0
        return ERROR_INT("&loc not defined", __func__, 1);
2012
0
    *ploc = 0;
2013
0
    if (!pixs || (pixGetDepth(pixs) != 1))
2014
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2015
2016
        /* Clip box to pixs if it exists */
2017
0
    pixGetDimensions(pixs, &bw, &bh, NULL);
2018
0
    if (box) {
2019
0
        if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
2020
0
            return ERROR_INT("invalid box", __func__, 1);
2021
0
        boxGetGeometry(boxt, &bx, &by, &bw, &bh);
2022
0
        boxDestroy(&boxt);
2023
0
    } else {
2024
0
        bx = by = 0;
2025
0
    }
2026
0
    xstart = bx;
2027
0
    ystart = by;
2028
0
    xend = bx + bw - 1;
2029
0
    yend = by + bh - 1;
2030
2031
0
    data = pixGetData(pixs);
2032
0
    wpl = pixGetWpl(pixs);
2033
0
    if (scanflag == L_FROM_LEFT) {
2034
0
        for (x = xstart; x <= xend; x++) {
2035
0
            for (y = ystart; y <= yend; y++) {
2036
0
                line = data + y * wpl;
2037
0
                if (GET_DATA_BIT(line, x)) {
2038
0
                    *ploc = x;
2039
0
                    return 0;
2040
0
                }
2041
0
            }
2042
0
        }
2043
0
    } else if (scanflag == L_FROM_RIGHT) {
2044
0
        for (x = xend; x >= xstart; x--) {
2045
0
            for (y = ystart; y <= yend; y++) {
2046
0
                line = data + y * wpl;
2047
0
                if (GET_DATA_BIT(line, x)) {
2048
0
                    *ploc = x;
2049
0
                    return 0;
2050
0
                }
2051
0
            }
2052
0
        }
2053
0
    } else if (scanflag == L_FROM_TOP) {
2054
0
        for (y = ystart; y <= yend; y++) {
2055
0
            line = data + y * wpl;
2056
0
            for (x = xstart; x <= xend; x++) {
2057
0
                if (GET_DATA_BIT(line, x)) {
2058
0
                    *ploc = y;
2059
0
                    return 0;
2060
0
                }
2061
0
            }
2062
0
        }
2063
0
    } else if (scanflag == L_FROM_BOT) {
2064
0
        for (y = yend; y >= ystart; y--) {
2065
0
            line = data + y * wpl;
2066
0
            for (x = xstart; x <= xend; x++) {
2067
0
                if (GET_DATA_BIT(line, x)) {
2068
0
                    *ploc = y;
2069
0
                    return 0;
2070
0
                }
2071
0
            }
2072
0
        }
2073
0
    } else {
2074
0
        return ERROR_INT("invalid scanflag", __func__, 1);
2075
0
    }
2076
2077
0
    return 1;  /* no fg found */
2078
0
}
2079
2080
2081
/*!
2082
 * \brief   pixClipBoxToEdges()
2083
 *
2084
 * \param[in]    pixs        1 bpp
2085
 * \param[in]    boxs        [optional] ; use full image if null
2086
 * \param[in]    lowthresh   threshold to choose clipping location
2087
 * \param[in]    highthresh  threshold required to find an edge
2088
 * \param[in]    maxwidth    max allowed width between low and high thresh locs
2089
 * \param[in]    factor      sampling factor along pixel counting direction
2090
 * \param[out]   ppixd       [optional] clipped pix returned
2091
 * \param[out]   pboxd       [optional] bounding box
2092
 * \return  0 if OK; 1 on error or if a fg edge is not found from
2093
 *              all four sides.
2094
 *
2095
 * <pre>
2096
 * Notes:
2097
 *      (1) At least one of {&pixd, &boxd} must be specified.
2098
 *      (2) If there are no fg pixels, the returned ptrs are null.
2099
 *      (3) This function attempts to locate rectangular "image" regions
2100
 *          of high-density fg pixels, that have well-defined edges
2101
 *          on the four sides.
2102
 *      (4) Edges are searched for on each side, iterating in order
2103
 *          from left, right, top and bottom.  As each new edge is
2104
 *          found, the search box is resized to use that location.
2105
 *          Once an edge is found, it is held.  If no more edges
2106
 *          are found in one iteration, the search fails.
2107
 *      (5) See pixScanForEdge() for usage of the thresholds and %maxwidth.
2108
 *      (6) The thresholds must be at least 1, and the low threshold
2109
 *          cannot be larger than the high threshold.
2110
 *      (7) If the low and high thresholds are both 1, this is equivalent
2111
 *          to pixClipBoxToForeground().
2112
 * </pre>
2113
 */
2114
l_ok
2115
pixClipBoxToEdges(PIX     *pixs,
2116
                  BOX     *boxs,
2117
                  l_int32  lowthresh,
2118
                  l_int32  highthresh,
2119
                  l_int32  maxwidth,
2120
                  l_int32  factor,
2121
                  PIX    **ppixd,
2122
                  BOX    **pboxd)
2123
0
{
2124
0
l_int32  w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
2125
0
l_int32  lfound, rfound, tfound, bfound, change;
2126
0
BOX     *boxt, *boxd;
2127
2128
0
    if (ppixd) *ppixd = NULL;
2129
0
    if (pboxd) *pboxd = NULL;
2130
0
    if (!ppixd && !pboxd)
2131
0
        return ERROR_INT("no output requested", __func__, 1);
2132
0
    if (!pixs || (pixGetDepth(pixs) != 1))
2133
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2134
0
    if (lowthresh < 1 || highthresh < 1 ||
2135
0
        lowthresh > highthresh || maxwidth < 1)
2136
0
        return ERROR_INT("invalid thresholds", __func__, 1);
2137
0
    factor = L_MIN(1, factor);
2138
2139
0
    if (lowthresh == 1 && highthresh == 1)
2140
0
        return pixClipBoxToForeground(pixs, boxs, ppixd, pboxd);
2141
2142
0
    pixGetDimensions(pixs, &w, &h, NULL);
2143
0
    if (boxs) {
2144
0
        boxGetGeometry(boxs, &bx, &by, &bw, &bh);
2145
0
        cbw = L_MIN(bw, w - bx);
2146
0
        cbh = L_MIN(bh, h - by);
2147
0
        if (cbw < 0 || cbh < 0)
2148
0
            return ERROR_INT("box not within image", __func__, 1);
2149
0
        boxt = boxCreate(bx, by, cbw, cbh);
2150
0
    } else {
2151
0
        boxt = boxCreate(0, 0, w, h);
2152
0
    }
2153
2154
0
    lfound = rfound = tfound = bfound = 0;
2155
0
    while (!lfound || !rfound || !tfound || !bfound) {
2156
0
        change = 0;
2157
0
        if (!lfound) {
2158
0
            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2159
0
                                factor, L_FROM_LEFT, &left)) {
2160
0
                lfound = 1;
2161
0
                change = 1;
2162
0
                boxRelocateOneSide(boxt, boxt, left, L_FROM_LEFT);
2163
0
            }
2164
0
        }
2165
0
        if (!rfound) {
2166
0
            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2167
0
                                factor, L_FROM_RIGHT, &right)) {
2168
0
                rfound = 1;
2169
0
                change = 1;
2170
0
                boxRelocateOneSide(boxt, boxt, right, L_FROM_RIGHT);
2171
0
            }
2172
0
        }
2173
0
        if (!tfound) {
2174
0
            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2175
0
                                factor, L_FROM_TOP, &top)) {
2176
0
                tfound = 1;
2177
0
                change = 1;
2178
0
                boxRelocateOneSide(boxt, boxt, top, L_FROM_TOP);
2179
0
            }
2180
0
        }
2181
0
        if (!bfound) {
2182
0
            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
2183
0
                                factor, L_FROM_BOT, &bottom)) {
2184
0
                bfound = 1;
2185
0
                change = 1;
2186
0
                boxRelocateOneSide(boxt, boxt, bottom, L_FROM_BOT);
2187
0
            }
2188
0
        }
2189
2190
#if DEBUG_EDGES
2191
        lept_stderr("iter: %d %d %d %d\n", lfound, rfound, tfound, bfound);
2192
#endif  /* DEBUG_EDGES */
2193
2194
0
        if (change == 0) break;
2195
0
    }
2196
0
    boxDestroy(&boxt);
2197
2198
0
    if (change == 0)
2199
0
        return ERROR_INT("not all edges found", __func__, 1);
2200
2201
0
    boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
2202
0
    if (ppixd)
2203
0
        *ppixd = pixClipRectangle(pixs, boxd, NULL);
2204
0
    if (pboxd)
2205
0
        *pboxd = boxd;
2206
0
    else
2207
0
        boxDestroy(&boxd);
2208
2209
0
    return 0;
2210
0
}
2211
2212
2213
/*!
2214
 * \brief   pixScanForEdge()
2215
 *
2216
 * \param[in]    pixs        1 bpp
2217
 * \param[in]    box         [optional] within which the search is conducted
2218
 * \param[in]    lowthresh   threshold to choose clipping location
2219
 * \param[in]    highthresh  threshold required to find an edge
2220
 * \param[in]    maxwidth    max allowed width between low and high thresh locs
2221
 * \param[in]    factor      sampling factor along pixel counting direction
2222
 * \param[in]    scanflag    direction of scan; e.g., L_FROM_LEFT
2223
 * \param[out]   ploc        location in scan direction of first black pixel
2224
 * \return  0 if OK; 1 on error or if the edge is not found
2225
 *
2226
 * <pre>
2227
 * Notes:
2228
 *      (1) If there are no fg pixels, the position is set to 0.
2229
 *          Caller must check the return value!
2230
 *      (2) Use %box == NULL to scan from edge of pixs
2231
 *      (3) As the scan progresses, the location where the sum of
2232
 *          pixels equals or excees %lowthresh is noted (loc).  The
2233
 *          scan is stopped when the sum of pixels equals or exceeds
2234
 *          %highthresh.  If the scan distance between loc and that
2235
 *          point does not exceed %maxwidth, an edge is found and
2236
 *          its position is taken to be loc.  %maxwidth implicitly
2237
 *          sets a minimum on the required gradient of the edge.
2238
 *      (4) The thresholds must be at least 1, and the low threshold
2239
 *          cannot be larger than the high threshold.
2240
 * </pre>
2241
 */
2242
l_ok
2243
pixScanForEdge(PIX      *pixs,
2244
               BOX      *box,
2245
               l_int32   lowthresh,
2246
               l_int32   highthresh,
2247
               l_int32   maxwidth,
2248
               l_int32   factor,
2249
               l_int32   scanflag,
2250
               l_int32  *ploc)
2251
0
{
2252
0
l_int32    bx, by, bw, bh, foundmin, loc, sum, wpl;
2253
0
l_int32    x, xstart, xend, y, ystart, yend;
2254
0
l_uint32  *data, *line;
2255
0
BOX       *boxt;
2256
2257
0
    if (!ploc)
2258
0
        return ERROR_INT("&ploc not defined", __func__, 1);
2259
0
    *ploc = 0;
2260
0
    if (!pixs || (pixGetDepth(pixs) != 1))
2261
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
2262
0
    if (lowthresh < 1 || highthresh < 1 ||
2263
0
        lowthresh > highthresh || maxwidth < 1)
2264
0
        return ERROR_INT("invalid thresholds", __func__, 1);
2265
0
    factor = L_MIN(1, factor);
2266
2267
        /* Clip box to pixs if it exists */
2268
0
    pixGetDimensions(pixs, &bw, &bh, NULL);
2269
0
    if (box) {
2270
0
        if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
2271
0
            return ERROR_INT("invalid box", __func__, 1);
2272
0
        boxGetGeometry(boxt, &bx, &by, &bw, &bh);
2273
0
        boxDestroy(&boxt);
2274
0
    } else {
2275
0
        bx = by = 0;
2276
0
    }
2277
0
    xstart = bx;
2278
0
    ystart = by;
2279
0
    xend = bx + bw - 1;
2280
0
    yend = by + bh - 1;
2281
2282
0
    data = pixGetData(pixs);
2283
0
    wpl = pixGetWpl(pixs);
2284
0
    foundmin = 0;
2285
0
    if (scanflag == L_FROM_LEFT) {
2286
0
        for (x = xstart; x <= xend; x++) {
2287
0
            sum = 0;
2288
0
            for (y = ystart; y <= yend; y += factor) {
2289
0
                line = data + y * wpl;
2290
0
                if (GET_DATA_BIT(line, x))
2291
0
                    sum++;
2292
0
            }
2293
0
            if (!foundmin && sum < lowthresh)
2294
0
                continue;
2295
0
            if (!foundmin) {  /* save the loc of the beginning of the edge */
2296
0
                foundmin = 1;
2297
0
                loc = x;
2298
0
            }
2299
0
            if (sum >= highthresh) {
2300
#if DEBUG_EDGES
2301
                lept_stderr("Left: x = %d, loc = %d\n", x, loc);
2302
#endif  /* DEBUG_EDGES */
2303
0
                if (x - loc < maxwidth) {
2304
0
                    *ploc = loc;
2305
0
                    return 0;
2306
0
                } else {
2307
0
                  return 1;
2308
0
                }
2309
0
            }
2310
0
        }
2311
0
    } else if (scanflag == L_FROM_RIGHT) {
2312
0
        for (x = xend; x >= xstart; x--) {
2313
0
            sum = 0;
2314
0
            for (y = ystart; y <= yend; y += factor) {
2315
0
                line = data + y * wpl;
2316
0
                if (GET_DATA_BIT(line, x))
2317
0
                    sum++;
2318
0
            }
2319
0
            if (!foundmin && sum < lowthresh)
2320
0
                continue;
2321
0
            if (!foundmin) {
2322
0
                foundmin = 1;
2323
0
                loc = x;
2324
0
            }
2325
0
            if (sum >= highthresh) {
2326
#if DEBUG_EDGES
2327
                lept_stderr("Right: x = %d, loc = %d\n", x, loc);
2328
#endif  /* DEBUG_EDGES */
2329
0
                if (loc - x < maxwidth) {
2330
0
                    *ploc = loc;
2331
0
                    return 0;
2332
0
                } else {
2333
0
                  return 1;
2334
0
                }
2335
0
            }
2336
0
        }
2337
0
    } else if (scanflag == L_FROM_TOP) {
2338
0
        for (y = ystart; y <= yend; y++) {
2339
0
            sum = 0;
2340
0
            line = data + y * wpl;
2341
0
            for (x = xstart; x <= xend; x += factor) {
2342
0
                if (GET_DATA_BIT(line, x))
2343
0
                    sum++;
2344
0
            }
2345
0
            if (!foundmin && sum < lowthresh)
2346
0
                continue;
2347
0
            if (!foundmin) {
2348
0
                foundmin = 1;
2349
0
                loc = y;
2350
0
            }
2351
0
            if (sum >= highthresh) {
2352
#if DEBUG_EDGES
2353
                lept_stderr("Top: y = %d, loc = %d\n", y, loc);
2354
#endif  /* DEBUG_EDGES */
2355
0
                if (y - loc < maxwidth) {
2356
0
                    *ploc = loc;
2357
0
                    return 0;
2358
0
                } else {
2359
0
                  return 1;
2360
0
                }
2361
0
            }
2362
0
        }
2363
0
    } else if (scanflag == L_FROM_BOT) {
2364
0
        for (y = yend; y >= ystart; y--) {
2365
0
            sum = 0;
2366
0
            line = data + y * wpl;
2367
0
            for (x = xstart; x <= xend; x += factor) {
2368
0
                if (GET_DATA_BIT(line, x))
2369
0
                    sum++;
2370
0
            }
2371
0
            if (!foundmin && sum < lowthresh)
2372
0
                continue;
2373
0
            if (!foundmin) {
2374
0
                foundmin = 1;
2375
0
                loc = y;
2376
0
            }
2377
0
            if (sum >= highthresh) {
2378
#if DEBUG_EDGES
2379
                lept_stderr("Bottom: y = %d, loc = %d\n", y, loc);
2380
#endif  /* DEBUG_EDGES */
2381
0
                if (loc - y < maxwidth) {
2382
0
                    *ploc = loc;
2383
0
                    return 0;
2384
0
                } else {
2385
0
                  return 1;
2386
0
                }
2387
0
            }
2388
0
        }
2389
0
    } else {
2390
0
        return ERROR_INT("invalid scanflag", __func__, 1);
2391
0
    }
2392
2393
0
    return 1;  /* edge not found */
2394
0
}
2395
2396
2397
/*---------------------------------------------------------------------*
2398
 *           Extract pixel averages and reversals along lines          *
2399
 *---------------------------------------------------------------------*/
2400
/*!
2401
 * \brief   pixExtractOnLine()
2402
 *
2403
 * \param[in]    pixs     1 bpp or 8 bpp; no colormap
2404
 * \param[in]    x1, y1   one end point for line
2405
 * \param[in]    x2, y2   another end pt for line
2406
 * \param[in]    factor   sampling; >= 1
2407
 * \return  na of pixel values along line, or NULL on error.
2408
 *
2409
 * <pre>
2410
 * Notes:
2411
 *      (1) Input end points are clipped to the pix.
2412
 *      (2) If the line is either horizontal, or closer to horizontal
2413
 *          than to vertical, the points will be extracted from left
2414
 *          to right in the pix.  Likewise, if the line is vertical,
2415
 *          or closer to vertical than to horizontal, the points will
2416
 *          be extracted from top to bottom.
2417
 *      (3) Can be used with numaCountReverals(), for example, to
2418
 *          characterize the intensity smoothness along a line.
2419
 * </pre>
2420
 */
2421
NUMA *
2422
pixExtractOnLine(PIX     *pixs,
2423
                 l_int32  x1,
2424
                 l_int32  y1,
2425
                 l_int32  x2,
2426
                 l_int32  y2,
2427
                 l_int32  factor)
2428
0
{
2429
0
l_int32    i, w, h, d, xmin, ymin, xmax, ymax, npts, direction;
2430
0
l_uint32   val;
2431
0
l_float32  x, y;
2432
0
l_float64  slope;
2433
0
NUMA      *na;
2434
0
PTA       *pta;
2435
2436
0
    if (!pixs)
2437
0
        return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2438
0
    pixGetDimensions(pixs, &w, &h, &d);
2439
0
    if (d != 1 && d != 8)
2440
0
        return (NUMA *)ERROR_PTR("d not 1 or 8 bpp", __func__, NULL);
2441
0
    if (pixGetColormap(pixs))
2442
0
        return (NUMA *)ERROR_PTR("pixs has a colormap", __func__, NULL);
2443
0
    if (factor < 1) {
2444
0
        L_WARNING("factor must be >= 1; setting to 1\n", __func__);
2445
0
        factor = 1;
2446
0
    }
2447
2448
        /* Clip line to the image */
2449
0
    x1 = L_MAX(0, L_MIN(x1, w - 1));
2450
0
    x2 = L_MAX(0, L_MIN(x2, w - 1));
2451
0
    y1 = L_MAX(0, L_MIN(y1, h - 1));
2452
0
    y2 = L_MAX(0, L_MIN(y2, h - 1));
2453
2454
0
    if (x1 == x2 && y1 == y2) {
2455
0
        pixGetPixel(pixs, x1, y1, &val);
2456
0
        na = numaCreate(1);
2457
0
        numaAddNumber(na, val);
2458
0
        return na;
2459
0
    }
2460
2461
0
    if (y1 == y2)
2462
0
        direction = L_HORIZONTAL_LINE;
2463
0
    else if (x1 == x2)
2464
0
        direction = L_VERTICAL_LINE;
2465
0
    else
2466
0
        direction = L_OBLIQUE_LINE;
2467
2468
0
    na = numaCreate(0);
2469
0
    if (direction == L_HORIZONTAL_LINE) {  /* plot against x */
2470
0
        xmin = L_MIN(x1, x2);
2471
0
        xmax = L_MAX(x1, x2);
2472
0
        numaSetParameters(na, xmin, factor);
2473
0
        for (i = xmin; i <= xmax; i += factor) {
2474
0
            pixGetPixel(pixs, i, y1, &val);
2475
0
            numaAddNumber(na, val);
2476
0
        }
2477
0
    } else if (direction == L_VERTICAL_LINE) {  /* plot against y */
2478
0
        ymin = L_MIN(y1, y2);
2479
0
        ymax = L_MAX(y1, y2);
2480
0
        numaSetParameters(na, ymin, factor);
2481
0
        for (i = ymin; i <= ymax; i += factor) {
2482
0
            pixGetPixel(pixs, x1, i, &val);
2483
0
            numaAddNumber(na, val);
2484
0
        }
2485
0
    } else {  /* direction == L_OBLIQUE_LINE */
2486
0
        slope = (l_float64)((y2 - y1) / (x2 - x1));
2487
0
        if (L_ABS(slope) < 1.0) {  /* quasi-horizontal */
2488
0
            xmin = L_MIN(x1, x2);
2489
0
            xmax = L_MAX(x1, x2);
2490
0
            ymin = (xmin == x1) ? y1 : y2;  /* pt that goes with xmin */
2491
0
            ymax = (ymin == y1) ? y2 : y1;  /* pt that goes with xmax */
2492
0
            pta = generatePtaLine(xmin, ymin, xmax, ymax);
2493
0
            numaSetParameters(na, xmin, (l_float32)factor);
2494
0
        } else {  /* quasi-vertical */
2495
0
            ymin = L_MIN(y1, y2);
2496
0
            ymax = L_MAX(y1, y2);
2497
0
            xmin = (ymin == y1) ? x1 : x2;  /* pt that goes with ymin */
2498
0
            xmax = (xmin == x1) ? x2 : x1;  /* pt that goes with ymax */
2499
0
            pta = generatePtaLine(xmin, ymin, xmax, ymax);
2500
0
            numaSetParameters(na, ymin, (l_float32)factor);
2501
0
        }
2502
0
        npts = ptaGetCount(pta);
2503
0
        for (i = 0; i < npts; i += factor) {
2504
0
            ptaGetPt(pta, i, &x, &y);
2505
0
            pixGetPixel(pixs, (l_int32)x, (l_int32)y, &val);
2506
0
            numaAddNumber(na, val);
2507
0
        }
2508
2509
#if 0  /* debugging */
2510
        pixPlotAlongPta(pixs, pta, GPLOT_PNG, NULL);
2511
#endif
2512
2513
0
        ptaDestroy(&pta);
2514
0
    }
2515
2516
0
    return na;
2517
0
}
2518
2519
2520
/*!
2521
 * \brief   pixAverageOnLine()
2522
 *
2523
 * \param[in]    pixs     1 bpp or 8 bpp; no colormap
2524
 * \param[in]    x1, y1   starting pt for line
2525
 * \param[in]    x2, y2   end pt for line
2526
 * \param[in]    factor   sampling; >= 1
2527
 * \return  average of pixel values along line, or NULL on error.
2528
 *
2529
 * <pre>
2530
 * Notes:
2531
 *      (1) The line must be either horizontal or vertical, so either
2532
 *          y1 == y2 (horizontal) or x1 == x2 (vertical).
2533
 *      (2) If horizontal, x1 must be <= x2.
2534
 *          If vertical, y1 must be <= y2.
2535
 *          characterize the intensity smoothness along a line.
2536
 *      (3) Input end points are clipped to the pix.
2537
 * </pre>
2538
 */
2539
l_float32
2540
pixAverageOnLine(PIX     *pixs,
2541
                 l_int32  x1,
2542
                 l_int32  y1,
2543
                 l_int32  x2,
2544
                 l_int32  y2,
2545
                 l_int32  factor)
2546
0
{
2547
0
l_int32    i, j, w, h, d, direction, count, wpl;
2548
0
l_uint32  *data, *line;
2549
0
l_float32  sum;
2550
2551
0
    if (!pixs)
2552
0
        return ERROR_INT("pixs not defined", __func__, 1);
2553
0
    pixGetDimensions(pixs, &w, &h, &d);
2554
0
    if (d != 1 && d != 8)
2555
0
        return ERROR_INT("d not 1 or 8 bpp", __func__, 1);
2556
0
    if (pixGetColormap(pixs))
2557
0
        return ERROR_INT("pixs has a colormap", __func__, 1);
2558
0
    if (x1 > x2 || y1 > y2)
2559
0
        return ERROR_INT("x1 > x2 or y1 > y2", __func__, 1);
2560
2561
0
    if (y1 == y2) {
2562
0
        x1 = L_MAX(0, x1);
2563
0
        x2 = L_MIN(w - 1, x2);
2564
0
        y1 = L_MAX(0, L_MIN(y1, h - 1));
2565
0
        direction = L_HORIZONTAL_LINE;
2566
0
    } else if (x1 == x2) {
2567
0
        y1 = L_MAX(0, y1);
2568
0
        y2 = L_MIN(h - 1, y2);
2569
0
        x1 = L_MAX(0, L_MIN(x1, w - 1));
2570
0
        direction = L_VERTICAL_LINE;
2571
0
    } else {
2572
0
        return ERROR_INT("line neither horiz nor vert", __func__, 1);
2573
0
    }
2574
2575
0
    if (factor < 1) {
2576
0
        L_WARNING("factor must be >= 1; setting to 1\n", __func__);
2577
0
        factor = 1;
2578
0
    }
2579
2580
0
    data = pixGetData(pixs);
2581
0
    wpl = pixGetWpl(pixs);
2582
0
    sum = 0;
2583
0
    count = 0;
2584
0
    if (direction == L_HORIZONTAL_LINE) {
2585
0
        line = data + y1 * wpl;
2586
0
        for (j = x1, count = 0; j <= x2; count++, j += factor) {
2587
0
            if (d == 1)
2588
0
                sum += GET_DATA_BIT(line, j);
2589
0
            else  /* d == 8 */
2590
0
                sum += GET_DATA_BYTE(line, j);
2591
0
        }
2592
0
    } else if (direction == L_VERTICAL_LINE) {
2593
0
        for (i = y1, count = 0; i <= y2; count++, i += factor) {
2594
0
            line = data + i * wpl;
2595
0
            if (d == 1)
2596
0
                sum += GET_DATA_BIT(line, x1);
2597
0
            else  /* d == 8 */
2598
0
                sum += GET_DATA_BYTE(line, x1);
2599
0
        }
2600
0
    }
2601
2602
0
    return sum / (l_float32)count;
2603
0
}
2604
2605
2606
/*!
2607
 * \brief   pixAverageIntensityProfile()
2608
 *
2609
 * \param[in]    pixs      any depth; colormap OK
2610
 * \param[in]    fract     fraction of image width or height to be used
2611
 * \param[in]    dir       averaging direction: L_HORIZONTAL_LINE or
2612
 *                         L_VERTICAL_LINE
2613
 * \param[in]    first,    last span of rows or columns to measure
2614
 * \param[in]    factor1   sampling along fast scan direction; >= 1
2615
 * \param[in]    factor2   sampling along slow scan direction; >= 1
2616
 * \return  na of reversal profile, or NULL on error.
2617
 *
2618
 * <pre>
2619
 * Notes:
2620
 *      (1) If d != 1 bpp, colormaps are removed and the result
2621
 *          is converted to 8 bpp.
2622
 *      (2) If %dir == L_HORIZONTAL_LINE, the intensity is averaged
2623
 *          along each horizontal raster line (sampled by %factor1),
2624
 *          and the profile is the array of these averages in the
2625
 *          vertical direction between %first and %last raster lines,
2626
 *          and sampled by %factor2.
2627
 *      (3) If %dir == L_VERTICAL_LINE, the intensity is averaged
2628
 *          along each vertical line (sampled by %factor1),
2629
 *          and the profile is the array of these averages in the
2630
 *          horizontal direction between %first and %last columns,
2631
 *          and sampled by %factor2.
2632
 *      (4) The averages are measured over the central %fract of the image.
2633
 *          Use %fract == 1.0 to average across the entire width or height.
2634
 * </pre>
2635
 */
2636
NUMA *
2637
pixAverageIntensityProfile(PIX       *pixs,
2638
                           l_float32  fract,
2639
                           l_int32    dir,
2640
                           l_int32    first,
2641
                           l_int32    last,
2642
                           l_int32    factor1,
2643
                           l_int32    factor2)
2644
0
{
2645
0
l_int32    i, j, w, h, d, start, end;
2646
0
l_float32  ave;
2647
0
NUMA      *nad;
2648
0
PIX       *pixr, *pixg;
2649
2650
0
    if (!pixs)
2651
0
        return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2652
0
    if (fract < 0.0 || fract > 1.0)
2653
0
        return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL);
2654
0
    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2655
0
        return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL);
2656
0
    if (first < 0) first = 0;
2657
0
    if (last < first)
2658
0
        return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL);
2659
0
    if (factor1 < 1) {
2660
0
        L_WARNING("factor1 must be >= 1; setting to 1\n", __func__);
2661
0
        factor1 = 1;
2662
0
    }
2663
0
    if (factor2 < 1) {
2664
0
        L_WARNING("factor2 must be >= 1; setting to 1\n", __func__);
2665
0
        factor2 = 1;
2666
0
    }
2667
2668
        /* Use 1 or 8 bpp, without colormap */
2669
0
    if (pixGetColormap(pixs))
2670
0
        pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
2671
0
    else
2672
0
        pixr = pixClone(pixs);
2673
0
    pixGetDimensions(pixr, &w, &h, &d);
2674
0
    if (d == 1)
2675
0
        pixg = pixClone(pixr);
2676
0
    else
2677
0
        pixg = pixConvertTo8(pixr, 0);
2678
2679
0
    nad = numaCreate(0);  /* output: samples in slow scan direction */
2680
0
    numaSetParameters(nad, 0, factor2);
2681
0
    if (dir == L_HORIZONTAL_LINE) {
2682
0
        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
2683
0
        end = w - start;
2684
0
        if (last > h - 1) {
2685
0
            L_WARNING("last > h - 1; clipping\n", __func__);
2686
0
            last = h - 1;
2687
0
        }
2688
0
        for (i = first; i <= last; i += factor2) {
2689
0
            ave = pixAverageOnLine(pixg, start, i, end, i, factor1);
2690
0
            numaAddNumber(nad, ave);
2691
0
        }
2692
0
    } else if (dir == L_VERTICAL_LINE) {
2693
0
        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
2694
0
        end = h - start;
2695
0
        if (last > w - 1) {
2696
0
            L_WARNING("last > w - 1; clipping\n", __func__);
2697
0
            last = w - 1;
2698
0
        }
2699
0
        for (j = first; j <= last; j += factor2) {
2700
0
            ave = pixAverageOnLine(pixg, j, start, j, end, factor1);
2701
0
            numaAddNumber(nad, ave);
2702
0
        }
2703
0
    }
2704
2705
0
    pixDestroy(&pixr);
2706
0
    pixDestroy(&pixg);
2707
0
    return nad;
2708
0
}
2709
2710
2711
/*!
2712
 * \brief   pixReversalProfile()
2713
 *
2714
 * \param[in]    pixs          any depth; colormap OK
2715
 * \param[in]    fract         fraction of image width or height to be used
2716
 * \param[in]    dir           profile direction: L_HORIZONTAL_LINE or
2717
 *                             L_VERTICAL_LINE
2718
 * \param[in]    first, last   span of rows or columns to measure
2719
 * \param[in]    minreversal   minimum change in intensity to trigger a reversal
2720
 * \param[in]    factor1       sampling along raster line (fast scan); >= 1
2721
 * \param[in]    factor2       sampling of raster lines (slow scan); >= 1
2722
 * \return  na of reversal profile, or NULL on error.
2723
 *
2724
 * <pre>
2725
 * Notes:
2726
 *      (1) If d != 1 bpp, colormaps are removed and the result
2727
 *          is converted to 8 bpp.
2728
 *      (2) If %dir == L_HORIZONTAL_LINE, the the reversals are counted
2729
 *          along each horizontal raster line (sampled by %factor1),
2730
 *          and the profile is the array of these sums in the
2731
 *          vertical direction between %first and %last raster lines,
2732
 *          and sampled by %factor2.
2733
 *      (3) If %dir == L_VERTICAL_LINE, the the reversals are counted
2734
 *          along each vertical column (sampled by %factor1),
2735
 *          and the profile is the array of these sums in the
2736
 *          horizontal direction between %first and %last columns,
2737
 *          and sampled by %factor2.
2738
 *      (4) For each row or column, the reversals are summed over the
2739
 *          central %fract of the image.  Use %fract == 1.0 to sum
2740
 *          across the entire width (of row) or height (of column).
2741
 *      (5) %minreversal is the relative change in intensity that is
2742
 *          required to resolve peaks and valleys.  A typical number for
2743
 *          locating text in 8 bpp might be 50.  For 1 bpp, minreversal
2744
 *          must be 1.
2745
 *      (6) The reversal profile is simply the number of reversals
2746
 *          in a row or column, vs the row or column index.
2747
 * </pre>
2748
 */
2749
NUMA *
2750
pixReversalProfile(PIX       *pixs,
2751
                   l_float32  fract,
2752
                   l_int32    dir,
2753
                   l_int32    first,
2754
                   l_int32    last,
2755
                   l_int32    minreversal,
2756
                   l_int32    factor1,
2757
                   l_int32    factor2)
2758
0
{
2759
0
l_int32   i, j, w, h, d, start, end, nr;
2760
0
NUMA     *naline, *nad;
2761
0
PIX      *pixr, *pixg;
2762
2763
0
    if (!pixs)
2764
0
        return (NUMA *)ERROR_PTR("pixs not defined", __func__, NULL);
2765
0
    if (fract < 0.0 || fract > 1.0)
2766
0
        return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", __func__, NULL);
2767
0
    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2768
0
        return (NUMA *)ERROR_PTR("invalid direction", __func__, NULL);
2769
0
    if (first < 0) first = 0;
2770
0
    if (last < first)
2771
0
        return (NUMA *)ERROR_PTR("last must be >= first", __func__, NULL);
2772
0
    if (factor1 < 1) {
2773
0
        L_WARNING("factor1 must be >= 1; setting to 1\n", __func__);
2774
0
        factor1 = 1;
2775
0
    }
2776
0
    if (factor2 < 1) {
2777
0
        L_WARNING("factor2 must be >= 1; setting to 1\n", __func__);
2778
0
        factor2 = 1;
2779
0
    }
2780
2781
        /* Use 1 or 8 bpp, without colormap */
2782
0
    if (pixGetColormap(pixs))
2783
0
        pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
2784
0
    else
2785
0
        pixr = pixClone(pixs);
2786
0
    pixGetDimensions(pixr, &w, &h, &d);
2787
0
    if (d == 1) {
2788
0
        pixg = pixClone(pixr);
2789
0
        minreversal = 1;  /* enforce this */
2790
0
    } else {
2791
0
        pixg = pixConvertTo8(pixr, 0);
2792
0
    }
2793
2794
0
    nad = numaCreate(0);  /* output: samples in slow scan direction */
2795
0
    numaSetParameters(nad, 0, factor2);
2796
0
    if (dir == L_HORIZONTAL_LINE) {
2797
0
        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
2798
0
        end = w - start;
2799
0
        if (last > h - 1) {
2800
0
            L_WARNING("last > h - 1; clipping\n", __func__);
2801
0
            last = h - 1;
2802
0
        }
2803
0
        for (i = first; i <= last; i += factor2) {
2804
0
            naline = pixExtractOnLine(pixg, start, i, end, i, factor1);
2805
0
            numaCountReversals(naline, minreversal, &nr, NULL);
2806
0
            numaAddNumber(nad, nr);
2807
0
            numaDestroy(&naline);
2808
0
        }
2809
0
    } else if (dir == L_VERTICAL_LINE) {
2810
0
        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
2811
0
        end = h - start;
2812
0
        if (last > w - 1) {
2813
0
            L_WARNING("last > w - 1; clipping\n", __func__);
2814
0
            last = w - 1;
2815
0
        }
2816
0
        for (j = first; j <= last; j += factor2) {
2817
0
            naline = pixExtractOnLine(pixg, j, start, j, end, factor1);
2818
0
            numaCountReversals(naline, minreversal, &nr, NULL);
2819
0
            numaAddNumber(nad, nr);
2820
0
            numaDestroy(&naline);
2821
0
        }
2822
0
    }
2823
2824
0
    pixDestroy(&pixr);
2825
0
    pixDestroy(&pixg);
2826
0
    return nad;
2827
0
}
2828
2829
2830
/*---------------------------------------------------------------------*
2831
 *                 Extract windowed variance along a line              *
2832
 *---------------------------------------------------------------------*/
2833
/*!
2834
 * \brief   pixWindowedVarianceOnLine()
2835
 *
2836
 * \param[in]    pixs     8 bpp; no colormap
2837
 * \param[in]    dir      L_HORIZONTAL_LINE or L_VERTICAL_LINE
2838
 * \param[in]    loc      location of the constant coordinate for the line
2839
 * \param[in]    c1, c2   end point coordinates for the line
2840
 * \param[in]    size     window size; must be > 1
2841
 * \param[out]   pnad     windowed square root of variance
2842
 * \return  0 if OK; 1 on error
2843
 *
2844
 * <pre>
2845
 * Notes:
2846
 *      (1) The returned variance array traverses the line starting
2847
 *          from the smallest coordinate, min(c1,c2).
2848
 *      (2) Line end points are clipped to pixs.
2849
 *      (3) The reference point for the variance calculation is the center of
2850
 *          the window.  Therefore, the numa start parameter from
2851
 *          pixExtractOnLine() is incremented by %size/2,
2852
 *          to align the variance values with the pixel coordinate.
2853
 *      (4) The square root of the variance is the RMS deviation from the mean.
2854
 * </pre>
2855
 */
2856
l_ok
2857
pixWindowedVarianceOnLine(PIX     *pixs,
2858
                          l_int32  dir,
2859
                          l_int32  loc,
2860
                          l_int32  c1,
2861
                          l_int32  c2,
2862
                          l_int32  size,
2863
                          NUMA   **pnad)
2864
0
{
2865
0
l_int32     i, j, w, h, cmin, cmax, maxloc, n, x, y;
2866
0
l_uint32    val;
2867
0
l_float32   norm, rootvar;
2868
0
l_float32  *array;
2869
0
l_float64   sum1, sum2, ave, var;
2870
0
NUMA       *na1, *nad;
2871
0
PTA        *pta;
2872
2873
0
    if (!pnad)
2874
0
        return ERROR_INT("&nad not defined", __func__, 1);
2875
0
    *pnad = NULL;
2876
0
    if (!pixs || pixGetDepth(pixs) != 8)
2877
0
        return ERROR_INT("pixs not defined or not 8bpp", __func__, 1);
2878
0
    if (size < 2)
2879
0
        return ERROR_INT("window size must be > 1", __func__, 1);
2880
0
    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
2881
0
        return ERROR_INT("invalid direction", __func__, 1);
2882
0
    pixGetDimensions(pixs, &w, &h, NULL);
2883
0
    maxloc = (dir == L_HORIZONTAL_LINE) ? h - 1 : w - 1;
2884
0
    if (loc < 0 || loc > maxloc)
2885
0
        return ERROR_INT("invalid line position", __func__, 1);
2886
2887
        /* Clip line to the image */
2888
0
    cmin = L_MIN(c1, c2);
2889
0
    cmax = L_MAX(c1, c2);
2890
0
    maxloc = (dir == L_HORIZONTAL_LINE) ? w - 1 : h - 1;
2891
0
    cmin = L_MAX(0, L_MIN(cmin, maxloc));
2892
0
    cmax = L_MAX(0, L_MIN(cmax, maxloc));
2893
0
    n = cmax - cmin + 1;
2894
2895
        /* Generate pta along the line */
2896
0
    pta = ptaCreate(n);
2897
0
    if (dir == L_HORIZONTAL_LINE) {
2898
0
        for (i = cmin; i <= cmax; i++)
2899
0
            ptaAddPt(pta, i, loc);
2900
0
    } else {  /* vertical line */
2901
0
        for (i = cmin; i <= cmax; i++)
2902
0
            ptaAddPt(pta, loc, i);
2903
0
    }
2904
2905
        /* Get numa of pixel values on the line */
2906
0
    na1 = numaCreate(n);
2907
0
    numaSetParameters(na1, cmin, 1);
2908
0
    for (i = 0; i < n; i++) {
2909
0
        ptaGetIPt(pta, i, &x, &y);
2910
0
        pixGetPixel(pixs, x, y, &val);
2911
0
        numaAddNumber(na1, val);
2912
0
    }
2913
0
    array = numaGetFArray(na1, L_NOCOPY);
2914
0
    ptaDestroy(&pta);
2915
2916
        /* Compute root variance on overlapping windows */
2917
0
    nad = numaCreate(n);
2918
0
    *pnad = nad;
2919
0
    numaSetParameters(nad, cmin + size / 2, 1);
2920
0
    norm = 1.0f / (l_float32)size;
2921
0
    for (i = 0; i < n - size; i++) {  /* along the line */
2922
0
        sum1 = sum2 = 0;
2923
0
        for (j = 0; j < size; j++) {  /* over the window */
2924
0
            val = array[i + j];
2925
0
            sum1 += val;
2926
0
            sum2 += (l_float64)(val) * val;
2927
0
        }
2928
0
        ave = norm * sum1;
2929
0
        var = norm * sum2 - ave * ave;
2930
0
        if (var < 0)  /* avoid small negative values from rounding effects */
2931
0
            var = 0.0;
2932
0
        rootvar = (l_float32)sqrt(var);
2933
0
        numaAddNumber(nad, rootvar);
2934
0
    }
2935
2936
0
    numaDestroy(&na1);
2937
0
    return 0;
2938
0
}
2939
2940
2941
/*---------------------------------------------------------------------*
2942
 *              Extract min/max of pixel values near lines             *
2943
 *---------------------------------------------------------------------*/
2944
/*!
2945
 * \brief   pixMinMaxNearLine()
2946
 *
2947
 * \param[in]    pixs        8 bpp; no colormap
2948
 * \param[in]    x1, y1      starting pt for line
2949
 * \param[in]    x2, y2      end pt for line
2950
 * \param[in]    dist        distance to search from line in each direction
2951
 * \param[in]    direction   L_SCAN_NEGATIVE, L_SCAN_POSITIVE, L_SCAN_BOTH
2952
 * \param[out]   pnamin      [optional] minimum values
2953
 * \param[out]   pnamax      [optional] maximum values
2954
 * \param[out]   pminave     [optional] average of minimum values
2955
 * \param[out]   pmaxave     [optional] average of maximum values
2956
 * \return  0 if OK; 1 on error or if there are no sampled points
2957
 *              within the image.
2958
 *
2959
 * <pre>
2960
 * Notes:
2961
 *      (1) If the line is more horizontal than vertical, the values
2962
 *          are computed for [x1, x2], and the pixels are taken
2963
 *          below and/or above the local y-value.  Otherwise, the
2964
 *          values are computed for [y1, y2] and the pixels are taken
2965
 *          to the left and/or right of the local x value.
2966
 *      (2) %direction specifies which side (or both sides) of the
2967
 *          line are scanned for min and max values.
2968
 *      (3) There are two ways to tell if the returned values of min
2969
 *          and max averages are valid: the returned values cannot be
2970
 *          negative and the function must return 0.
2971
 *      (4) All accessed pixels are clipped to the pix.
2972
 * </pre>
2973
 */
2974
l_ok
2975
pixMinMaxNearLine(PIX        *pixs,
2976
                  l_int32     x1,
2977
                  l_int32     y1,
2978
                  l_int32     x2,
2979
                  l_int32     y2,
2980
                  l_int32     dist,
2981
                  l_int32     direction,
2982
                  NUMA      **pnamin,
2983
                  NUMA      **pnamax,
2984
                  l_float32  *pminave,
2985
                  l_float32  *pmaxave)
2986
0
{
2987
0
l_int32    i, j, w, h, d, x, y, n, dir, found, minval, maxval, negloc, posloc;
2988
0
l_uint32   val;
2989
0
l_float32  sum;
2990
0
NUMA      *namin, *namax;
2991
0
PTA       *pta;
2992
2993
0
    if (pnamin) *pnamin = NULL;
2994
0
    if (pnamax) *pnamax = NULL;
2995
0
    if (pminave) *pminave = UNDEF;
2996
0
    if (pmaxave) *pmaxave = UNDEF;
2997
0
    if (!pnamin && !pnamax && !pminave && !pmaxave)
2998
0
        return ERROR_INT("no output requested", __func__, 1);
2999
0
    if (!pixs)
3000
0
        return ERROR_INT("pixs not defined", __func__, 1);
3001
0
    pixGetDimensions(pixs, &w, &h, &d);
3002
0
    if (d != 8 || pixGetColormap(pixs))
3003
0
        return ERROR_INT("pixs not 8 bpp or has colormap", __func__, 1);
3004
0
    dist = L_ABS(dist);
3005
0
    if (direction != L_SCAN_NEGATIVE && direction != L_SCAN_POSITIVE &&
3006
0
        direction != L_SCAN_BOTH)
3007
0
        return ERROR_INT("invalid direction", __func__, 1);
3008
3009
0
    pta = generatePtaLine(x1, y1, x2, y2);
3010
0
    n = ptaGetCount(pta);
3011
0
    dir = (L_ABS(x1 - x2) == n - 1) ? L_HORIZ : L_VERT;
3012
0
    namin = numaCreate(n);
3013
0
    namax = numaCreate(n);
3014
0
    negloc = -dist;
3015
0
    posloc = dist;
3016
0
    if (direction == L_SCAN_NEGATIVE)
3017
0
        posloc = 0;
3018
0
    else if (direction == L_SCAN_POSITIVE)
3019
0
        negloc = 0;
3020
0
    for (i = 0; i < n; i++) {
3021
0
        ptaGetIPt(pta, i, &x, &y);
3022
0
        minval = 255;
3023
0
        maxval = 0;
3024
0
        found = FALSE;
3025
0
        if (dir == L_HORIZ) {
3026
0
            if (x < 0 || x >= w) continue;
3027
0
            for (j = negloc; j <= posloc; j++) {
3028
0
                if (y + j < 0 || y + j >= h) continue;
3029
0
                pixGetPixel(pixs, x, y + j, &val);
3030
0
                found = TRUE;
3031
0
                if (val < minval) minval = val;
3032
0
                if (val > maxval) maxval = val;
3033
0
            }
3034
0
        } else {  /* dir == L_VERT */
3035
0
            if (y < 0 || y >= h) continue;
3036
0
            for (j = negloc; j <= posloc; j++) {
3037
0
                if (x + j < 0 || x + j >= w) continue;
3038
0
                pixGetPixel(pixs, x + j, y, &val);
3039
0
                found = TRUE;
3040
0
                if (val < minval) minval = val;
3041
0
                if (val > maxval) maxval = val;
3042
0
            }
3043
0
        }
3044
0
        if (found) {
3045
0
            numaAddNumber(namin, minval);
3046
0
            numaAddNumber(namax, maxval);
3047
0
        }
3048
0
    }
3049
3050
0
    n = numaGetCount(namin);
3051
0
    if (n == 0) {
3052
0
        numaDestroy(&namin);
3053
0
        numaDestroy(&namax);
3054
0
        ptaDestroy(&pta);
3055
0
        return ERROR_INT("no output from this line", __func__, 1);
3056
0
    }
3057
3058
0
    if (pminave) {
3059
0
        numaGetSum(namin, &sum);
3060
0
        *pminave = sum / n;
3061
0
    }
3062
0
    if (pmaxave) {
3063
0
        numaGetSum(namax, &sum);
3064
0
        *pmaxave = sum / n;
3065
0
    }
3066
0
    if (pnamin)
3067
0
        *pnamin = namin;
3068
0
    else
3069
0
        numaDestroy(&namin);
3070
0
    if (pnamax)
3071
0
        *pnamax = namax;
3072
0
    else
3073
0
        numaDestroy(&namax);
3074
0
    ptaDestroy(&pta);
3075
0
    return 0;
3076
0
}
3077
3078
3079
/*---------------------------------------------------------------------*
3080
 *                     Rank row and column transforms                  *
3081
 *---------------------------------------------------------------------*/
3082
/*!
3083
 * \brief   pixRankRowTransform()
3084
 *
3085
 * \param[in]    pixs   8 bpp; no colormap
3086
 * \return  pixd with pixels sorted in each row, from
3087
 *                    min to max value
3088
 *
3089
 * <pre>
3090
 * Notes:
3091
 *     (1) The time is O(n) in the number of pixels and runs about
3092
 *         100 Mpixels/sec on a 3 GHz machine.
3093
 * </pre>
3094
 */
3095
PIX *
3096
pixRankRowTransform(PIX  *pixs)
3097
0
{
3098
0
l_int32    i, j, k, m, w, h, wpl, val;
3099
0
l_int32    histo[256];
3100
0
l_uint32  *datas, *datad, *lines, *lined;
3101
0
PIX       *pixd;
3102
3103
0
    if (!pixs)
3104
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3105
0
    if (pixGetDepth(pixs) != 8)
3106
0
        return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
3107
0
    if (pixGetColormap(pixs))
3108
0
        return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL);
3109
3110
0
    pixGetDimensions(pixs, &w, &h, NULL);
3111
0
    pixd = pixCreateTemplate(pixs);
3112
0
    datas = pixGetData(pixs);
3113
0
    datad = pixGetData(pixd);
3114
0
    wpl = pixGetWpl(pixs);
3115
0
    for (i = 0; i < h; i++) {
3116
0
        memset(histo, 0, 1024);
3117
0
        lines = datas + i * wpl;
3118
0
        lined = datad + i * wpl;
3119
0
        for (j = 0; j < w; j++) {
3120
0
            val = GET_DATA_BYTE(lines, j);
3121
0
            histo[val]++;
3122
0
        }
3123
0
        for (m = 0, j = 0; m < 256; m++) {
3124
0
            for (k = 0; k < histo[m]; k++, j++)
3125
0
                SET_DATA_BYTE(lined, j, m);
3126
0
        }
3127
0
    }
3128
3129
0
    return pixd;
3130
0
}
3131
3132
3133
/*!
3134
 * \brief   pixRankColumnTransform()
3135
 *
3136
 * \param[in]    pixs   8 bpp; no colormap
3137
 * \return  pixd with pixels sorted in each column, from
3138
 *                    min to max value
3139
 *
3140
 * <pre>
3141
 * Notes:
3142
 *     (1) The time is O(n) in the number of pixels and runs about
3143
 *         50 Mpixels/sec on a 3 GHz machine.
3144
 * </pre>
3145
 */
3146
PIX *
3147
pixRankColumnTransform(PIX  *pixs)
3148
0
{
3149
0
l_int32    i, j, k, m, w, h, val;
3150
0
l_int32    histo[256];
3151
0
void     **lines8, **lined8;
3152
0
PIX       *pixd;
3153
3154
0
    if (!pixs)
3155
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3156
0
    if (pixGetDepth(pixs) != 8)
3157
0
        return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
3158
0
    if (pixGetColormap(pixs))
3159
0
        return (PIX *)ERROR_PTR("pixs has a colormap", __func__, NULL);
3160
3161
0
    pixGetDimensions(pixs, &w, &h, NULL);
3162
0
    pixd = pixCreateTemplate(pixs);
3163
0
    lines8 = pixGetLinePtrs(pixs, NULL);
3164
0
    lined8 = pixGetLinePtrs(pixd, NULL);
3165
0
    for (j = 0; j < w; j++) {
3166
0
        memset(histo, 0, 1024);
3167
0
        for (i = 0; i < h; i++) {
3168
0
            val = GET_DATA_BYTE(lines8[i], j);
3169
0
            histo[val]++;
3170
0
        }
3171
0
        for (m = 0, i = 0; m < 256; m++) {
3172
0
            for (k = 0; k < histo[m]; k++, i++)
3173
0
                SET_DATA_BYTE(lined8[i], j, m);
3174
0
        }
3175
0
    }
3176
3177
0
    LEPT_FREE(lines8);
3178
0
    LEPT_FREE(lined8);
3179
0
    return pixd;
3180
0
}