Coverage Report

Created: 2025-11-16 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/leptonica/src/morph.c
Line
Count
Source
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 morph.c
29
 * <pre>
30
 *
31
 *     Generic binary morphological ops implemented with rasterop
32
 *         PIX     *pixDilate()
33
 *         PIX     *pixErode()
34
 *         PIX     *pixHMT()
35
 *         PIX     *pixOpen()
36
 *         PIX     *pixClose()
37
 *         PIX     *pixCloseSafe()
38
 *         PIX     *pixOpenGeneralized()
39
 *         PIX     *pixCloseGeneralized()
40
 *
41
 *     Binary morphological (raster) ops with brick Sels
42
 *         PIX     *pixDilateBrick()
43
 *         PIX     *pixErodeBrick()
44
 *         PIX     *pixOpenBrick()
45
 *         PIX     *pixCloseBrick()
46
 *         PIX     *pixCloseSafeBrick()
47
 *
48
 *     Binary composed morphological (raster) ops with brick Sels
49
 *         l_int32  selectComposableSels()
50
 *         l_int32  selectComposableSizes()
51
 *         PIX     *pixDilateCompBrick()
52
 *         PIX     *pixErodeCompBrick()
53
 *         PIX     *pixOpenCompBrick()
54
 *         PIX     *pixCloseCompBrick()
55
 *         PIX     *pixCloseSafeCompBrick()
56
 *
57
 *     Functions associated with boundary conditions
58
 *         void     resetMorphBoundaryCondition()
59
 *         l_int32  getMorphBorderPixelColor()
60
 *
61
 *     Static helpers for arg processing
62
 *         static PIX     *processMorphArgs1()
63
 *         static PIX     *processMorphArgs2()
64
 *
65
 *  You are provided with many simple ways to do binary morphology.
66
 *  In particular, if you are using brick Sels, there are six
67
 *  convenient methods, all specially tailored for separable operations
68
 *  on brick Sels.  A "brick" Sel is a Sel that is a rectangle
69
 *  of solid SEL_HITs with the origin at or near the center.
70
 *  Note that a brick Sel can have one dimension of size 1.
71
 *  This is very common.  All the brick Sel operations are
72
 *  separable, meaning the operation is done first in the horizontal
73
 *  direction and then in the vertical direction.  If one of the
74
 *  dimensions is 1, this is a special case where the operation is
75
 *  only performed in the other direction.
76
 *
77
 *  These six brick Sel methods are enumerated as follows:
78
 *
79
 *  (1) Brick Sels: pix*Brick(), where * = {Dilate, Erode, Open, Close}.
80
 *      These are separable rasterop implementations.  The Sels are
81
 *      automatically generated, used, and destroyed at the end.
82
 *      You can get the result as a new Pix, in-place back into the src Pix,
83
 *      or written to another existing Pix.
84
 *
85
 *  (2) Brick Sels: pix*CompBrick(), where * = {Dilate, Erode, Open, Close}.
86
 *      These are separable, 2-way composite, rasterop implementations.
87
 *      The Sels are automatically generated, used, and destroyed at the end.
88
 *      You can get the result as a new Pix, in-place back into the src Pix,
89
 *      or written to another existing Pix.  For large Sels, these are
90
 *      considerably faster than the corresponding pix*Brick() functions.
91
 *      N.B.:  The size of the Sels that are actually used are typically
92
 *      close to, but not exactly equal to, the size input to the function.
93
 *
94
 *  (3) Brick Sels: pix*BrickDwa(), where * = {Dilate, Erode, Open, Close}.
95
 *      These are separable dwa (destination word accumulation)
96
 *      implementations.  They use auto-gen'd dwa code.  You can get
97
 *      the result as a new Pix, in-place back into the src Pix,
98
 *      or written to another existing Pix.  This is typically
99
 *      about 3x faster than the analogous rasterop pix*Brick()
100
 *      function, but it has the limitation that the Sel size must
101
 *      be less than 63.  This is pre-set to work on a number
102
 *      of pre-generated Sels.  If you want to use other Sels, the
103
 *      code can be auto-gen'd for them; see the instructions in morphdwa.c.
104
 *
105
 *  (4) Same as (1), but you run it through pixMorphSequence(), with
106
 *      the sequence string either compiled in or generated using snprintf.
107
 *      All intermediate images and Sels are created, used and destroyed.
108
 *      You always get the result as a new Pix.  For example, you can
109
 *      specify a separable 11 x 17 brick opening as "o11.17",
110
 *      or you can specify the horizontal and vertical operations
111
 *      explicitly as "o11.1 + o1.11".  See morphseq.c for details.
112
 *
113
 *  (5) Same as (2), but you run it through pixMorphCompSequence(), with
114
 *      the sequence string either compiled in or generated using snprintf.
115
 *      All intermediate images and Sels are created, used and destroyed.
116
 *      You always get the result as a new Pix.  See morphseq.c for details.
117
 *
118
 *  (6) Same as (3), but you run it through pixMorphSequenceDwa(), with
119
 *      the sequence string either compiled in or generated using snprintf.
120
 *      All intermediate images and Sels are created, used and destroyed.
121
 *      You always get the result as a new Pix.  See morphseq.c for details.
122
 *
123
 *  If you are using Sels that are not bricks, you have two choices:
124
 *      (a) simplest: use the basic rasterop implementations (pixDilate(), ...)
125
 *      (b) fastest: generate the destination word accumumlation (dwa)
126
 *          code for your Sels and compile it with the library.
127
 *
128
 *      For an example, see flipdetect.c, which gives implementations
129
 *      using hit-miss Sels with both the rasterop and dwa versions.
130
 *      For the latter, the dwa code resides in fliphmtgen.c, and it
131
 *      was generated by prog/flipselgen.c.  Both the rasterop and dwa
132
 *      implementations are tested by prog/fliptest.c.
133
 *
134
 *  A global constant MORPH_BC is used to set the boundary conditions
135
 *  for rasterop-based binary morphology.  MORPH_BC, in morph.c,
136
 *  is set by default to ASYMMETRIC_MORPH_BC for a non-symmetric
137
 *  convention for boundary pixels in dilation and erosion:
138
 *      All pixels outside the image are assumed to be OFF
139
 *      for both dilation and erosion.
140
 *  To use a symmetric definition, see comments in pixErode()
141
 *  and reset MORPH_BC to SYMMETRIC_MORPH_BC, using
142
 *  resetMorphBoundaryCondition().
143
 *
144
 *  Boundary artifacts are possible in closing when the non-symmetric
145
 *  boundary conditions are used, because foreground pixels very close
146
 *  to the edge can be removed.  This can be avoided by using either
147
 *  the symmetric boundary conditions or the function pixCloseSafe(),
148
 *  which adds a border before the operation and removes it afterwards.
149
 *
150
 *  The hit-miss transform (HMT) is the bit-and of 2 erosions:
151
 *     (erosion of the src by the hits)  &  (erosion of the bit-inverted
152
 *                                           src by the misses)
153
 *
154
 *  The 'generalized opening' is an HMT followed by a dilation that uses
155
 *  only the hits of the hit-miss Sel.
156
 *  The 'generalized closing' is a dilation (again, with the hits
157
 *  of a hit-miss Sel), followed by the HMT.
158
 *  Both of these 'generalized' functions are idempotent.
159
 *
160
 *  These functions are extensively tested in prog/binmorph1_reg.c,
161
 *  prog/binmorph2_reg.c, and prog/binmorph3_reg.c.
162
 * </pre>
163
 */
164
165
#ifdef HAVE_CONFIG_H
166
#include <config_auto.h>
167
#endif  /* HAVE_CONFIG_H */
168
169
#include <math.h>
170
#include "allheaders.h"
171
172
    /* Global constant; initialized here; must be declared extern
173
     * in other files to access it directly.  However, in most
174
     * cases that is not necessary, because it can be reset
175
     * using resetMorphBoundaryCondition().  */
176
LEPT_DLL l_int32  MORPH_BC = ASYMMETRIC_MORPH_BC;
177
178
    /* We accept this cost in extra rasterops for decomposing exactly. */
179
static const l_int32  ACCEPTABLE_COST = 5;
180
181
    /* Static helpers for arg processing */
182
static PIX * processMorphArgs1(PIX *pixd, PIX *pixs, SEL *sel, PIX **ppixt);
183
static PIX * processMorphArgs2(PIX *pixd, PIX *pixs, SEL *sel);
184
185
186
/*-----------------------------------------------------------------*
187
 *    Generic binary morphological ops implemented with rasterop   *
188
 *-----------------------------------------------------------------*/
189
/*!
190
 * \brief   pixDilate()
191
 *
192
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
193
 *                       or different from pixs
194
 * \param[in]    pixs    1 bpp
195
 * \param[in]    sel
196
 * \return  pixd
197
 *
198
 * <pre>
199
 * Notes:
200
 *      (1) This dilates src using hits in Sel.
201
 *      (2) There are three cases:
202
 *          (a) pixd == null   (result into new pixd)
203
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
204
 *          (c) pixd != pixs   (puts result into existing pixd)
205
 *      (3) For clarity, if the case is known, use these patterns:
206
 *          (a) pixd = pixDilate(NULL, pixs, ...);
207
 *          (b) pixDilate(pixs, pixs, ...);
208
 *          (c) pixDilate(pixd, pixs, ...);
209
 *      (4) The size of the result is determined by pixs.
210
 * </pre>
211
 */
212
PIX *
213
pixDilate(PIX  *pixd,
214
          PIX  *pixs,
215
          SEL  *sel)
216
12
{
217
12
l_int32  i, j, w, h, sx, sy, cx, cy, seldata;
218
12
PIX     *pixt;
219
220
12
    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
221
0
        return (PIX *)ERROR_PTR("processMorphArgs1 failed", __func__, pixd);
222
223
12
    pixGetDimensions(pixs, &w, &h, NULL);
224
12
    selGetParameters(sel, &sy, &sx, &cy, &cx);
225
12
    pixClearAll(pixd);
226
432
    for (i = 0; i < sy; i++) {
227
840
        for (j = 0; j < sx; j++) {
228
420
            seldata = sel->data[i][j];
229
420
            if (seldata == 1) {   /* src | dst */
230
420
                pixRasterop(pixd, j - cx, i - cy, w, h, PIX_SRC | PIX_DST,
231
420
                            pixt, 0, 0);
232
420
            }
233
420
        }
234
420
    }
235
236
12
    pixDestroy(&pixt);
237
12
    return pixd;
238
12
}
239
240
241
/*!
242
 * \brief   pixErode()
243
 *
244
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
245
 *                       or different from pixs
246
 * \param[in]    pixs    1 bpp
247
 * \param[in]    sel
248
 * \return  pixd
249
 *
250
 * <pre>
251
 * Notes:
252
 *      (1) This erodes src using hits in Sel.
253
 *      (2) There are three cases:
254
 *          (a) pixd == null   (result into new pixd)
255
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
256
 *          (c) pixd != pixs   (puts result into existing pixd)
257
 *      (3) For clarity, if the case is known, use these patterns:
258
 *          (a) pixd = pixErode(NULL, pixs, ...);
259
 *          (b) pixErode(pixs, pixs, ...);
260
 *          (c) pixErode(pixd, pixs, ...);
261
 *      (4) The size of the result is determined by pixs.
262
 * </pre>
263
 */
264
PIX *
265
pixErode(PIX  *pixd,
266
         PIX  *pixs,
267
         SEL  *sel)
268
12
{
269
12
l_int32  i, j, w, h, sx, sy, cx, cy, seldata;
270
12
l_int32  xp, yp, xn, yn;
271
12
PIX     *pixt;
272
273
12
    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
274
0
        return (PIX *)ERROR_PTR("processMorphArgs1 failed", __func__, pixd);
275
276
12
    pixGetDimensions(pixs, &w, &h, NULL);
277
12
    selGetParameters(sel, &sy, &sx, &cy, &cx);
278
12
    pixSetAll(pixd);
279
432
    for (i = 0; i < sy; i++) {
280
840
        for (j = 0; j < sx; j++) {
281
420
            seldata = sel->data[i][j];
282
420
            if (seldata == 1) {   /* src & dst */
283
420
                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
284
420
                                pixt, 0, 0);
285
420
            }
286
420
        }
287
420
    }
288
289
        /* Clear near edges.  We do this for the asymmetric boundary
290
         * condition convention that implements erosion assuming all
291
         * pixels surrounding the image are OFF.  If you use a
292
         * use a symmetric b.c. convention, where the erosion is
293
         * implemented assuming pixels surrounding the image
294
         * are ON, these operations are omitted.  */
295
12
    if (MORPH_BC == ASYMMETRIC_MORPH_BC) {
296
12
        selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
297
12
        if (xp > 0)
298
0
            pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
299
12
        if (xn > 0)
300
0
            pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
301
12
        if (yp > 0)
302
12
            pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
303
12
        if (yn > 0)
304
12
            pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
305
12
    }
306
307
12
    pixDestroy(&pixt);
308
12
    return pixd;
309
12
}
310
311
312
/*!
313
 * \brief   pixHMT()
314
 *
315
 * \param[in]    pixd   [optional]; this can be null, equal to pixs,
316
 *                      or different from pixs
317
 * \param[in]    pixs   1 bpp
318
 * \param[in]    sel
319
 * \return  pixd
320
 *
321
 * <pre>
322
 * Notes:
323
 *      (1) The hit-miss transform erodes the src, using both hits
324
 *          and misses in the Sel.  It ANDs the shifted src for hits
325
 *          and ANDs the inverted shifted src for misses.
326
 *      (2) There are three cases:
327
 *          (a) pixd == null   (result into new pixd)
328
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
329
 *          (c) pixd != pixs   (puts result into existing pixd)
330
 *      (3) For clarity, if the case is known, use these patterns:
331
 *          (a) pixd = pixHMT(NULL, pixs, ...);
332
 *          (b) pixHMT(pixs, pixs, ...);
333
 *          (c) pixHMT(pixd, pixs, ...);
334
 *      (4) The size of the result is determined by pixs.
335
 * </pre>
336
 */
337
PIX *
338
pixHMT(PIX  *pixd,
339
       PIX  *pixs,
340
       SEL  *sel)
341
0
{
342
0
l_int32  i, j, w, h, sx, sy, cx, cy, firstrasterop, seldata;
343
0
l_int32  xp, yp, xn, yn;
344
0
PIX     *pixt;
345
346
0
    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
347
0
        return (PIX *)ERROR_PTR("processMorphArgs1 failed", __func__, pixd);
348
349
0
    pixGetDimensions(pixs, &w, &h, NULL);
350
0
    selGetParameters(sel, &sy, &sx, &cy, &cx);
351
0
    firstrasterop = TRUE;
352
0
    for (i = 0; i < sy; i++) {
353
0
        for (j = 0; j < sx; j++) {
354
0
            seldata = sel->data[i][j];
355
0
            if (seldata == 1) {  /* hit */
356
0
                if (firstrasterop == TRUE) {  /* src only */
357
0
                    pixClearAll(pixd);
358
0
                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC,
359
0
                                pixt, 0, 0);
360
0
                    firstrasterop = FALSE;
361
0
                } else {   /* src & dst */
362
0
                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
363
0
                                pixt, 0, 0);
364
0
                }
365
0
            } else if (seldata == 2) {  /* miss */
366
0
                if (firstrasterop == TRUE) {  /* ~src only */
367
0
                    pixSetAll(pixd);
368
0
                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_NOT(PIX_SRC),
369
0
                             pixt, 0, 0);
370
0
                    firstrasterop = FALSE;
371
0
                } else {  /* ~src & dst */
372
0
                    pixRasterop(pixd, cx - j, cy - i, w, h,
373
0
                                PIX_NOT(PIX_SRC) & PIX_DST,
374
0
                                pixt, 0, 0);
375
0
                }
376
0
            }
377
0
        }
378
0
    }
379
380
        /* Clear near edges */
381
0
    selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
382
0
    if (xp > 0)
383
0
        pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
384
0
    if (xn > 0)
385
0
        pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
386
0
    if (yp > 0)
387
0
        pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
388
0
    if (yn > 0)
389
0
        pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
390
391
0
    pixDestroy(&pixt);
392
0
    return pixd;
393
0
}
394
395
396
/*!
397
 * \brief   pixOpen()
398
 *
399
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
400
 *                       or different from pixs
401
 * \param[in]    pixs    1 bpp
402
 * \param[in]    sel
403
 * \return  pixd
404
 *
405
 * <pre>
406
 * Notes:
407
 *      (1) Generic morphological opening, using hits in the Sel.
408
 *      (2) There are three cases:
409
 *          (a) pixd == null   (result into new pixd)
410
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
411
 *          (c) pixd != pixs   (puts result into existing pixd)
412
 *      (3) For clarity, if the case is known, use these patterns:
413
 *          (a) pixd = pixOpen(NULL, pixs, ...);
414
 *          (b) pixOpen(pixs, pixs, ...);
415
 *          (c) pixOpen(pixd, pixs, ...);
416
 *      (4) The size of the result is determined by pixs.
417
 * </pre>
418
 */
419
PIX *
420
pixOpen(PIX  *pixd,
421
        PIX  *pixs,
422
        SEL  *sel)
423
0
{
424
0
PIX  *pixt;
425
426
0
    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
427
0
        return (PIX *)ERROR_PTR("pixd not returned", __func__, pixd);
428
429
0
    if ((pixt = pixErode(NULL, pixs, sel)) == NULL)
430
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, pixd);
431
0
    pixDilate(pixd, pixt, sel);
432
0
    pixDestroy(&pixt);
433
434
0
    return pixd;
435
0
}
436
437
438
/*!
439
 * \brief   pixClose()
440
 *
441
 * \param[in]    pixd [optional]; this can be null, equal to pixs,
442
 *                    or different from pixs
443
 * \param[in]    pixs 1 bpp
444
 * \param[in]    sel
445
 * \return  pixd
446
 *
447
 * <pre>
448
 * Notes:
449
 *      (1) Generic morphological closing, using hits in the Sel.
450
 *      (2) This implementation is a strict dual of the opening if
451
 *          symmetric boundary conditions are used (see notes at top
452
 *          of this file).
453
 *      (3) There are three cases:
454
 *          (a) pixd == null   (result into new pixd)
455
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
456
 *          (c) pixd != pixs   (puts result into existing pixd)
457
 *      (4) For clarity, if the case is known, use these patterns:
458
 *          (a) pixd = pixClose(NULL, pixs, ...);
459
 *          (b) pixClose(pixs, pixs, ...);
460
 *          (c) pixClose(pixd, pixs, ...);
461
 *      (5) The size of the result is determined by pixs.
462
 * </pre>
463
 */
464
PIX *
465
pixClose(PIX  *pixd,
466
         PIX  *pixs,
467
         SEL  *sel)
468
12
{
469
12
PIX  *pixt;
470
471
12
    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
472
0
        return (PIX *)ERROR_PTR("pixd not returned", __func__, pixd);
473
474
12
    if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
475
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, pixd);
476
12
    pixErode(pixd, pixt, sel);
477
12
    pixDestroy(&pixt);
478
479
12
    return pixd;
480
12
}
481
482
483
/*!
484
 * \brief   pixCloseSafe()
485
 *
486
 * \param[in]    pixd   [optional]; this can be null, equal to pixs,
487
 *                      or different from pixs
488
 * \param[in]    pixs   1 bpp
489
 * \param[in]    sel
490
 * \return  pixd
491
 *
492
 * <pre>
493
 * Notes:
494
 *      (1) Generic morphological closing, using hits in the Sel.
495
 *      (2) If non-symmetric boundary conditions are used, this
496
 *          function adds a border of OFF pixels that is of
497
 *          sufficient size to avoid losing pixels from the dilation,
498
 *          and it removes the border after the operation is finished.
499
 *          It thus enforces a correct extensive result for closing.
500
 *      (3) If symmetric b.c. are used, it is not necessary to add
501
 *          and remove this border.
502
 *      (4) There are three cases:
503
 *          (a) pixd == null   (result into new pixd)
504
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
505
 *          (c) pixd != pixs   (puts result into existing pixd)
506
 *      (5) For clarity, if the case is known, use these patterns:
507
 *          (a) pixd = pixCloseSafe(NULL, pixs, ...);
508
 *          (b) pixCloseSafe(pixs, pixs, ...);
509
 *          (c) pixCloseSafe(pixd, pixs, ...);
510
 *      (6) The size of the result is determined by pixs.
511
 * </pre>
512
 */
513
PIX *
514
pixCloseSafe(PIX  *pixd,
515
             PIX  *pixs,
516
             SEL  *sel)
517
0
{
518
0
l_int32  xp, yp, xn, yn, xmax, xbord;
519
0
PIX     *pixt1, *pixt2;
520
521
0
    if (!pixs)
522
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
523
0
    if (!sel)
524
0
        return (PIX *)ERROR_PTR("sel not defined", __func__, pixd);
525
0
    if (pixGetDepth(pixs) != 1)
526
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
527
528
        /* Symmetric b.c. handles correctly without added pixels */
529
0
    if (MORPH_BC == SYMMETRIC_MORPH_BC)
530
0
        return pixClose(pixd, pixs, sel);
531
532
0
    selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
533
0
    xmax = L_MAX(xp, xn);
534
0
    xbord = 32 * ((xmax + 31) / 32);  /* full 32 bit words */
535
536
0
    if ((pixt1 = pixAddBorderGeneral(pixs, xbord, xbord, yp, yn, 0)) == NULL)
537
0
        return (PIX *)ERROR_PTR("pixt1 not made", __func__, pixd);
538
0
    pixClose(pixt1, pixt1, sel);
539
0
    if ((pixt2 = pixRemoveBorderGeneral(pixt1, xbord, xbord, yp, yn)) == NULL)
540
0
        return (PIX *)ERROR_PTR("pixt2 not made", __func__, pixd);
541
0
    pixDestroy(&pixt1);
542
543
0
    if (!pixd)
544
0
        return pixt2;
545
546
0
    pixCopy(pixd, pixt2);
547
0
    pixDestroy(&pixt2);
548
0
    return pixd;
549
0
}
550
551
552
/*!
553
 * \brief   pixOpenGeneralized()
554
 *
555
 * \param[in]    pixd   [optional]; this can be null, equal to pixs,
556
 *                      or different from pixs
557
 * \param[in]    pixs   1 bpp
558
 * \param[in]    sel
559
 * \return  pixd
560
 *
561
 * <pre>
562
 * Notes:
563
 *      (1) Generalized morphological opening, using both hits and
564
 *          misses in the Sel.
565
 *      (2) This does a hit-miss transform, followed by a dilation
566
 *          using the hits.
567
 *      (3) There are three cases:
568
 *          (a) pixd == null   (result into new pixd)
569
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
570
 *          (c) pixd != pixs   (puts result into existing pixd)
571
 *      (4) For clarity, if the case is known, use these patterns:
572
 *          (a) pixd = pixOpenGeneralized(NULL, pixs, ...);
573
 *          (b) pixOpenGeneralized(pixs, pixs, ...);
574
 *          (c) pixOpenGeneralized(pixd, pixs, ...);
575
 *      (5) The size of the result is determined by pixs.
576
 * </pre>
577
 */
578
PIX *
579
pixOpenGeneralized(PIX  *pixd,
580
                   PIX  *pixs,
581
                   SEL  *sel)
582
0
{
583
0
PIX  *pixt;
584
585
0
    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
586
0
        return (PIX *)ERROR_PTR("pixd not returned", __func__, pixd);
587
588
0
    if ((pixt = pixHMT(NULL, pixs, sel)) == NULL)
589
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, pixd);
590
0
    pixDilate(pixd, pixt, sel);
591
0
    pixDestroy(&pixt);
592
0
    return pixd;
593
0
}
594
595
596
/*!
597
 * \brief   pixCloseGeneralized()
598
 *
599
 * \param[in]    pixd   [optional]; this can be null, equal to pixs,
600
 *                      or different from pixs
601
 * \param[in]    pixs   1 bpp
602
 * \param[in]    sel
603
 * \return  pixd
604
 *
605
 * <pre>
606
 * Notes:
607
 *      (1) Generalized morphological closing, using both hits and
608
 *          misses in the Sel.
609
 *      (2) This does a dilation using the hits, followed by a
610
 *          hit-miss transform.
611
 *      (3) This operation is a dual of the generalized opening.
612
 *      (4) There are three cases:
613
 *          (a) pixd == null   (result into new pixd)
614
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
615
 *          (c) pixd != pixs   (puts result into existing pixd)
616
 *      (5) For clarity, if the case is known, use these patterns:
617
 *          (a) pixd = pixCloseGeneralized(NULL, pixs, ...);
618
 *          (b) pixCloseGeneralized(pixs, pixs, ...);
619
 *          (c) pixCloseGeneralized(pixd, pixs, ...);
620
 *      (6) The size of the result is determined by pixs.
621
 * </pre>
622
 */
623
PIX *
624
pixCloseGeneralized(PIX  *pixd,
625
                    PIX  *pixs,
626
                    SEL  *sel)
627
0
{
628
0
PIX  *pixt;
629
630
0
    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
631
0
        return (PIX *)ERROR_PTR("pixd not returned", __func__, pixd);
632
633
0
    if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
634
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, pixd);
635
0
    pixHMT(pixd, pixt, sel);
636
0
    pixDestroy(&pixt);
637
638
0
    return pixd;
639
0
}
640
641
642
/*-----------------------------------------------------------------*
643
 *          Binary morphological (raster) ops with brick Sels      *
644
 *-----------------------------------------------------------------*/
645
/*!
646
 * \brief   pixDilateBrick()
647
 *
648
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
649
 *                       or different from pixs
650
 * \param[in]    pixs    1 bpp
651
 * \param[in]    hsize   width of brick Sel
652
 * \param[in]    vsize   height of brick Sel
653
 * \return  pixd
654
 *
655
 * <pre>
656
 * Notes:
657
 *      (1) Sel is a brick with all elements being hits
658
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
659
 *      (3) Do separably if both hsize and vsize are > 1.
660
 *      (4) There are three cases:
661
 *          (a) pixd == null   (result into new pixd)
662
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
663
 *          (c) pixd != pixs   (puts result into existing pixd)
664
 *      (5) For clarity, if the case is known, use these patterns:
665
 *          (a) pixd = pixDilateBrick(NULL, pixs, ...);
666
 *          (b) pixDilateBrick(pixs, pixs, ...);
667
 *          (c) pixDilateBrick(pixd, pixs, ...);
668
 *      (6) The size of the result is determined by pixs.
669
 * </pre>
670
 */
671
PIX *
672
pixDilateBrick(PIX     *pixd,
673
               PIX     *pixs,
674
               l_int32  hsize,
675
               l_int32  vsize)
676
0
{
677
0
PIX  *pixt;
678
0
SEL  *sel, *selh, *selv;
679
680
0
    if (!pixs)
681
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
682
0
    if (pixGetDepth(pixs) != 1)
683
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
684
0
    if (hsize < 1 || vsize < 1)
685
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
686
687
0
    if (hsize == 1 && vsize == 1)
688
0
        return pixCopy(pixd, pixs);
689
0
    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
690
0
        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
691
0
        if (!sel)
692
0
            return (PIX *)ERROR_PTR("sel not made", __func__, pixd);
693
0
        pixd = pixDilate(pixd, pixs, sel);
694
0
        selDestroy(&sel);
695
0
    } else {
696
0
        if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
697
0
            return (PIX *)ERROR_PTR("selh not made", __func__, pixd);
698
0
        if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
699
0
            selDestroy(&selh);
700
0
            return (PIX *)ERROR_PTR("selv not made", __func__, pixd);
701
0
        }
702
0
        pixt = pixDilate(NULL, pixs, selh);
703
0
        pixd = pixDilate(pixd, pixt, selv);
704
0
        pixDestroy(&pixt);
705
0
        selDestroy(&selh);
706
0
        selDestroy(&selv);
707
0
    }
708
709
0
    return pixd;
710
0
}
711
712
713
/*!
714
 * \brief   pixErodeBrick()
715
 *
716
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
717
 *                       or different from pixs
718
 * \param[in]    pixs    1 bpp
719
 * \param[in]    hsize   width of brick Sel
720
 * \param[in]    vsize   height of brick Sel
721
 * \return  pixd
722
 *
723
 * <pre>
724
 * Notes:
725
 *      (1) Sel is a brick with all elements being hits
726
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
727
 *      (3) Do separably if both hsize and vsize are > 1.
728
 *      (4) There are three cases:
729
 *          (a) pixd == null   (result into new pixd)
730
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
731
 *          (c) pixd != pixs   (puts result into existing pixd)
732
 *      (5) For clarity, if the case is known, use these patterns:
733
 *          (a) pixd = pixErodeBrick(NULL, pixs, ...);
734
 *          (b) pixErodeBrick(pixs, pixs, ...);
735
 *          (c) pixErodeBrick(pixd, pixs, ...);
736
 *      (6) The size of the result is determined by pixs.
737
 * </pre>
738
 */
739
PIX *
740
pixErodeBrick(PIX     *pixd,
741
              PIX     *pixs,
742
              l_int32  hsize,
743
              l_int32  vsize)
744
0
{
745
0
PIX  *pixt;
746
0
SEL  *sel, *selh, *selv;
747
748
0
    if (!pixs)
749
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
750
0
    if (pixGetDepth(pixs) != 1)
751
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
752
0
    if (hsize < 1 || vsize < 1)
753
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
754
755
0
    if (hsize == 1 && vsize == 1)
756
0
        return pixCopy(pixd, pixs);
757
0
    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
758
0
        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
759
0
        if (!sel)
760
0
            return (PIX *)ERROR_PTR("sel not made", __func__, pixd);
761
0
        pixd = pixErode(pixd, pixs, sel);
762
0
        selDestroy(&sel);
763
0
    } else {
764
0
        if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
765
0
            return (PIX *)ERROR_PTR("selh not made", __func__, pixd);
766
0
        if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
767
0
            selDestroy(&selh);
768
0
            return (PIX *)ERROR_PTR("selv not made", __func__, pixd);
769
0
        }
770
0
        pixt = pixErode(NULL, pixs, selh);
771
0
        pixd = pixErode(pixd, pixt, selv);
772
0
        pixDestroy(&pixt);
773
0
        selDestroy(&selh);
774
0
        selDestroy(&selv);
775
0
    }
776
777
0
    return pixd;
778
0
}
779
780
781
/*!
782
 * \brief   pixOpenBrick()
783
 *
784
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
785
 *                       or different from pixs
786
 * \param[in]    pixs    1 bpp
787
 * \param[in]    hsize   width of brick Sel
788
 * \param[in]    vsize   height of brick Sel
789
 * \return  pixd, or NULL on error
790
 *
791
 * <pre>
792
 * Notes:
793
 *      (1) Sel is a brick with all elements being hits
794
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
795
 *      (3) Do separably if both hsize and vsize are > 1.
796
 *      (4) There are three cases:
797
 *          (a) pixd == null   (result into new pixd)
798
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
799
 *          (c) pixd != pixs   (puts result into existing pixd)
800
 *      (5) For clarity, if the case is known, use these patterns:
801
 *          (a) pixd = pixOpenBrick(NULL, pixs, ...);
802
 *          (b) pixOpenBrick(pixs, pixs, ...);
803
 *          (c) pixOpenBrick(pixd, pixs, ...);
804
 *      (6) The size of the result is determined by pixs.
805
 * </pre>
806
 */
807
PIX *
808
pixOpenBrick(PIX     *pixd,
809
             PIX     *pixs,
810
             l_int32  hsize,
811
             l_int32  vsize)
812
0
{
813
0
PIX  *pixt;
814
0
SEL  *sel, *selh, *selv;
815
816
0
    if (!pixs)
817
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
818
0
    if (pixGetDepth(pixs) != 1)
819
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
820
0
    if (hsize < 1 || vsize < 1)
821
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
822
823
0
    if (hsize == 1 && vsize == 1)
824
0
        return pixCopy(pixd, pixs);
825
0
    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
826
0
        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
827
0
        if (!sel)
828
0
            return (PIX *)ERROR_PTR("sel not made", __func__, pixd);
829
0
        pixd = pixOpen(pixd, pixs, sel);
830
0
        selDestroy(&sel);
831
0
    } else {  /* do separably */
832
0
        if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
833
0
            return (PIX *)ERROR_PTR("selh not made", __func__, pixd);
834
0
        if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
835
0
            selDestroy(&selh);
836
0
            return (PIX *)ERROR_PTR("selv not made", __func__, pixd);
837
0
        }
838
0
        pixt = pixErode(NULL, pixs, selh);
839
0
        pixd = pixErode(pixd, pixt, selv);
840
0
        pixDilate(pixt, pixd, selh);
841
0
        pixDilate(pixd, pixt, selv);
842
0
        pixDestroy(&pixt);
843
0
        selDestroy(&selh);
844
0
        selDestroy(&selv);
845
0
    }
846
847
0
    return pixd;
848
0
}
849
850
851
/*!
852
 * \brief   pixCloseBrick()
853
 *
854
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
855
 *                       or different from pixs
856
 * \param[in]    pixs    1 bpp
857
 * \param[in]    hsize   width of brick Sel
858
 * \param[in]    vsize   height of brick Sel
859
 * \return  pixd, or NULL on error
860
 *
861
 * <pre>
862
 * Notes:
863
 *      (1) Sel is a brick with all elements being hits
864
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
865
 *      (3) Do separably if both hsize and vsize are > 1.
866
 *      (4) There are three cases:
867
 *          (a) pixd == null   (result into new pixd)
868
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
869
 *          (c) pixd != pixs   (puts result into existing pixd)
870
 *      (5) For clarity, if the case is known, use these patterns:
871
 *          (a) pixd = pixCloseBrick(NULL, pixs, ...);
872
 *          (b) pixCloseBrick(pixs, pixs, ...);
873
 *          (c) pixCloseBrick(pixd, pixs, ...);
874
 *      (6) The size of the result is determined by pixs.
875
 * </pre>
876
 */
877
PIX *
878
pixCloseBrick(PIX     *pixd,
879
              PIX     *pixs,
880
              l_int32  hsize,
881
              l_int32  vsize)
882
0
{
883
0
PIX  *pixt;
884
0
SEL  *sel, *selh, *selv;
885
886
0
    if (!pixs)
887
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
888
0
    if (pixGetDepth(pixs) != 1)
889
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
890
0
    if (hsize < 1 || vsize < 1)
891
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
892
893
0
    if (hsize == 1 && vsize == 1)
894
0
        return pixCopy(pixd, pixs);
895
0
    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
896
0
        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
897
0
        if (!sel)
898
0
            return (PIX *)ERROR_PTR("sel not made", __func__, pixd);
899
0
        pixd = pixClose(pixd, pixs, sel);
900
0
        selDestroy(&sel);
901
0
    } else {  /* do separably */
902
0
        if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
903
0
            return (PIX *)ERROR_PTR("selh not made", __func__, pixd);
904
0
        if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
905
0
            selDestroy(&selh);
906
0
            return (PIX *)ERROR_PTR("selv not made", __func__, pixd);
907
0
        }
908
0
        pixt = pixDilate(NULL, pixs, selh);
909
0
        pixd = pixDilate(pixd, pixt, selv);
910
0
        pixErode(pixt, pixd, selh);
911
0
        pixErode(pixd, pixt, selv);
912
0
        pixDestroy(&pixt);
913
0
        selDestroy(&selh);
914
0
        selDestroy(&selv);
915
0
    }
916
917
0
    return pixd;
918
0
}
919
920
921
/*!
922
 * \brief   pixCloseSafeBrick()
923
 *
924
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
925
 *                       or different from pixs
926
 * \param[in]    pixs    1 bpp
927
 * \param[in]    hsize   width of brick Sel
928
 * \param[in]    vsize   height of brick Sel
929
 * \return  pixd, or NULL on error
930
 *
931
 * <pre>
932
 * Notes:
933
 *      (1) Sel is a brick with all elements being hits
934
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
935
 *      (3) Do separably if both hsize and vsize are > 1.
936
 *      (4) Safe closing adds a border of 0 pixels, of sufficient size so
937
 *          that all pixels in input image are processed within
938
 *          32-bit words in the expanded image.  As a result, there is
939
 *          no special processing for pixels near the boundary, and there
940
 *          are no boundary effects.  The border is removed at the end.
941
 *      (5) There are three cases:
942
 *          (a) pixd == null   (result into new pixd)
943
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
944
 *          (c) pixd != pixs   (puts result into existing pixd)
945
 *      (6) For clarity, if the case is known, use these patterns:
946
 *          (a) pixd = pixCloseBrick(NULL, pixs, ...);
947
 *          (b) pixCloseBrick(pixs, pixs, ...);
948
 *          (c) pixCloseBrick(pixd, pixs, ...);
949
 *      (7) The size of the result is determined by pixs.
950
 * </pre>
951
 */
952
PIX *
953
pixCloseSafeBrick(PIX     *pixd,
954
                  PIX     *pixs,
955
                  l_int32  hsize,
956
                  l_int32  vsize)
957
12
{
958
12
l_int32  maxtrans, bordsize;
959
12
PIX     *pixsb, *pixt, *pixdb;
960
12
SEL     *sel, *selh, *selv;
961
962
12
    if (!pixs)
963
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
964
12
    if (pixGetDepth(pixs) != 1)
965
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
966
12
    if (hsize < 1 || vsize < 1)
967
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
968
969
12
    if (hsize == 1 && vsize == 1)
970
0
        return pixCopy(pixd, pixs);
971
972
        /* Symmetric b.c. handles correctly without added pixels */
973
12
    if (MORPH_BC == SYMMETRIC_MORPH_BC)
974
0
        return pixCloseBrick(pixd, pixs, hsize, vsize);
975
976
12
    maxtrans = L_MAX(hsize / 2, vsize / 2);
977
12
    bordsize = 32 * ((maxtrans + 31) / 32);  /* full 32 bit words */
978
12
    pixsb = pixAddBorder(pixs, bordsize, 0);
979
980
12
    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
981
12
        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
982
12
        if (!sel) {
983
0
            pixDestroy(&pixsb);
984
0
            return (PIX *)ERROR_PTR("sel not made", __func__, pixd);
985
0
        }
986
12
        pixdb = pixClose(NULL, pixsb, sel);
987
12
        selDestroy(&sel);
988
12
    } else {  /* do separably */
989
0
        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
990
0
        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
991
0
        if (!selh || !selv) {
992
0
            selDestroy(&selh);
993
0
            selDestroy(&selv);
994
0
            pixDestroy(&pixsb);
995
0
            return (PIX *)ERROR_PTR("selh and selv not both made",
996
0
                                    __func__, pixd);
997
0
        }
998
0
        pixt = pixDilate(NULL, pixsb, selh);
999
0
        pixdb = pixDilate(NULL, pixt, selv);
1000
0
        pixErode(pixt, pixdb, selh);
1001
0
        pixErode(pixdb, pixt, selv);
1002
0
        pixDestroy(&pixt);
1003
0
        selDestroy(&selh);
1004
0
        selDestroy(&selv);
1005
0
    }
1006
1007
12
    pixt = pixRemoveBorder(pixdb, bordsize);
1008
12
    pixDestroy(&pixsb);
1009
12
    pixDestroy(&pixdb);
1010
1011
12
    if (!pixd) {
1012
12
        pixd = pixt;
1013
12
    } else {
1014
0
        pixCopy(pixd, pixt);
1015
0
        pixDestroy(&pixt);
1016
0
    }
1017
12
    return pixd;
1018
12
}
1019
1020
1021
/*-----------------------------------------------------------------*
1022
 *     Binary composed morphological (raster) ops with brick Sels  *
1023
 *-----------------------------------------------------------------*/
1024
/* \brief   selectComposableSels()
1025
 *
1026
 * \param[in]    size         of composed sel
1027
 * \param[in]    direction    L_HORIZ, L_VERT
1028
 * \param[out]   psel1        [optional] contiguous sel; can be null
1029
 * \param[out]   psel2        [optional] comb sel; can be null
1030
 * \return   0 if OK, 1 on error
1031
 *
1032
 * <pre>
1033
 * Notes:
1034
 *      (1) When using composable Sels, where the original Sel is
1035
 *          decomposed into two, the best you can do in terms
1036
 *          of reducing the computation is by a factor:
1037
 *
1038
 *               2 * sqrt(size) / size
1039
 *
1040
 *          In practice, you get quite close to this.  E.g.,
1041
 *
1042
 *             Sel size     |   Optimum reduction factor
1043
 *             --------         ------------------------
1044
 *                36        |          1/3
1045
 *                64        |          1/4
1046
 *               144        |          1/6
1047
 *               256        |          1/8
1048
 * </pre>
1049
 */
1050
l_int32
1051
selectComposableSels(l_int32  size,
1052
                     l_int32  direction,
1053
                     SEL    **psel1,
1054
                     SEL    **psel2)
1055
0
{
1056
0
l_int32  factor1, factor2;
1057
1058
0
    if (!psel1 && !psel2)
1059
0
        return ERROR_INT("neither &sel1 nor &sel2 are defined", __func__, 1);
1060
0
    if (psel1) *psel1 = NULL;
1061
0
    if (psel2) *psel2 = NULL;
1062
0
    if (size < 1 || size > 10000)
1063
0
        return ERROR_INT("size < 1 or size > 10000", __func__, 1);
1064
0
    if (direction != L_HORIZ && direction != L_VERT)
1065
0
        return ERROR_INT("invalid direction", __func__, 1);
1066
1067
0
    if (selectComposableSizes(size, &factor1, &factor2))
1068
0
        return ERROR_INT("factors not found", __func__, 1);
1069
1070
0
    if (psel1) {
1071
0
        if (direction == L_HORIZ)
1072
0
            *psel1 = selCreateBrick(1, factor1, 0, factor1 / 2, SEL_HIT);
1073
0
        else
1074
0
            *psel1 = selCreateBrick(factor1, 1, factor1 / 2 , 0, SEL_HIT);
1075
0
    }
1076
0
    if (psel2)
1077
0
        *psel2 = selCreateComb(factor1, factor2, direction);
1078
0
    return 0;
1079
0
}
1080
1081
1082
/*!
1083
 * \brief   selectComposableSizes()
1084
 *
1085
 * \param[in]    size       of sel to be decomposed
1086
 * \param[out]   pfactor1   larger factor
1087
 * \param[out]   pfactor2   smaller factor
1088
 * \return  0 if OK, 1 on error
1089
 *
1090
 * <pre>
1091
 * Notes:
1092
 *      (1) This works for Sel sizes up to 10000, which seems sufficient.
1093
 *      (2) The composable sel size is typically within +- 1 of
1094
 *          the requested size.  Up to size = 300, the maximum difference
1095
 *          is +- 2.
1096
 *      (3) We choose an overall cost function where the penalty for
1097
 *          the size difference between input and actual is 4 times
1098
 *          the penalty for additional rasterops.
1099
 *      (4) Returned values: factor1 >= factor2
1100
 *          If size > 1, then factor1 > 1.
1101
 * </pre>
1102
 */
1103
l_ok
1104
selectComposableSizes(l_int32   size,
1105
                      l_int32  *pfactor1,
1106
                      l_int32  *pfactor2)
1107
0
{
1108
0
l_int32  i, midval, val1, val2m, val2p;
1109
0
l_int32  index, prodm, prodp;
1110
0
l_int32  mincost, totcost, rastcostm, rastcostp, diffm, diffp;
1111
0
l_int32  lowval[256];
1112
0
l_int32  hival[256];
1113
0
l_int32  rastcost[256];  /* excess in sum of sizes (extra rasterops) */
1114
0
l_int32  diff[256];  /* diff between product (sel size) and input size */
1115
1116
0
    if (size < 1 || size > 10000)
1117
0
        return ERROR_INT("size < 1 or size > 10000", __func__, 1);
1118
0
    if (!pfactor1 || !pfactor2)
1119
0
        return ERROR_INT("&factor1 or &factor2 not defined", __func__, 1);
1120
1121
0
    midval = (l_int32)(sqrt((l_float64)size) + 0.001);
1122
0
    if (midval * midval == size) {
1123
0
        *pfactor1 = *pfactor2 = midval;
1124
0
        return 0;
1125
0
    }
1126
1127
        /* Set up arrays.  For each val1, optimize for lowest diff,
1128
         * and save the rastcost, the diff, and the two factors. */
1129
0
    for (val1 = midval + 1, i = 0; val1 > 0; val1--, i++) {
1130
0
        val2m = size / val1;
1131
0
        val2p = val2m + 1;
1132
0
        prodm = val1 * val2m;
1133
0
        prodp = val1 * val2p;
1134
0
        rastcostm = val1 + val2m - 2 * midval;
1135
0
        rastcostp = val1 + val2p - 2 * midval;
1136
0
        diffm = L_ABS(size - prodm);
1137
0
        diffp = L_ABS(size - prodp);
1138
0
        if (diffm <= diffp) {
1139
0
            lowval[i] = L_MIN(val1, val2m);
1140
0
            hival[i] = L_MAX(val1, val2m);
1141
0
            rastcost[i] = rastcostm;
1142
0
            diff[i] = diffm;
1143
0
        } else {
1144
0
            lowval[i] = L_MIN(val1, val2p);
1145
0
            hival[i] = L_MAX(val1, val2p);
1146
0
            rastcost[i] = rastcostp;
1147
0
            diff[i] = diffp;
1148
0
        }
1149
0
    }
1150
1151
        /* Choose the optimum factors; use cost ratio 4 on diff */
1152
0
    mincost = 10000;
1153
0
    index = 1;  /* unimportant initial value */
1154
0
    for (i = 0; i < midval + 1; i++) {
1155
0
        if (diff[i] == 0 && rastcost[i] < ACCEPTABLE_COST) {
1156
0
            *pfactor1 = hival[i];
1157
0
            *pfactor2 = lowval[i];
1158
0
            return 0;
1159
0
        }
1160
0
        totcost = 4 * diff[i] + rastcost[i];
1161
0
        if (totcost < mincost) {
1162
0
            mincost = totcost;
1163
0
            index = i;
1164
0
        }
1165
0
    }
1166
0
    *pfactor1 = hival[index];
1167
0
    *pfactor2 = lowval[index];
1168
1169
0
    return 0;
1170
0
}
1171
1172
1173
/*!
1174
 * \brief   pixDilateCompBrick()
1175
 *
1176
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
1177
 *                       or different from pixs
1178
 * \param[in]    pixs    1 bpp
1179
 * \param[in]    hsize   width of brick Sel
1180
 * \param[in]    vsize   height of brick Sel
1181
 * \return  pixd, or NULL on error
1182
 *
1183
 * <pre>
1184
 * Notes:
1185
 *      (1) Sel is a brick with all elements being hits
1186
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
1187
 *      (3) Do compositely for each dimension > 1.
1188
 *      (4) Do separably if both hsize and vsize are > 1.
1189
 *      (5) There are three cases:
1190
 *          (a) pixd == null   (result into new pixd)
1191
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
1192
 *          (c) pixd != pixs   (puts result into existing pixd)
1193
 *      (6) For clarity, if the case is known, use these patterns:
1194
 *          (a) pixd = pixDilateCompBrick(NULL, pixs, ...);
1195
 *          (b) pixDilateCompBrick(pixs, pixs, ...);
1196
 *          (c) pixDilateCompBrick(pixd, pixs, ...);
1197
 *      (7) The dimensions of the resulting image are determined by pixs.
1198
 *      (8) CAUTION: both hsize and vsize are being decomposed.
1199
 *          The decomposer chooses a product of sizes (call them
1200
 *          'terms') for each that is close to the input size,
1201
 *          but not necessarily equal to it.  It attempts to optimize:
1202
 *             (a) for consistency with the input values: the product
1203
 *                 of terms is close to the input size
1204
 *             (b) for efficiency of the operation: the sum of the
1205
 *                 terms is small; ideally about twice the square
1206
 *                 root of the input size.
1207
 *          So, for example, if the input hsize = 37, which is
1208
 *          a prime number, the decomposer will break this into two
1209
 *          terms, 6 and 6, so that the net result is a dilation
1210
 *          with hsize = 36.
1211
 * </pre>
1212
 */
1213
PIX *
1214
pixDilateCompBrick(PIX     *pixd,
1215
                   PIX     *pixs,
1216
                   l_int32  hsize,
1217
                   l_int32  vsize)
1218
0
{
1219
0
PIX  *pix1, *pix2, *pix3;
1220
0
SEL  *selh1 = NULL;
1221
0
SEL  *selh2 = NULL;
1222
0
SEL  *selv1 = NULL;
1223
0
SEL  *selv2 = NULL;
1224
1225
0
    if (!pixs)
1226
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1227
0
    if (pixGetDepth(pixs) != 1)
1228
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1229
0
    if (hsize < 1 || vsize < 1)
1230
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
1231
1232
0
    if (hsize == 1 && vsize == 1)
1233
0
        return pixCopy(pixd, pixs);
1234
0
    if (hsize > 1) {
1235
0
        if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
1236
0
            selDestroy(&selh1);
1237
0
            selDestroy(&selh2);
1238
0
            return (PIX *)ERROR_PTR("horiz sels not made", __func__, pixd);
1239
0
        }
1240
0
    }
1241
0
    if (vsize > 1) {
1242
0
        if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
1243
0
            selDestroy(&selh1);
1244
0
            selDestroy(&selh2);
1245
0
            selDestroy(&selv1);
1246
0
            selDestroy(&selv2);
1247
0
            return (PIX *)ERROR_PTR("vert sels not made", __func__, pixd);
1248
0
        }
1249
0
    }
1250
1251
0
    pix1 = pixAddBorder(pixs, 32, 0);
1252
0
    if (vsize == 1) {
1253
0
        pix2 = pixDilate(NULL, pix1, selh1);
1254
0
        pix3 = pixDilate(NULL, pix2, selh2);
1255
0
    } else if (hsize == 1) {
1256
0
        pix2 = pixDilate(NULL, pix1, selv1);
1257
0
        pix3 = pixDilate(NULL, pix2, selv2);
1258
0
    } else {
1259
0
        pix2 = pixDilate(NULL, pix1, selh1);
1260
0
        pix3 = pixDilate(NULL, pix2, selh2);
1261
0
        pixDilate(pix2, pix3, selv1);
1262
0
        pixDilate(pix3, pix2, selv2);
1263
0
    }
1264
0
    pixDestroy(&pix1);
1265
0
    pixDestroy(&pix2);
1266
1267
0
    selDestroy(&selh1);
1268
0
    selDestroy(&selh2);
1269
0
    selDestroy(&selv1);
1270
0
    selDestroy(&selv2);
1271
1272
0
    pix1 = pixRemoveBorder(pix3, 32);
1273
0
    pixDestroy(&pix3);
1274
0
    if (!pixd)
1275
0
        return pix1;
1276
0
    pixCopy(pixd, pix1);
1277
0
    pixDestroy(&pix1);
1278
0
    return pixd;
1279
0
}
1280
1281
1282
/*!
1283
 * \brief   pixErodeCompBrick()
1284
 *
1285
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
1286
 *                       or different from pixs
1287
 * \param[in]    pixs    1 bpp
1288
 * \param[in]    hsize   width of brick Sel
1289
 * \param[in]    vsize   height of brick Sel
1290
 * \return  pixd, or NULL on error
1291
 *
1292
 * <pre>
1293
 * Notes:
1294
 *      (1) Sel is a brick with all elements being hits
1295
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
1296
 *      (3) Do compositely for each dimension > 1.
1297
 *      (4) Do separably if both hsize and vsize are > 1.
1298
 *      (5) There are three cases:
1299
 *          (a) pixd == null   (result into new pixd)
1300
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
1301
 *          (c) pixd != pixs   (puts result into existing pixd)
1302
 *      (6) For clarity, if the case is known, use these patterns:
1303
 *          (a) pixd = pixErodeCompBrick(NULL, pixs, ...);
1304
 *          (b) pixErodeCompBrick(pixs, pixs, ...);
1305
 *          (c) pixErodeCompBrick(pixd, pixs, ...);
1306
 *      (7) The dimensions of the resulting image are determined by pixs.
1307
 *      (8) CAUTION: both hsize and vsize are being decomposed.
1308
 *          The decomposer chooses a product of sizes (call them
1309
 *          'terms') for each that is close to the input size,
1310
 *          but not necessarily equal to it.  It attempts to optimize:
1311
 *             (a) for consistency with the input values: the product
1312
 *                 of terms is close to the input size
1313
 *             (b) for efficiency of the operation: the sum of the
1314
 *                 terms is small; ideally about twice the square
1315
 *                 root of the input size.
1316
 *          So, for example, if the input hsize = 37, which is
1317
 *          a prime number, the decomposer will break this into two
1318
 *          terms, 6 and 6, so that the net result is a dilation
1319
 *          with hsize = 36.
1320
 * </pre>
1321
 */
1322
PIX *
1323
pixErodeCompBrick(PIX     *pixd,
1324
                  PIX     *pixs,
1325
                  l_int32  hsize,
1326
                  l_int32  vsize)
1327
0
{
1328
0
PIX  *pixt;
1329
0
SEL  *selh1 = NULL;
1330
0
SEL  *selh2 = NULL;
1331
0
SEL  *selv1 = NULL;
1332
0
SEL  *selv2 = NULL;
1333
1334
0
    if (!pixs)
1335
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1336
0
    if (pixGetDepth(pixs) != 1)
1337
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1338
0
    if (hsize < 1 || vsize < 1)
1339
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
1340
1341
0
    if (hsize == 1 && vsize == 1)
1342
0
        return pixCopy(pixd, pixs);
1343
0
    if (hsize > 1) {
1344
0
        if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
1345
0
            selDestroy(&selh1);
1346
0
            selDestroy(&selh2);
1347
0
            return (PIX *)ERROR_PTR("horiz sels not made", __func__, pixd);
1348
0
        }
1349
0
    }
1350
0
    if (vsize > 1) {
1351
0
        if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
1352
0
            selDestroy(&selh1);
1353
0
            selDestroy(&selh2);
1354
0
            selDestroy(&selv1);
1355
0
            selDestroy(&selv2);
1356
0
            return (PIX *)ERROR_PTR("vert sels not made", __func__, pixd);
1357
0
        }
1358
0
    }
1359
1360
0
    if (vsize == 1) {
1361
0
        pixt = pixErode(NULL, pixs, selh1);
1362
0
        pixd = pixErode(pixd, pixt, selh2);
1363
0
    } else if (hsize == 1) {
1364
0
        pixt = pixErode(NULL, pixs, selv1);
1365
0
        pixd = pixErode(pixd, pixt, selv2);
1366
0
    } else {
1367
0
        pixt = pixErode(NULL, pixs, selh1);
1368
0
        pixd = pixErode(pixd, pixt, selh2);
1369
0
        pixErode(pixt, pixd, selv1);
1370
0
        pixErode(pixd, pixt, selv2);
1371
0
    }
1372
0
    pixDestroy(&pixt);
1373
1374
0
    selDestroy(&selh1);
1375
0
    selDestroy(&selh2);
1376
0
    selDestroy(&selv1);
1377
0
    selDestroy(&selv2);
1378
0
    return pixd;
1379
0
}
1380
1381
1382
/*!
1383
 * \brief   pixOpenCompBrick()
1384
 *
1385
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
1386
 *                       or different from pixs
1387
 * \param[in]    pixs    1 bpp
1388
 * \param[in]    hsize   width of brick Sel
1389
 * \param[in]    vsize   height of brick Sel
1390
 * \return  pixd, or NULL on error
1391
 *
1392
 * <pre>
1393
 * Notes:
1394
 *      (1) Sel is a brick with all elements being hits
1395
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
1396
 *      (3) Do compositely for each dimension > 1.
1397
 *      (4) Do separably if both hsize and vsize are > 1.
1398
 *      (5) There are three cases:
1399
 *          (a) pixd == null   (result into new pixd)
1400
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
1401
 *          (c) pixd != pixs   (puts result into existing pixd)
1402
 *      (6) For clarity, if the case is known, use these patterns:
1403
 *          (a) pixd = pixOpenCompBrick(NULL, pixs, ...);
1404
 *          (b) pixOpenCompBrick(pixs, pixs, ...);
1405
 *          (c) pixOpenCompBrick(pixd, pixs, ...);
1406
 *      (7) The dimensions of the resulting image are determined by pixs.
1407
 *      (8) CAUTION: both hsize and vsize are being decomposed.
1408
 *          The decomposer chooses a product of sizes (call them
1409
 *          'terms') for each that is close to the input size,
1410
 *          but not necessarily equal to it.  It attempts to optimize:
1411
 *             (a) for consistency with the input values: the product
1412
 *                 of terms is close to the input size
1413
 *             (b) for efficiency of the operation: the sum of the
1414
 *                 terms is small; ideally about twice the square
1415
 *                 root of the input size.
1416
 *          So, for example, if the input hsize = 37, which is
1417
 *          a prime number, the decomposer will break this into two
1418
 *          terms, 6 and 6, so that the net result is a dilation
1419
 *          with hsize = 36.
1420
 * </pre>
1421
 */
1422
PIX *
1423
pixOpenCompBrick(PIX     *pixd,
1424
                 PIX     *pixs,
1425
                 l_int32  hsize,
1426
                 l_int32  vsize)
1427
0
{
1428
0
PIX  *pixt;
1429
0
SEL  *selh1 = NULL;
1430
0
SEL  *selh2 = NULL;
1431
0
SEL  *selv1 = NULL;
1432
0
SEL  *selv2 = NULL;
1433
1434
0
    if (!pixs)
1435
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1436
0
    if (pixGetDepth(pixs) != 1)
1437
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1438
0
    if (hsize < 1 || vsize < 1)
1439
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
1440
1441
0
    if (hsize == 1 && vsize == 1)
1442
0
        return pixCopy(pixd, pixs);
1443
0
    if (hsize > 1) {
1444
0
        if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
1445
0
            selDestroy(&selh1);
1446
0
            selDestroy(&selh2);
1447
0
            return (PIX *)ERROR_PTR("horiz sels not made", __func__, pixd);
1448
0
        }
1449
0
    }
1450
0
    if (vsize > 1) {
1451
0
        if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
1452
0
            selDestroy(&selh1);
1453
0
            selDestroy(&selh2);
1454
0
            selDestroy(&selv1);
1455
0
            selDestroy(&selv2);
1456
0
            return (PIX *)ERROR_PTR("vert sels not made", __func__, pixd);
1457
0
        }
1458
0
    }
1459
1460
0
    if (vsize == 1) {
1461
0
        pixt = pixErode(NULL, pixs, selh1);
1462
0
        pixd = pixErode(pixd, pixt, selh2);
1463
0
        pixDilate(pixt, pixd, selh1);
1464
0
        pixDilate(pixd, pixt, selh2);
1465
0
    } else if (hsize == 1) {
1466
0
        pixt = pixErode(NULL, pixs, selv1);
1467
0
        pixd = pixErode(pixd, pixt, selv2);
1468
0
        pixDilate(pixt, pixd, selv1);
1469
0
        pixDilate(pixd, pixt, selv2);
1470
0
    } else {  /* do separably */
1471
0
        pixt = pixErode(NULL, pixs, selh1);
1472
0
        pixd = pixErode(pixd, pixt, selh2);
1473
0
        pixErode(pixt, pixd, selv1);
1474
0
        pixErode(pixd, pixt, selv2);
1475
0
        pixDilate(pixt, pixd, selh1);
1476
0
        pixDilate(pixd, pixt, selh2);
1477
0
        pixDilate(pixt, pixd, selv1);
1478
0
        pixDilate(pixd, pixt, selv2);
1479
0
    }
1480
0
    pixDestroy(&pixt);
1481
1482
0
    selDestroy(&selh1);
1483
0
    selDestroy(&selh2);
1484
0
    selDestroy(&selv1);
1485
0
    selDestroy(&selv2);
1486
0
    return pixd;
1487
0
}
1488
1489
1490
/*!
1491
 * \brief   pixCloseCompBrick()
1492
 *
1493
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
1494
 *                       or different from pixs
1495
 * \param[in]    pixs    1 bpp
1496
 * \param[in]    hsize   width of brick Sel
1497
 * \param[in]    vsize   height of brick Sel
1498
 * \return  pixd, or NULL on error
1499
 *
1500
 * <pre>
1501
 * Notes:
1502
 *      (1) Sel is a brick with all elements being hits
1503
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
1504
 *      (3) Do compositely for each dimension > 1.
1505
 *      (4) Do separably if both hsize and vsize are > 1.
1506
 *      (5) There are three cases:
1507
 *          (a) pixd == null   (result into new pixd)
1508
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
1509
 *          (c) pixd != pixs   (puts result into existing pixd)
1510
 *      (6) For clarity, if the case is known, use these patterns:
1511
 *          (a) pixd = pixCloseCompBrick(NULL, pixs, ...);
1512
 *          (b) pixCloseCompBrick(pixs, pixs, ...);
1513
 *          (c) pixCloseCompBrick(pixd, pixs, ...);
1514
 *      (7) The dimensions of the resulting image are determined by pixs.
1515
 *      (8) CAUTION: both hsize and vsize are being decomposed.
1516
 *          The decomposer chooses a product of sizes (call them
1517
 *          'terms') for each that is close to the input size,
1518
 *          but not necessarily equal to it.  It attempts to optimize:
1519
 *             (a) for consistency with the input values: the product
1520
 *                 of terms is close to the input size
1521
 *             (b) for efficiency of the operation: the sum of the
1522
 *                 terms is small; ideally about twice the square
1523
 *                 root of the input size.
1524
 *          So, for example, if the input hsize = 37, which is
1525
 *          a prime number, the decomposer will break this into two
1526
 *          terms, 6 and 6, so that the net result is a dilation
1527
 *          with hsize = 36.
1528
 * </pre>
1529
 */
1530
PIX *
1531
pixCloseCompBrick(PIX     *pixd,
1532
                  PIX     *pixs,
1533
                  l_int32  hsize,
1534
                  l_int32  vsize)
1535
0
{
1536
0
PIX  *pixt;
1537
0
SEL  *selh1 = NULL;
1538
0
SEL  *selh2 = NULL;
1539
0
SEL  *selv1 = NULL;
1540
0
SEL  *selv2 = NULL;
1541
1542
0
    if (!pixs)
1543
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1544
0
    if (pixGetDepth(pixs) != 1)
1545
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1546
0
    if (hsize < 1 || vsize < 1)
1547
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
1548
1549
0
    if (hsize == 1 && vsize == 1)
1550
0
        return pixCopy(pixd, pixs);
1551
0
    if (hsize > 1) {
1552
0
        if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
1553
0
            selDestroy(&selh1);
1554
0
            selDestroy(&selh2);
1555
0
            return (PIX *)ERROR_PTR("horiz sels not made", __func__, pixd);
1556
0
        }
1557
0
    }
1558
0
    if (vsize > 1) {
1559
0
        if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
1560
0
            selDestroy(&selh1);
1561
0
            selDestroy(&selh2);
1562
0
            selDestroy(&selv1);
1563
0
            selDestroy(&selv2);
1564
0
            return (PIX *)ERROR_PTR("vert sels not made", __func__, pixd);
1565
0
        }
1566
0
    }
1567
1568
0
    if (vsize == 1) {
1569
0
        pixt = pixDilate(NULL, pixs, selh1);
1570
0
        pixd = pixDilate(pixd, pixt, selh2);
1571
0
        pixErode(pixt, pixd, selh1);
1572
0
        pixErode(pixd, pixt, selh2);
1573
0
    } else if (hsize == 1) {
1574
0
        pixt = pixDilate(NULL, pixs, selv1);
1575
0
        pixd = pixDilate(pixd, pixt, selv2);
1576
0
        pixErode(pixt, pixd, selv1);
1577
0
        pixErode(pixd, pixt, selv2);
1578
0
    } else {  /* do separably */
1579
0
        pixt = pixDilate(NULL, pixs, selh1);
1580
0
        pixd = pixDilate(pixd, pixt, selh2);
1581
0
        pixDilate(pixt, pixd, selv1);
1582
0
        pixDilate(pixd, pixt, selv2);
1583
0
        pixErode(pixt, pixd, selh1);
1584
0
        pixErode(pixd, pixt, selh2);
1585
0
        pixErode(pixt, pixd, selv1);
1586
0
        pixErode(pixd, pixt, selv2);
1587
0
    }
1588
0
    pixDestroy(&pixt);
1589
1590
0
    selDestroy(&selh1);
1591
0
    selDestroy(&selh2);
1592
0
    selDestroy(&selv1);
1593
0
    selDestroy(&selv2);
1594
0
    return pixd;
1595
0
}
1596
1597
1598
/*!
1599
 * \brief   pixCloseSafeCompBrick()
1600
 *
1601
 * \param[in]    pixd    [optional]; this can be null, equal to pixs,
1602
 *                       or different from pixs
1603
 * \param[in]    pixs    1 bpp
1604
 * \param[in]    hsize   width of brick Sel
1605
 * \param[in]    vsize   height of brick Sel
1606
 * \return  pixd, or NULL on error
1607
 *
1608
 * <pre>
1609
 * Notes:
1610
 *      (1) Sel is a brick with all elements being hits
1611
 *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
1612
 *      (3) Do compositely for each dimension > 1.
1613
 *      (4) Do separably if both hsize and vsize are > 1.
1614
 *      (5) Safe closing adds a border of 0 pixels, of sufficient size so
1615
 *          that all pixels in input image are processed within
1616
 *          32-bit words in the expanded image.  As a result, there is
1617
 *          no special processing for pixels near the boundary, and there
1618
 *          are no boundary effects.  The border is removed at the end.
1619
 *      (6) There are three cases:
1620
 *          (a) pixd == null   (result into new pixd)
1621
 *          (b) pixd == pixs   (in-place; writes result back to pixs)
1622
 *          (c) pixd != pixs   (puts result into existing pixd)
1623
 *      (7) For clarity, if the case is known, use these patterns:
1624
 *          (a) pixd = pixCloseSafeCompBrick(NULL, pixs, ...);
1625
 *          (b) pixCloseSafeCompBrick(pixs, pixs, ...);
1626
 *          (c) pixCloseSafeCompBrick(pixd, pixs, ...);
1627
 *      (8) The dimensions of the resulting image are determined by pixs.
1628
 *      (9) CAUTION: both hsize and vsize are being decomposed.
1629
 *          The decomposer chooses a product of sizes (call them
1630
 *          'terms') for each that is close to the input size,
1631
 *          but not necessarily equal to it.  It attempts to optimize:
1632
 *             (a) for consistency with the input values: the product
1633
 *                 of terms is close to the input size
1634
 *             (b) for efficiency of the operation: the sum of the
1635
 *                 terms is small; ideally about twice the square
1636
 *                 root of the input size.
1637
 *          So, for example, if the input hsize = 37, which is
1638
 *          a prime number, the decomposer will break this into two
1639
 *          terms, 6 and 6, so that the net result is a dilation
1640
 *          with hsize = 36.
1641
 * </pre>
1642
 */
1643
PIX *
1644
pixCloseSafeCompBrick(PIX     *pixd,
1645
                      PIX     *pixs,
1646
                      l_int32  hsize,
1647
                      l_int32  vsize)
1648
0
{
1649
0
l_int32  maxtrans, bordsize;
1650
0
PIX     *pixsb, *pixt, *pixdb;
1651
0
SEL     *selh1 = NULL;
1652
0
SEL     *selh2 = NULL;
1653
0
SEL     *selv1 = NULL;
1654
0
SEL     *selv2 = NULL;
1655
1656
0
    if (!pixs)
1657
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1658
0
    if (pixGetDepth(pixs) != 1)
1659
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1660
0
    if (hsize < 1 || vsize < 1)
1661
0
        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
1662
1663
0
    if (hsize == 1 && vsize == 1)
1664
0
        return pixCopy(pixd, pixs);
1665
1666
        /* Symmetric b.c. handles correctly without added pixels */
1667
0
    if (MORPH_BC == SYMMETRIC_MORPH_BC)
1668
0
        return pixCloseCompBrick(pixd, pixs, hsize, vsize);
1669
1670
0
    if (hsize > 1) {
1671
0
        if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
1672
0
            selDestroy(&selh1);
1673
0
            selDestroy(&selh2);
1674
0
            return (PIX *)ERROR_PTR("horiz sels not made", __func__, pixd);
1675
0
        }
1676
0
    }
1677
0
    if (vsize > 1) {
1678
0
        if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
1679
0
            selDestroy(&selh1);
1680
0
            selDestroy(&selh2);
1681
0
            selDestroy(&selv1);
1682
0
            selDestroy(&selv2);
1683
0
            return (PIX *)ERROR_PTR("vert sels not made", __func__, pixd);
1684
0
        }
1685
0
    }
1686
1687
0
    maxtrans = L_MAX(hsize / 2, vsize / 2);
1688
0
    bordsize = 32 * ((maxtrans + 31) / 32);  /* full 32 bit words */
1689
0
    pixsb = pixAddBorder(pixs, bordsize, 0);
1690
1691
0
    if (vsize == 1) {
1692
0
        pixt = pixDilate(NULL, pixsb, selh1);
1693
0
        pixdb = pixDilate(NULL, pixt, selh2);
1694
0
        pixErode(pixt, pixdb, selh1);
1695
0
        pixErode(pixdb, pixt, selh2);
1696
0
    } else if (hsize == 1) {
1697
0
        pixt = pixDilate(NULL, pixsb, selv1);
1698
0
        pixdb = pixDilate(NULL, pixt, selv2);
1699
0
        pixErode(pixt, pixdb, selv1);
1700
0
        pixErode(pixdb, pixt, selv2);
1701
0
    } else {  /* do separably */
1702
0
        pixt = pixDilate(NULL, pixsb, selh1);
1703
0
        pixdb = pixDilate(NULL, pixt, selh2);
1704
0
        pixDilate(pixt, pixdb, selv1);
1705
0
        pixDilate(pixdb, pixt, selv2);
1706
0
        pixErode(pixt, pixdb, selh1);
1707
0
        pixErode(pixdb, pixt, selh2);
1708
0
        pixErode(pixt, pixdb, selv1);
1709
0
        pixErode(pixdb, pixt, selv2);
1710
0
    }
1711
0
    pixDestroy(&pixt);
1712
1713
0
    pixt = pixRemoveBorder(pixdb, bordsize);
1714
0
    pixDestroy(&pixsb);
1715
0
    pixDestroy(&pixdb);
1716
1717
0
    if (!pixd) {
1718
0
        pixd = pixt;
1719
0
    } else {
1720
0
        pixCopy(pixd, pixt);
1721
0
        pixDestroy(&pixt);
1722
0
    }
1723
1724
0
    selDestroy(&selh1);
1725
0
    selDestroy(&selh2);
1726
0
    selDestroy(&selv1);
1727
0
    selDestroy(&selv2);
1728
0
    return pixd;
1729
0
}
1730
1731
1732
/*-----------------------------------------------------------------*
1733
 *           Functions associated with boundary conditions         *
1734
 *-----------------------------------------------------------------*/
1735
/*!
1736
 * \brief   resetMorphBoundaryCondition()
1737
 *
1738
 * \param[in]    bc    SYMMETRIC_MORPH_BC, ASYMMETRIC_MORPH_BC
1739
 * \return  void
1740
 */
1741
void
1742
resetMorphBoundaryCondition(l_int32  bc)
1743
0
{
1744
0
    if (bc != SYMMETRIC_MORPH_BC && bc != ASYMMETRIC_MORPH_BC) {
1745
0
        L_WARNING("invalid bc; using asymmetric\n", __func__);
1746
0
        bc = ASYMMETRIC_MORPH_BC;
1747
0
    }
1748
0
    MORPH_BC = bc;
1749
0
    return;
1750
0
}
1751
1752
1753
/*!
1754
 * \brief   getMorphBorderPixelColor()
1755
 *
1756
 * \param[in]    type L_MORPH_DILATE, L_MORPH_ERODE
1757
 * \param[in]    depth of pix
1758
 * \return  color of border pixels for this operation
1759
 */
1760
l_uint32
1761
getMorphBorderPixelColor(l_int32  type,
1762
                         l_int32  depth)
1763
0
{
1764
0
    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE)
1765
0
        return ERROR_INT("invalid type", __func__, 0);
1766
0
    if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
1767
0
        depth != 16 && depth != 32)
1768
0
        return ERROR_INT("invalid depth", __func__, 0);
1769
1770
0
    if (MORPH_BC == ASYMMETRIC_MORPH_BC || type == L_MORPH_DILATE)
1771
0
        return 0;
1772
1773
        /* Symmetric & erosion */
1774
0
    if (depth < 32)
1775
0
        return ((1 << depth) - 1);
1776
0
    else  /* depth == 32 */
1777
0
        return 0xffffff00;
1778
0
}
1779
1780
1781
/*-----------------------------------------------------------------*
1782
 *               Static helpers for arg processing                 *
1783
 *-----------------------------------------------------------------*/
1784
/*!
1785
 * \brief   processMorphArgs1()
1786
 *
1787
 * \param[in]       pixd   [optional]; this can be null, equal to pixs,
1788
 *                         or different from pixs
1789
 * \param[in]       pixs   1 bpp
1790
 * \param[in]       sel
1791
 * \param[out]      ppixt  copy or clone of %pixs
1792
 * \return  pixd, or NULL on error.
1793
 *
1794
 * <pre>
1795
 * Notes:
1796
 *      (1) This is used for generic erosion, dilation and HMT.
1797
 * </pre>
1798
 */
1799
static PIX *
1800
processMorphArgs1(PIX   *pixd,
1801
                  PIX   *pixs,
1802
                  SEL   *sel,
1803
                  PIX  **ppixt)
1804
24
{
1805
24
l_int32  sx, sy;
1806
1807
24
    if (!ppixt)
1808
0
        return (PIX *)ERROR_PTR("&pixt not defined", __func__, pixd);
1809
24
    *ppixt = NULL;
1810
24
    if (!pixs)
1811
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1812
24
    if (!sel)
1813
0
        return (PIX *)ERROR_PTR("sel not defined", __func__, pixd);
1814
24
    if (pixGetDepth(pixs) != 1)
1815
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1816
1817
24
    selGetParameters(sel, &sx, &sy, NULL, NULL);
1818
24
    if (sx == 0 || sy == 0)
1819
0
        return (PIX *)ERROR_PTR("sel of size 0", __func__, pixd);
1820
1821
        /* We require pixd to exist and to be the same size as pixs.
1822
         * Further, pixt must be a copy (or clone) of pixs.  */
1823
24
    if (!pixd) {
1824
12
        if ((pixd = pixCreateTemplate(pixs)) == NULL)
1825
0
            return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1826
12
        *ppixt = pixClone(pixs);
1827
12
    } else {
1828
12
        pixResizeImageData(pixd, pixs);
1829
12
        if (pixd == pixs) {  /* in-place; must make a copy of pixs */
1830
0
            if ((*ppixt = pixCopy(NULL, pixs)) == NULL)
1831
0
                return (PIX *)ERROR_PTR("pixt not made", __func__, pixd);
1832
12
        } else {
1833
12
            *ppixt = pixClone(pixs);
1834
12
        }
1835
12
    }
1836
24
    return pixd;
1837
24
}
1838
1839
1840
/*!
1841
 * \brief   processMorphArgs2()
1842
 *
1843
 *  This is used for generic openings and closings.
1844
 */
1845
static PIX *
1846
processMorphArgs2(PIX   *pixd,
1847
                  PIX   *pixs,
1848
                  SEL   *sel)
1849
12
{
1850
12
l_int32  sx, sy;
1851
1852
12
    if (!pixs)
1853
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
1854
12
    if (!sel)
1855
0
        return (PIX *)ERROR_PTR("sel not defined", __func__, pixd);
1856
12
    if (pixGetDepth(pixs) != 1)
1857
0
        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
1858
1859
12
    selGetParameters(sel, &sx, &sy, NULL, NULL);
1860
12
    if (sx == 0 || sy == 0)
1861
0
        return (PIX *)ERROR_PTR("sel of size 0", __func__, pixd);
1862
1863
12
    if (!pixd)
1864
12
        return pixCreateTemplate(pixs);
1865
0
    pixResizeImageData(pixd, pixs);
1866
0
    return pixd;
1867
12
}