Coverage Report

Created: 2025-06-13 06:48

/src/leptonica/src/adaptmap.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 adaptmap.c
29
 * <pre>
30
 *
31
 *  -------------------------------------------------------------------
32
 *
33
 *  Image binarization algorithms are found in:
34
 *     grayquant.c:   standard, simple, general grayscale quantization
35
 *     adaptmap.c:    local adaptive; mostly gray-to-gray in preparation
36
 *                    for binarization
37
 *     binarize.c:    special binarization methods, locally adaptive.
38
 *     pageseg.c:     locally adaptive cleaning operation with several options
39
 *
40
 *  -------------------------------------------------------------------
41
 *
42
 *      Clean background to white using background normalization
43
 *          PIX       *pixCleanBackgroundToWhite()
44
 *
45
 *      Adaptive background normalization (top-level functions)
46
 *          PIX       *pixBackgroundNormSimple()     8 and 32 bpp
47
 *          PIX       *pixBackgroundNorm()           8 and 32 bpp
48
 *          PIX       *pixBackgroundNormMorph()      8 and 32 bpp
49
 *
50
 *      Arrays of inverted background values for normalization (16 bpp)
51
 *          l_int32    pixBackgroundNormGrayArray()   8 bpp input
52
 *          l_int32    pixBackgroundNormRGBArrays()   32 bpp input
53
 *          l_int32    pixBackgroundNormGrayArrayMorph()   8 bpp input
54
 *          l_int32    pixBackgroundNormRGBArraysMorph()   32 bpp input
55
 *
56
 *      Measurement of local background
57
 *          l_int32    pixGetBackgroundGrayMap()        8 bpp
58
 *          l_int32    pixGetBackgroundRGBMap()         32 bpp
59
 *          l_int32    pixGetBackgroundGrayMapMorph()   8 bpp
60
 *          l_int32    pixGetBackgroundRGBMapMorph()    32 bpp
61
 *          l_int32    pixFillMapHoles()
62
 *          PIX       *pixExtendByReplication()         8 bpp
63
 *          l_int32    pixSmoothConnectedRegions()      8 bpp
64
 *
65
 *      Measurement of local foreground
66
 *          l_int32    pixGetForegroundGrayMap()        8 bpp
67
 *
68
 *      Generate inverted background map for each component
69
 *          PIX       *pixGetInvBackgroundMap()   16 bpp
70
 *
71
 *      Apply inverse background map to image
72
 *          PIX       *pixApplyInvBackgroundGrayMap()   8 bpp
73
 *          PIX       *pixApplyInvBackgroundRGBMap()    32 bpp
74
 *
75
 *      Apply variable map
76
 *          PIX       *pixApplyVariableGrayMap()        8 bpp
77
 *
78
 *      Non-adaptive (global) mapping
79
 *          PIX       *pixGlobalNormRGB()               32 bpp or cmapped
80
 *          PIX       *pixGlobalNormNoSatRGB()          32 bpp
81
 *
82
 *      Adaptive threshold spread normalization
83
 *          l_int32    pixThresholdSpreadNorm()         8 bpp
84
 *
85
 *      Adaptive background normalization (flexible adaptaption)
86
 *          PIX       *pixBackgroundNormFlex()          8 bpp
87
 *
88
 *      Adaptive contrast normalization
89
 *          PIX             *pixContrastNorm()          8 bpp
90
 *          static l_int32   pixMinMaxTiles()
91
 *          static l_int32   pixSetLowContrast()
92
 *          static PIX      *pixLinearTRCTiled()
93
 *          static l_int32  *iaaGetLinearTRC()
94
 *
95
 *      Adaptive normalization with MinMax conversion of RGB to gray,
96
 *      contrast enhancement and optional 2x upscale binarization
97
 *          PIX             *pixBackgroundNormTo1MinMax()
98
 *          PIX             *pixConvertTo8MinMax()
99
 *          static l_int32  *pixSelectiveContrastMod()
100
 *
101
 *  Background normalization is done by generating a reduced map (or set
102
 *  of maps) representing the estimated background value of the
103
 *  input image, and using this to shift the pixel values so that
104
 *  this background value is set to some constant value.
105
 *
106
 *  Specifically, normalization has 3 steps:
107
 *    (1) Generate a background map at a reduced scale.
108
 *    (2) Make the array of inverted background values by inverting
109
 *        the map.  The result is an array of local multiplicative factors.
110
 *    (3) Apply this inverse background map to the image
111
 *
112
 *  The inverse background arrays can be generated in two different ways here:
113
 *    (1) Remove the 'foreground' pixels and average over the remaining
114
 *        pixels in each tile.  Propagate values into tiles where
115
 *        values have not been assigned, either because there was not
116
 *        enough background in the tile or because the tile is covered
117
 *        by a foreground region described by an image mask.
118
 *        After the background map is made, the inverse map is generated by
119
 *        smoothing over some number of adjacent tiles
120
 *        (block convolution) and then inverting.
121
 *    (2) Remove the foreground pixels using a morphological closing
122
 *        on a subsampled version of the image.  Propagate values
123
 *        into pixels covered by an optional image mask.  Invert the
124
 *        background map without preconditioning by convolutional smoothing.
125
 *
126
 *  Other methods for adaptively normalizing the image are also given here.
127
 *
128
 *  (1) pixThresholdSpreadNorm() computes a local threshold over the image
129
 *      and normalizes the input pixel values so that this computed threshold
130
 *      is a constant across the entire image.
131
 *
132
 *  (2) pixContrastNorm() computes and applies a local TRC so that the
133
 *      local dynamic range is expanded to the full 8 bits, where the
134
 *      darkest pixels are mapped to 0 and the lightest to 255.  This is
135
 *      useful for improving the appearance of pages with very light
136
 *      foreground or very dark background, and where the local TRC
137
 *      function doesn't change rapidly with position.
138
 *
139
 *  Adaptive binarization is done in two steps:
140
 *    (1) Background normalization by some method
141
 *    (2) Global thresholding with a value appropriate to the normalization.
142
 *  There are several high-level functions in leptonica for doing adaptive
143
 *  binarization on grayscale and color images, such as:
144
 *    * pixAdaptThresholdToBinary()   (in grayquant.c)
145
 *    * pixConvertTo1Adaptive()       (in pixconv.c)
146
 *    * pixCleanImage()               (in pageseg.c)
147
 * </pre>
148
 */
149
150
#ifdef HAVE_CONFIG_H
151
#include <config_auto.h>
152
#endif  /* HAVE_CONFIG_H */
153
154
#include "allheaders.h"
155
156
    /* Default input parameters for pixBackgroundNormSimple()
157
     * Notes:
158
     *    (1) mincount must never exceed the tile area (width * height)
159
     *    (2) bgval must be sufficiently below 255 to avoid accidental
160
     *        saturation; otherwise it should be large to avoid
161
     *        shrinking the dynamic range
162
     *    (3) results should otherwise not be sensitive to these values
163
     */
164
static const l_int32  DefaultTileWidth = 10;    /*!< default tile width    */
165
static const l_int32  DefaultTileHeight = 15;   /*!< default tile height   */
166
static const l_int32  DefaultFgThreshold = 60;  /*!< default fg threshold  */
167
static const l_int32  DefaultMinCount = 40;     /*!< default minimum count */
168
static const l_int32  DefaultBgVal = 200;       /*!< default bg value      */
169
static const l_int32  DefaultXSmoothSize = 2;  /*!< default x smooth size */
170
static const l_int32  DefaultYSmoothSize = 1;  /*!< default y smooth size */
171
172
static l_int32 pixMinMaxTiles(PIX *pixs, l_int32 sx, l_int32 sy,
173
                              l_int32 mindiff, l_int32 smoothx, l_int32 smoothy,
174
                              PIX **ppixmin, PIX **ppixmax);
175
static l_int32 pixSetLowContrast(PIX *pixs1, PIX *pixs2, l_int32 mindiff);
176
static PIX *pixLinearTRCTiled(PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy,
177
                              PIX *pixmin, PIX *pixmax);
178
static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff);
179
180
static l_ok pixSelectiveContrastMod(PIX *pixs, l_int32 contrast);
181
182
#ifndef  NO_CONSOLE_IO
183
#define  DEBUG_GLOBAL    0    /*!< set to 1 to debug pixGlobalNormNoSatRGB() */
184
#endif  /* ~NO_CONSOLE_IO */
185
186
/*------------------------------------------------------------------*
187
 *      Clean background to white using background normalization    *
188
 *------------------------------------------------------------------*/
189
/*!
190
 * \brief   pixCleanBackgroundToWhite()
191
 *
192
 * \param[in]    pixs       8 bpp grayscale or 32 bpp rgb
193
 * \param[in]    pixim      [optional] 1 bpp 'image' mask; can be null
194
 * \param[in]    pixg       [optional] 8 bpp grayscale version; can be null
195
 * \param[in]    gamma      gamma correction; must be > 0.0; typically ~1.0
196
 * \param[in]    blackval   dark value to set to black (0)
197
 * \param[in]    whiteval   light value to set to white (255)
198
 * \return  pixd 8 bpp or 32 bpp rgb, or NULL on error
199
 *
200
 * <pre>
201
 * Notes:
202
 *    (1) This is a simplified interface for cleaning an image.
203
 *        For comparison, see pixAdaptThresholdToBinaryGen().
204
 *    (2) The suggested default values for the input parameters are:
205
 *          gamma:    1.0  (reduce this to increase the contrast; e.g.,
206
 *                          for light text)
207
 *          blackval   70  (a bit more than 60)
208
 *          whiteval  190  (a bit less than 200)
209
 *    (3) Note: the whiteval must not exceed 200, which is the value
210
 *        that the background is set to in pixBackgroundNormSimple().
211
 * </pre>
212
 */
213
PIX *
214
pixCleanBackgroundToWhite(PIX       *pixs,
215
                          PIX       *pixim,
216
                          PIX       *pixg,
217
                          l_float32  gamma,
218
                          l_int32    blackval,
219
                          l_int32    whiteval)
220
0
{
221
0
l_int32  d;
222
0
PIX     *pixd;
223
224
0
    if (!pixs)
225
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
226
0
    d = pixGetDepth(pixs);
227
0
    if (d != 8 && d != 32)
228
0
        return (PIX *)ERROR_PTR("depth not 8 or 32", __func__, NULL);
229
0
    if (whiteval > 200) {
230
0
        L_WARNING("white value %d must not exceed 200; reset to 190",
231
0
                  __func__, whiteval);
232
0
        whiteval = 190;
233
0
    }
234
235
0
    pixd = pixBackgroundNormSimple(pixs, pixim, pixg);
236
0
    if (!pixd)
237
0
        return (PIX *)ERROR_PTR("background norm failedd", __func__, NULL);
238
0
    pixGammaTRC(pixd, pixd, gamma, blackval, whiteval);
239
0
    return pixd;
240
0
}
241
242
243
/*------------------------------------------------------------------*
244
 *                Adaptive background normalization                 *
245
 *------------------------------------------------------------------*/
246
/*!
247
 * \brief   pixBackgroundNormSimple()
248
 *
249
 * \param[in]    pixs     8 bpp grayscale or 32 bpp rgb
250
 * \param[in]    pixim    [optional] 1 bpp 'image' mask; can be null
251
 * \param[in]    pixg     [optional] 8 bpp grayscale version; can be null
252
 * \return  pixd 8 bpp or 32 bpp rgb, or NULL on error
253
 *
254
 * <pre>
255
 * Notes:
256
 *    (1) This is a simplified interface to pixBackgroundNorm(),
257
 *        where seven parameters are defaulted.
258
 *    (2) The input image is either grayscale or rgb.
259
 *    (3) See pixBackgroundNorm() for usage and function.
260
 * </pre>
261
 */
262
PIX *
263
pixBackgroundNormSimple(PIX  *pixs,
264
                        PIX  *pixim,
265
                        PIX  *pixg)
266
0
{
267
0
    return pixBackgroundNorm(pixs, pixim, pixg,
268
0
                             DefaultTileWidth, DefaultTileHeight,
269
0
                             DefaultFgThreshold, DefaultMinCount,
270
0
                             DefaultBgVal, DefaultXSmoothSize,
271
0
                             DefaultYSmoothSize);
272
0
}
273
274
275
/*!
276
 * \brief   pixBackgroundNorm()
277
 *
278
 * \param[in]    pixs        8 bpp grayscale or 32 bpp rgb
279
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null
280
 * \param[in]    pixg        [optional] 8 bpp grayscale version; can be null
281
 * \param[in]    sx, sy      tile size in pixels
282
 * \param[in]    thresh      threshold for determining foreground
283
 * \param[in]    mincount    min threshold on counts in a tile
284
 * \param[in]    bgval       target bg val; typ. > 128
285
 * \param[in]    smoothx     half-width of block convolution kernel width
286
 * \param[in]    smoothy     half-width of block convolution kernel height
287
 * \return  pixd 8 bpp or 32 bpp rgb, or NULL on error
288
 *
289
 * <pre>
290
 * Notes:
291
 *    (1) This is a top-level interface for normalizing the image intensity
292
 *        by mapping the image so that the background is near the input
293
 *        value %bgval.
294
 *    (2) The input image is either grayscale or rgb.
295
 *    (3) For each component in the input image, the background value
296
 *        in each tile is estimated using the values in the tile that
297
 *        are not part of the foreground, where the foreground is
298
 *        determined by %thresh.
299
 *    (4) An optional binary mask can be specified, with the foreground
300
 *        pixels typically over image regions.  The resulting background
301
 *        map values will be determined by surrounding pixels that are
302
 *        not under the mask foreground.  The origin (0,0) of this mask
303
 *        is assumed to be aligned with the origin of the input image.
304
 *        This binary mask must not fully cover pixs, because then there
305
 *        will be no pixels in the input image available to compute
306
 *        the background.
307
 *    (5) An optional grayscale version of the input pixs can be supplied.
308
 *        The only reason to do this is if the input is RGB and this
309
 *        grayscale version can be used elsewhere.  If the input is RGB
310
 *        and this is not supplied, it is made internally using only
311
 *        the green component, and destroyed after use.
312
 *    (6) The dimensions of the pixel tile (%sx, %sy) give the amount
313
 *        by which the map is reduced in size from the input image.
314
 *    (7) The input image is binarized using %thresh, in order to
315
 *        locate the foreground components.  If this is set too low,
316
 *        some actual foreground may be used to determine the maps;
317
 *        if set too high, there may not be enough background
318
 *        to determine the map values accurately.  Typically, it is
319
 *        better to err by setting the threshold too high.
320
 *    (8) A %mincount threshold is a minimum count of pixels in a
321
 *        tile for which a background reading is made, in order for that
322
 *        pixel in the map to be valid.  This number should perhaps be
323
 *        at least 1/3 the size of the tile.
324
 *    (9) A %bgval target background value for the normalized image.  This
325
 *        should be at least 128.  If set too close to 255, some
326
 *        clipping will occur in the result.  It is recommended to use
327
 *        %bgval = 200.
328
 *    (10) Two factors, %smoothx and %smoothy, are input for smoothing
329
 *         the map.  Each low-pass filter kernel dimension is
330
 *         is 2 * (smoothing factor) + 1, so a
331
 *         value of 0 means no smoothing. A value of 1 or 2 is recommended.
332
 *    (11) See pixCleanBackgroundToWhite().  The recommended value for %bgval
333
 *         is 200.  As done there, pixBackgroundNorm() is typically followed
334
 *         by pixGammaTRC(), where the maxval must not not exceed %bgval.
335
 * </pre>
336
 */
337
PIX *
338
pixBackgroundNorm(PIX     *pixs,
339
                  PIX     *pixim,
340
                  PIX     *pixg,
341
                  l_int32  sx,
342
                  l_int32  sy,
343
                  l_int32  thresh,
344
                  l_int32  mincount,
345
                  l_int32  bgval,
346
                  l_int32  smoothx,
347
                  l_int32  smoothy)
348
0
{
349
0
l_int32  d, allfg;
350
0
PIX     *pixm, *pixmi, *pixd;
351
0
PIX     *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
352
353
0
    if (!pixs)
354
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
355
0
    d = pixGetDepth(pixs);
356
0
    if (d != 8 && d != 32)
357
0
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
358
0
    if (sx < 4 || sy < 4)
359
0
        return (PIX *)ERROR_PTR("sx and sy must be >= 4", __func__, NULL);
360
0
    if (mincount > sx * sy) {
361
0
        L_WARNING("mincount too large for tile size\n", __func__);
362
0
        mincount = (sx * sy) / 3;
363
0
    }
364
365
        /* If pixim exists, verify that it is not all foreground. */
366
0
    if (pixim) {
367
0
        pixInvert(pixim, pixim);
368
0
        pixZero(pixim, &allfg);
369
0
        pixInvert(pixim, pixim);
370
0
        if (allfg)
371
0
            return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL);
372
0
    }
373
374
0
    pixd = NULL;
375
0
    if (d == 8) {
376
0
        pixm = NULL;
377
0
        pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
378
0
        if (!pixm) {
379
0
            L_WARNING("map not made; return a copy of the source\n", __func__);
380
0
            return pixCopy(NULL, pixs);
381
0
        }
382
383
0
        pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
384
0
        if (!pixmi) {
385
0
            L_WARNING("pixmi not made; return a copy of source\n", __func__);
386
0
            pixDestroy(&pixm);
387
0
            return pixCopy(NULL, pixs);
388
0
        } else {
389
0
            pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy);
390
0
        }
391
392
0
        pixDestroy(&pixm);
393
0
        pixDestroy(&pixmi);
394
0
    }
395
0
    else {
396
0
        pixmr = pixmg = pixmb = NULL;
397
0
        pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh,
398
0
                               mincount, &pixmr, &pixmg, &pixmb);
399
0
        if (!pixmr || !pixmg || !pixmb) {
400
0
            pixDestroy(&pixmr);
401
0
            pixDestroy(&pixmg);
402
0
            pixDestroy(&pixmb);
403
0
            L_WARNING("map not made; return a copy of the source\n", __func__);
404
0
            return pixCopy(NULL, pixs);
405
0
        }
406
407
0
        pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
408
0
        pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
409
0
        pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
410
0
        if (!pixmri || !pixmgi || !pixmbi) {
411
0
            L_WARNING("not all pixm*i are made; return src copy\n", __func__);
412
0
            pixd = pixCopy(NULL, pixs);
413
0
        } else {
414
0
            pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
415
0
                                               sx, sy);
416
0
        }
417
418
0
        pixDestroy(&pixmr);
419
0
        pixDestroy(&pixmg);
420
0
        pixDestroy(&pixmb);
421
0
        pixDestroy(&pixmri);
422
0
        pixDestroy(&pixmgi);
423
0
        pixDestroy(&pixmbi);
424
0
    }
425
426
0
    if (!pixd)
427
0
        ERROR_PTR("pixd not made", __func__, NULL);
428
0
    pixCopyResolution(pixd, pixs);
429
0
    return pixd;
430
0
}
431
432
433
/*!
434
 * \brief   pixBackgroundNormMorph()
435
 *
436
 * \param[in]    pixs        8 bpp grayscale or 32 bpp rgb
437
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null
438
 * \param[in]    reduction   at which morph closings are done; between 2 and 16
439
 * \param[in]    size        of square Sel for the closing; use an odd number
440
 * \param[in]    bgval       target bg val; typ. > 128
441
 * \return  pixd 8 bpp, or NULL on error
442
 *
443
 * <pre>
444
 * Notes:
445
 *    (1) This is a top-level interface for normalizing the image intensity
446
 *        by mapping the image so that the background is near the input
447
 *        value 'bgval'.
448
 *    (2) The input image is either grayscale or rgb.
449
 *    (3) For each component in the input image, the background value
450
 *        is estimated using a grayscale closing; hence the 'Morph'
451
 *        in the function name.
452
 *    (4) An optional binary mask can be specified, with the foreground
453
 *        pixels typically over image regions.  The resulting background
454
 *        map values will be determined by surrounding pixels that are
455
 *        not under the mask foreground.  The origin (0,0) of this mask
456
 *        is assumed to be aligned with the origin of the input image.
457
 *        This binary mask must not fully cover pixs, because then there
458
 *        will be no pixels in the input image available to compute
459
 *        the background.
460
 *    (5) The map is computed at reduced size (given by 'reduction')
461
 *        from the input pixs and optional pixim.  At this scale,
462
 *        pixs is closed to remove the background, using a square Sel
463
 *        of odd dimension.  The product of reduction * size should be
464
 *        large enough to remove most of the text foreground.
465
 *    (6) No convolutional smoothing needs to be done on the map before
466
 *        inverting it.
467
 *    (7) A 'bgval' target background value for the normalized image.  This
468
 *        should be at least 128.  If set too close to 255, some
469
 *        clipping will occur in the result.
470
 * </pre>
471
 */
472
PIX *
473
pixBackgroundNormMorph(PIX     *pixs,
474
                       PIX     *pixim,
475
                       l_int32  reduction,
476
                       l_int32  size,
477
                       l_int32  bgval)
478
0
{
479
0
l_int32    d, allfg;
480
0
PIX       *pixm, *pixmi, *pixd;
481
0
PIX       *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
482
483
0
    if (!pixs)
484
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
485
0
    d = pixGetDepth(pixs);
486
0
    if (d != 8 && d != 32)
487
0
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
488
0
    if (reduction < 2 || reduction > 16)
489
0
        return (PIX *)ERROR_PTR("reduction must be between 2 and 16",
490
0
                                __func__, NULL);
491
492
        /* If pixim exists, verify that it is not all foreground. */
493
0
    if (pixim) {
494
0
        pixInvert(pixim, pixim);
495
0
        pixZero(pixim, &allfg);
496
0
        pixInvert(pixim, pixim);
497
0
        if (allfg)
498
0
            return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL);
499
0
    }
500
501
0
    pixd = NULL;
502
0
    if (d == 8) {
503
0
        pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
504
0
        if (!pixm)
505
0
            return (PIX *)ERROR_PTR("pixm not made", __func__, NULL);
506
0
        pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
507
0
        if (!pixmi)
508
0
            ERROR_PTR("pixmi not made", __func__, NULL);
509
0
        else
510
0
            pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi,
511
0
                                                reduction, reduction);
512
0
        pixDestroy(&pixm);
513
0
        pixDestroy(&pixmi);
514
0
    }
515
0
    else {  /* d == 32 */
516
0
        pixmr = pixmg = pixmb = NULL;
517
0
        pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
518
0
                                    &pixmr, &pixmg, &pixmb);
519
0
        if (!pixmr || !pixmg || !pixmb) {
520
0
            pixDestroy(&pixmr);
521
0
            pixDestroy(&pixmg);
522
0
            pixDestroy(&pixmb);
523
0
            return (PIX *)ERROR_PTR("not all pixm*", __func__, NULL);
524
0
        }
525
526
0
        pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
527
0
        pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
528
0
        pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
529
0
        if (!pixmri || !pixmgi || !pixmbi)
530
0
            ERROR_PTR("not all pixm*i are made", __func__, NULL);
531
0
        else
532
0
            pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
533
0
                                               reduction, reduction);
534
535
0
        pixDestroy(&pixmr);
536
0
        pixDestroy(&pixmg);
537
0
        pixDestroy(&pixmb);
538
0
        pixDestroy(&pixmri);
539
0
        pixDestroy(&pixmgi);
540
0
        pixDestroy(&pixmbi);
541
0
    }
542
543
0
    if (!pixd)
544
0
        ERROR_PTR("pixd not made", __func__, NULL);
545
0
    pixCopyResolution(pixd, pixs);
546
0
    return pixd;
547
0
}
548
549
550
/*-------------------------------------------------------------------------*
551
 *      Arrays of inverted background values for normalization             *
552
 *-------------------------------------------------------------------------*
553
 *  Notes for these four functions:                                        *
554
 *      (1) They are useful if you need to save the actual mapping array.  *
555
 *      (2) They could be used in the top-level functions but are          *
556
 *          not because their use makes those functions less clear.        *
557
 *      (3) Each component in the input pixs generates a 16 bpp pix array. *
558
 *-------------------------------------------------------------------------*/
559
/*!
560
 * \brief   pixBackgroundNormGrayArray()
561
 *
562
 * \param[in]    pixs       8 bpp grayscale
563
 * \param[in]    pixim      [optional] 1 bpp 'image' mask; can be null
564
 * \param[in]    sx, sy     tile size in pixels
565
 * \param[in]    thresh     threshold for determining foreground
566
 * \param[in]    mincount   min threshold on counts in a tile
567
 * \param[in]    bgval      target bg val; typ. > 128
568
 * \param[in]    smoothx    half-width of block convolution kernel width
569
 * \param[in]    smoothy    half-width of block convolution kernel height
570
 * \param[out]   ppixd      16 bpp array of inverted background value
571
 * \return  0 if OK, 1 on error
572
 *
573
 * <pre>
574
 * Notes:
575
 *    (1) See notes in pixBackgroundNorm().
576
 *    (2) This returns a 16 bpp pix that can be used by
577
 *        pixApplyInvBackgroundGrayMap() to generate a normalized version
578
 *        of the input pixs.
579
 * </pre>
580
 */
581
l_ok
582
pixBackgroundNormGrayArray(PIX     *pixs,
583
                           PIX     *pixim,
584
                           l_int32  sx,
585
                           l_int32  sy,
586
                           l_int32  thresh,
587
                           l_int32  mincount,
588
                           l_int32  bgval,
589
                           l_int32  smoothx,
590
                           l_int32  smoothy,
591
                           PIX    **ppixd)
592
0
{
593
0
l_int32  allfg;
594
0
PIX     *pixm;
595
596
0
    if (!ppixd)
597
0
        return ERROR_INT("&pixd not defined", __func__, 1);
598
0
    *ppixd = NULL;
599
0
    if (!pixs || pixGetDepth(pixs) != 8)
600
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
601
0
    if (pixGetColormap(pixs))
602
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
603
0
    if (pixim && pixGetDepth(pixim) != 1)
604
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
605
0
    if (sx < 4 || sy < 4)
606
0
        return ERROR_INT("sx and sy must be >= 4", __func__, 1);
607
0
    if (mincount > sx * sy) {
608
0
        L_WARNING("mincount too large for tile size\n", __func__);
609
0
        mincount = (sx * sy) / 3;
610
0
    }
611
612
        /* If pixim exists, verify that it is not all foreground. */
613
0
    if (pixim) {
614
0
        pixInvert(pixim, pixim);
615
0
        pixZero(pixim, &allfg);
616
0
        pixInvert(pixim, pixim);
617
0
        if (allfg)
618
0
            return ERROR_INT("pixim all foreground", __func__, 1);
619
0
    }
620
621
0
    pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
622
0
    if (!pixm)
623
0
        return ERROR_INT("pixm not made", __func__, 1);
624
0
    *ppixd = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
625
0
    pixCopyResolution(*ppixd, pixs);
626
0
    pixDestroy(&pixm);
627
0
    return 0;
628
0
}
629
630
631
/*!
632
 * \brief   pixBackgroundNormRGBArrays()
633
 *
634
 * \param[in]    pixs       32 bpp rgb
635
 * \param[in]    pixim      [optional] 1 bpp 'image' mask; can be null
636
 * \param[in]    pixg       [optional] 8 bpp grayscale version; can be null
637
 * \param[in]    sx, sy     tile size in pixels
638
 * \param[in]    thresh     threshold for determining foreground
639
 * \param[in]    mincount   min threshold on counts in a tile
640
 * \param[in]    bgval      target bg val; typ. > 128
641
 * \param[in]    smoothx    half-width of block convolution kernel width
642
 * \param[in]    smoothy    half-width of block convolution kernel height
643
 * \param[out]   ppixr      16 bpp array of inverted R background value
644
 * \param[out]   ppixg      16 bpp array of inverted G background value
645
 * \param[out]   ppixb      16 bpp array of inverted B background value
646
 * \return  0 if OK, 1 on error
647
 *
648
 * <pre>
649
 * Notes:
650
 *    (1) See notes in pixBackgroundNorm().
651
 *    (2) This returns a set of three 16 bpp pix that can be used by
652
 *        pixApplyInvBackgroundGrayMap() to generate a normalized version
653
 *        of each component of the input pixs.
654
 * </pre>
655
 */
656
l_ok
657
pixBackgroundNormRGBArrays(PIX     *pixs,
658
                           PIX     *pixim,
659
                           PIX     *pixg,
660
                           l_int32  sx,
661
                           l_int32  sy,
662
                           l_int32  thresh,
663
                           l_int32  mincount,
664
                           l_int32  bgval,
665
                           l_int32  smoothx,
666
                           l_int32  smoothy,
667
                           PIX    **ppixr,
668
                           PIX    **ppixg,
669
                           PIX    **ppixb)
670
0
{
671
0
l_int32  allfg;
672
0
PIX     *pixmr, *pixmg, *pixmb;
673
674
0
    if (!ppixr || !ppixg || !ppixb)
675
0
        return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1);
676
0
    *ppixr = *ppixg = *ppixb = NULL;
677
0
    if (!pixs)
678
0
        return ERROR_INT("pixs not defined", __func__, 1);
679
0
    if (pixGetDepth(pixs) != 32)
680
0
        return ERROR_INT("pixs not 32 bpp", __func__, 1);
681
0
    if (pixim && pixGetDepth(pixim) != 1)
682
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
683
0
    if (sx < 4 || sy < 4)
684
0
        return ERROR_INT("sx and sy must be >= 4", __func__, 1);
685
0
    if (mincount > sx * sy) {
686
0
        L_WARNING("mincount too large for tile size\n", __func__);
687
0
        mincount = (sx * sy) / 3;
688
0
    }
689
690
        /* If pixim exists, verify that it is not all foreground. */
691
0
    if (pixim) {
692
0
        pixInvert(pixim, pixim);
693
0
        pixZero(pixim, &allfg);
694
0
        pixInvert(pixim, pixim);
695
0
        if (allfg)
696
0
            return ERROR_INT("pixim all foreground", __func__, 1);
697
0
    }
698
699
0
    pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, mincount,
700
0
                           &pixmr, &pixmg, &pixmb);
701
0
    if (!pixmr || !pixmg || !pixmb) {
702
0
        pixDestroy(&pixmr);
703
0
        pixDestroy(&pixmg);
704
0
        pixDestroy(&pixmb);
705
0
        return ERROR_INT("not all pixm* made", __func__, 1);
706
0
    }
707
708
0
    *ppixr = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
709
0
    *ppixg = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
710
0
    *ppixb = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
711
0
    pixDestroy(&pixmr);
712
0
    pixDestroy(&pixmg);
713
0
    pixDestroy(&pixmb);
714
0
    return 0;
715
0
}
716
717
718
/*!
719
 * \brief   pixBackgroundNormGrayArrayMorph()
720
 *
721
 * \param[in]    pixs        8 bpp grayscale
722
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null
723
 * \param[in]    reduction   at which morph closings are done; between 2 and 16
724
 * \param[in]    size        of square Sel for the closing; use an odd number
725
 * \param[in]    bgval       target bg val; typ. > 128
726
 * \param[out]   ppixd       16 bpp array of inverted background value
727
 * \return  0 if OK, 1 on error
728
 *
729
 * <pre>
730
 * Notes:
731
 *    (1) See notes in pixBackgroundNormMorph().
732
 *    (2) This returns a 16 bpp pix that can be used by
733
 *        pixApplyInvBackgroundGrayMap() to generate a normalized version
734
 *        of the input pixs.
735
 * </pre>
736
 */
737
l_ok
738
pixBackgroundNormGrayArrayMorph(PIX     *pixs,
739
                                PIX     *pixim,
740
                                l_int32  reduction,
741
                                l_int32  size,
742
                                l_int32  bgval,
743
                                PIX    **ppixd)
744
0
{
745
0
l_int32  allfg;
746
0
PIX     *pixm;
747
748
0
    if (!ppixd)
749
0
        return ERROR_INT("&pixd not defined", __func__, 1);
750
0
    *ppixd = NULL;
751
0
    if (!pixs)
752
0
        return ERROR_INT("pixs not defined", __func__, 1);
753
0
    if (pixGetDepth(pixs) != 8)
754
0
        return ERROR_INT("pixs not 8 bpp", __func__, 1);
755
0
    if (pixim && pixGetDepth(pixim) != 1)
756
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
757
0
    if (reduction < 2 || reduction > 16)
758
0
        return ERROR_INT("reduction must be between 2 and 16", __func__, 1);
759
760
        /* If pixim exists, verify that it is not all foreground. */
761
0
    if (pixim) {
762
0
        pixInvert(pixim, pixim);
763
0
        pixZero(pixim, &allfg);
764
0
        pixInvert(pixim, pixim);
765
0
        if (allfg)
766
0
            return ERROR_INT("pixim all foreground", __func__, 1);
767
0
    }
768
769
0
    pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
770
0
    if (!pixm)
771
0
        return ERROR_INT("pixm not made", __func__, 1);
772
0
    *ppixd = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
773
0
    pixCopyResolution(*ppixd, pixs);
774
0
    pixDestroy(&pixm);
775
0
    return 0;
776
0
}
777
778
779
/*!
780
 * \brief   pixBackgroundNormRGBArraysMorph()
781
 *
782
 * \param[in]    pixs        32 bpp rgb
783
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null
784
 * \param[in]    reduction   at which morph closings are done; between 2 and 16
785
 * \param[in]    size        of square Sel for the closing; use an odd number
786
 * \param[in]    bgval       target bg val; typ. > 128
787
 * \param[out]   ppixr       16 bpp array of inverted R background value
788
 * \param[out]   ppixg       16 bpp array of inverted G background value
789
 * \param[out]   ppixb       16 bpp array of inverted B background value
790
 * \return  0 if OK, 1 on error
791
 *
792
 * <pre>
793
 * Notes:
794
 *    (1) See notes in pixBackgroundNormMorph().
795
 *    (2) This returns a set of three 16 bpp pix that can be used by
796
 *        pixApplyInvBackgroundGrayMap() to generate a normalized version
797
 *        of each component of the input pixs.
798
 * </pre>
799
 */
800
l_ok
801
pixBackgroundNormRGBArraysMorph(PIX     *pixs,
802
                                PIX     *pixim,
803
                                l_int32  reduction,
804
                                l_int32  size,
805
                                l_int32  bgval,
806
                                PIX    **ppixr,
807
                                PIX    **ppixg,
808
                                PIX    **ppixb)
809
0
{
810
0
l_int32  allfg;
811
0
PIX     *pixmr, *pixmg, *pixmb;
812
813
0
    if (!ppixr || !ppixg || !ppixb)
814
0
        return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1);
815
0
    *ppixr = *ppixg = *ppixb = NULL;
816
0
    if (!pixs)
817
0
        return ERROR_INT("pixs not defined", __func__, 1);
818
0
    if (pixGetDepth(pixs) != 32)
819
0
        return ERROR_INT("pixs not 32 bpp", __func__, 1);
820
0
    if (pixim && pixGetDepth(pixim) != 1)
821
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
822
0
    if (reduction < 2 || reduction > 16)
823
0
        return ERROR_INT("reduction must be between 2 and 16", __func__, 1);
824
825
        /* If pixim exists, verify that it is not all foreground. */
826
0
    if (pixim) {
827
0
        pixInvert(pixim, pixim);
828
0
        pixZero(pixim, &allfg);
829
0
        pixInvert(pixim, pixim);
830
0
        if (allfg)
831
0
            return ERROR_INT("pixim all foreground", __func__, 1);
832
0
    }
833
834
0
    pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
835
0
                                &pixmr, &pixmg, &pixmb);
836
0
    if (!pixmr || !pixmg || !pixmb) {
837
0
        pixDestroy(&pixmr);
838
0
        pixDestroy(&pixmg);
839
0
        pixDestroy(&pixmb);
840
0
        return ERROR_INT("not all pixm* made", __func__, 1);
841
0
    }
842
843
0
    *ppixr = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
844
0
    *ppixg = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
845
0
    *ppixb = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
846
0
    pixDestroy(&pixmr);
847
0
    pixDestroy(&pixmg);
848
0
    pixDestroy(&pixmb);
849
0
    return 0;
850
0
}
851
852
853
/*------------------------------------------------------------------*
854
 *                 Measurement of local background                  *
855
 *------------------------------------------------------------------*/
856
/*!
857
 * \brief   pixGetBackgroundGrayMap()
858
 *
859
 * \param[in]    pixs       8 bpp grayscale; not cmapped
860
 * \param[in]    pixim      [optional] 1 bpp 'image' mask; can be null;
861
 *                          it should not have only foreground pixels
862
 * \param[in]    sx, sy     tile size in pixels
863
 * \param[in]    thresh     threshold for determining foreground
864
 * \param[in]    mincount   min threshold on counts in a tile
865
 * \param[out]   ppixd      8 bpp grayscale map
866
 * \return  0 if OK, 1 on error
867
 *
868
 * <pre>
869
 * Notes:
870
 *      (1) The background is measured in regions that don't have
871
 *          images.  It is then propagated into the image regions,
872
 *          and finally smoothed in each image region.
873
 * </pre>
874
 */
875
l_ok
876
pixGetBackgroundGrayMap(PIX     *pixs,
877
                        PIX     *pixim,
878
                        l_int32  sx,
879
                        l_int32  sy,
880
                        l_int32  thresh,
881
                        l_int32  mincount,
882
                        PIX    **ppixd)
883
0
{
884
0
l_int32    w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf;
885
0
l_int32    xim, yim, delx, nx, ny, i, j, k, m;
886
0
l_int32    count, sum, val8;
887
0
l_int32    empty, fgpixels;
888
0
l_uint32  *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef;
889
0
l_float32  scalex, scaley;
890
0
PIX       *pixd, *piximi, *pixb, *pixf, *pixims;
891
892
0
    if (!ppixd)
893
0
        return ERROR_INT("&pixd not defined", __func__, 1);
894
0
    *ppixd = NULL;
895
0
    if (!pixs || pixGetDepth(pixs) != 8)
896
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
897
0
    if (pixGetColormap(pixs))
898
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
899
0
    if (pixim && pixGetDepth(pixim) != 1)
900
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
901
0
    if (sx < 4 || sy < 4)
902
0
        return ERROR_INT("sx and sy must be >= 4", __func__, 1);
903
0
    if (mincount > sx * sy) {
904
0
        L_WARNING("mincount too large for tile size\n", __func__);
905
0
        mincount = (sx * sy) / 3;
906
0
    }
907
908
        /* Evaluate the 'image' mask, pixim, and make sure
909
         * it is not all fg. */
910
0
    fgpixels = 0;  /* boolean for existence of fg pixels in the image mask. */
911
0
    if (pixim) {
912
0
        piximi = pixInvert(NULL, pixim);  /* set non-'image' pixels to 1 */
913
0
        pixZero(piximi, &empty);
914
0
        pixDestroy(&piximi);
915
0
        if (empty)
916
0
            return ERROR_INT("pixim all fg; no background", __func__, 1);
917
0
        pixZero(pixim, &empty);
918
0
        if (!empty)  /* there are fg pixels in pixim */
919
0
            fgpixels = 1;
920
0
    }
921
922
        /* Generate the foreground mask, pixf, which is at full resolution.
923
         * mask has a value 0 for pixels in pixs that are likely to be
924
         * in the background, which is the condition for including
925
         * those source pixels when taking an average of the background
926
         * values for each tile.  */
927
0
    pixb = pixThresholdToBinary(pixs, thresh);
928
0
    pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
929
0
    pixDestroy(&pixb);
930
0
    if (!pixf)
931
0
        return ERROR_INT("pixf not made", __func__, 1);
932
933
934
    /* ------------- Set up the output map pixd --------------- */
935
        /* In pixd, each 8 bit pixel represents a tile of size (sx, sy)
936
         * in pixs.  Each pixel in pixd will be filled with the average
937
         * background value of that tile in pixs.  */
938
0
    w = pixGetWidth(pixs);
939
0
    h = pixGetHeight(pixs);
940
0
    wd = (w + sx - 1) / sx;
941
0
    hd = (h + sy - 1) / sy;
942
0
    pixd = pixCreate(wd, hd, 8);
943
944
        /* Here we only compute map values in pixd for tiles that
945
         * are complete.  In general, the extreme right and bottom
946
         * tiles in pixs, which correspond to the rightmost column
947
         * and bottom row of pixd, are not complete.  These will
948
         * be filled in later.
949
         *
950
         * Use the full resolution mask pixf to decide which pixels
951
         * are used in each tile to estimate the background value.
952
         * After this operation, pixels in the background map pixd
953
         * that have not been set must be filled using adjacent pixels. */
954
0
    nx = w / sx;
955
0
    ny = h / sy;
956
0
    wpls = pixGetWpl(pixs);
957
0
    datas = pixGetData(pixs);
958
0
    wpld = pixGetWpl(pixd);
959
0
    datad = pixGetData(pixd);
960
0
    wplf = pixGetWpl(pixf);
961
0
    dataf = pixGetData(pixf);
962
0
    for (i = 0; i < ny; i++) {
963
0
        lines = datas + sy * i * wpls;
964
0
        linef = dataf + sy * i * wplf;
965
0
        lined = datad + i * wpld;
966
0
        for (j = 0; j < nx; j++) {
967
0
            delx = j * sx;
968
0
            sum = 0;
969
0
            count = 0;
970
0
            for (k = 0; k < sy; k++) {
971
0
                for (m = 0; m < sx; m++) {
972
0
                    if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
973
0
                        sum += GET_DATA_BYTE(lines + k * wpls, delx + m);
974
0
                        count++;
975
0
                    }
976
0
                }
977
0
            }
978
0
            if (count >= mincount) {
979
0
                val8 = sum / count;
980
0
                SET_DATA_BYTE(lined, j, val8);
981
0
            }
982
0
        }
983
0
    }
984
0
    pixDestroy(&pixf);
985
986
        /* If there is an optional mask with fg pixels, erase the previous
987
         * calculation for the corresponding map pixels, setting the
988
         * map values to 0.   Then, when all the map holes are filled,
989
         * these erased pixels will be set by the surrounding map values,
990
         * along with the ones not set above using the background values.
991
         *
992
         * The calculation here is relatively efficient: for each pixel
993
         * in pixd (which corresponds to a tile of mask pixels in pixim)
994
         * we look only at the pixel in pixim that is at the center
995
         * of the tile.  If the mask pixel is ON, we reset the map
996
         * pixel in pixd to 0, so that it can later be filled in. */
997
0
    pixims = NULL;
998
0
    if (pixim && fgpixels) {
999
0
        wim = pixGetWidth(pixim);
1000
0
        him = pixGetHeight(pixim);
1001
0
        dataim = pixGetData(pixim);
1002
0
        wplim = pixGetWpl(pixim);
1003
0
        for (i = 0; i < ny; i++) {
1004
0
            yim = i * sy + sy / 2;
1005
0
            if (yim >= him)
1006
0
                break;
1007
0
            lineim = dataim + yim * wplim;
1008
0
            for (j = 0; j < nx; j++) {
1009
0
                xim = j * sx + sx / 2;
1010
0
                if (xim >= wim)
1011
0
                    break;
1012
0
                if (GET_DATA_BIT(lineim, xim))
1013
0
                    pixSetPixel(pixd, j, i, 0);
1014
0
            }
1015
0
        }
1016
0
    }
1017
1018
        /* Fill all the holes in the map.
1019
         * Note that (nx,ny) represent the numbers of complete tiles
1020
         * in the x and y directions of pixs, whereas the dimensions
1021
         * of pixd are usually larger by 1. */
1022
0
    if (pixFillMapHoles(pixd, nx, ny, L_FILL_BLACK)) {
1023
0
        pixDestroy(&pixd);
1024
0
        L_WARNING("can't fill holes and make the map\n", __func__);
1025
0
        return 1;
1026
0
    }
1027
1028
        /* Finally, for each connected region corresponding to the
1029
         * 'image' mask, reset all pixels to their average value.
1030
         * Each of these components represents an image (or part of one)
1031
         * in the input, and this smooths the background values
1032
         * in each of these regions. */
1033
0
    if (pixim && fgpixels) {
1034
0
        scalex = 1. / (l_float32)sx;
1035
0
        scaley = 1. / (l_float32)sy;
1036
0
        pixims = pixScaleBySampling(pixim, scalex, scaley);
1037
0
        pixSmoothConnectedRegions(pixd, pixims, 2);
1038
0
        pixDestroy(&pixims);
1039
0
    }
1040
1041
0
    *ppixd = pixd;
1042
0
    pixCopyResolution(*ppixd, pixs);
1043
0
    return 0;
1044
0
}
1045
1046
1047
/*!
1048
 * \brief   pixGetBackgroundRGBMap()
1049
 *
1050
 * \param[in]    pixs       32 bpp rgb
1051
 * \param[in]    pixim      [optional] 1 bpp 'image' mask; can be null; it
1052
 *                          should not have all foreground pixels
1053
 * \param[in]    pixg       [optional] 8 bpp grayscale version; can be null
1054
 * \param[in]    sx, sy     tile size in pixels
1055
 * \param[in]    thresh     threshold for determining foreground
1056
 * \param[in]    mincount   min threshold on counts in a tile
1057
 * \param[out]   ppixmr     red component map
1058
 * \param[out]   ppixmg     green component map
1059
 * \param[out]   ppixmb     blue component map
1060
 * \return  0 if OK, 1 on error
1061
 *
1062
 * <pre>
1063
 * Notes:
1064
 *      (1) If pixg, which is a grayscale version of pixs, is provided,
1065
 *          use this internally to generate the foreground mask.
1066
 *          Otherwise, a grayscale version of pixs will be generated
1067
 *          from the green component only, used, and destroyed.
1068
 * </pre>
1069
 */
1070
l_ok
1071
pixGetBackgroundRGBMap(PIX     *pixs,
1072
                       PIX     *pixim,
1073
                       PIX     *pixg,
1074
                       l_int32  sx,
1075
                       l_int32  sy,
1076
                       l_int32  thresh,
1077
                       l_int32  mincount,
1078
                       PIX    **ppixmr,
1079
                       PIX    **ppixmg,
1080
                       PIX    **ppixmb)
1081
0
{
1082
0
l_int32    w, h, wm, hm, wim, him, wpls, wplim, wplf;
1083
0
l_int32    xim, yim, delx, nx, ny, i, j, k, m;
1084
0
l_int32    count, rsum, gsum, bsum, rval, gval, bval;
1085
0
l_int32    empty, fgpixels;
1086
0
l_uint32   pixel;
1087
0
l_uint32  *datas, *dataim, *dataf, *lines, *lineim, *linef;
1088
0
l_float32  scalex, scaley;
1089
0
PIX       *piximi, *pixgc, *pixb, *pixf, *pixims;
1090
0
PIX       *pixmr, *pixmg, *pixmb;
1091
1092
0
    if (!ppixmr || !ppixmg || !ppixmb)
1093
0
        return ERROR_INT("&pixm* not all defined", __func__, 1);
1094
0
    *ppixmr = *ppixmg = *ppixmb = NULL;
1095
0
    if (!pixs)
1096
0
        return ERROR_INT("pixs not defined", __func__, 1);
1097
0
    if (pixGetDepth(pixs) != 32)
1098
0
        return ERROR_INT("pixs not 32 bpp", __func__, 1);
1099
0
    if (pixim && pixGetDepth(pixim) != 1)
1100
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
1101
0
    if (sx < 4 || sy < 4)
1102
0
        return ERROR_INT("sx and sy must be >= 4", __func__, 1);
1103
0
    if (mincount > sx * sy) {
1104
0
        L_WARNING("mincount too large for tile size\n", __func__);
1105
0
        mincount = (sx * sy) / 3;
1106
0
    }
1107
1108
        /* Evaluate the mask pixim and make sure it is not all foreground */
1109
0
    fgpixels = 0;  /* boolean for existence of fg mask pixels */
1110
0
    if (pixim) {
1111
0
        piximi = pixInvert(NULL, pixim);  /* set non-'image' pixels to 1 */
1112
0
        pixZero(piximi, &empty);
1113
0
        pixDestroy(&piximi);
1114
0
        if (empty)
1115
0
            return ERROR_INT("pixim all fg; no background", __func__, 1);
1116
0
        pixZero(pixim, &empty);
1117
0
        if (!empty)  /* there are fg pixels in pixim */
1118
0
            fgpixels = 1;
1119
0
    }
1120
1121
        /* Generate the foreground mask.  These pixels will be
1122
         * ignored when computing the background values. */
1123
0
    if (pixg)  /* use the input grayscale version if it is provided */
1124
0
        pixgc = pixClone(pixg);
1125
0
    else
1126
0
        pixgc = pixConvertRGBToGrayFast(pixs);
1127
0
    pixb = pixThresholdToBinary(pixgc, thresh);
1128
0
    pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
1129
0
    pixDestroy(&pixgc);
1130
0
    pixDestroy(&pixb);
1131
1132
        /* Generate the output mask images */
1133
0
    w = pixGetWidth(pixs);
1134
0
    h = pixGetHeight(pixs);
1135
0
    wm = (w + sx - 1) / sx;
1136
0
    hm = (h + sy - 1) / sy;
1137
0
    pixmr = pixCreate(wm, hm, 8);
1138
0
    pixmg = pixCreate(wm, hm, 8);
1139
0
    pixmb = pixCreate(wm, hm, 8);
1140
1141
    /* ------------- Set up the mapping images --------------- */
1142
        /* Note: we only compute map values in tiles that are complete.
1143
         * In general, tiles at right and bottom edges will not be
1144
         * complete, and we must fill them in later. */
1145
0
    nx = w / sx;
1146
0
    ny = h / sy;
1147
0
    wpls = pixGetWpl(pixs);
1148
0
    datas = pixGetData(pixs);
1149
0
    wplf = pixGetWpl(pixf);
1150
0
    dataf = pixGetData(pixf);
1151
0
    for (i = 0; i < ny; i++) {
1152
0
        lines = datas + sy * i * wpls;
1153
0
        linef = dataf + sy * i * wplf;
1154
0
        for (j = 0; j < nx; j++) {
1155
0
            delx = j * sx;
1156
0
            rsum = gsum = bsum = 0;
1157
0
            count = 0;
1158
0
            for (k = 0; k < sy; k++) {
1159
0
                for (m = 0; m < sx; m++) {
1160
0
                    if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
1161
0
                        pixel = *(lines + k * wpls + delx + m);
1162
0
                        rsum += (pixel >> 24);
1163
0
                        gsum += ((pixel >> 16) & 0xff);
1164
0
                        bsum += ((pixel >> 8) & 0xff);
1165
0
                        count++;
1166
0
                    }
1167
0
                }
1168
0
            }
1169
0
            if (count >= mincount) {
1170
0
                rval = rsum / count;
1171
0
                gval = gsum / count;
1172
0
                bval = bsum / count;
1173
0
                pixSetPixel(pixmr, j, i, rval);
1174
0
                pixSetPixel(pixmg, j, i, gval);
1175
0
                pixSetPixel(pixmb, j, i, bval);
1176
0
            }
1177
0
        }
1178
0
    }
1179
0
    pixDestroy(&pixf);
1180
1181
        /* If there is an optional mask with fg pixels, erase the previous
1182
         * calculation for the corresponding map pixels, setting the
1183
         * map values in each of the 3 color maps to 0.   Then, when
1184
         * all the map holes are filled, these erased pixels will
1185
         * be set by the surrounding map values. */
1186
0
    if (pixim) {
1187
0
        wim = pixGetWidth(pixim);
1188
0
        him = pixGetHeight(pixim);
1189
0
        dataim = pixGetData(pixim);
1190
0
        wplim = pixGetWpl(pixim);
1191
0
        for (i = 0; i < ny; i++) {
1192
0
            yim = i * sy + sy / 2;
1193
0
            if (yim >= him)
1194
0
                break;
1195
0
            lineim = dataim + yim * wplim;
1196
0
            for (j = 0; j < nx; j++) {
1197
0
                xim = j * sx + sx / 2;
1198
0
                if (xim >= wim)
1199
0
                    break;
1200
0
                if (GET_DATA_BIT(lineim, xim)) {
1201
0
                    pixSetPixel(pixmr, j, i, 0);
1202
0
                    pixSetPixel(pixmg, j, i, 0);
1203
0
                    pixSetPixel(pixmb, j, i, 0);
1204
0
                }
1205
0
            }
1206
0
        }
1207
0
    }
1208
1209
    /* ----------------- Now fill in the holes ----------------------- */
1210
0
    if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
1211
0
        pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
1212
0
        pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
1213
0
        pixDestroy(&pixmr);
1214
0
        pixDestroy(&pixmg);
1215
0
        pixDestroy(&pixmb);
1216
0
        L_WARNING("can't make the maps\n", __func__);
1217
0
        return 1;
1218
0
    }
1219
1220
        /* Finally, for each connected region corresponding to the
1221
         * fg mask, reset all pixels to their average value. */
1222
0
    if (pixim && fgpixels) {
1223
0
        scalex = 1. / (l_float32)sx;
1224
0
        scaley = 1. / (l_float32)sy;
1225
0
        pixims = pixScaleBySampling(pixim, scalex, scaley);
1226
0
        pixSmoothConnectedRegions(pixmr, pixims, 2);
1227
0
        pixSmoothConnectedRegions(pixmg, pixims, 2);
1228
0
        pixSmoothConnectedRegions(pixmb, pixims, 2);
1229
0
        pixDestroy(&pixims);
1230
0
    }
1231
1232
0
    *ppixmr = pixmr;
1233
0
    *ppixmg = pixmg;
1234
0
    *ppixmb = pixmb;
1235
0
    pixCopyResolution(*ppixmr, pixs);
1236
0
    pixCopyResolution(*ppixmg, pixs);
1237
0
    pixCopyResolution(*ppixmb, pixs);
1238
0
    return 0;
1239
0
}
1240
1241
1242
/*!
1243
 * \brief   pixGetBackgroundGrayMapMorph()
1244
 *
1245
 * \param[in]    pixs        8 bpp grayscale; not cmapped
1246
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null; it
1247
 *                           should not have all foreground pixels
1248
 * \param[in]    reduction   factor at which closing is performed
1249
 * \param[in]    size        of square Sel for the closing; use an odd number
1250
 * \param[out]   ppixm       grayscale map
1251
 * \return  0 if OK, 1 on error
1252
 */
1253
l_ok
1254
pixGetBackgroundGrayMapMorph(PIX     *pixs,
1255
                             PIX     *pixim,
1256
                             l_int32  reduction,
1257
                             l_int32  size,
1258
                             PIX    **ppixm)
1259
0
{
1260
0
l_int32    nx, ny, empty, fgpixels;
1261
0
l_float32  scale;
1262
0
PIX       *pixm, *pix1, *pix2, *pix3, *pixims;
1263
1264
0
    if (!ppixm)
1265
0
        return ERROR_INT("&pixm not defined", __func__, 1);
1266
0
    *ppixm = NULL;
1267
0
    if (!pixs || pixGetDepth(pixs) != 8)
1268
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
1269
0
    if (pixGetColormap(pixs))
1270
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
1271
0
    if (pixim && pixGetDepth(pixim) != 1)
1272
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
1273
1274
        /* Evaluate the mask pixim and make sure it is not all foreground. */
1275
0
    fgpixels = 0;  /* boolean for existence of fg mask pixels */
1276
0
    if (pixim) {
1277
0
        pixInvert(pixim, pixim);  /* set background pixels to 1 */
1278
0
        pixZero(pixim, &empty);
1279
0
        if (empty)
1280
0
            return ERROR_INT("pixim all fg; no background", __func__, 1);
1281
0
        pixInvert(pixim, pixim);  /* revert to original mask */
1282
0
        pixZero(pixim, &empty);
1283
0
        if (!empty)  /* there are fg pixels in pixim */
1284
0
            fgpixels = 1;
1285
0
    }
1286
1287
        /* Downscale as requested and do the closing to get the background. */
1288
0
    scale = 1. / (l_float32)reduction;
1289
0
    pix1 = pixScaleBySampling(pixs, scale, scale);
1290
0
    pix2 = pixCloseGray(pix1, size, size);
1291
0
    pix3 = pixExtendByReplication(pix2, 1, 1);
1292
0
    pixDestroy(&pix1);
1293
0
    pixDestroy(&pix2);
1294
1295
        /* Downscale the image mask, if any, and remove it from the
1296
         * background.  These pixels will be filled in (twice). */
1297
0
    pixims = NULL;
1298
0
    if (pixim) {
1299
0
        pixims = pixScale(pixim, scale, scale);
1300
0
        pixm = pixConvertTo8(pixims, FALSE);
1301
0
        pixAnd(pixm, pixm, pix3);
1302
0
    }
1303
0
    else
1304
0
        pixm = pixClone(pix3);
1305
0
    pixDestroy(&pix3);
1306
1307
        /* Fill all the holes in the map. */
1308
0
    nx = pixGetWidth(pixs) / reduction;
1309
0
    ny = pixGetHeight(pixs) / reduction;
1310
0
    if (pixFillMapHoles(pixm, nx, ny, L_FILL_BLACK)) {
1311
0
        pixDestroy(&pixm);
1312
0
        pixDestroy(&pixims);
1313
0
        L_WARNING("can't make the map\n", __func__);
1314
0
        return 1;
1315
0
    }
1316
1317
        /* Finally, for each connected region corresponding to the
1318
         * fg mask, reset all pixels to their average value. */
1319
0
    if (pixim && fgpixels)
1320
0
        pixSmoothConnectedRegions(pixm, pixims, 2);
1321
0
    pixDestroy(&pixims);
1322
1323
0
    *ppixm = pixm;
1324
0
    pixCopyResolution(*ppixm, pixs);
1325
0
    return 0;
1326
0
}
1327
1328
1329
/*!
1330
 * \brief   pixGetBackgroundRGBMapMorph()
1331
 *
1332
 * \param[in]    pixs        32 bpp rgb
1333
 * \param[in]    pixim       [optional] 1 bpp 'image' mask; can be null; it
1334
 *                           should not have all foreground pixels
1335
 * \param[in]    reduction   factor at which closing is performed
1336
 * \param[in]    size        of square Sel for the closing; use an odd number
1337
 * \param[out]   ppixmr      red component map
1338
 * \param[out]   ppixmg      green component map
1339
 * \param[out]   ppixmb      blue component map
1340
 * \return  0 if OK, 1 on error
1341
 */
1342
l_ok
1343
pixGetBackgroundRGBMapMorph(PIX     *pixs,
1344
                            PIX     *pixim,
1345
                            l_int32  reduction,
1346
                            l_int32  size,
1347
                            PIX    **ppixmr,
1348
                            PIX    **ppixmg,
1349
                            PIX    **ppixmb)
1350
0
{
1351
0
l_int32    nx, ny, empty, fgpixels;
1352
0
l_float32  scale;
1353
0
PIX       *pixm, *pixmr, *pixmg, *pixmb, *pix1, *pix2, *pix3, *pixims;
1354
1355
0
    if (!ppixmr || !ppixmg || !ppixmb)
1356
0
        return ERROR_INT("&pixm* not all defined", __func__, 1);
1357
0
    *ppixmr = *ppixmg = *ppixmb = NULL;
1358
0
    if (!pixs)
1359
0
        return ERROR_INT("pixs not defined", __func__, 1);
1360
0
    if (pixGetDepth(pixs) != 32)
1361
0
        return ERROR_INT("pixs not 32 bpp", __func__, 1);
1362
0
    if (pixim && pixGetDepth(pixim) != 1)
1363
0
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
1364
1365
        /* Evaluate the mask pixim and make sure it is not all foreground. */
1366
0
    fgpixels = 0;  /* boolean for existence of fg mask pixels */
1367
0
    if (pixim) {
1368
0
        pixInvert(pixim, pixim);  /* set background pixels to 1 */
1369
0
        pixZero(pixim, &empty);
1370
0
        if (empty)
1371
0
            return ERROR_INT("pixim all fg; no background", __func__, 1);
1372
0
        pixInvert(pixim, pixim);  /* revert to original mask */
1373
0
        pixZero(pixim, &empty);
1374
0
        if (!empty)  /* there are fg pixels in pixim */
1375
0
            fgpixels = 1;
1376
0
    }
1377
1378
        /* Generate an 8 bpp version of the image mask, if it exists */
1379
0
    scale = 1. / (l_float32)reduction;
1380
0
    pixims = NULL;
1381
0
    pixm = NULL;
1382
0
    if (pixim) {
1383
0
        pixims = pixScale(pixim, scale, scale);
1384
0
        pixm = pixConvertTo8(pixims, FALSE);
1385
0
    }
1386
1387
        /* Downscale as requested and do the closing to get the background.
1388
         * Then remove the image mask pixels from the background.  They
1389
         * will be filled in (twice) later.  Do this for all 3 components. */
1390
0
    pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED);
1391
0
    pix2 = pixCloseGray(pix1, size, size);
1392
0
    pix3 = pixExtendByReplication(pix2, 1, 1);
1393
0
    if (pixim)
1394
0
        pixmr = pixAnd(NULL, pixm, pix3);
1395
0
    else
1396
0
        pixmr = pixClone(pix3);
1397
0
    pixDestroy(&pix1);
1398
0
    pixDestroy(&pix2);
1399
0
    pixDestroy(&pix3);
1400
1401
0
    pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN);
1402
0
    pix2 = pixCloseGray(pix1, size, size);
1403
0
    pix3 = pixExtendByReplication(pix2, 1, 1);
1404
0
    if (pixim)
1405
0
        pixmg = pixAnd(NULL, pixm, pix3);
1406
0
    else
1407
0
        pixmg = pixClone(pix3);
1408
0
    pixDestroy(&pix1);
1409
0
    pixDestroy(&pix2);
1410
0
    pixDestroy(&pix3);
1411
1412
0
    pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE);
1413
0
    pix2 = pixCloseGray(pix1, size, size);
1414
0
    pix3 = pixExtendByReplication(pix2, 1, 1);
1415
0
    if (pixim)
1416
0
        pixmb = pixAnd(NULL, pixm, pix3);
1417
0
    else
1418
0
        pixmb = pixClone(pix3);
1419
0
    pixDestroy(&pixm);
1420
0
    pixDestroy(&pix1);
1421
0
    pixDestroy(&pix2);
1422
0
    pixDestroy(&pix3);
1423
1424
        /* Fill all the holes in the three maps. */
1425
0
    nx = pixGetWidth(pixs) / reduction;
1426
0
    ny = pixGetHeight(pixs) / reduction;
1427
0
    if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
1428
0
        pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
1429
0
        pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
1430
0
        pixDestroy(&pixmr);
1431
0
        pixDestroy(&pixmg);
1432
0
        pixDestroy(&pixmb);
1433
0
        pixDestroy(&pixims);
1434
0
        L_WARNING("can't make the maps\n", __func__);
1435
0
        return 1;
1436
0
    }
1437
1438
        /* Finally, for each connected region corresponding to the
1439
         * fg mask in each component, reset all pixels to their
1440
         * average value. */
1441
0
    if (pixim && fgpixels) {
1442
0
        pixSmoothConnectedRegions(pixmr, pixims, 2);
1443
0
        pixSmoothConnectedRegions(pixmg, pixims, 2);
1444
0
        pixSmoothConnectedRegions(pixmb, pixims, 2);
1445
0
        pixDestroy(&pixims);
1446
0
    }
1447
1448
0
    *ppixmr = pixmr;
1449
0
    *ppixmg = pixmg;
1450
0
    *ppixmb = pixmb;
1451
0
    pixCopyResolution(*ppixmr, pixs);
1452
0
    pixCopyResolution(*ppixmg, pixs);
1453
0
    pixCopyResolution(*ppixmb, pixs);
1454
0
    return 0;
1455
0
}
1456
1457
1458
/*!
1459
 * \brief   pixFillMapHoles()
1460
 *
1461
 * \param[in]    pix        8 bpp; a map, with one pixel for each tile in
1462
 *                          a larger image
1463
 * \param[in]    nx         number of horizontal pixel tiles that are entirely
1464
 *                          covered with pixels in the original source image
1465
 * \param[in]    ny         ditto for the number of vertical pixel tiles
1466
 * \param[in]    filltype   L_FILL_WHITE or L_FILL_BLACK
1467
 * \return  0 if OK, 1 on error
1468
 *
1469
 * <pre>
1470
 * Notes:
1471
 *      (1) This is an in-place operation on pix (the map).  pix is
1472
 *          typically a low-resolution version of some other image
1473
 *          from which it was derived, where each pixel in pix
1474
 *          corresponds to a rectangular tile (say, m x n) of pixels
1475
 *          in the larger image.  All we need to know about the larger
1476
 *          image is whether or not the rightmost column and bottommost
1477
 *          row of pixels in pix correspond to tiles that are
1478
 *          only partially covered by pixels in the larger image.
1479
 *      (2) Typically, some number of pixels in the input map are
1480
 *          not known, and their values must be determined by near
1481
 *          pixels that are known.  These unknown pixels are the 'holes'.
1482
 *          They can take on only two values, 0 and 255, and the
1483
 *          instruction about which to fill is given by the filltype flag.
1484
 *      (3) The "holes" can come from two sources.  The first is when there
1485
 *          are not enough foreground or background pixels in a tile;
1486
 *          the second is when a tile is at least partially covered
1487
 *          by an image mask.  If we're filling holes in a fg mask,
1488
 *          the holes are initialized to black (0) and use L_FILL_BLACK.
1489
 *          For filling holes in a bg mask, initialize the holes to
1490
 *          white (255) and use L_FILL_WHITE.
1491
 *      (4) If w is the map width, nx = w or nx = w - 1; ditto for h and ny.
1492
 * </pre>
1493
 */
1494
l_ok
1495
pixFillMapHoles(PIX     *pix,
1496
                l_int32  nx,
1497
                l_int32  ny,
1498
                l_int32  filltype)
1499
0
{
1500
0
l_int32   w, h, y, nmiss, goodcol, i, j, found, ival, valtest;
1501
0
l_uint32  val, lastval;
1502
0
NUMA     *na;  /* indicates if there is any data in the column */
1503
1504
0
    if (!pix || pixGetDepth(pix) != 8)
1505
0
        return ERROR_INT("pix not defined or not 8 bpp", __func__, 1);
1506
0
    if (pixGetColormap(pix))
1507
0
        return ERROR_INT("pix is colormapped", __func__, 1);
1508
1509
    /* ------------- Fill holes in the mapping image columns ----------- */
1510
0
    pixGetDimensions(pix, &w, &h, NULL);
1511
0
    na = numaCreate(0);  /* holds flag for which columns have data */
1512
0
    nmiss = 0;
1513
0
    valtest = (filltype == L_FILL_WHITE) ? 255 : 0;
1514
1515
        /* For columns that have at least one valid pixel, fill in the
1516
         * holes in that column, and enter 1 in the array na.  Otherwise,
1517
         * enter 0 in the array. */
1518
0
    for (j = 0; j < nx; j++) {  /* do it by columns */
1519
0
        found = FALSE;
1520
0
        for (i = 0; i < ny; i++) {
1521
0
            pixGetPixel(pix, j, i, &val);
1522
0
            if (val != valtest) {
1523
0
                y = i;
1524
0
                found = TRUE;
1525
0
                break;
1526
0
            }
1527
0
        }
1528
0
        if (found == FALSE) {
1529
0
            numaAddNumber(na, 0);  /* no data in the column */
1530
0
            nmiss++;
1531
0
        } else {
1532
0
            numaAddNumber(na, 1);  /* data in the column */
1533
0
            for (i = y - 1; i >= 0; i--)  /* replicate upwards to top */
1534
0
                pixSetPixel(pix, j, i, val);
1535
0
            pixGetPixel(pix, j, 0, &lastval);
1536
0
            for (i = 1; i < h; i++) {  /* set going down to bottom */
1537
0
                pixGetPixel(pix, j, i, &val);
1538
0
                if (val == valtest)
1539
0
                    pixSetPixel(pix, j, i, lastval);
1540
0
                else
1541
0
                    lastval = val;
1542
0
            }
1543
0
        }
1544
0
    }
1545
1546
0
    if (nmiss == nx) {  /* no data in any column! */
1547
0
        numaDestroy(&na);
1548
0
        L_WARNING("no bg found; no data in any column\n", __func__);
1549
0
        return 1;
1550
0
    }
1551
1552
    /* ---------- Fill in missing columns by replication ----------- */
1553
0
    if (nmiss > 0) {  /* replicate columns */
1554
            /* Find the first good column */
1555
0
        goodcol = 0;
1556
0
        for (j = 0; j < nx; j++) {
1557
0
            numaGetIValue(na, j, &ival);
1558
0
            if (ival == 1) {
1559
0
                goodcol = j;
1560
0
                break;
1561
0
            }
1562
0
        }
1563
            /* Copy the columns backward to column 0 */
1564
0
        if (goodcol > 0) {
1565
0
            for (j = goodcol - 1; j >= 0; j--)
1566
0
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j + 1, 0);
1567
0
        }
1568
            /* Copy the columns forward to column nx - 1 */
1569
0
        for (j = goodcol + 1; j < nx; j++) {
1570
0
            numaGetIValue(na, j, &ival);
1571
0
            if (ival == 0) {  /* empty; copy from column to the left of j */
1572
0
                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j - 1, 0);
1573
0
            }
1574
0
        }
1575
0
    }
1576
0
    if (w > nx) {  /* replicate the last column */
1577
0
        pixRasterop(pix, w - 1, 0, 1, h, PIX_SRC, pix, w - 2, 0);
1578
0
    }
1579
1580
0
    numaDestroy(&na);
1581
0
    return 0;
1582
0
}
1583
1584
1585
/*!
1586
 * \brief   pixExtendByReplication()
1587
 *
1588
 * \param[in]    pixs    8 bpp
1589
 * \param[in]    addw    number of extra pixels horizontally to add
1590
 * \param[in]    addh    number of extra pixels vertically to add
1591
 * \return  pixd extended with replicated pixel values, or NULL on error
1592
 *
1593
 * <pre>
1594
 * Notes:
1595
 *      (1) The pixel values are extended to the left and down, as required.
1596
 * </pre>
1597
 */
1598
PIX *
1599
pixExtendByReplication(PIX     *pixs,
1600
                       l_int32  addw,
1601
                       l_int32  addh)
1602
0
{
1603
0
l_int32   w, h, i, j;
1604
0
l_uint32  val;
1605
0
PIX      *pixd;
1606
1607
0
    if (!pixs || pixGetDepth(pixs) != 8)
1608
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1609
1610
0
    if (addw == 0 && addh == 0)
1611
0
        return pixCopy(NULL, pixs);
1612
1613
0
    pixGetDimensions(pixs, &w, &h, NULL);
1614
0
    if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL)
1615
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1616
0
    pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
1617
1618
0
    if (addw > 0) {
1619
0
        for (i = 0; i < h; i++) {
1620
0
            pixGetPixel(pixd, w - 1, i, &val);
1621
0
            for (j = 0; j < addw; j++)
1622
0
                pixSetPixel(pixd, w + j, i, val);
1623
0
        }
1624
0
    }
1625
1626
0
    if (addh > 0) {
1627
0
        for (j = 0; j < w + addw; j++) {
1628
0
            pixGetPixel(pixd, j, h - 1, &val);
1629
0
            for (i = 0; i < addh; i++)
1630
0
                pixSetPixel(pixd, j, h + i, val);
1631
0
        }
1632
0
    }
1633
1634
0
    pixCopyResolution(pixd, pixs);
1635
0
    return pixd;
1636
0
}
1637
1638
1639
/*!
1640
 * \brief   pixSmoothConnectedRegions()
1641
 *
1642
 * \param[in]    pixs    8 bpp grayscale; no colormap
1643
 * \param[in]    pixm    [optional] 1 bpp; if null, this is a no-op
1644
 * \param[in]    factor  subsampling factor for getting average; >= 1
1645
 * \return  0 if OK, 1 on error
1646
 *
1647
 * <pre>
1648
 * Notes:
1649
 *      (1) The pixels in pixs corresponding to those in each
1650
 *          8-connected region in the mask are set to the average value.
1651
 *      (2) This is required for adaptive mapping to avoid the
1652
 *          generation of stripes in the background map, due to
1653
 *          variations in the pixel values near the edges of mask regions.
1654
 *      (3) This function is optimized for background smoothing, where
1655
 *          there are a relatively small number of components.  It will
1656
 *          be inefficient if used where there are many small components.
1657
 * </pre>
1658
 */
1659
l_ok
1660
pixSmoothConnectedRegions(PIX     *pixs,
1661
                          PIX     *pixm,
1662
                          l_int32  factor)
1663
0
{
1664
0
l_int32    empty, i, n, x, y;
1665
0
l_float32  aveval;
1666
0
BOXA      *boxa;
1667
0
PIX       *pixmc;
1668
0
PIXA      *pixa;
1669
1670
0
    if (!pixs || pixGetDepth(pixs) != 8)
1671
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
1672
0
    if (pixGetColormap(pixs))
1673
0
        return ERROR_INT("pixs has colormap", __func__, 1);
1674
0
    if (!pixm) {
1675
0
        L_INFO("pixm not defined\n", __func__);
1676
0
        return 0;
1677
0
    }
1678
0
    if (pixGetDepth(pixm) != 1)
1679
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
1680
0
    pixZero(pixm, &empty);
1681
0
    if (empty) {
1682
0
        L_INFO("pixm has no fg pixels; nothing to do\n", __func__);
1683
0
        return 0;
1684
0
    }
1685
1686
0
    boxa = pixConnComp(pixm, &pixa, 8);
1687
0
    n = boxaGetCount(boxa);
1688
0
    for (i = 0; i < n; i++) {
1689
0
        if ((pixmc = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
1690
0
            L_WARNING("missing pixmc!\n", __func__);
1691
0
            continue;
1692
0
        }
1693
0
        boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL);
1694
0
        pixGetAverageMasked(pixs, pixmc, x, y, factor, L_MEAN_ABSVAL, &aveval);
1695
0
        pixPaintThroughMask(pixs, pixmc, x, y, (l_int32)aveval);
1696
0
        pixDestroy(&pixmc);
1697
0
    }
1698
1699
0
    boxaDestroy(&boxa);
1700
0
    pixaDestroy(&pixa);
1701
0
    return 0;
1702
0
}
1703
1704
1705
/*------------------------------------------------------------------*
1706
 *                 Measurement of local foreground                  *
1707
 *------------------------------------------------------------------*/
1708
#if 0    /* Not working properly: do not use */
1709
1710
/*!
1711
 * \brief   pixGetForegroundGrayMap()
1712
 *
1713
 * \param[in]    pixs      8 bpp
1714
 * \param[in]    pixim     [optional] 1 bpp 'image' mask; can be null
1715
 * \param[in]    sx, sy    src tile size, in pixels
1716
 * \param[in]    thresh    threshold for determining foreground
1717
 * \param[out]   ppixd     8 bpp grayscale map
1718
 * \return  0 if OK, 1 on error
1719
 *
1720
 * <pre>
1721
 * Notes:
1722
 *      (1) Each (sx, sy) tile of pixs gets mapped to one pixel in pixd.
1723
 *      (2) pixd is the estimate of the fg (darkest) value within each tile.
1724
 *      (3) All pixels in pixd that are in 'image' regions, as specified
1725
 *          by pixim, are given the background value 0.
1726
 *      (4) For pixels in pixd that can't directly be given a fg value,
1727
 *          the value is inferred by propagating from neighboring pixels.
1728
 *      (5) In practice, pixd can be used to normalize the fg, and
1729
 *          it can be done after background normalization.
1730
 *      (6) The overall procedure is:
1731
 *            ~ reduce 2x by sampling
1732
 *            ~ paint all 'image' pixels white, so that they don't
1733
 *            ~ participate in the Min reduction
1734
 *            ~ do a further (sx, sy) Min reduction -- think of
1735
 *              it as a large opening followed by subsampling by the
1736
 *              reduction factors
1737
 *            ~ threshold the result to identify fg, and set the
1738
 *              bg pixels to 255 (these are 'holes')
1739
 *            ~ fill holes by propagation from fg values
1740
 *            ~ replicatively expand by 2x, arriving at the final
1741
 *              resolution of pixd
1742
 *            ~ smooth with a 17x17 kernel
1743
 *            ~ paint the 'image' regions black
1744
 * </pre>
1745
 */
1746
l_ok
1747
pixGetForegroundGrayMap(PIX     *pixs,
1748
                        PIX     *pixim,
1749
                        l_int32  sx,
1750
                        l_int32  sy,
1751
                        l_int32  thresh,
1752
                        PIX    **ppixd)
1753
{
1754
l_int32  w, h, d, wd, hd;
1755
l_int32  empty, fgpixels;
1756
PIX     *pixd, *piximi, *pixim2, *pixims, *pixs2, *pixb, *pixt1, *pixt2, *pixt3;
1757
1758
    if (!ppixd)
1759
        return ERROR_INT("&pixd not defined", __func__, 1);
1760
    *ppixd = NULL;
1761
    if (!pixs)
1762
        return ERROR_INT("pixs not defined", __func__, 1);
1763
    pixGetDimensions(pixs, &w, &h, &d);
1764
    if (d != 8)
1765
        return ERROR_INT("pixs not 8 bpp", __func__, 1);
1766
    if (pixim && pixGetDepth(pixim) != 1)
1767
        return ERROR_INT("pixim not 1 bpp", __func__, 1);
1768
    if (sx < 2 || sy < 2)
1769
        return ERROR_INT("sx and sy must be >= 2", __func__, 1);
1770
1771
        /* Generate pixd, which is reduced by the factors (sx, sy). */
1772
    wd = (w + sx - 1) / sx;
1773
    hd = (h + sy - 1) / sy;
1774
    pixd = pixCreate(wd, hd, 8);
1775
    *ppixd = pixd;
1776
1777
        /* Evaluate the 'image' mask, pixim.  If it is all fg,
1778
         * the output pixd has all pixels with value 0. */
1779
    fgpixels = 0;  /* boolean for existence of fg pixels in the image mask. */
1780
    if (pixim) {
1781
        piximi = pixInvert(NULL, pixim);  /* set non-image pixels to 1 */
1782
        pixZero(piximi, &empty);
1783
        pixDestroy(&piximi);
1784
        if (empty)  /* all 'image'; return with all pixels set to 0 */
1785
            return 0;
1786
        pixZero(pixim, &empty);
1787
        if (!empty)  /* there are fg pixels in pixim */
1788
            fgpixels = 1;
1789
    }
1790
1791
        /* 2x subsampling; paint white through 'image' mask. */
1792
    pixs2 = pixScaleBySampling(pixs, 0.5, 0.5);
1793
    if (pixim && fgpixels) {
1794
        pixim2 = pixReduceBinary2(pixim, NULL);
1795
        pixPaintThroughMask(pixs2, pixim2, 0, 0, 255);
1796
        pixDestroy(&pixim2);
1797
    }
1798
1799
        /* Min (erosion) downscaling; total reduction (4 sx, 4 sy). */
1800
    pixt1 = pixScaleGrayMinMax(pixs2, sx, sy, L_CHOOSE_MIN);
1801
1802
/*    pixDisplay(pixt1, 300, 200); */
1803
1804
        /* Threshold to identify fg; paint bg pixels to white. */
1805
    pixb = pixThresholdToBinary(pixt1, thresh);  /* fg pixels */
1806
    pixInvert(pixb, pixb);
1807
    pixPaintThroughMask(pixt1, pixb, 0, 0, 255);
1808
    pixDestroy(&pixb);
1809
1810
        /* Replicative expansion by 2x to (sx, sy). */
1811
    pixt2 = pixExpandReplicate(pixt1, 2);
1812
1813
/*    pixDisplay(pixt2, 500, 200); */
1814
1815
        /* Fill holes in the fg by propagation */
1816
    pixFillMapHoles(pixt2, w / sx, h / sy, L_FILL_WHITE);
1817
1818
/*    pixDisplay(pixt2, 700, 200); */
1819
1820
        /* Smooth with 17x17 kernel. */
1821
    pixt3 = pixBlockconv(pixt2, 8, 8);
1822
    pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixt3, 0, 0);
1823
1824
        /* Paint the image parts black. */
1825
    pixims = pixScaleBySampling(pixim, 1. / sx, 1. / sy);
1826
    pixPaintThroughMask(pixd, pixims, 0, 0, 0);
1827
1828
    pixDestroy(&pixs2);
1829
    pixDestroy(&pixt1);
1830
    pixDestroy(&pixt2);
1831
    pixDestroy(&pixt3);
1832
    return 0;
1833
}
1834
#endif   /* Not working properly: do not use */
1835
1836
1837
/*------------------------------------------------------------------*
1838
 *                  Generate inverted background map                *
1839
 *------------------------------------------------------------------*/
1840
/*!
1841
 * \brief   pixGetInvBackgroundMap()
1842
 *
1843
 * \param[in]    pixs       8 bpp grayscale; no colormap
1844
 * \param[in]    bgval      target bg val; typ. > 128
1845
 * \param[in]    smoothx    half-width of block convolution kernel width
1846
 * \param[in]    smoothy    half-width of block convolution kernel height
1847
 * \return  pixd 16 bpp, or NULL on error
1848
 *
1849
 * <pre>
1850
 * Notes:
1851
 *     (1) bgval should typically be > 120 and < 240
1852
 *     (2) pixd is a normalization image; the original image is
1853
 *       multiplied by pixd and the result is divided by 256.
1854
 * </pre>
1855
 */
1856
PIX *
1857
pixGetInvBackgroundMap(PIX     *pixs,
1858
                       l_int32  bgval,
1859
                       l_int32  smoothx,
1860
                       l_int32  smoothy)
1861
0
{
1862
0
l_int32    w, h, wplsm, wpld, i, j;
1863
0
l_int32    val, val16;
1864
0
l_uint32  *datasm, *datad, *linesm, *lined;
1865
0
PIX       *pixsm, *pixd;
1866
1867
0
    if (!pixs || pixGetDepth(pixs) != 8)
1868
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1869
0
    if (pixGetColormap(pixs))
1870
0
        return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
1871
0
    pixGetDimensions(pixs, &w, &h, NULL);
1872
0
    if (w < 5 || h < 5)
1873
0
        return (PIX *)ERROR_PTR("w and h must be >= 5", __func__, NULL);
1874
1875
        /* smooth the map image */
1876
0
    pixsm = pixBlockconv(pixs, smoothx, smoothy);
1877
0
    datasm = pixGetData(pixsm);
1878
0
    wplsm = pixGetWpl(pixsm);
1879
1880
        /* invert the map image, scaling up to preserve dynamic range */
1881
0
    pixd = pixCreate(w, h, 16);
1882
0
    datad = pixGetData(pixd);
1883
0
    wpld = pixGetWpl(pixd);
1884
0
    for (i = 0; i < h; i++) {
1885
0
        linesm = datasm + i * wplsm;
1886
0
        lined = datad + i * wpld;
1887
0
        for (j = 0; j < w; j++) {
1888
0
            val = GET_DATA_BYTE(linesm, j);
1889
0
            if (val > 0)
1890
0
                val16 = (256 * bgval) / val;
1891
0
            else {  /* shouldn't happen */
1892
0
                L_WARNING("smoothed bg has 0 pixel!\n", __func__);
1893
0
                val16 = bgval / 2;
1894
0
            }
1895
0
            SET_DATA_TWO_BYTES(lined, j, val16);
1896
0
        }
1897
0
    }
1898
1899
0
    pixDestroy(&pixsm);
1900
0
    pixCopyResolution(pixd, pixs);
1901
0
    return pixd;
1902
0
}
1903
1904
1905
/*------------------------------------------------------------------*
1906
 *                    Apply background map to image                 *
1907
 *------------------------------------------------------------------*/
1908
/*!
1909
 * \brief   pixApplyInvBackgroundGrayMap()
1910
 *
1911
 * \param[in]    pixs    8 bpp grayscale; no colormap
1912
 * \param[in]    pixm    16 bpp, inverse background map
1913
 * \param[in]    sx      tile width in pixels
1914
 * \param[in]    sy      tile height in pixels
1915
 * \return  pixd 8 bpp, or NULL on error
1916
 */
1917
PIX *
1918
pixApplyInvBackgroundGrayMap(PIX     *pixs,
1919
                             PIX     *pixm,
1920
                             l_int32  sx,
1921
                             l_int32  sy)
1922
0
{
1923
0
l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
1924
0
l_int32    vals, vald;
1925
0
l_uint32   val16;
1926
0
l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
1927
0
PIX       *pixd;
1928
1929
0
    if (!pixs || pixGetDepth(pixs) != 8)
1930
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
1931
0
    if (pixGetColormap(pixs))
1932
0
        return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL);
1933
0
    if (!pixm || pixGetDepth(pixm) != 16)
1934
0
        return (PIX *)ERROR_PTR("pixm undefined or not 16 bpp", __func__, NULL);
1935
0
    if (sx == 0 || sy == 0)
1936
0
        return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL);
1937
1938
0
    datas = pixGetData(pixs);
1939
0
    wpls = pixGetWpl(pixs);
1940
0
    pixGetDimensions(pixs, &w, &h, NULL);
1941
0
    pixGetDimensions(pixm, &wm, &hm, NULL);
1942
0
    if ((pixd = pixCreateTemplate(pixs)) == NULL)
1943
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1944
0
    datad = pixGetData(pixd);
1945
0
    wpld = pixGetWpl(pixd);
1946
0
    for (i = 0; i < hm; i++) {
1947
0
        lines = datas + sy * i * wpls;
1948
0
        lined = datad + sy * i * wpld;
1949
0
        yoff = sy * i;
1950
0
        for (j = 0; j < wm; j++) {
1951
0
            pixGetPixel(pixm, j, i, &val16);
1952
0
            xoff = sx * j;
1953
0
            for (k = 0; k < sy && yoff + k < h; k++) {
1954
0
                flines = lines + k * wpls;
1955
0
                flined = lined + k * wpld;
1956
0
                for (m = 0; m < sx && xoff + m < w; m++) {
1957
0
                    vals = GET_DATA_BYTE(flines, xoff + m);
1958
0
                    vald = (vals * val16) / 256;
1959
0
                    vald = L_MIN(vald, 255);
1960
0
                    SET_DATA_BYTE(flined, xoff + m, vald);
1961
0
                }
1962
0
            }
1963
0
        }
1964
0
    }
1965
1966
0
    return pixd;
1967
0
}
1968
1969
1970
/*!
1971
 * \brief   pixApplyInvBackgroundRGBMap()
1972
 *
1973
 * \param[in]    pixs    32 bpp rbg
1974
 * \param[in]    pixmr   16 bpp, red inverse background map
1975
 * \param[in]    pixmg   16 bpp, green inverse background map
1976
 * \param[in]    pixmb   16 bpp, blue inverse background map
1977
 * \param[in]    sx      tile width in pixels
1978
 * \param[in]    sy      tile height in pixels
1979
 * \return  pixd 32 bpp rbg, or NULL on error
1980
 */
1981
PIX *
1982
pixApplyInvBackgroundRGBMap(PIX     *pixs,
1983
                            PIX     *pixmr,
1984
                            PIX     *pixmg,
1985
                            PIX     *pixmb,
1986
                            l_int32  sx,
1987
                            l_int32  sy)
1988
0
{
1989
0
l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
1990
0
l_int32    rvald, gvald, bvald;
1991
0
l_uint32   vals;
1992
0
l_uint32   rval16, gval16, bval16;
1993
0
l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
1994
0
PIX       *pixd;
1995
1996
0
    if (!pixs)
1997
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1998
0
    if (pixGetDepth(pixs) != 32)
1999
0
        return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL);
2000
0
    if (!pixmr || !pixmg || !pixmb)
2001
0
        return (PIX *)ERROR_PTR("pix maps not all defined", __func__, NULL);
2002
0
    if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 ||
2003
0
        pixGetDepth(pixmb) != 16)
2004
0
        return (PIX *)ERROR_PTR("pix maps not all 16 bpp", __func__, NULL);
2005
0
    if (sx == 0 || sy == 0)
2006
0
        return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL);
2007
2008
0
    datas = pixGetData(pixs);
2009
0
    wpls = pixGetWpl(pixs);
2010
0
    w = pixGetWidth(pixs);
2011
0
    h = pixGetHeight(pixs);
2012
0
    wm = pixGetWidth(pixmr);
2013
0
    hm = pixGetHeight(pixmr);
2014
0
    if ((pixd = pixCreateTemplate(pixs)) == NULL)
2015
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2016
0
    datad = pixGetData(pixd);
2017
0
    wpld = pixGetWpl(pixd);
2018
0
    for (i = 0; i < hm; i++) {
2019
0
        lines = datas + sy * i * wpls;
2020
0
        lined = datad + sy * i * wpld;
2021
0
        yoff = sy * i;
2022
0
        for (j = 0; j < wm; j++) {
2023
0
            pixGetPixel(pixmr, j, i, &rval16);
2024
0
            pixGetPixel(pixmg, j, i, &gval16);
2025
0
            pixGetPixel(pixmb, j, i, &bval16);
2026
0
            xoff = sx * j;
2027
0
            for (k = 0; k < sy && yoff + k < h; k++) {
2028
0
                flines = lines + k * wpls;
2029
0
                flined = lined + k * wpld;
2030
0
                for (m = 0; m < sx && xoff + m < w; m++) {
2031
0
                    vals = *(flines + xoff + m);
2032
0
                    rvald = ((vals >> 24) * rval16) / 256;
2033
0
                    rvald = L_MIN(rvald, 255);
2034
0
                    gvald = (((vals >> 16) & 0xff) * gval16) / 256;
2035
0
                    gvald = L_MIN(gvald, 255);
2036
0
                    bvald = (((vals >> 8) & 0xff) * bval16) / 256;
2037
0
                    bvald = L_MIN(bvald, 255);
2038
0
                    composeRGBPixel(rvald, gvald, bvald, flined + xoff + m);
2039
0
                }
2040
0
            }
2041
0
        }
2042
0
    }
2043
2044
0
    return pixd;
2045
0
}
2046
2047
2048
/*------------------------------------------------------------------*
2049
 *                         Apply variable map                       *
2050
 *------------------------------------------------------------------*/
2051
/*!
2052
 * \brief   pixApplyVariableGrayMap()
2053
 *
2054
 * \param[in]    pixs     8 bpp
2055
 * \param[in]    pixg     8 bpp, variable map
2056
 * \param[in]    target   typ. 128 for threshold
2057
 * \return  pixd 8 bpp, or NULL on error
2058
 *
2059
 * <pre>
2060
 * Notes:
2061
 *      (1) Suppose you have an image that you want to transform based
2062
 *          on some photometric measurement at each point, such as the
2063
 *          threshold value for binarization.  Representing the photometric
2064
 *          measurement as an image pixg, you can threshold in input image
2065
 *          using pixVarThresholdToBinary().  Alternatively, you can map
2066
 *          the input image pointwise so that the threshold over the
2067
 *          entire image becomes a constant, such as 128.  For example,
2068
 *          if a pixel in pixg is 150 and the target is 128, the
2069
 *          corresponding pixel in pixs is mapped linearly to a value
2070
 *          (128/150) of the input value.  If the resulting mapped image
2071
 *          pixd were then thresholded at 128, you would obtain the
2072
 *          same result as a direct binarization using pixg with
2073
 *          pixVarThresholdToBinary().
2074
 *      (2) The sizes of pixs and pixg must be equal.
2075
 * </pre>
2076
 */
2077
PIX *
2078
pixApplyVariableGrayMap(PIX     *pixs,
2079
                        PIX     *pixg,
2080
                        l_int32  target)
2081
0
{
2082
0
l_int32    i, j, w, h, d, wpls, wplg, wpld, vals, valg, vald;
2083
0
l_uint8   *lut;
2084
0
l_uint32  *datas, *datag, *datad, *lines, *lineg, *lined;
2085
0
l_float32  fval;
2086
0
PIX       *pixd;
2087
2088
0
    if (!pixs)
2089
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2090
0
    if (!pixg)
2091
0
        return (PIX *)ERROR_PTR("pixg not defined", __func__, NULL);
2092
0
    if (!pixSizesEqual(pixs, pixg))
2093
0
        return (PIX *)ERROR_PTR("pix sizes not equal", __func__, NULL);
2094
0
    pixGetDimensions(pixs, &w, &h, &d);
2095
0
    if (d != 8)
2096
0
        return (PIX *)ERROR_PTR("depth not 8 bpp", __func__, NULL);
2097
2098
        /* Generate a LUT for the mapping if the image is large enough
2099
         * to warrant the overhead.  The LUT is of size 2^16.  For the
2100
         * index to the table, get the MSB from pixs and the LSB from pixg.
2101
         * Note: this LUT is bigger than the typical 32K L1 cache, so
2102
         * we expect cache misses.  L2 latencies are about 5ns.  But
2103
         * division is slooooow.  For large images, this function is about
2104
         * 4x faster when using the LUT.  C'est la vie.  */
2105
0
    lut = NULL;
2106
0
    if (w * h > 100000) {  /* more pixels than 2^16 */
2107
0
        lut = (l_uint8 *)LEPT_CALLOC(0x10000, sizeof(l_uint8));
2108
0
        for (i = 0; i < 256; i++) {
2109
0
            for (j = 0; j < 256; j++) {
2110
0
                fval = (l_float32)(i * target) / (j + 0.5);
2111
0
                lut[(i << 8) + j] = L_MIN(255, (l_int32)(fval + 0.5));
2112
0
            }
2113
0
        }
2114
0
    }
2115
2116
0
    if ((pixd = pixCreate(w, h, 8)) == NULL) {
2117
0
        LEPT_FREE(lut);
2118
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2119
0
    }
2120
0
    pixCopyResolution(pixd, pixs);
2121
0
    datad = pixGetData(pixd);
2122
0
    wpld = pixGetWpl(pixd);
2123
0
    datas = pixGetData(pixs);
2124
0
    wpls = pixGetWpl(pixs);
2125
0
    datag = pixGetData(pixg);
2126
0
    wplg = pixGetWpl(pixg);
2127
0
    for (i = 0; i < h; i++) {
2128
0
        lines = datas + i * wpls;
2129
0
        lineg = datag + i * wplg;
2130
0
        lined = datad + i * wpld;
2131
0
        if (lut) {
2132
0
            for (j = 0; j < w; j++) {
2133
0
                vals = GET_DATA_BYTE(lines, j);
2134
0
                valg = GET_DATA_BYTE(lineg, j);
2135
0
                vald = lut[(vals << 8) + valg];
2136
0
                SET_DATA_BYTE(lined, j, vald);
2137
0
            }
2138
0
        }
2139
0
        else {
2140
0
            for (j = 0; j < w; j++) {
2141
0
                vals = GET_DATA_BYTE(lines, j);
2142
0
                valg = GET_DATA_BYTE(lineg, j);
2143
0
                fval = (l_float32)(vals * target) / (valg + 0.5);
2144
0
                vald = L_MIN(255, (l_int32)(fval + 0.5));
2145
0
                SET_DATA_BYTE(lined, j, vald);
2146
0
            }
2147
0
        }
2148
0
    }
2149
2150
0
    LEPT_FREE(lut);
2151
0
    return pixd;
2152
0
}
2153
2154
2155
/*------------------------------------------------------------------*
2156
 *                  Non-adaptive (global) mapping                   *
2157
 *------------------------------------------------------------------*/
2158
/*!
2159
 * \brief   pixGlobalNormRGB()
2160
 *
2161
 * \param[in]    pixd     [optional] null, existing or equal to pixs
2162
 * \param[in]    pixs     32 bpp rgb, or colormapped
2163
 * \param[in]    rval, gval, bval   pixel values in pixs that are
2164
 *                                  linearly mapped to mapval
2165
 * \param[in]    mapval   use 255 for mapping to white
2166
 * \return  pixd 32 bpp rgb or colormapped, or NULL on error
2167
 *
2168
 * <pre>
2169
 * Notes:
2170
 *    (1) The value of pixd determines if the results are written to a
2171
 *        new pix (use NULL), in-place to pixs (use pixs), or to some
2172
 *        other existing pix.
2173
 *    (2) This does a global normalization of an image where the
2174
 *        r,g,b color components are not balanced.  Thus, white in pixs is
2175
 *        represented by a set of r,g,b values that are not all 255.
2176
 *    (3) The input values (rval, gval, bval) should be chosen to
2177
 *        represent the gray color (mapval, mapval, mapval) in src.
2178
 *        Thus, this function will map (rval, gval, bval) to that gray color.
2179
 *    (4) Typically, mapval = 255, so that (rval, gval, bval)
2180
 *        corresponds to the white point of src.  In that case, these
2181
 *        parameters should be chosen so that few pixels have higher values.
2182
 *    (5) In all cases, we do a linear TRC separately on each of the
2183
 *        components, saturating at 255.
2184
 *    (6) If the input pix is 8 bpp without a colormap, you can get
2185
 *        this functionality with mapval = 255 by calling:
2186
 *            pixGammaTRC(pixd, pixs, 1.0, 0, bgval);
2187
 *        where bgval is the value you want to be mapped to 255.
2188
 *        Or more generally, if you want bgval to be mapped to mapval:
2189
 *            pixGammaTRC(pixd, pixs, 1.0, 0, 255 * bgval / mapval);
2190
 * </pre>
2191
 */
2192
PIX *
2193
pixGlobalNormRGB(PIX     *pixd,
2194
                 PIX     *pixs,
2195
                 l_int32  rval,
2196
                 l_int32  gval,
2197
                 l_int32  bval,
2198
                 l_int32  mapval)
2199
0
{
2200
0
l_int32    w, h, d, i, j, ncolors, rv, gv, bv, wpl;
2201
0
l_int32   *rarray, *garray, *barray;
2202
0
l_uint32  *data, *line;
2203
0
NUMA      *nar, *nag, *nab;
2204
0
PIXCMAP   *cmap;
2205
2206
0
    if (!pixs)
2207
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2208
0
    cmap = pixGetColormap(pixs);
2209
0
    pixGetDimensions(pixs, &w, &h, &d);
2210
0
    if (!cmap && d != 32)
2211
0
        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL);
2212
0
    if (mapval <= 0) {
2213
0
        L_WARNING("mapval must be > 0; setting to 255\n", __func__);
2214
0
        mapval = 255;
2215
0
    }
2216
2217
        /* Prepare pixd to be a copy of pixs */
2218
0
    if ((pixd = pixCopy(pixd, pixs)) == NULL)
2219
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2220
2221
        /* Generate the TRC maps for each component.  Make sure the
2222
         * upper range for each color is greater than zero. */
2223
0
    nar = numaGammaTRC(1.0, 0, L_MAX(1, 255 * rval / mapval));
2224
0
    nag = numaGammaTRC(1.0, 0, L_MAX(1, 255 * gval / mapval));
2225
0
    nab = numaGammaTRC(1.0, 0, L_MAX(1, 255 * bval / mapval));
2226
2227
        /* Extract copies of the internal arrays */
2228
0
    rarray = numaGetIArray(nar);
2229
0
    garray = numaGetIArray(nag);
2230
0
    barray = numaGetIArray(nab);
2231
0
    if (!nar || !nag || !nab || !rarray || !garray || !barray) {
2232
0
        L_ERROR("allocation failure in arrays\n", __func__);
2233
0
        goto cleanup_arrays;
2234
0
    }
2235
2236
0
    if (cmap) {
2237
0
        ncolors = pixcmapGetCount(cmap);
2238
0
        for (i = 0; i < ncolors; i++) {
2239
0
            pixcmapGetColor(cmap, i, &rv, &gv, &bv);
2240
0
            pixcmapResetColor(cmap, i, rarray[rv], garray[gv], barray[bv]);
2241
0
        }
2242
0
    }
2243
0
    else {
2244
0
        data = pixGetData(pixd);
2245
0
        wpl = pixGetWpl(pixd);
2246
0
        for (i = 0; i < h; i++) {
2247
0
            line = data + i * wpl;
2248
0
            for (j = 0; j < w; j++) {
2249
0
                extractRGBValues(line[j], &rv, &gv, &bv);
2250
0
                composeRGBPixel(rarray[rv], garray[gv], barray[bv], line + j);
2251
0
            }
2252
0
        }
2253
0
    }
2254
2255
0
cleanup_arrays:
2256
0
    numaDestroy(&nar);
2257
0
    numaDestroy(&nag);
2258
0
    numaDestroy(&nab);
2259
0
    LEPT_FREE(rarray);
2260
0
    LEPT_FREE(garray);
2261
0
    LEPT_FREE(barray);
2262
0
    return pixd;
2263
0
}
2264
2265
2266
/*!
2267
 * \brief   pixGlobalNormNoSatRGB()
2268
 *
2269
 * \param[in]    pixd       [optional] null, existing or equal to pixs
2270
 * \param[in]    pixs       32 bpp rgb
2271
 * \param[in]    rval, gval, bval   pixel values in pixs that are
2272
 *                                  linearly mapped to mapval; but see below
2273
 * \param[in]    factor     subsampling factor; integer >= 1
2274
 * \param[in]    rank       between 0.0 and 1.0; typ. use a value near 1.0
2275
 * \return  pixd 32 bpp rgb, or NULL on error
2276
 *
2277
 * <pre>
2278
 * Notes:
2279
 *    (1) This is a version of pixGlobalNormRGB(), where the output
2280
 *        intensity is scaled back so that a controlled fraction of
2281
 *        pixel components is allowed to saturate.  See comments in
2282
 *        pixGlobalNormRGB().
2283
 *    (2) The value of pixd determines if the results are written to a
2284
 *        new pix (use NULL), in-place to pixs (use pixs), or to some
2285
 *        other existing pix.
2286
 *    (3) This does a global normalization of an image where the
2287
 *        r,g,b color components are not balanced.  Thus, white in pixs is
2288
 *        represented by a set of r,g,b values that are not all 255.
2289
 *    (4) The input values (rval, gval, bval) can be chosen to be the
2290
 *        color that, after normalization, becomes white background.
2291
 *        For images that are mostly background, the closer these values
2292
 *        are to the median component values, the closer the resulting
2293
 *        background will be to gray, becoming white at the brightest places.
2294
 *    (5) The mapval used in pixGlobalNormRGB() is computed here to
2295
 *        avoid saturation of any component in the image (save for a
2296
 *        fraction of the pixels given by the input rank value).
2297
 * </pre>
2298
 */
2299
PIX *
2300
pixGlobalNormNoSatRGB(PIX       *pixd,
2301
                      PIX       *pixs,
2302
                      l_int32    rval,
2303
                      l_int32    gval,
2304
                      l_int32    bval,
2305
                      l_int32    factor,
2306
                      l_float32  rank)
2307
0
{
2308
0
l_int32    mapval;
2309
0
l_float32  rankrval, rankgval, rankbval;
2310
0
l_float32  rfract, gfract, bfract, maxfract;
2311
2312
0
    if (!pixs)
2313
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2314
0
    if (pixGetDepth(pixs) != 32)
2315
0
        return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL);
2316
0
    if (factor < 1)
2317
0
        return (PIX *)ERROR_PTR("sampling factor < 1", __func__, NULL);
2318
0
    if (rank < 0.0 || rank > 1.0)
2319
0
        return (PIX *)ERROR_PTR("rank not in [0.0 ... 1.0]", __func__, NULL);
2320
0
    if (rval <= 0 || gval <= 0 || bval <= 0)
2321
0
        return (PIX *)ERROR_PTR("invalid estim. color values", __func__, NULL);
2322
2323
        /* The max value for each component may be larger than the
2324
         * input estimated background value.  In that case, mapping
2325
         * for those pixels would saturate.  To prevent saturation,
2326
         * we compute the fraction for each component by which we
2327
         * would oversaturate.  Then take the max of these, and
2328
         * reduce, uniformly over all components, the output intensity
2329
         * by this value.  Then no component will saturate.
2330
         * In practice, if rank < 1.0, a fraction of pixels
2331
         * may have a component saturate.  By keeping rank close to 1.0,
2332
         * that fraction can be made arbitrarily small. */
2333
0
    pixGetRankValueMaskedRGB(pixs, NULL, 0, 0, factor, rank, &rankrval,
2334
0
                             &rankgval, &rankbval);
2335
0
    rfract = rankrval / (l_float32)rval;
2336
0
    gfract = rankgval / (l_float32)gval;
2337
0
    bfract = rankbval / (l_float32)bval;
2338
0
    maxfract = L_MAX(rfract, gfract);
2339
0
    maxfract = L_MAX(maxfract, bfract);
2340
#if  DEBUG_GLOBAL
2341
    lept_stderr("rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n",
2342
                rankrval, rankgval, rankbval);
2343
    lept_stderr("rfract = %7.4f, gfract = %7.4f, bfract = %7.4f\n",
2344
                rfract, gfract, bfract);
2345
#endif  /* DEBUG_GLOBAL */
2346
2347
0
    mapval = (l_int32)(255. / maxfract);
2348
0
    pixd = pixGlobalNormRGB(pixd, pixs, rval, gval, bval, mapval);
2349
0
    return pixd;
2350
0
}
2351
2352
2353
/*------------------------------------------------------------------*
2354
 *              Adaptive threshold spread normalization             *
2355
 *------------------------------------------------------------------*/
2356
/*!
2357
 * \brief   pixThresholdSpreadNorm()
2358
 *
2359
 * \param[in]    pixs              8 bpp grayscale; not colormapped
2360
 * \param[in]    filtertype        L_SOBEL_EDGE or L_TWO_SIDED_EDGE;
2361
 * \param[in]    edgethresh        threshold on magnitude of edge filter;
2362
 *                                 typ 10-20
2363
 * \param[in]    smoothx, smoothy  half-width of convolution kernel applied to
2364
 *                                 spread threshold: use 0 for no smoothing
2365
 * \param[in]    gamma             gamma correction; typ. about 0.7
2366
 * \param[in]    minval            input value that gives 0 for output; typ. -25
2367
 * \param[in]    maxval            input value that gives 255 for output;
2368
 *                                 typ. 255
2369
 * \param[in]    targetthresh      target threshold for normalization
2370
 * \param[out]   ppixth            [optional] computed local threshold value
2371
 * \param[out]   ppixb             [optional] thresholded normalized image
2372
 * \param[out]   ppixd             [optional] normalized image
2373
 * \return  0 if OK, 1 on error
2374
 *
2375
 * <pre>
2376
 * Notes:
2377
 *      (1) The basis of this approach is the use of seed spreading
2378
 *          on a (possibly) sparse set of estimates for the local threshold.
2379
 *          The resulting dense estimates are smoothed by convolution
2380
 *          and used to either threshold the input image or normalize it
2381
 *          with a local transformation that linearly maps the pixels so
2382
 *          that the local threshold estimate becomes constant over the
2383
 *          resulting image.  This approach is one of several that
2384
 *          have been suggested (and implemented) by Ray Smith.
2385
 *      (2) You can use either the Sobel or TwoSided edge filters.
2386
 *          The results appear to be similar, using typical values
2387
 *          of edgethresh in the rang 10-20.
2388
 *      (3) To skip the trc enhancement, use gamma = 1.0, minval = 0
2389
 *          and maxval = 255.
2390
 *      (4) For the normalized image pixd, each pixel is linearly mapped
2391
 *          in such a way that the local threshold is equal to targetthresh.
2392
 *      (5) The full width and height of the convolution kernel
2393
 *          are (2 * smoothx + 1) and (2 * smoothy + 1).
2394
 *      (6) This function can be used with the pixtiling utility if the
2395
 *          images are too large.  See pixOtsuAdaptiveThreshold() for
2396
 *          an example of this.
2397
 * </pre>
2398
 */
2399
l_ok
2400
pixThresholdSpreadNorm(PIX       *pixs,
2401
                       l_int32    filtertype,
2402
                       l_int32    edgethresh,
2403
                       l_int32    smoothx,
2404
                       l_int32    smoothy,
2405
                       l_float32  gamma,
2406
                       l_int32    minval,
2407
                       l_int32    maxval,
2408
                       l_int32    targetthresh,
2409
                       PIX      **ppixth,
2410
                       PIX      **ppixb,
2411
                       PIX      **ppixd)
2412
0
{
2413
0
PIX  *pixe, *pixet, *pixsd, *pixg1, *pixg2, *pixth;
2414
2415
0
    if (ppixth) *ppixth = NULL;
2416
0
    if (ppixb) *ppixb = NULL;
2417
0
    if (ppixd) *ppixd = NULL;
2418
0
    if (!pixs || pixGetDepth(pixs) != 8)
2419
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
2420
0
    if (pixGetColormap(pixs))
2421
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
2422
0
    if (!ppixth && !ppixb && !ppixd)
2423
0
        return ERROR_INT("no output requested", __func__, 1);
2424
0
    if (filtertype != L_SOBEL_EDGE && filtertype != L_TWO_SIDED_EDGE)
2425
0
        return ERROR_INT("invalid filter type", __func__, 1);
2426
2427
        /* Get the thresholded edge pixels.  These are the ones
2428
         * that have values in pixs near the local optimal fg/bg threshold. */
2429
0
    if (filtertype == L_SOBEL_EDGE)
2430
0
        pixe = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES);
2431
0
    else  /* L_TWO_SIDED_EDGE */
2432
0
        pixe = pixTwoSidedEdgeFilter(pixs, L_VERTICAL_EDGES);
2433
0
    pixet = pixThresholdToBinary(pixe, edgethresh);
2434
0
    pixInvert(pixet, pixet);
2435
2436
        /* Build a seed image whose only nonzero values are those
2437
         * values of pixs corresponding to pixels in the fg of pixet. */
2438
0
    pixsd = pixCreateTemplate(pixs);
2439
0
    pixCombineMasked(pixsd, pixs, pixet);
2440
2441
        /* Spread the seed and optionally smooth to reduce noise */
2442
0
    pixg1 = pixSeedspread(pixsd, 4);
2443
0
    pixg2 = pixBlockconv(pixg1, smoothx, smoothy);
2444
2445
        /* Optionally do a gamma enhancement */
2446
0
    pixth = pixGammaTRC(NULL, pixg2, gamma, minval, maxval);
2447
2448
        /* Do the mapping and thresholding */
2449
0
    if (ppixd) {
2450
0
        *ppixd = pixApplyVariableGrayMap(pixs, pixth, targetthresh);
2451
0
        if (ppixb)
2452
0
            *ppixb = pixThresholdToBinary(*ppixd, targetthresh);
2453
0
    }
2454
0
    else if (ppixb)
2455
0
        *ppixb = pixVarThresholdToBinary(pixs, pixth);
2456
2457
0
    if (ppixth)
2458
0
        *ppixth = pixth;
2459
0
    else
2460
0
        pixDestroy(&pixth);
2461
2462
0
    pixDestroy(&pixe);
2463
0
    pixDestroy(&pixet);
2464
0
    pixDestroy(&pixsd);
2465
0
    pixDestroy(&pixg1);
2466
0
    pixDestroy(&pixg2);
2467
0
    return 0;
2468
0
}
2469
2470
2471
/*------------------------------------------------------------------*
2472
 *      Adaptive background normalization (flexible adaptaption)    *
2473
 *------------------------------------------------------------------*/
2474
/*!
2475
 * \brief   pixBackgroundNormFlex()
2476
 *
2477
 * \param[in]    pixs               8 bpp grayscale; not colormapped
2478
 * \param[in]    sx, sy             desired tile dimensions; size may vary;
2479
 *                                  use values between 3 and 10
2480
 * \param[in]    smoothx, smoothy   half-width of convolution kernel applied to
2481
 *                                  threshold array: use values between 1 and 3
2482
 * \param[in]    delta              difference parameter in basin filling;
2483
 *                                  use 0 to skip
2484
 * \return  pixd 8 bpp, background-normalized), or NULL on error
2485
 *
2486
 * <pre>
2487
 * Notes:
2488
 *      (1) This does adaptation flexibly to a quickly varying background.
2489
 *          For that reason, all input parameters should be small.
2490
 *      (2) sx and sy give the tile size; they should be in [5 - 7].
2491
 *      (3) The full width and height of the convolution kernel
2492
 *          are (2 * smoothx + 1) and (2 * smoothy + 1).  They
2493
 *          should be in [1 - 2].
2494
 *      (4) Basin filling is used to fill the large fg regions.  The
2495
 *          parameter %delta measures the height that the black
2496
 *          background is raised from the local minima.  By raising
2497
 *          the background, it is possible to threshold the large
2498
 *          fg regions to foreground.  If %delta is too large,
2499
 *          bg regions will be lifted, causing thickening of
2500
 *          the fg regions.  Use 0 to skip.
2501
 * </pre>
2502
 */
2503
PIX *
2504
pixBackgroundNormFlex(PIX     *pixs,
2505
                      l_int32  sx,
2506
                      l_int32  sy,
2507
                      l_int32  smoothx,
2508
                      l_int32  smoothy,
2509
                      l_int32  delta)
2510
0
{
2511
0
l_float32  scalex, scaley;
2512
0
PIX       *pixt, *pixsd, *pixmin, *pixbg, *pixbgi, *pixd;
2513
2514
0
    if (!pixs || pixGetDepth(pixs) != 8)
2515
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL);
2516
0
    if (pixGetColormap(pixs))
2517
0
        return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL);
2518
0
    if (sx < 3 || sy < 3)
2519
0
        return (PIX *)ERROR_PTR("sx and/or sy less than 3", __func__, NULL);
2520
0
    if (sx > 10 || sy > 10)
2521
0
        return (PIX *)ERROR_PTR("sx and/or sy exceed 10", __func__, NULL);
2522
0
    if (smoothx < 1 || smoothy < 1)
2523
0
        return (PIX *)ERROR_PTR("smooth params less than 1", __func__, NULL);
2524
0
    if (smoothx > 3 || smoothy > 3)
2525
0
        return (PIX *)ERROR_PTR("smooth params exceed 3", __func__, NULL);
2526
2527
        /* Generate the bg estimate using smoothed average with subsampling */
2528
0
    scalex = 1. / (l_float32)sx;
2529
0
    scaley = 1. / (l_float32)sy;
2530
0
    pixt = pixScaleSmooth(pixs, scalex, scaley);
2531
2532
        /* Do basin filling on the bg estimate if requested */
2533
0
    if (delta <= 0)
2534
0
        pixsd = pixClone(pixt);
2535
0
    else {
2536
0
        pixLocalExtrema(pixt, 0, 0, &pixmin, NULL);
2537
0
        pixsd = pixSeedfillGrayBasin(pixmin, pixt, delta, 4);
2538
0
        pixDestroy(&pixmin);
2539
0
    }
2540
0
    pixbg = pixExtendByReplication(pixsd, 1, 1);
2541
2542
        /* Map the bg to 200 */
2543
0
    pixbgi = pixGetInvBackgroundMap(pixbg, 200, smoothx, smoothy);
2544
0
    pixd = pixApplyInvBackgroundGrayMap(pixs, pixbgi, sx, sy);
2545
2546
0
    pixDestroy(&pixt);
2547
0
    pixDestroy(&pixsd);
2548
0
    pixDestroy(&pixbg);
2549
0
    pixDestroy(&pixbgi);
2550
0
    return pixd;
2551
0
}
2552
2553
2554
/*------------------------------------------------------------------*
2555
 *                    Adaptive contrast normalization               *
2556
 *------------------------------------------------------------------*/
2557
/*!
2558
 * \brief   pixContrastNorm()
2559
 *
2560
 * \param[in]    pixd               [optional] 8 bpp; null or equal to pixs
2561
 * \param[in]    pixs               8 bpp grayscale; not colormapped
2562
 * \param[in]    sx, sy             tile dimensions
2563
 * \param[in]    mindiff            minimum difference to accept as valid
2564
 * \param[in]    smoothx, smoothy   half-width of convolution kernel applied to
2565
 *                                  min and max arrays: use 0 for no smoothing
2566
 * \return  pixd always
2567
 *
2568
 * <pre>
2569
 * Notes:
2570
 *      (1) This function adaptively attempts to expand the contrast
2571
 *          to the full dynamic range in each tile.  If the contrast in
2572
 *          a tile is smaller than %mindiff, it uses the min and max
2573
 *          pixel values from neighboring tiles.  It also can use
2574
 *          convolution to smooth the min and max values from
2575
 *          neighboring tiles.  After all that processing, it is
2576
 *          possible that the actual pixel values in the tile are outside
2577
 *          the computed [min ... max] range for local contrast
2578
 *          normalization.  Such pixels are taken to be at either 0
2579
 *          (if below the min) or 255 (if above the max).
2580
 *      (2) pixd can be equal to pixs (in-place operation) or
2581
 *          null (makes a new pixd).
2582
 *      (3) sx and sy give the tile size; they are typically at least 20.
2583
 *      (4) mindiff is used to eliminate results for tiles where it is
2584
 *          likely that either fg or bg is missing.  A value around 50
2585
 *          or more is reasonable.
2586
 *      (5) The full width and height of the convolution kernel
2587
 *          are (2 * smoothx + 1) and (2 * smoothy + 1).  Some smoothing
2588
 *          is typically useful, and we limit the smoothing half-widths
2589
 *          to the range from 0 to 8.
2590
 *      (6) A linear TRC (gamma = 1.0) is applied to increase the contrast
2591
 *          in each tile.  The result can subsequently be globally corrected,
2592
 *          by applying pixGammaTRC() with arbitrary values of gamma
2593
 *          and the 0 and 255 points of the mapping.
2594
 * </pre>
2595
 */
2596
PIX *
2597
pixContrastNorm(PIX       *pixd,
2598
                PIX       *pixs,
2599
                l_int32    sx,
2600
                l_int32    sy,
2601
                l_int32    mindiff,
2602
                l_int32    smoothx,
2603
                l_int32    smoothy)
2604
0
{
2605
0
PIX  *pixmin, *pixmax;
2606
2607
0
    if (!pixs || pixGetDepth(pixs) != 8)
2608
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd);
2609
0
    if (pixd && pixd != pixs)
2610
0
        return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd);
2611
0
    if (pixGetColormap(pixs))
2612
0
        return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd);
2613
0
    if (sx < 5 || sy < 5)
2614
0
        return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd);
2615
0
    if (smoothx < 0 || smoothy < 0)
2616
0
        return (PIX *)ERROR_PTR("smooth params less than 0", __func__, pixd);
2617
0
    if (smoothx > 8 || smoothy > 8)
2618
0
        return (PIX *)ERROR_PTR("smooth params exceed 8", __func__, pixd);
2619
2620
        /* Get the min and max pixel values in each tile, and represent
2621
         * each value as a pixel in pixmin and pixmax, respectively. */
2622
0
    pixMinMaxTiles(pixs, sx, sy, mindiff, smoothx, smoothy, &pixmin, &pixmax);
2623
2624
        /* For each tile, do a linear expansion of the dynamic range
2625
         * of pixels so that the min value is mapped to 0 and the
2626
         * max value is mapped to 255.  */
2627
0
    pixd = pixLinearTRCTiled(pixd, pixs, sx, sy, pixmin, pixmax);
2628
2629
0
    pixDestroy(&pixmin);
2630
0
    pixDestroy(&pixmax);
2631
0
    return pixd;
2632
0
}
2633
2634
2635
/*!
2636
 * \brief   pixMinMaxTiles()
2637
 *
2638
 * \param[in]    pixs               8 bpp grayscale; not colormapped
2639
 * \param[in]    sx, sy             tile dimensions
2640
 * \param[in]    mindiff            minimum difference to accept as valid
2641
 * \param[in]    smoothx, smoothy   half-width of convolution kernel applied to
2642
 *                                  min and max arrays: use 0 for no smoothing
2643
 * \param[out]   ppixmin            tiled minima
2644
 * \param[out]   ppixmax            tiled maxima
2645
 * \return  0 if OK, 1 on error
2646
 *
2647
 * <pre>
2648
 * Notes:
2649
 *      (1) This computes filtered and smoothed values for the min and
2650
 *          max pixel values in each tile of the image.
2651
 *      (2) See pixContrastNorm() for usage.
2652
 * </pre>
2653
 */
2654
static l_ok
2655
pixMinMaxTiles(PIX     *pixs,
2656
               l_int32  sx,
2657
               l_int32  sy,
2658
               l_int32  mindiff,
2659
               l_int32  smoothx,
2660
               l_int32  smoothy,
2661
               PIX    **ppixmin,
2662
               PIX    **ppixmax)
2663
0
{
2664
0
l_int32  w, h;
2665
0
PIX     *pixmin1, *pixmax1, *pixmin2, *pixmax2;
2666
2667
0
    if (ppixmin) *ppixmin = NULL;
2668
0
    if (ppixmax) *ppixmax = NULL;
2669
0
    if (!ppixmin || !ppixmax)
2670
0
        return ERROR_INT("&pixmin or &pixmax undefined", __func__, 1);
2671
0
    if (!pixs || pixGetDepth(pixs) != 8)
2672
0
        return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1);
2673
0
    if (pixGetColormap(pixs))
2674
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
2675
0
    if (sx < 5 || sy < 5)
2676
0
        return ERROR_INT("sx and/or sy less than 3", __func__, 1);
2677
0
    if (smoothx < 0 || smoothy < 0)
2678
0
        return ERROR_INT("smooth params less than 0", __func__, 1);
2679
0
    if (smoothx > 5 || smoothy > 5)
2680
0
        return ERROR_INT("smooth params exceed 5", __func__, 1);
2681
2682
        /* Get the min and max values in each tile */
2683
0
    pixmin1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MIN);
2684
0
    pixmax1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MAX);
2685
2686
0
    pixmin2 = pixExtendByReplication(pixmin1, 1, 1);
2687
0
    pixmax2 = pixExtendByReplication(pixmax1, 1, 1);
2688
0
    pixDestroy(&pixmin1);
2689
0
    pixDestroy(&pixmax1);
2690
2691
        /* Make sure no value is 0 */
2692
0
    pixAddConstantGray(pixmin2, 1);
2693
0
    pixAddConstantGray(pixmax2, 1);
2694
2695
        /* Generate holes where the contrast is too small */
2696
0
    pixSetLowContrast(pixmin2, pixmax2, mindiff);
2697
2698
        /* Fill the holes (0 values) */
2699
0
    pixGetDimensions(pixmin2, &w, &h, NULL);
2700
0
    pixFillMapHoles(pixmin2, w, h, L_FILL_BLACK);
2701
0
    pixFillMapHoles(pixmax2, w, h, L_FILL_BLACK);
2702
2703
        /* Smooth if requested */
2704
0
    if (smoothx > 0 || smoothy > 0) {
2705
0
        smoothx = L_MIN(smoothx, (w - 1) / 2);
2706
0
        smoothy = L_MIN(smoothy, (h - 1) / 2);
2707
0
        *ppixmin = pixBlockconv(pixmin2, smoothx, smoothy);
2708
0
        *ppixmax = pixBlockconv(pixmax2, smoothx, smoothy);
2709
0
    }
2710
0
    else {
2711
0
        *ppixmin = pixClone(pixmin2);
2712
0
        *ppixmax = pixClone(pixmax2);
2713
0
    }
2714
0
    pixCopyResolution(*ppixmin, pixs);
2715
0
    pixCopyResolution(*ppixmax, pixs);
2716
0
    pixDestroy(&pixmin2);
2717
0
    pixDestroy(&pixmax2);
2718
2719
0
    return 0;
2720
0
}
2721
2722
2723
/*!
2724
 * \brief   pixSetLowContrast()
2725
 *
2726
 * \param[in]    pixs1      8 bpp
2727
 * \param[in]    pixs2      8 bpp
2728
 * \param[in]    mindiff    minimum difference to accept as valid
2729
 * \return  0 if OK; 1 if no pixel diffs are large enough, or on error
2730
 *
2731
 * <pre>
2732
 * Notes:
2733
 *      (1) This compares corresponding pixels in pixs1 and pixs2.
2734
 *          When they differ by less than %mindiff, set the pixel
2735
 *          values to 0 in each.  Each pixel typically represents a tile
2736
 *          in a larger image, and a very small difference between
2737
 *          the min and max in the tile indicates that the min and max
2738
 *          values are not to be trusted.
2739
 *      (2) If contrast (pixel difference) detection is expected to fail,
2740
 *          caller should check return value.
2741
 * </pre>
2742
 */
2743
static l_ok
2744
pixSetLowContrast(PIX     *pixs1,
2745
                  PIX     *pixs2,
2746
                  l_int32  mindiff)
2747
0
{
2748
0
l_int32    i, j, w, h, d, wpl, val1, val2, found;
2749
0
l_uint32  *data1, *data2, *line1, *line2;
2750
2751
0
    if (!pixs1 || !pixs2)
2752
0
        return ERROR_INT("pixs1 and pixs2 not both defined", __func__, 1);
2753
0
    if (pixSizesEqual(pixs1, pixs2) == 0)
2754
0
        return ERROR_INT("pixs1 and pixs2 not equal size", __func__, 1);
2755
0
    pixGetDimensions(pixs1, &w, &h, &d);
2756
0
    if (d != 8)
2757
0
        return ERROR_INT("depth not 8 bpp", __func__, 1);
2758
0
    if (mindiff > 254) return 0;
2759
2760
0
    data1 = pixGetData(pixs1);
2761
0
    data2 = pixGetData(pixs2);
2762
0
    wpl = pixGetWpl(pixs1);
2763
0
    found = 0;  /* init to not finding any diffs >= mindiff */
2764
0
    for (i = 0; i < h; i++) {
2765
0
        line1 = data1 + i * wpl;
2766
0
        line2 = data2 + i * wpl;
2767
0
        for (j = 0; j < w; j++) {
2768
0
            val1 = GET_DATA_BYTE(line1, j);
2769
0
            val2 = GET_DATA_BYTE(line2, j);
2770
0
            if (L_ABS(val1 - val2) >= mindiff) {
2771
0
                found = 1;
2772
0
                break;
2773
0
            }
2774
0
        }
2775
0
        if (found) break;
2776
0
    }
2777
0
    if (!found) {
2778
0
        L_WARNING("no pixel pair diffs as large as mindiff\n", __func__);
2779
0
        pixClearAll(pixs1);
2780
0
        pixClearAll(pixs2);
2781
0
        return 1;
2782
0
    }
2783
2784
0
    for (i = 0; i < h; i++) {
2785
0
        line1 = data1 + i * wpl;
2786
0
        line2 = data2 + i * wpl;
2787
0
        for (j = 0; j < w; j++) {
2788
0
            val1 = GET_DATA_BYTE(line1, j);
2789
0
            val2 = GET_DATA_BYTE(line2, j);
2790
0
            if (L_ABS(val1 - val2) < mindiff) {
2791
0
                SET_DATA_BYTE(line1, j, 0);
2792
0
                SET_DATA_BYTE(line2, j, 0);
2793
0
            }
2794
0
        }
2795
0
    }
2796
2797
0
    return 0;
2798
0
}
2799
2800
2801
/*!
2802
 * \brief   pixLinearTRCTiled()
2803
 *
2804
 * \param[in]    pixd     [optional] 8 bpp
2805
 * \param[in]    pixs     8 bpp, not colormapped
2806
 * \param[in]    sx, sy   tile dimensions
2807
 * \param[in]    pixmin   pix of min values in tiles
2808
 * \param[in]    pixmax   pix of max values in tiles
2809
 * \return  pixd always
2810
 *
2811
 * <pre>
2812
 * Notes:
2813
 *      (1) pixd can be equal to pixs (in-place operation) or
2814
 *          null (makes a new pixd).
2815
 *      (2) sx and sy give the tile size; they are typically at least 20.
2816
 *      (3) pixmin and pixmax are generated by pixMinMaxTiles()
2817
 *      (4) For each tile, this does a linear expansion of the dynamic
2818
 *          range so that the min value in the tile becomes 0 and the
2819
 *          max value in the tile becomes 255.
2820
 *      (5) The LUTs that do the mapping are generated as needed
2821
 *          and stored for reuse in an integer array within the ptr array iaa[].
2822
 * </pre>
2823
 */
2824
static PIX *
2825
pixLinearTRCTiled(PIX       *pixd,
2826
                  PIX       *pixs,
2827
                  l_int32    sx,
2828
                  l_int32    sy,
2829
                  PIX       *pixmin,
2830
                  PIX       *pixmax)
2831
0
{
2832
0
l_int32    i, j, k, m, w, h, wt, ht, wpl, wplt, xoff, yoff;
2833
0
l_int32    minval, maxval, val, sval;
2834
0
l_int32   *ia;
2835
0
l_int32  **iaa;
2836
0
l_uint32  *data, *datamin, *datamax, *line, *tline, *linemin, *linemax;
2837
2838
0
    if (!pixs || pixGetDepth(pixs) != 8)
2839
0
        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd);
2840
0
    if (pixd && pixd != pixs)
2841
0
        return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd);
2842
0
    if (pixGetColormap(pixs))
2843
0
        return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd);
2844
0
    if (!pixmin || !pixmax)
2845
0
        return (PIX *)ERROR_PTR("pixmin & pixmax not defined", __func__, pixd);
2846
0
    if (sx < 5 || sy < 5)
2847
0
        return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd);
2848
2849
0
    iaa = (l_int32 **)LEPT_CALLOC(256, sizeof(l_int32 *));
2850
0
    if ((pixd = pixCopy(pixd, pixs)) == NULL) {
2851
0
        LEPT_FREE(iaa);
2852
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
2853
0
    }
2854
0
    pixGetDimensions(pixd, &w, &h, NULL);
2855
2856
0
    data = pixGetData(pixd);
2857
0
    wpl = pixGetWpl(pixd);
2858
0
    datamin = pixGetData(pixmin);
2859
0
    datamax = pixGetData(pixmax);
2860
0
    wplt = pixGetWpl(pixmin);
2861
0
    pixGetDimensions(pixmin, &wt, &ht, NULL);
2862
0
    for (i = 0; i < ht; i++) {
2863
0
        line = data + sy * i * wpl;
2864
0
        linemin = datamin + i * wplt;
2865
0
        linemax = datamax + i * wplt;
2866
0
        yoff = sy * i;
2867
0
        for (j = 0; j < wt; j++) {
2868
0
            xoff = sx * j;
2869
0
            minval = GET_DATA_BYTE(linemin, j);
2870
0
            maxval = GET_DATA_BYTE(linemax, j);
2871
0
            if (maxval == minval) {
2872
0
                L_ERROR("shouldn't happen! i,j = %d,%d, minval = %d\n",
2873
0
                        __func__, i, j, minval);
2874
0
                continue;
2875
0
            }
2876
0
            if ((ia = iaaGetLinearTRC(iaa, maxval - minval)) == NULL) {
2877
0
                L_ERROR("failure to make ia for j = %d!\n", __func__, j);
2878
0
                continue;
2879
0
            }
2880
0
            for (k = 0; k < sy && yoff + k < h; k++) {
2881
0
                tline = line + k * wpl;
2882
0
                for (m = 0; m < sx && xoff + m < w; m++) {
2883
0
                    val = GET_DATA_BYTE(tline, xoff + m);
2884
0
                    sval = val - minval;
2885
0
                    sval = L_MAX(0, sval);
2886
0
                    SET_DATA_BYTE(tline, xoff + m, ia[sval]);
2887
0
                }
2888
0
            }
2889
0
        }
2890
0
    }
2891
2892
0
    for (i = 0; i < 256; i++)
2893
0
        LEPT_FREE(iaa[i]);
2894
0
    LEPT_FREE(iaa);
2895
0
    return pixd;
2896
0
}
2897
2898
2899
/*!
2900
 * \brief   iaaGetLinearTRC()
2901
 *
2902
 * \param[in]    iaa     bare array of ptrs to l_int32
2903
 * \param[in]    diff    between min and max pixel values that are
2904
 *                       to be mapped to 0 and 255
2905
 * \return  ia LUT with input (val - minval) and output a
2906
 *                  value between 0 and 255)
2907
 */
2908
static l_int32 *
2909
iaaGetLinearTRC(l_int32  **iaa,
2910
                l_int32    diff)
2911
0
{
2912
0
l_int32    i;
2913
0
l_int32   *ia;
2914
0
l_float32  factor;
2915
2916
0
    if (!iaa)
2917
0
        return (l_int32 *)ERROR_PTR("iaa not defined", __func__, NULL);
2918
2919
0
    if (iaa[diff] != NULL)  /* already have it */
2920
0
       return iaa[diff];
2921
2922
0
    ia = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2923
0
    iaa[diff] = ia;
2924
0
    if (diff == 0) {  /* shouldn't happen */
2925
0
        for (i = 0; i < 256; i++)
2926
0
            ia[i] = 128;
2927
0
    }
2928
0
    else {
2929
0
        factor = 255. / (l_float32)diff;
2930
0
        for (i = 0; i < diff + 1; i++)
2931
0
            ia[i] = (l_int32)(factor * i + 0.5);
2932
0
        for (i = diff + 1; i < 256; i++)
2933
0
            ia[i] = 255;
2934
0
    }
2935
2936
0
    return ia;
2937
0
}
2938
2939
2940
/*------------------------------------------------------------------*
2941
 *   Adaptive normalization with MinMax conversion of RGB to gray,  *
2942
 *   contrast enhancement and optional 2x upscale binarization      *
2943
 *------------------------------------------------------------------*/
2944
/*!
2945
 * \brief   pixBackgroundNormTo1MinMax()
2946
 *
2947
 * \param[in]    pixs          any depth, with or without colormap
2948
 * \param[in]    contrast      1 to 10: 1 reduces contrast; 10 is maximum
2949
 *                             enhancement
2950
 * \param[in]    scalefactor   1 (no change); 2 (2x upscale)
2951
 * \return  1 bpp pix if OK; NULL on error
2952
 *
2953
 * <pre>
2954
 * Notes:
2955
 *    (1) This is a convenience binarization function that does four things:
2956
 *        * Generates a grayscale image with color enhancement to gray
2957
 *        * Background normalization
2958
 *        * Optional contrast enhancement
2959
 *        * Binarizes either at input resolution or with 2x upscaling
2960
 *    (2) If the %pixs is 1 bpp, returns a copy.
2961
 *    (3) The contrast increasing parameter %contrast takes values {1, ... 10}.
2962
 *        For decent scans, contrast = 1 is recommended.  Use a larger
2963
 *        value if important details are lost in binarization.
2964
 *    (4) Valid values of %scalefactor are 1 and 2.
2965
 * </pre>
2966
 */
2967
PIX *
2968
pixBackgroundNormTo1MinMax(PIX     *pixs,
2969
                           l_int32  contrast,
2970
                           l_int32  scalefactor)
2971
0
{
2972
0
PIX  *pix1, *pix2, *pixd;
2973
2974
0
    if (!pixs)
2975
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
2976
0
    if (contrast < 1 || contrast > 10)
2977
0
        return (PIX *)ERROR_PTR("contrast not in [1 ... 10]", __func__, NULL);
2978
0
    if (scalefactor != 1 && scalefactor != 2)
2979
0
        return (PIX *)ERROR_PTR("scalefactor not 1 or 2", __func__, NULL);
2980
2981
0
    if (pixGetDepth(pixs) == 1) {
2982
0
        pixd = pixCopy(NULL, pixs);
2983
0
    } else {
2984
0
        pix1 = pixConvertTo8MinMax(pixs);
2985
0
        pix2 = pixBackgroundNormSimple(pix1, NULL, NULL);
2986
0
        pixSelectiveContrastMod(pix2, contrast);
2987
0
        if (scalefactor == 1)
2988
0
            pixd = pixThresholdToBinary(pix2, 180);
2989
0
        else  /* scalefactor == 2 */
2990
0
            pixd = pixScaleGray2xLIThresh(pix2, 180);
2991
0
        pixDestroy(&pix1);
2992
0
        pixDestroy(&pix2);
2993
0
    }
2994
0
    return pixd;
2995
0
}
2996
2997
2998
/*!
2999
 * \brief   pixConvertTo8MinMax()
3000
 *
3001
 * \param[in]    pixs       any depth, with or without colormap
3002
 * \return  8 bpp pix if OK; NULL on error
3003
 *
3004
 * <pre>
3005
 * Notes:
3006
 *    (1) This is a special version of pixConvert1To8() that removes any
3007
 *        existing colormap and uses pixConvertRGBToGrayMinMax()
3008
 *        to strongly render color into black.
3009
 * </pre>
3010
 */
3011
PIX *
3012
pixConvertTo8MinMax(PIX  *pixs)
3013
0
{
3014
0
l_int32  d;
3015
3016
0
    if (!pixs)
3017
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3018
3019
0
    d = pixGetDepth(pixs);
3020
0
    if (d == 1) {
3021
0
        return pixConvert1To8(NULL, pixs, 255, 0);
3022
0
    } else if (d == 2) {
3023
0
        return pixConvert2To8(pixs, 0, 85, 170, 255, FALSE);
3024
0
    } else if (d == 4) {
3025
0
        return pixConvert4To8(pixs, FALSE);
3026
0
    } else if (d == 8) {
3027
0
        if (pixGetColormap(pixs) != NULL)
3028
0
            return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
3029
0
        else
3030
0
            return pixCopy(NULL, pixs);
3031
0
    } else if (d == 16) {
3032
0
        return pixConvert16To8(pixs, L_MS_BYTE);
3033
0
    } else if (d == 32) {
3034
0
        return pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MIN);
3035
0
    }
3036
3037
0
    L_ERROR("Invalid depth d = %d\n", __func__, d);
3038
0
    return NULL;
3039
0
}
3040
3041
3042
/*!
3043
 * \brief   pixSelectiveContrastMod()
3044
 *
3045
 * \param[in]    pixs       8 bpp without colormap
3046
 * \param[in]    contrast   1 (default value) for some contrast reduction;
3047
 *                          10 for maximum contrast enhancement.
3048
 * \return  0 if OK, 1 on error
3049
 *
3050
 * <pre>
3051
 * Notes:
3052
 *    (1) This does in-place contrast enhancement on 8 bpp grayscale that
3053
 *        has been background normalized to 200.  Therefore, there should
3054
 *        be no gray pixels above 200 in %pixs.  For general contrast
3055
 *        enhancement on gray or color images, see pixContrastTRC().
3056
 *    (2) Caller restricts %contrast to [1 ... 10].
3057
 *    (3) Use %contrast = 1 for minimum contrast enhancement (which will
3058
 *        remove some speckle noise) and %contrast = 10 for maximum
3059
 *        darkening.
3060
 *    (4) We use 200 for the white point in all transforms.  Using a
3061
 *        white point above 200 will darken all grayscale pixels.
3062
 * </pre>
3063
 */
3064
static l_ok
3065
pixSelectiveContrastMod(PIX     *pixs,
3066
                        l_int32  contrast)
3067
0
{
3068
0
    if (!pixs || pixGetDepth(pixs) != 8)
3069
0
        return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1);
3070
3071
0
    if (contrast == 1)
3072
0
        pixGammaTRC(pixs, pixs, 2.0, 50, 200);
3073
0
    else if (contrast == 2)
3074
0
        pixGammaTRC(pixs, pixs, 1.8, 60, 200);
3075
0
    else if (contrast == 3)
3076
0
        pixGammaTRC(pixs, pixs, 1.6, 70, 200);
3077
0
    else if (contrast == 4)
3078
0
        pixGammaTRC(pixs, pixs, 1.4, 80, 200);
3079
0
    else if (contrast == 5)
3080
0
        pixGammaTRC(pixs, pixs, 1.2, 90, 200);
3081
0
    else if (contrast == 6)
3082
0
        pixGammaTRC(pixs, pixs, 1.0, 100, 200);
3083
0
    else if (contrast == 7)
3084
0
        pixGammaTRC(pixs, pixs, 0.85, 110, 200);
3085
0
    else if (contrast == 8)
3086
0
        pixGammaTRC(pixs, pixs, 0.7, 120, 200);
3087
0
    else if (contrast == 9)
3088
0
        pixGammaTRC(pixs, pixs, 0.6, 130, 200);
3089
0
    else  /* contrast == 10 */
3090
0
        pixGammaTRC(pixs, pixs, 0.5, 140, 200);
3091
3092
0
    return 0;
3093
0
}
3094