Coverage Report

Created: 2024-06-18 06:05

/src/leptonica/src/pix3.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 pix3.c
29
 * <pre>
30
 *
31
 *    This file has these operations:
32
 *
33
 *      (1) Mask-directed operations
34
 *      (2) Full-image bit-logical operations
35
 *      (3) Foreground pixel counting operations on 1 bpp images
36
 *      (4) Average and variance of pixel values
37
 *      (5) Mirrored tiling of a smaller image
38
 *
39
 *
40
 *    Masked operations
41
 *           l_int32     pixSetMasked()
42
 *           l_int32     pixSetMaskedGeneral()
43
 *           l_int32     pixCombineMasked()
44
 *           l_int32     pixCombineMaskedGeneral()
45
 *           l_int32     pixPaintThroughMask()
46
 *           l_int32     pixCopyWithBoxa()  -- this is boxa-directed
47
 *           PIX        *pixPaintSelfThroughMask()
48
 *           PIX        *pixMakeMaskFromVal()
49
 *           PIX        *pixMakeMaskFromLUT()
50
 *           PIX        *pixMakeArbMaskFromRGB()
51
 *           PIX        *pixSetUnderTransparency()
52
 *           PIX        *pixMakeAlphaFromMask()
53
 *           l_int32     pixGetColorNearMaskBoundary()
54
 *           PIX        *pixDisplaySelectedPixels()  -- for debugging
55
 *
56
 *    One and two-image boolean operations on arbitrary depth images
57
 *           PIX        *pixInvert()
58
 *           PIX        *pixOr()
59
 *           PIX        *pixAnd()
60
 *           PIX        *pixXor()
61
 *           PIX        *pixSubtract()
62
 *
63
 *    Foreground pixel counting in 1 bpp images
64
 *           l_int32     pixZero()
65
 *           l_int32     pixForegroundFraction()
66
 *           NUMA       *pixaCountPixels()
67
 *           l_int32     pixCountPixels()
68
 *           l_int32     pixCountPixelsInRect()
69
 *           NUMA       *pixCountByRow()
70
 *           NUMA       *pixCountByColumn()
71
 *           NUMA       *pixCountPixelsByRow()
72
 *           NUMA       *pixCountPixelsByColumn()
73
 *           l_int32     pixCountPixelsInRow()
74
 *           NUMA       *pixGetMomentByColumn()
75
 *           l_int32     pixThresholdPixelSum()
76
 *           l_int32    *makePixelSumTab8()
77
 *           l_int32    *makePixelCentroidTab8()
78
 *
79
 *    Average of pixel values in gray images
80
 *           NUMA       *pixAverageByRow()
81
 *           NUMA       *pixAverageByColumn()
82
 *           l_int32     pixAverageInRect()
83
 *
84
 *    Average of pixel values in RGB images
85
 *           l_int32     pixAverageInRectRGB()
86
 *
87
 *    Variance of pixel values in gray images
88
 *           NUMA       *pixVarianceByRow()
89
 *           NUMA       *pixVarianceByColumn()
90
 *           l_int32     pixVarianceInRect()
91
 *
92
 *    Average of absolute value of pixel differences in gray images
93
 *           NUMA       *pixAbsDiffByRow()
94
 *           NUMA       *pixAbsDiffByColumn()
95
 *           l_int32     pixAbsDiffInRect()
96
 *           l_int32     pixAbsDiffOnLine()
97
 *
98
 *    Count of pixels with specific value
99
 *           l_int32     pixCountArbInRect()
100
 *
101
 *    Mirrored tiling
102
 *           PIX        *pixMirroredTiling()
103
 *
104
 *    Representative tile near but outside region
105
 *           l_int32     pixFindRepCloseTile()
106
 *
107
 *    Static helper function
108
 *           static BOXA    *findTileRegionsForSearch()
109
 * </pre>
110
 */
111
112
#ifdef HAVE_CONFIG_H
113
#include <config_auto.h>
114
#endif  /* HAVE_CONFIG_H */
115
116
#include <string.h>
117
#include <math.h>
118
#include "allheaders.h"
119
120
static BOXA *findTileRegionsForSearch(BOX *box, l_int32 w, l_int32 h,
121
                                      l_int32 searchdir, l_int32 mindist,
122
                                      l_int32 tsize, l_int32 ntiles);
123
124
#ifndef  NO_CONSOLE_IO
125
#define   EQUAL_SIZE_WARNING      0
126
#endif  /* ~NO_CONSOLE_IO */
127
128
/*-------------------------------------------------------------*
129
 *                        Masked operations                    *
130
 *-------------------------------------------------------------*/
131
/*!
132
 * \brief   pixSetMasked()
133
 *
134
 * \param[in]   pixd   1, 2, 4, 8, 16 or 32 bpp; or colormapped
135
 * \param[in]   pixm   [optional] 1 bpp mask; no operation if NULL
136
 * \param[in]   val    value to set at each masked pixel
137
 * \return  0 if OK; 1 on error
138
 *
139
 * <pre>
140
 * Notes:
141
 *      (1) In-place operation.
142
 *      (2) NOTE: For cmapped images, this calls pixSetMaskedCmap().
143
 *          %val must be the 32-bit color representation of the RGB pixel.
144
 *          It is not the index into the colormap!
145
 *      (2) If pixm == NULL, a warning is given.
146
 *      (3) This is an implicitly aligned operation, where the UL
147
 *          corners of pixd and pixm coincide.  A warning is
148
 *          issued if the two image sizes differ significantly,
149
 *          but the operation proceeds.
150
 *      (4) Each pixel in pixd that co-locates with an ON pixel
151
 *          in pixm is set to the specified input value.
152
 *          Other pixels in pixd are not changed.
153
 *      (5) You can visualize this as painting the color through
154
 *          the mask, as a stencil.
155
 *      (6) If you do not want to have the UL corners aligned,
156
 *          use the function pixSetMaskedGeneral(), which requires
157
 *          you to input the UL corner of pixm relative to pixd.
158
 *      (7) Implementation details: see comments in pixPaintThroughMask()
159
 *          for when we use rasterop to do the painting.
160
 * </pre>
161
 */
162
l_ok
163
pixSetMasked(PIX      *pixd,
164
             PIX      *pixm,
165
             l_uint32  val)
166
0
{
167
0
l_int32    wd, hd, wm, hm, w, h, d, wpld, wplm;
168
0
l_int32    i, j, rval, gval, bval;
169
0
l_uint32  *datad, *datam, *lined, *linem;
170
171
0
    if (!pixd)
172
0
        return ERROR_INT("pixd not defined", __func__, 1);
173
0
    if (!pixm) {
174
0
        L_WARNING("no mask; nothing to do\n", __func__);
175
0
        return 0;
176
0
    }
177
0
    if (pixGetColormap(pixd)) {
178
0
        extractRGBValues(val, &rval, &gval, &bval);
179
0
        return pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval);
180
0
    }
181
182
0
    if (pixGetDepth(pixm) != 1)
183
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
184
0
    d = pixGetDepth(pixd);
185
0
    if (d == 1)
186
0
        val &= 1;
187
0
    else if (d == 2)
188
0
        val &= 3;
189
0
    else if (d == 4)
190
0
        val &= 0x0f;
191
0
    else if (d == 8)
192
0
        val &= 0xff;
193
0
    else if (d == 16)
194
0
        val &= 0xffff;
195
0
    else if (d != 32)
196
0
        return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", __func__, 1);
197
0
    pixGetDimensions(pixm, &wm, &hm, NULL);
198
199
        /* If d == 1, use rasterop; it's about 25x faster */
200
0
    if (d == 1) {
201
0
        if (val == 0) {
202
0
            PIX *pixmi = pixInvert(NULL, pixm);
203
0
            pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmi, 0, 0);
204
0
            pixDestroy(&pixmi);
205
0
        } else {  /* val == 1 */
206
0
            pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixm, 0, 0);
207
0
        }
208
0
        return 0;
209
0
    }
210
211
        /* For d < 32, use rasterop for val == 0 (black); ~3x faster. */
212
0
    if (d < 32 && val == 0) {
213
0
        PIX *pixmd = pixUnpackBinary(pixm, d, 1);
214
0
        pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmd, 0, 0);
215
0
        pixDestroy(&pixmd);
216
0
        return 0;
217
0
    }
218
219
        /* For d < 32, use rasterop for val == maxval (white); ~3x faster. */
220
0
    if (d < 32 && val == ((1 << d) - 1)) {
221
0
        PIX *pixmd = pixUnpackBinary(pixm, d, 0);
222
0
        pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixmd, 0, 0);
223
0
        pixDestroy(&pixmd);
224
0
        return 0;
225
0
    }
226
227
0
    pixGetDimensions(pixd, &wd, &hd, &d);
228
0
    w = L_MIN(wd, wm);
229
0
    h = L_MIN(hd, hm);
230
0
    if (L_ABS(wd - wm) > 7 || L_ABS(hd - hm) > 7)  /* allow a small tolerance */
231
0
        L_WARNING("pixd and pixm sizes differ\n", __func__);
232
233
0
    datad = pixGetData(pixd);
234
0
    datam = pixGetData(pixm);
235
0
    wpld = pixGetWpl(pixd);
236
0
    wplm = pixGetWpl(pixm);
237
0
    for (i = 0; i < h; i++) {
238
0
        lined = datad + i * wpld;
239
0
        linem = datam + i * wplm;
240
0
        for (j = 0; j < w; j++) {
241
0
            if (GET_DATA_BIT(linem, j)) {
242
0
                switch(d)
243
0
                {
244
0
                case 2:
245
0
                    SET_DATA_DIBIT(lined, j, val);
246
0
                    break;
247
0
                case 4:
248
0
                    SET_DATA_QBIT(lined, j, val);
249
0
                    break;
250
0
                case 8:
251
0
                    SET_DATA_BYTE(lined, j, val);
252
0
                    break;
253
0
                case 16:
254
0
                    SET_DATA_TWO_BYTES(lined, j, val);
255
0
                    break;
256
0
                case 32:
257
0
                    *(lined + j) = val;
258
0
                    break;
259
0
                default:
260
0
                    return ERROR_INT("shouldn't get here", __func__, 1);
261
0
                }
262
0
            }
263
0
        }
264
0
    }
265
266
0
    return 0;
267
0
}
268
269
270
/*!
271
 * \brief   pixSetMaskedGeneral()
272
 *
273
 * \param[in]   pixd    8, 16 or 32 bpp
274
 * \param[in]   pixm    [optional] 1 bpp mask; no operation if null
275
 * \param[in]   val     value to set at each masked pixel
276
 * \param[in]   x, y    location of UL corner of pixm relative to pixd;
277
 *                      can be negative
278
 * \return  0 if OK; 1 on error
279
 *
280
 * <pre>
281
 * Notes:
282
 *      (1) This is an in-place operation.
283
 *      (2) Alignment is explicit.  If you want the UL corners of
284
 *          the two images to be aligned, use pixSetMasked().
285
 *      (3) A typical use would be painting through the foreground
286
 *          of a small binary mask pixm, located somewhere on a
287
 *          larger pixd.  Other pixels in pixd are not changed.
288
 *      (4) You can visualize this as painting the color through
289
 *          the mask, as a stencil.
290
 *      (5) This uses rasterop to handle clipping and different depths of pixd.
291
 *      (6) If pixd has a colormap, you should call pixPaintThroughMask().
292
 *      (7) Why is this function here, if pixPaintThroughMask() does the
293
 *          same thing, and does it more generally?  I've retained it here
294
 *          to show how one can paint through a mask using only full
295
 *          image rasterops, rather than pixel peeking in pixm and poking
296
 *          in pixd.  It's somewhat baroque, but I found it amusing.
297
 * </pre>
298
 */
299
l_ok
300
pixSetMaskedGeneral(PIX      *pixd,
301
                    PIX      *pixm,
302
                    l_uint32  val,
303
                    l_int32   x,
304
                    l_int32   y)
305
0
{
306
0
l_int32    wm, hm, d;
307
0
PIX       *pixmu, *pixc;
308
309
0
    if (!pixd)
310
0
        return ERROR_INT("pixd not defined", __func__, 1);
311
0
    if (!pixm)  /* nothing to do */
312
0
        return 0;
313
314
0
    d = pixGetDepth(pixd);
315
0
    if (d != 8 && d != 16 && d != 32)
316
0
        return ERROR_INT("pixd not 8, 16 or 32 bpp", __func__, 1);
317
0
    if (pixGetDepth(pixm) != 1)
318
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
319
320
        /* Unpack binary to depth d, with inversion:  1 --> 0, 0 --> 0xff... */
321
0
    if ((pixmu = pixUnpackBinary(pixm, d, 1)) == NULL)
322
0
        return ERROR_INT("pixmu not made", __func__, 1);
323
324
        /* Clear stenciled pixels in pixd */
325
0
    pixGetDimensions(pixm, &wm, &hm, NULL);
326
0
    pixRasterop(pixd, x, y, wm, hm, PIX_SRC & PIX_DST, pixmu, 0, 0);
327
328
        /* Generate image with requisite color */
329
0
    if ((pixc = pixCreateTemplate(pixmu)) == NULL) {
330
0
        pixDestroy(&pixmu);
331
0
        return ERROR_INT("pixc not made", __func__, 1);
332
0
    }
333
0
    pixSetAllArbitrary(pixc, val);
334
335
        /* Invert stencil mask, and paint color color into stencil */
336
0
    pixInvert(pixmu, pixmu);
337
0
    pixAnd(pixmu, pixmu, pixc);
338
339
        /* Finally, repaint stenciled pixels, with val, in pixd */
340
0
    pixRasterop(pixd, x, y, wm, hm, PIX_SRC | PIX_DST, pixmu, 0, 0);
341
342
0
    pixDestroy(&pixmu);
343
0
    pixDestroy(&pixc);
344
0
    return 0;
345
0
}
346
347
348
/*!
349
 * \brief   pixCombineMasked()
350
 *
351
 * \param[in]   pixd   1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
352
 * \param[in]   pixs   1 bpp, 8 bpp gray or 32 bpp rgb; no cmap
353
 * \param[in]   pixm   [optional] 1 bpp mask; no operation if NULL
354
 * \return  0 if OK; 1 on error
355
 *
356
 * <pre>
357
 * Notes:
358
 *      (1) In-place operation; pixd is changed.
359
 *      (2) This sets each pixel in pixd that co-locates with an ON
360
 *          pixel in pixm to the corresponding value of pixs.
361
 *      (3) pixs and pixd must be the same depth and not colormapped.
362
 *      (4) All three input pix are aligned at the UL corner, and the
363
 *          operation is clipped to the intersection of all three images.
364
 *      (5) If pixm == NULL, it's a no-op.
365
 *      (6) Implementation: see notes in pixCombineMaskedGeneral().
366
 *          For 8 bpp selective masking, you might guess that it
367
 *          would be faster to generate an 8 bpp version of pixm,
368
 *          using pixConvert1To8(pixm, 0, 255), and then use a
369
 *          general combine operation
370
 *               d = (d & ~m) | (s & m)
371
 *          on a word-by-word basis.  Not always.  The word-by-word
372
 *          combine takes a time that is independent of the mask data.
373
 *          If the mask is relatively sparse, the byte-check method
374
 *          is actually faster!
375
 * </pre>
376
 */
377
l_ok
378
pixCombineMasked(PIX  *pixd,
379
                 PIX  *pixs,
380
                 PIX  *pixm)
381
0
{
382
0
l_int32    w, h, d, ws, hs, ds, wm, hm, dm, wmin, hmin;
383
0
l_int32    wpl, wpls, wplm, i, j, val;
384
0
l_uint32  *data, *datas, *datam, *line, *lines, *linem;
385
0
PIX       *pixt;
386
387
0
    if (!pixm)  /* nothing to do */
388
0
        return 0;
389
0
    if (!pixd)
390
0
        return ERROR_INT("pixd not defined", __func__, 1);
391
0
    if (!pixs)
392
0
        return ERROR_INT("pixs not defined", __func__, 1);
393
0
    pixGetDimensions(pixd, &w, &h, &d);
394
0
    pixGetDimensions(pixs, &ws, &hs, &ds);
395
0
    pixGetDimensions(pixm, &wm, &hm, &dm);
396
0
    if (d != ds)
397
0
        return ERROR_INT("pixs and pixd depths differ", __func__, 1);
398
0
    if (dm != 1)
399
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
400
0
    if (d != 1 && d != 8 && d != 32)
401
0
        return ERROR_INT("pixd not 1, 8 or 32 bpp", __func__, 1);
402
0
    if (pixGetColormap(pixd) || pixGetColormap(pixs))
403
0
        return ERROR_INT("pixs and/or pixd is cmapped", __func__, 1);
404
405
        /* For d = 1, use rasterop.  pixt is the part from pixs, under
406
         * the fg of pixm, that is to be combined with pixd.  We also
407
         * use pixt to remove all fg of pixd that is under the fg of pixm.
408
         * Then pixt and pixd are combined by ORing. */
409
0
    wmin = L_MIN(w, L_MIN(ws, wm));
410
0
    hmin = L_MIN(h, L_MIN(hs, hm));
411
0
    if (d == 1) {
412
0
        pixt = pixAnd(NULL, pixs, pixm);
413
0
        pixRasterop(pixd, 0, 0, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
414
0
                    pixm, 0, 0);
415
0
        pixRasterop(pixd, 0, 0, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
416
0
        pixDestroy(&pixt);
417
0
        return 0;
418
0
    }
419
420
0
    data = pixGetData(pixd);
421
0
    datas = pixGetData(pixs);
422
0
    datam = pixGetData(pixm);
423
0
    wpl = pixGetWpl(pixd);
424
0
    wpls = pixGetWpl(pixs);
425
0
    wplm = pixGetWpl(pixm);
426
0
    if (d == 8) {
427
0
        for (i = 0; i < hmin; i++) {
428
0
            line = data + i * wpl;
429
0
            lines = datas + i * wpls;
430
0
            linem = datam + i * wplm;
431
0
            for (j = 0; j < wmin; j++) {
432
0
                if (GET_DATA_BIT(linem, j)) {
433
0
                   val = GET_DATA_BYTE(lines, j);
434
0
                   SET_DATA_BYTE(line, j, val);
435
0
                }
436
0
            }
437
0
        }
438
0
    } else {  /* d == 32 */
439
0
        for (i = 0; i < hmin; i++) {
440
0
            line = data + i * wpl;
441
0
            lines = datas + i * wpls;
442
0
            linem = datam + i * wplm;
443
0
            for (j = 0; j < wmin; j++) {
444
0
                if (GET_DATA_BIT(linem, j))
445
0
                   line[j] = lines[j];
446
0
            }
447
0
        }
448
0
    }
449
450
0
    return 0;
451
0
}
452
453
454
/*!
455
 * \brief   pixCombineMaskedGeneral()
456
 *
457
 * \param[in]   pixd   1 bpp, 8 bpp gray or 32 bpp rgb
458
 * \param[in]   pixs   1 bpp, 8 bpp gray or 32 bpp rgb
459
 * \param[in]   pixm   [optional] 1 bpp mask
460
 * \param[in]   x, y   origin of pixs and pixm relative to pixd; can be negative
461
 * \return  0 if OK; 1 on error
462
 *
463
 * <pre>
464
 * Notes:
465
 *      (1) In-place operation; pixd is changed.
466
 *      (2) This is a generalized version of pixCombinedMasked(), where
467
 *          the source and mask can be placed at the same (arbitrary)
468
 *          location relative to pixd.
469
 *      (3) pixs and pixd must be the same depth and not colormapped.
470
 *      (4) The UL corners of both pixs and pixm are aligned with
471
 *          the point (x, y) of pixd, and the operation is clipped to
472
 *          the intersection of all three images.
473
 *      (5) If pixm == NULL, it's a no-op.
474
 *      (6) Implementation.  There are two ways to do these.  In the first,
475
 *          we use rasterop, ORing the part of pixs under the mask
476
 *          with pixd (which has been appropriately cleared there first).
477
 *          In the second, the mask is used one pixel at a time to
478
 *          selectively replace pixels of pixd with those of pixs.
479
 *          Here, we use rasterop for 1 bpp and pixel-wise replacement
480
 *          for 8 and 32 bpp.  To use rasterop for 8 bpp, for example,
481
 *          we must first generate an 8 bpp version of the mask.
482
 *          The code is simple:
483
 *
484
 *             Pix *pixm8 = pixConvert1To8(NULL, pixm, 0, 255);
485
 *             Pix *pixt = pixAnd(NULL, pixs, pixm8);
486
 *             pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
487
 *                         pixm8, 0, 0);
488
 *             pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST,
489
 *                         pixt, 0, 0);
490
 *             pixDestroy(&pixt);
491
 *             pixDestroy(&pixm8);
492
 * </pre>
493
 */
494
l_ok
495
pixCombineMaskedGeneral(PIX      *pixd,
496
                        PIX      *pixs,
497
                        PIX      *pixm,
498
                        l_int32   x,
499
                        l_int32   y)
500
0
{
501
0
l_int32    d, w, h, ws, hs, ds, wm, hm, dm, wmin, hmin;
502
0
l_int32    wpl, wpls, wplm, i, j, val;
503
0
l_uint32  *data, *datas, *datam, *line, *lines, *linem;
504
0
PIX       *pixt;
505
506
0
    if (!pixm)  /* nothing to do */
507
0
        return 0;
508
0
    if (!pixd)
509
0
        return ERROR_INT("pixd not defined", __func__, 1);
510
0
    if (!pixs)
511
0
        return ERROR_INT("pixs not defined", __func__, 1);
512
0
    pixGetDimensions(pixd, &w, &h, &d);
513
0
    pixGetDimensions(pixs, &ws, &hs, &ds);
514
0
    pixGetDimensions(pixm, &wm, &hm, &dm);
515
0
    if (d != ds)
516
0
        return ERROR_INT("pixs and pixd depths differ", __func__, 1);
517
0
    if (dm != 1)
518
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
519
0
    if (d != 1 && d != 8 && d != 32)
520
0
        return ERROR_INT("pixd not 1, 8 or 32 bpp", __func__, 1);
521
0
    if (pixGetColormap(pixd) || pixGetColormap(pixs))
522
0
        return ERROR_INT("pixs and/or pixd is cmapped", __func__, 1);
523
524
        /* For d = 1, use rasterop.  pixt is the part from pixs, under
525
         * the fg of pixm, that is to be combined with pixd.  We also
526
         * use pixt to remove all fg of pixd that is under the fg of pixm.
527
         * Then pixt and pixd are combined by ORing. */
528
0
    wmin = L_MIN(ws, wm);
529
0
    hmin = L_MIN(hs, hm);
530
0
    if (d == 1) {
531
0
        pixt = pixAnd(NULL, pixs, pixm);
532
0
        pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
533
0
                    pixm, 0, 0);
534
0
        pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
535
0
        pixDestroy(&pixt);
536
0
        return 0;
537
0
    }
538
539
0
    wpl = pixGetWpl(pixd);
540
0
    data = pixGetData(pixd);
541
0
    wpls = pixGetWpl(pixs);
542
0
    datas = pixGetData(pixs);
543
0
    wplm = pixGetWpl(pixm);
544
0
    datam = pixGetData(pixm);
545
546
0
    for (i = 0; i < hmin; i++) {
547
0
        if (y + i < 0 || y + i >= h) continue;
548
0
        line = data + (y + i) * wpl;
549
0
        lines = datas + i * wpls;
550
0
        linem = datam + i * wplm;
551
0
        for (j = 0; j < wmin; j++) {
552
0
            if (x + j < 0 || x + j >= w) continue;
553
0
            if (GET_DATA_BIT(linem, j)) {
554
0
                switch (d)
555
0
                {
556
0
                case 8:
557
0
                    val = GET_DATA_BYTE(lines, j);
558
0
                    SET_DATA_BYTE(line, x + j, val);
559
0
                    break;
560
0
                case 32:
561
0
                    *(line + x + j) = *(lines + j);
562
0
                    break;
563
0
                default:
564
0
                    return ERROR_INT("shouldn't get here", __func__, 1);
565
0
                }
566
0
            }
567
0
        }
568
0
    }
569
570
0
    return 0;
571
0
}
572
573
574
/*!
575
 * \brief   pixPaintThroughMask()
576
 *
577
 * \param[in]   pixd   1, 2, 4, 8, 16 or 32 bpp; or colormapped
578
 * \param[in]   pixm   [optional] 1 bpp mask
579
 * \param[in]   x, y   origin of pixm relative to pixd; can be negative
580
 * \param[in]   val    pixel value to set at each masked pixel
581
 * \return  0 if OK; 1 on error
582
 *
583
 * <pre>
584
 * Notes:
585
 *      (1) In-place operation.  Calls pixSetMaskedCmap() for colormapped
586
 *          images.
587
 *      (2) For 1, 2, 4, 8 and 16 bpp gray, we take the appropriate
588
 *          number of least significant bits of val.
589
 *      (3) If pixm == NULL, it's a no-op.
590
 *      (4) The mask origin is placed at (x,y) on pixd, and the
591
 *          operation is clipped to the intersection of rectangles.
592
 *      (5) For rgb, the components in val are in the canonical locations,
593
 *          with red in location COLOR_RED, etc.
594
 *      (6) Implementation detail 1:
595
 *          For painting with val == 0 or val == maxval, you can use rasterop.
596
 *          If val == 0, invert the mask so that it's 0 over the region
597
 *          into which you want to write, and use PIX_SRC & PIX_DST to
598
 *          clear those pixels.  To write with val = maxval (all 1's),
599
 *          use PIX_SRC | PIX_DST to set all bits under the mask.
600
 *      (7) Implementation detail 2:
601
 *          The rasterop trick can be used for depth > 1 as well.
602
 *          For val == 0, generate the mask for depth d from the binary
603
 *          mask using
604
 *              pixmd = pixUnpackBinary(pixm, d, 1);
605
 *          and use pixRasterop() with PIX_MASK.  For val == maxval,
606
 *              pixmd = pixUnpackBinary(pixm, d, 0);
607
 *          and use pixRasterop() with PIX_PAINT.
608
 *          But note that if d == 32 bpp, it is about 3x faster to use
609
 *          the general implementation (not pixRasterop()).
610
 *      (8) Implementation detail 3:
611
 *          It might be expected that the switch in the inner loop will
612
 *          cause large branching delays and should be avoided.
613
 *          This is not the case, because the entrance is always the
614
 *          same and the compiler can correctly predict the jump.
615
 * </pre>
616
 */
617
l_ok
618
pixPaintThroughMask(PIX      *pixd,
619
                    PIX      *pixm,
620
                    l_int32   x,
621
                    l_int32   y,
622
                    l_uint32  val)
623
0
{
624
0
l_int32    d, w, h, wm, hm, wpl, wplm, i, j, rval, gval, bval;
625
0
l_uint32  *data, *datam, *line, *linem;
626
627
0
    if (!pixm)  /* nothing to do */
628
0
        return 0;
629
0
    if (!pixd)
630
0
        return ERROR_INT("pixd not defined", __func__, 1);
631
0
    if (pixGetColormap(pixd)) {
632
0
        extractRGBValues(val, &rval, &gval, &bval);
633
0
        return pixSetMaskedCmap(pixd, pixm, x, y, rval, gval, bval);
634
0
    }
635
636
0
    if (pixGetDepth(pixm) != 1)
637
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
638
0
    d = pixGetDepth(pixd);
639
0
    if (d == 1)
640
0
        val &= 1;
641
0
    else if (d == 2)
642
0
        val &= 3;
643
0
    else if (d == 4)
644
0
        val &= 0x0f;
645
0
    else if (d == 8)
646
0
        val &= 0xff;
647
0
    else if (d == 16)
648
0
        val &= 0xffff;
649
0
    else if (d != 32)
650
0
        return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", __func__, 1);
651
0
    pixGetDimensions(pixm, &wm, &hm, NULL);
652
653
        /* If d == 1, use rasterop; it's about 25x faster. */
654
0
    if (d == 1) {
655
0
        if (val == 0) {
656
0
            PIX *pixmi = pixInvert(NULL, pixm);
657
0
            pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmi, 0, 0);
658
0
            pixDestroy(&pixmi);
659
0
        } else {  /* val == 1 */
660
0
            pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixm, 0, 0);
661
0
        }
662
0
        return 0;
663
0
    }
664
665
        /* For d < 32, use rasterop if val == 0 (black); ~3x faster. */
666
0
    if (d < 32 && val == 0) {
667
0
        PIX *pixmd = pixUnpackBinary(pixm, d, 1);
668
0
        pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmd, 0, 0);
669
0
        pixDestroy(&pixmd);
670
0
        return 0;
671
0
    }
672
673
        /* For d < 32, use rasterop if val == maxval (white); ~3x faster. */
674
0
    if (d < 32 && val == ((1 << d) - 1)) {
675
0
        PIX *pixmd = pixUnpackBinary(pixm, d, 0);
676
0
        pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixmd, 0, 0);
677
0
        pixDestroy(&pixmd);
678
0
        return 0;
679
0
    }
680
681
        /* All other cases */
682
0
    pixGetDimensions(pixd, &w, &h, NULL);
683
0
    wpl = pixGetWpl(pixd);
684
0
    data = pixGetData(pixd);
685
0
    wplm = pixGetWpl(pixm);
686
0
    datam = pixGetData(pixm);
687
0
    for (i = 0; i < hm; i++) {
688
0
        if (y + i < 0 || y + i >= h) continue;
689
0
        line = data + (y + i) * wpl;
690
0
        linem = datam + i * wplm;
691
0
        for (j = 0; j < wm; j++) {
692
0
            if (x + j < 0 || x + j >= w) continue;
693
0
            if (GET_DATA_BIT(linem, j)) {
694
0
                switch (d)
695
0
                {
696
0
                case 2:
697
0
                    SET_DATA_DIBIT(line, x + j, val);
698
0
                    break;
699
0
                case 4:
700
0
                    SET_DATA_QBIT(line, x + j, val);
701
0
                    break;
702
0
                case 8:
703
0
                    SET_DATA_BYTE(line, x + j, val);
704
0
                    break;
705
0
                case 16:
706
0
                    SET_DATA_TWO_BYTES(line, x + j, val);
707
0
                    break;
708
0
                case 32:
709
0
                    *(line + x + j) = val;
710
0
                    break;
711
0
                default:
712
0
                    return ERROR_INT("shouldn't get here", __func__, 1);
713
0
                }
714
0
            }
715
0
        }
716
0
    }
717
718
0
    return 0;
719
0
}
720
721
722
/*!
723
 * \brief   pixCopyWithBoxa()
724
 *
725
 * \param[in]   pixs         all depths; cmap ok
726
 * \param[in]   boxa         e.g., from components of a photomask
727
 * \param[in]   background   L_SET_WHITE or L_SET_BLACK
728
 * \return  pixd or NULL on error
729
 *
730
 * <pre>
731
 * Notes:
732
 *      (1) Pixels from pixs are copied ("blitted") through each box into pixd.
733
 *      (2) Pixels not copied are preset to either white or black.
734
 *      (3) This fast and simple implementation can use rasterop because
735
 *          each region to be copied is rectangular.
736
 *      (4) A much slower implementation that doesn't use rasterop would make
737
 *          a 1 bpp mask from the boxa and then copy, pixel by pixel,
738
 *          through the mask:
739
 *             pixGetDimensions(pixs, &w, &h, NULL);
740
 *             pixm = pixCreate(w, h, 1);
741
 *             pixm = pixMaskBoxa(pixm, pixm, boxa);
742
 *             pixd = pixCreateTemplate(pixs);
743
 *             pixSetBlackOrWhite(pixd, background);
744
 *             pixCombineMasked(pixd, pixs, pixm);
745
 *             pixDestroy(&pixm);
746
 * </pre>
747
 */
748
PIX *
749
pixCopyWithBoxa(PIX     *pixs,
750
                BOXA    *boxa,
751
                l_int32  background)
752
0
{
753
0
l_int32  i, n, x, y, w, h;
754
0
PIX     *pixd;
755
756
0
    if (!pixs)
757
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
758
0
    if (!boxa)
759
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
760
0
    if (background != L_SET_WHITE && background != L_SET_BLACK)
761
0
        return (PIX *)ERROR_PTR("invalid background", __func__, NULL);
762
763
0
    pixd = pixCreateTemplate(pixs);
764
0
    pixSetBlackOrWhite(pixd, background);
765
0
    n = boxaGetCount(boxa);
766
0
    for (i = 0; i < n; i++) {
767
0
        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
768
0
        pixRasterop(pixd, x, y, w, h, PIX_SRC, pixs, x, y);
769
0
    }
770
0
    return pixd;
771
0
}
772
773
774
/*!
775
 * \brief   pixPaintSelfThroughMask()
776
 *
777
 * \param[in]   pixd       8 bpp gray or 32 bpp rgb; not colormapped
778
 * \param[in]   pixm       1 bpp mask
779
 * \param[in]   x, y       origin of pixm relative to pixd; must not be negative
780
 * \param[in]   searchdir  L_HORIZ, L_VERT or L_BOTH_DIRECTIONS
781
 * \param[in]   mindist    min distance of nearest tile edge to box; >= 0
782
 * \param[in]   tilesize   requested size for tiling; may be reduced
783
 * \param[in]   ntiles     number of tiles tested in each row/column
784
 * \param[in]   distblend  distance outside the fg used for blending with pixs
785
 * \return  0 if OK; 1 on error
786
 *
787
 * <pre>
788
 * Notes:
789
 *      (1) In-place operation; pixd is changed.
790
 *      (2) If pixm == NULL, it's a no-op.
791
 *      (3) The mask origin is placed at (x,y) on pixd, and the
792
 *          operation is clipped to the intersection of pixd and the
793
 *          fg of the mask.
794
 *      (4) %tsize is the the requested size for tiling.  The actual
795
 *          actual size for each c.c. will be bounded by the minimum
796
 *          dimension of the c.c.
797
 *      (5) For %mindist, %searchdir and %ntiles, see pixFindRepCloseTile().
798
 *          They determine the set of possible tiles that can be used
799
 *          to build a larger mirrored tile to paint onto pixd through
800
 *          the c.c. of pixm.
801
 *      (6) %distblend is used for alpha blending.  It is only applied
802
 *          if there is exactly one c.c. in the mask.  Use distblend == 0
803
 *          to skip blending and just paint through the 1 bpp mask.
804
 *      (7) To apply blending to more than 1 component, call this function
805
 *          repeatedly with %pixm, %x and %y representing one component of
806
 *          the mask each time.  This would be done as follows, for an
807
 *          underlying image pixs and mask pixm of components to fill:
808
 *              Boxa *boxa = pixConnComp(pixm, &pixa, 8);
809
 *              n = boxaGetCount(boxa);
810
 *              for (i = 0; i < n; i++) {
811
 *                  Pix *pix = pixaGetPix(pixa, i, L_CLONE);
812
 *                  Box *box = pixaGetBox(pixa, i, L_CLONE);
813
 *                  boxGetGeometry(box, &bx, &by, &bw, &bh);
814
 *                  pixPaintSelfThroughMask(pixs, pix, bx, by, searchdir,
815
 *                                     mindist, tilesize, ntiles, distblend);
816
 *                  pixDestroy(&pix);
817
 *                  boxDestroy(&box);
818
 *              }
819
 *              pixaDestroy(&pixa);
820
 *              boxaDestroy(&boxa);
821
 *      (8) If no tiles can be found, this falls back to estimating the
822
 *          color near the boundary of the region to be textured.
823
 *      (9) This can be used to replace the pixels in some regions of
824
 *          an image by selected neighboring pixels.  The mask represents
825
 *          the pixels to be replaced.  For each connected component in
826
 *          the mask, this function selects up to two tiles of neighboring
827
 *          pixels to be used for replacement of pixels represented by
828
 *          the component (i.e., under the FG of that component in the mask).
829
 *          After selection, mirror replication is used to generate an
830
 *          image that is large enough to cover the component.  Alpha
831
 *          blending can also be used outside of the component, but near the
832
 *          edge, to blur the transition between painted and original pixels.
833
 * </pre>
834
 */
835
l_ok
836
pixPaintSelfThroughMask(PIX      *pixd,
837
                        PIX      *pixm,
838
                        l_int32   x,
839
                        l_int32   y,
840
                        l_int32   searchdir,
841
                        l_int32   mindist,
842
                        l_int32   tilesize,
843
                        l_int32   ntiles,
844
                        l_int32   distblend)
845
0
{
846
0
l_int32   w, h, d, wm, hm, dm, i, n, bx, by, bw, bh, edgeblend, retval, minside;
847
0
l_uint32  pixval;
848
0
BOX      *box, *boxv, *boxh;
849
0
BOXA     *boxa;
850
0
PIX      *pixf, *pixv, *pixh, *pix1, *pix2, *pix3, *pix4, *pix5;
851
0
PIXA     *pixa;
852
853
0
    if (!pixm)  /* nothing to do */
854
0
        return 0;
855
0
    if (!pixd)
856
0
        return ERROR_INT("pixd not defined", __func__, 1);
857
0
    if (pixGetColormap(pixd) != NULL)
858
0
        return ERROR_INT("pixd has colormap", __func__, 1);
859
0
    pixGetDimensions(pixd, &w, &h, &d);
860
0
    if (d != 8 && d != 32)
861
0
        return ERROR_INT("pixd not 8 or 32 bpp", __func__, 1);
862
0
    pixGetDimensions(pixm, &wm, &hm, &dm);
863
0
    if (dm != 1)
864
0
        return ERROR_INT("pixm not 1 bpp", __func__, 1);
865
0
    if (x < 0 || y < 0)
866
0
        return ERROR_INT("x and y must be non-negative", __func__, 1);
867
0
    if (searchdir != L_HORIZ && searchdir != L_VERT &&
868
0
        searchdir != L_BOTH_DIRECTIONS)
869
0
        return ERROR_INT("invalid searchdir", __func__, 1);
870
0
    if (tilesize < 2)
871
0
        return ERROR_INT("tilesize must be >= 2", __func__, 1);
872
0
    if (distblend < 0)
873
0
        return ERROR_INT("distblend must be >= 0", __func__, 1);
874
875
        /* Embed mask in full sized mask */
876
0
    if (wm < w || hm < h) {
877
0
        pixf = pixCreate(w, h, 1);
878
0
        pixRasterop(pixf, x, y, wm, hm, PIX_SRC, pixm, 0, 0);
879
0
    } else {
880
0
        pixf = pixCopy(NULL, pixm);
881
0
    }
882
883
        /* Get connected components of mask */
884
0
    boxa = pixConnComp(pixf, &pixa, 8);
885
0
    if ((n = pixaGetCount(pixa)) == 0) {
886
0
        L_WARNING("no fg in mask\n", __func__);
887
0
        pixDestroy(&pixf);
888
0
        pixaDestroy(&pixa);
889
0
        boxaDestroy(&boxa);
890
0
        return 1;
891
0
    }
892
0
    boxaDestroy(&boxa);
893
894
        /* For each c.c., generate one or two representative tiles for
895
         * texturizing and apply through the mask.  The input 'tilesize'
896
         * is the requested value.  Note that if there is exactly one
897
         * component, and blending at the edge is requested, an alpha mask
898
         * is generated, which is larger than the bounding box of the c.c. */
899
0
    edgeblend = (n == 1 && distblend > 0) ? 1 : 0;
900
0
    if (distblend > 0 && n > 1)
901
0
        L_WARNING("%d components; can not blend at edges\n", __func__, n);
902
0
    retval = 0;
903
0
    for (i = 0; i < n; i++) {
904
0
        if (edgeblend) {
905
0
            pix1 = pixMakeAlphaFromMask(pixf, distblend, &box);
906
0
        } else {
907
0
            pix1 = pixaGetPix(pixa, i, L_CLONE);
908
0
            box = pixaGetBox(pixa, i, L_CLONE);
909
0
        }
910
0
        boxGetGeometry(box, &bx, &by, &bw, &bh);
911
0
        minside = L_MIN(bw, bh);
912
913
0
        boxh = boxv = NULL;
914
0
        if (searchdir == L_HORIZ || searchdir == L_BOTH_DIRECTIONS) {
915
0
            pixFindRepCloseTile(pixd, box, L_HORIZ, mindist,
916
0
                                L_MIN(minside, tilesize), ntiles, &boxh, 0);
917
0
        }
918
0
        if (searchdir == L_VERT || searchdir == L_BOTH_DIRECTIONS) {
919
0
            pixFindRepCloseTile(pixd, box, L_VERT, mindist,
920
0
                                L_MIN(minside, tilesize), ntiles, &boxv, 0);
921
0
        }
922
0
        if (!boxh && !boxv) {
923
0
            L_WARNING("tile region not selected; paint color near boundary\n",
924
0
                      __func__);
925
0
            pixDestroy(&pix1);
926
0
            pix1 = pixaGetPix(pixa, i, L_CLONE);
927
0
            pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL);
928
0
            retval = pixGetColorNearMaskBoundary(pixd, pixm, box, distblend,
929
0
                                                 &pixval, 0);
930
0
            pixSetMaskedGeneral(pixd, pix1, pixval, bx, by);
931
0
            pixDestroy(&pix1);
932
0
            boxDestroy(&box);
933
0
            continue;
934
0
        }
935
936
            /* Extract the selected squares from pixd */
937
0
        pixh = (boxh) ? pixClipRectangle(pixd, boxh, NULL) : NULL;
938
0
        pixv = (boxv) ? pixClipRectangle(pixd, boxv, NULL) : NULL;
939
0
        if (pixh && pixv)
940
0
            pix2 = pixBlend(pixh, pixv, 0, 0, 0.5);
941
0
        else if (pixh)
942
0
            pix2 = pixClone(pixh);
943
0
        else  /* pixv */
944
0
            pix2 = pixClone(pixv);
945
0
        pixDestroy(&pixh);
946
0
        pixDestroy(&pixv);
947
0
        boxDestroy(&boxh);
948
0
        boxDestroy(&boxv);
949
950
            /* Generate an image the size of the b.b. of the c.c.,
951
             * possibly extended by the blending distance, which
952
             * is then either painted through the c.c. mask or
953
             * blended using the alpha mask for that c.c.  */
954
0
        pix3 = pixMirroredTiling(pix2, bw, bh);
955
0
        if (edgeblend) {
956
0
            pix4 = pixClipRectangle(pixd, box, NULL);
957
0
            pix5 = pixBlendWithGrayMask(pix4, pix3, pix1, 0, 0);
958
0
            pixRasterop(pixd, bx, by, bw, bh, PIX_SRC, pix5, 0, 0);
959
0
            pixDestroy(&pix4);
960
0
            pixDestroy(&pix5);
961
0
        } else {
962
0
            pixCombineMaskedGeneral(pixd, pix3, pix1, bx, by);
963
0
        }
964
0
        pixDestroy(&pix1);
965
0
        pixDestroy(&pix2);
966
0
        pixDestroy(&pix3);
967
0
        boxDestroy(&box);
968
0
    }
969
970
0
    pixaDestroy(&pixa);
971
0
    pixDestroy(&pixf);
972
0
    return retval;
973
0
}
974
975
976
/*!
977
 * \brief   pixMakeMaskFromVal()
978
 *
979
 * \param[in]   pixs   2, 4 or 8 bpp; can be colormapped
980
 * \param[in]   val    pixel value
981
 * \return  pixd 1 bpp mask, or NULL on error
982
 *
983
 * <pre>
984
 * Notes:
985
 *      (1) This generates a 1 bpp mask image, where a 1 is written in
986
 *          the mask for each pixel in pixs that has a value %val.
987
 *      (2) If no pixels have the value, an empty mask is generated.
988
 * </pre>
989
 */
990
PIX *
991
pixMakeMaskFromVal(PIX     *pixs,
992
                   l_int32  val)
993
0
{
994
0
l_int32    w, h, d, i, j, sval, wpls, wpld;
995
0
l_uint32  *datas, *datad, *lines, *lined;
996
0
PIX       *pixd;
997
998
0
    if (!pixs)
999
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1000
0
    pixGetDimensions(pixs, &w, &h, &d);
1001
0
    if (d != 2 && d != 4 && d != 8)
1002
0
        return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", __func__, NULL);
1003
1004
0
    pixd = pixCreate(w, h, 1);
1005
0
    pixCopyResolution(pixd, pixs);
1006
0
    pixCopyInputFormat(pixd, pixs);
1007
0
    datas = pixGetData(pixs);
1008
0
    datad = pixGetData(pixd);
1009
0
    wpls = pixGetWpl(pixs);
1010
0
    wpld = pixGetWpl(pixd);
1011
0
    for (i = 0; i < h; i++) {
1012
0
        lines = datas + i * wpls;
1013
0
        lined = datad + i * wpld;
1014
0
        for (j = 0; j < w; j++) {
1015
0
            if (d == 2)
1016
0
                sval = GET_DATA_DIBIT(lines, j);
1017
0
            else if (d == 4)
1018
0
                sval = GET_DATA_QBIT(lines, j);
1019
0
            else  /* d == 8 */
1020
0
                sval = GET_DATA_BYTE(lines, j);
1021
0
            if (sval == val)
1022
0
                SET_DATA_BIT(lined, j);
1023
0
        }
1024
0
    }
1025
1026
0
    return pixd;
1027
0
}
1028
1029
1030
/*!
1031
 * \brief   pixMakeMaskFromLUT()
1032
 *
1033
 * \param[in]   pixs   2, 4 or 8 bpp; can be colormapped
1034
 * \param[in]   tab    256-entry LUT; 1 means to write to mask
1035
 * \return  pixd 1 bpp mask, or NULL on error
1036
 *
1037
 * <pre>
1038
 * Notes:
1039
 *      (1) This generates a 1 bpp mask image, where a 1 is written in
1040
 *          the mask for each pixel in pixs that has a value corresponding
1041
 *          to a 1 in the LUT.
1042
 *      (2) The LUT should be of size 256.
1043
 * </pre>
1044
 */
1045
PIX *
1046
pixMakeMaskFromLUT(PIX      *pixs,
1047
                   l_int32  *tab)
1048
0
{
1049
0
l_int32    w, h, d, i, j, val, wpls, wpld;
1050
0
l_uint32  *datas, *datad, *lines, *lined;
1051
0
PIX       *pixd;
1052
1053
0
    if (!pixs)
1054
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1055
0
    if (!tab)
1056
0
        return (PIX *)ERROR_PTR("tab not defined", __func__, NULL);
1057
0
    pixGetDimensions(pixs, &w, &h, &d);
1058
0
    if (d != 2 && d != 4 && d != 8)
1059
0
        return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", __func__, NULL);
1060
1061
0
    pixd = pixCreate(w, h, 1);
1062
0
    pixCopyResolution(pixd, pixs);
1063
0
    pixCopyInputFormat(pixd, pixs);
1064
0
    datas = pixGetData(pixs);
1065
0
    datad = pixGetData(pixd);
1066
0
    wpls = pixGetWpl(pixs);
1067
0
    wpld = pixGetWpl(pixd);
1068
0
    for (i = 0; i < h; i++) {
1069
0
        lines = datas + i * wpls;
1070
0
        lined = datad + i * wpld;
1071
0
        for (j = 0; j < w; j++) {
1072
0
            if (d == 2)
1073
0
                val = GET_DATA_DIBIT(lines, j);
1074
0
            else if (d == 4)
1075
0
                val = GET_DATA_QBIT(lines, j);
1076
0
            else  /* d == 8 */
1077
0
                val = GET_DATA_BYTE(lines, j);
1078
0
            if (tab[val] == 1)
1079
0
                SET_DATA_BIT(lined, j);
1080
0
        }
1081
0
    }
1082
1083
0
    return pixd;
1084
0
}
1085
1086
1087
/*!
1088
 * \brief   pixMakeArbMaskFromRGB()
1089
 *
1090
 * \param[in]   pixs         32 bpp RGB
1091
 * \param[in]   rc, gc, bc   arithmetic factors; can be negative
1092
 * \param[in]   thresh       lower threshold on weighted sum of components
1093
 * \return  pixd 1 bpp mask, or NULL on error
1094
 *
1095
 * <pre>
1096
 * Notes:
1097
 *      (1) This generates a 1 bpp mask image, where a 1 is written in
1098
 *          the mask for each pixel in pixs that satisfies
1099
 *               rc * rval + gc * gval + bc * bval > thresh
1100
 *          where rval is the red component, etc.
1101
 *      (2) Unlike with pixConvertToGray(), there are no constraints
1102
 *          on the color coefficients, which can be negative.  For
1103
 *          example, a mask that discriminates against red and in favor
1104
 *          of blue will have rc < 0.0 and bc > 0.0.
1105
 *      (3) To make the result independent of intensity (the 'V' in HSV),
1106
 *          select coefficients so that %thresh = 0.  Then the result
1107
 *          is not changed when all components are multiplied by the
1108
 *          same constant (as long as nothing saturates).  This can be
1109
 *          useful if, for example, the illumination is not uniform.
1110
 * </pre>
1111
 */
1112
PIX *
1113
pixMakeArbMaskFromRGB(PIX       *pixs,
1114
                      l_float32  rc,
1115
                      l_float32  gc,
1116
                      l_float32  bc,
1117
                      l_float32  thresh)
1118
0
{
1119
0
PIX  *pix1, *pix2;
1120
1121
0
    if (!pixs || pixGetDepth(pixs) != 32)
1122
0
        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", __func__, NULL);
1123
0
    if (thresh >= 255.0) thresh = 254.0;  /* avoid 8 bit overflow */
1124
1125
0
    if ((pix1 = pixConvertRGBToGrayArb(pixs, rc, gc, bc)) == NULL)
1126
0
        return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL);
1127
0
    pix2 = pixThresholdToBinary(pix1, thresh + 1);
1128
0
    pixInvert(pix2, pix2);
1129
0
    pixDestroy(&pix1);
1130
0
    return pix2;
1131
0
}
1132
1133
1134
/*!
1135
 * \brief   pixSetUnderTransparency()
1136
 *
1137
 * \param[in]   pixs    32 bpp rgba
1138
 * \param[in]   val     32 bit unsigned color to use where alpha == 0
1139
 * \param[in]   debug   displays layers of pixs
1140
 * \return  pixd 32 bpp rgba, or NULL on error
1141
 *
1142
 * <pre>
1143
 * Notes:
1144
 *      (1) This sets the r, g and b components under every fully
1145
 *          transparent alpha component to %val.  The alpha components
1146
 *          are unchanged.
1147
 *      (2) Full transparency is denoted by alpha == 0.  Setting
1148
 *          all pixels to a constant %val where alpha is transparent
1149
 *          can improve compressibility by reducing the entropy.
1150
 *      (3) The visual result depends on how the image is displayed.
1151
 *          (a) For display devices that respect the use of the alpha
1152
 *              layer, this will not affect the appearance.
1153
 *          (b) For typical leptonica operations, alpha is ignored,
1154
 *              so there will be a change in appearance because this
1155
 *              resets the rgb values in the fully transparent region.
1156
 *      (4) pixRead() and pixWrite() will, by default, read and write
1157
 *          4-component (rgba) pix in png format.  To ignore the alpha
1158
 *          component after reading, or omit it on writing, pixSetSpp(..., 3).
1159
 *      (5) Here are some examples:
1160
 *          * To convert all fully transparent pixels in a 4 component
1161
 *            (rgba) png file to white:
1162
 *              pixs = pixRead(<infile>);
1163
 *              pixd = pixSetUnderTransparency(pixs, 0xffffff00, 0);
1164
 *          * To write pixd with the alpha component:
1165
 *              pixWrite(<outfile>, pixd, IFF_PNG);
1166
 *          * To write and rgba image without the alpha component, first do:
1167
 *              pixSetSpp(pixd, 3);
1168
 *            If you later want to use the alpha, spp must be reset to 4.
1169
 *          * (fancier) To remove the alpha by blending the image over
1170
 *            a white background:
1171
 *              pixRemoveAlpha()
1172
 *            This changes all pixel values where the alpha component is
1173
 *            not opaque (255).
1174
 *      (6) Caution.  rgb images in leptonica typically have value 0 in
1175
 *          the alpha channel, which is fully transparent.  If spp for
1176
 *          such an image were changed from 3 to 4, the image becomes
1177
 *          fully transparent, and this function will set each pixel to %val.
1178
 *          If you really want to set every pixel to the same value,
1179
 *          use pixSetAllArbitrary().
1180
 *      (7) This is useful for compressing an RGBA image where the part
1181
 *          of the image that is fully transparent is random junk; compression
1182
 *          is typically improved by setting that region to a constant.
1183
 *          For rendering as a 3 component RGB image over a uniform
1184
 *          background of arbitrary color, use pixAlphaBlendUniform().
1185
 * </pre>
1186
 */
1187
PIX *
1188
pixSetUnderTransparency(PIX      *pixs,
1189
                        l_uint32  val,
1190
                        l_int32   debug)
1191
0
{
1192
0
PIX  *pixg, *pixm, *pixt, *pixd;
1193
1194
0
    if (!pixs || pixGetDepth(pixs) != 32)
1195
0
        return (PIX *)ERROR_PTR("pixs not defined or not 32 bpp",
1196
0
                                __func__, NULL);
1197
1198
0
    if (pixGetSpp(pixs) != 4) {
1199
0
        L_WARNING("no alpha channel; returning a copy\n", __func__);
1200
0
        return pixCopy(NULL, pixs);
1201
0
    }
1202
1203
        /* Make a mask from the alpha component with ON pixels
1204
         * wherever the alpha component is fully transparent (0).
1205
         * The hard way:
1206
         *     l_int32 *lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
1207
         *     lut[0] = 1;
1208
         *     pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
1209
         *     pixm = pixMakeMaskFromLUT(pixg, lut);
1210
         *     LEPT_FREE(lut);
1211
         * But there's an easier way to set pixels in a mask where
1212
         * the alpha component is 0 ...  */
1213
0
    pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
1214
0
    pixm = pixThresholdToBinary(pixg, 1);
1215
1216
0
    if (debug) {
1217
0
        pixt = pixDisplayLayersRGBA(pixs, 0xffffff00, 600);
1218
0
        pixDisplay(pixt, 0, 0);
1219
0
        pixDestroy(&pixt);
1220
0
    }
1221
1222
0
    pixd = pixCopy(NULL, pixs);
1223
0
    pixSetMasked(pixd, pixm, (val & 0xffffff00));
1224
0
    pixDestroy(&pixg);
1225
0
    pixDestroy(&pixm);
1226
0
    return pixd;
1227
0
}
1228
1229
1230
/*!
1231
 * \brief   pixMakeAlphaFromMask()
1232
 *
1233
 * \param[in]    pixs   1 bpp
1234
 * \param[in]    dist   blending distance; typically 10 - 30
1235
 * \param[out]   pbox   [optional] use NULL to get the full size
1236
 * \return  pixd (8 bpp gray, or NULL on error
1237
 *
1238
 * <pre>
1239
 * Notes:
1240
 *      (1) This generates a 8 bpp alpha layer that is opaque (256)
1241
 *          over the FG of pixs, and goes transparent linearly away
1242
 *          from the FG pixels, decaying to 0 (transparent) is an
1243
 *          8-connected distance given by %dist.  If %dist == 0,
1244
 *          this does a simple conversion from 1 to 8 bpp.
1245
 *      (2) If &box == NULL, this returns an alpha mask that is the
1246
 *          full size of pixs.  Otherwise, the returned mask pixd covers
1247
 *          just the FG pixels of pixs, expanded by %dist in each
1248
 *          direction (if possible), and the returned box gives the
1249
 *          location of the returned mask relative to pixs.
1250
 *      (3) This is useful for painting through a mask and allowing
1251
 *          blending of the painted image with an underlying image
1252
 *          in the mask background for pixels near foreground mask pixels.
1253
 *          For example, with an underlying rgb image pix1, an overlaying
1254
 *          image rgb pix2, binary mask pixm, and dist > 0, this
1255
 *          blending is achieved with:
1256
 *              pix3 = pixMakeAlphaFromMask(pixm, dist, &box);
1257
 *              boxGetGeometry(box, &x, &y, NULL, NULL);
1258
 *              pix4 = pixBlendWithGrayMask(pix1, pix2, pix3, x, y);
1259
 * </pre>
1260
 */
1261
PIX *
1262
pixMakeAlphaFromMask(PIX     *pixs,
1263
                     l_int32  dist,
1264
                     BOX    **pbox)
1265
0
{
1266
0
l_int32  w, h;
1267
0
BOX     *box1, *box2;
1268
0
PIX     *pix1, *pixd;
1269
1270
0
    if (pbox) *pbox = NULL;
1271
0
    if (!pixs || pixGetDepth(pixs) != 1)
1272
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
1273
0
    if (dist < 0)
1274
0
        return (PIX *)ERROR_PTR("dist must be >= 0", __func__, NULL);
1275
1276
        /* If requested, extract just the region to be affected by the mask */
1277
0
    if (pbox) {
1278
0
        pixClipToForeground(pixs, NULL, &box1);
1279
0
        if (!box1) {
1280
0
            L_WARNING("no ON pixels in mask\n", __func__);
1281
0
            return pixCreateTemplate(pixs);  /* all background (0) */
1282
0
        }
1283
1284
0
        boxAdjustSides(box1, box1, -dist, dist, -dist, dist);
1285
0
        pixGetDimensions(pixs, &w, &h, NULL);
1286
0
        box2 = boxClipToRectangle(box1, w, h);
1287
0
        *pbox = box2;
1288
0
        pix1 = pixClipRectangle(pixs, box2, NULL);
1289
0
        boxDestroy(&box1);
1290
0
    } else {
1291
0
        pix1 = pixCopy(NULL, pixs);
1292
0
    }
1293
1294
0
    if (dist == 0) {
1295
0
        pixd = pixConvert1To8(NULL, pix1, 0, 255);
1296
0
        pixDestroy(&pix1);
1297
0
        return pixd;
1298
0
    }
1299
1300
        /* Blur the boundary of the input mask */
1301
0
    pixInvert(pix1, pix1);
1302
0
    pixd = pixDistanceFunction(pix1, 8, 8, L_BOUNDARY_FG);
1303
0
    pixMultConstantGray(pixd, 256.0f / dist);
1304
0
    pixInvert(pixd, pixd);
1305
0
    pixDestroy(&pix1);
1306
0
    return pixd;
1307
0
}
1308
1309
1310
/*!
1311
 * \brief   pixGetColorNearMaskBoundary()
1312
 *
1313
 * \param[in]    pixs    32 bpp rgb
1314
 * \param[in]    pixm    1 bpp mask, full image
1315
 * \param[in]    box     region of mask; typically b.b. of a component
1316
 * \param[in]    dist    distance into BG from mask boundary to use
1317
 * \param[out]   pval    average pixel value
1318
 * \param[in]    debug   1 to output mask images
1319
 * \return  0 if OK, 1 on error.
1320
 *
1321
 * <pre>
1322
 * Notes:
1323
 *      (1) This finds the average color in a set of pixels that are
1324
 *          roughly a distance %dist from the c.c. boundary and in the
1325
 *          background of the mask image.
1326
 * </pre>
1327
 */
1328
l_ok
1329
pixGetColorNearMaskBoundary(PIX       *pixs,
1330
                            PIX       *pixm,
1331
                            BOX       *box,
1332
                            l_int32    dist,
1333
                            l_uint32  *pval,
1334
                            l_int32    debug)
1335
0
{
1336
0
char       op[64];
1337
0
l_int32    empty, bx, by;
1338
0
l_float32  rval, gval, bval;
1339
0
BOX       *box1, *box2;
1340
0
PIX       *pix1, *pix2, *pix3;
1341
1342
0
    if (!pval)
1343
0
        return ERROR_INT("&pval not defined", __func__, 1);
1344
0
    *pval = 0xffffff00;  /* white */
1345
0
    if (!pixs || pixGetDepth(pixs) != 32)
1346
0
        return ERROR_INT("pixs undefined or not 32 bpp", __func__, 1);
1347
0
    if (!pixm || pixGetDepth(pixm) != 1)
1348
0
        return ERROR_INT("pixm undefined or not 1 bpp", __func__, 1);
1349
0
    if (!box)
1350
0
        return ERROR_INT("box not defined", __func__, 1);
1351
0
    if (dist < 0)
1352
0
        return ERROR_INT("dist must be >= 0", __func__, 1);
1353
1354
        /* Clip mask piece, expanded beyond %box by (%dist + 5) on each side.
1355
         * box1 is the region requested; box2 is the actual region retrieved,
1356
         * which is clipped to %pixm */
1357
0
    box1 = boxAdjustSides(NULL, box, -dist - 5, dist + 5, -dist - 5, dist + 5);
1358
0
    pix1 = pixClipRectangle(pixm, box1, &box2);
1359
1360
        /* Expand FG by %dist into the BG */
1361
0
    if (dist == 0) {
1362
0
        pix2 = pixCopy(NULL, pix1);
1363
0
    } else {
1364
0
        snprintf(op, sizeof(op), "d%d.%d", 2 * dist, 2 * dist);
1365
0
        pix2 = pixMorphSequence(pix1, op, 0);
1366
0
    }
1367
1368
        /* Expand again by 5 pixels on all sides (dilate 11x11) and XOR,
1369
         * getting the annulus of FG pixels between %dist and %dist + 5 */
1370
0
    pix3 = pixCopy(NULL, pix2);
1371
0
    pixDilateBrick(pix3, pix3, 11, 11);
1372
0
    pixXor(pix3, pix3, pix2);
1373
0
    pixZero(pix3, &empty);
1374
0
    if (!empty) {
1375
            /* Scan the same region in %pixs, to get average under FG in pix3 */
1376
0
        boxGetGeometry(box2, &bx, &by, NULL, NULL);
1377
0
        pixGetAverageMaskedRGB(pixs, pix3, bx, by, 1, L_MEAN_ABSVAL,
1378
0
                               &rval, &gval, &bval);
1379
0
        composeRGBPixel((l_int32)(rval + 0.5), (l_int32)(gval + 0.5),
1380
0
                        (l_int32)(bval + 0.5), pval);
1381
0
    } else {
1382
0
        L_WARNING("no pixels found\n", __func__);
1383
0
    }
1384
1385
0
    if (debug) {
1386
0
        lept_rmdir("masknear");  /* erase previous images */
1387
0
        lept_mkdir("masknear");
1388
0
        pixWriteDebug("/tmp/masknear/input.png", pix1, IFF_PNG);
1389
0
        pixWriteDebug("/tmp/masknear/adjusted.png", pix2, IFF_PNG);
1390
0
        pixWriteDebug("/tmp/masknear/outerfive.png", pix3, IFF_PNG);
1391
0
        lept_stderr("Input box; with adjusted sides; clipped\n");
1392
0
        boxPrintStreamInfo(stderr, box);
1393
0
        boxPrintStreamInfo(stderr, box1);
1394
0
        boxPrintStreamInfo(stderr, box2);
1395
0
    }
1396
1397
0
    pixDestroy(&pix1);
1398
0
    pixDestroy(&pix2);
1399
0
    pixDestroy(&pix3);
1400
0
    boxDestroy(&box1);
1401
0
    boxDestroy(&box2);
1402
0
    return 0;
1403
0
}
1404
1405
1406
/*!
1407
 * \brief   pixDisplaySelectedPixels()
1408
 *
1409
 * \param[in]    pixs    [optional] any depth
1410
 * \param[in]    pixm    1 bpp mask, aligned UL corner with %pixs
1411
 * \param[in]    sel     [optional] pattern to paint at each pixel in pixm
1412
 * \param[in]    val     rgb rendering of pattern
1413
 * \return  pixd, or NULL on error
1414
 *
1415
 * <pre>
1416
 * Notes:
1417
 *      (1) For every fg pixel in %pixm, this paints the pattern in %sel
1418
 *          in color %val on a copy of %pixs.
1419
 *      (2) The implementation is to dilate %pixm by %sel, and then
1420
 *          paint through the dilated mask onto %pixs.
1421
 *      (3) If %pixs == NULL, it paints on a white image.
1422
 *      (4) If %sel == NULL, it paints only the pixels in the input %pixm.
1423
 *      (5) This visualization would typically be used in debugging.
1424
 * </pre>
1425
 */
1426
PIX *
1427
pixDisplaySelectedPixels(PIX      *pixs,
1428
                         PIX      *pixm,
1429
                         SEL      *sel,
1430
                         l_uint32  val)
1431
0
{
1432
0
l_int32  w, h;
1433
0
PIX     *pix1, *pix2;
1434
1435
0
    if (!pixm || pixGetDepth(pixm) != 1)
1436
0
        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", __func__, NULL);
1437
1438
0
    if (pixs) {
1439
0
        pix1 = pixConvertTo32(pixs);
1440
0
    } else {
1441
0
        pixGetDimensions(pixm, &w, &h, NULL);
1442
0
        pix1 = pixCreate(w, h, 32);
1443
0
        pixSetAll(pix1);
1444
0
    }
1445
1446
0
    if (sel)
1447
0
       pix2 = pixDilate(NULL, pixm, sel);
1448
0
    else
1449
0
       pix2 = pixClone(pixm);
1450
0
    pixSetMasked(pix1, pix2, val);
1451
0
    pixDestroy(&pix2);
1452
0
    return pix1;
1453
0
}
1454
1455
1456
/*-------------------------------------------------------------*
1457
 *    One and two-image boolean ops on arbitrary depth images  *
1458
 *-------------------------------------------------------------*/
1459
/*!
1460
 * \brief   pixInvert()
1461
 *
1462
 * \param[in]   pixd  [optional]; this can be null, equal to pixs,
1463
 *                    or different from pixs
1464
 * \param[in]   pixs
1465
 * \return  pixd, or NULL on error
1466
 *
1467
 * <pre>
1468
 * Notes:
1469
 *      (1) This inverts pixs, for all pixel depths.
1470
 *      (2) There are 3 cases:
1471
 *           (a) pixd == null,   ~src --> new pixd
1472
 *           (b) pixd == pixs,   ~src --> src  (in-place)
1473
 *           (c) pixd != pixs,   ~src --> input pixd
1474
 *      (3) For clarity, if the case is known, use these patterns:
1475
 *           (a) pixd = pixInvert(NULL, pixs);
1476
 *           (b) pixInvert(pixs, pixs);
1477
 *           (c) pixInvert(pixd, pixs);
1478
 * </pre>
1479
 */
1480
PIX *
1481
pixInvert(PIX  *pixd,
1482
          PIX  *pixs)
1483
0
{
1484
0
    if (!pixs)
1485
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
1486
1487
        /* Prepare pixd for in-place operation */
1488
0
    if ((pixd = pixCopy(pixd, pixs)) == NULL)
1489
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
1490
1491
0
    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1492
0
                PIX_NOT(PIX_DST), NULL, 0, 0);   /* invert pixd */
1493
1494
0
    return pixd;
1495
0
}
1496
1497
1498
/*!
1499
 * \brief   pixOr()
1500
 *
1501
 * \param[in]   pixd    [optional]; this can be null, equal to pixs1,
1502
 *                      different from pixs1
1503
 * \param[in]   pixs1   can be == pixd
1504
 * \param[in]   pixs2   must be != pixd
1505
 * \return  pixd always
1506
 *
1507
 * <pre>
1508
 * Notes:
1509
 *      (1) This gives the union of two images with equal depth,
1510
 *          aligning them to the UL corner.  pixs1 and pixs2
1511
 *          need not have the same width and height.
1512
 *      (2) There are 3 cases:
1513
 *            (a) pixd == null,   (src1 | src2) --> new pixd
1514
 *            (b) pixd == pixs1,  (src1 | src2) --> src1  (in-place)
1515
 *            (c) pixd != pixs1,  (src1 | src2) --> input pixd
1516
 *      (3) For clarity, if the case is known, use these patterns:
1517
 *            (a) pixd = pixOr(NULL, pixs1, pixs2);
1518
 *            (b) pixOr(pixs1, pixs1, pixs2);
1519
 *            (c) pixOr(pixd, pixs1, pixs2);
1520
 *      (4) The size of the result is determined by pixs1.
1521
 *      (5) The depths of pixs1 and pixs2 must be equal.
1522
 *      (6) Note carefully that the order of pixs1 and pixs2 only matters
1523
 *          for the in-place case.  For in-place, you must have
1524
 *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
1525
 *          result: the copy puts pixs1 image data in pixs2, and
1526
 *          the rasterop is then between pixs2 and pixs2 (a no-op).
1527
 * </pre>
1528
 */
1529
PIX *
1530
pixOr(PIX  *pixd,
1531
      PIX  *pixs1,
1532
      PIX  *pixs2)
1533
0
{
1534
0
    if (!pixs1)
1535
0
        return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1536
0
    if (!pixs2)
1537
0
        return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1538
0
    if (pixd == pixs2)
1539
0
        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1540
0
    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1541
0
        return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1542
1543
#if  EQUAL_SIZE_WARNING
1544
    if (!pixSizesEqual(pixs1, pixs2))
1545
        L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1546
#endif  /* EQUAL_SIZE_WARNING */
1547
1548
        /* Prepare pixd to be a copy of pixs1 */
1549
0
    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1550
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1551
1552
        /* src1 | src2 --> dest */
1553
0
    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1554
0
                PIX_SRC | PIX_DST, pixs2, 0, 0);
1555
1556
0
    return pixd;
1557
0
}
1558
1559
1560
/*!
1561
 * \brief   pixAnd()
1562
 *
1563
 * \param[in]   pixd    [optional]; this can be null, equal to pixs1,
1564
 *                      different from pixs1
1565
 * \param[in]   pixs1   can be == pixd
1566
 * \param[in]   pixs2   must be != pixd
1567
 * \return  pixd always
1568
 *
1569
 * <pre>
1570
 * Notes:
1571
 *      (1) This gives the intersection of two images with equal depth,
1572
 *          aligning them to the the UL corner.  pixs1 and pixs2
1573
 *          need not have the same width and height.
1574
 *      (2) There are 3 cases:
1575
 *            (a) pixd == null,   (src1 & src2) --> new pixd
1576
 *            (b) pixd == pixs1,  (src1 & src2) --> src1  (in-place)
1577
 *            (c) pixd != pixs1,  (src1 & src2) --> input pixd
1578
 *      (3) For clarity, if the case is known, use these patterns:
1579
 *            (a) pixd = pixAnd(NULL, pixs1, pixs2);
1580
 *            (b) pixAnd(pixs1, pixs1, pixs2);
1581
 *            (c) pixAnd(pixd, pixs1, pixs2);
1582
 *      (4) The size of the result is determined by pixs1.
1583
 *      (5) The depths of pixs1 and pixs2 must be equal.
1584
 *      (6) Note carefully that the order of pixs1 and pixs2 only matters
1585
 *          for the in-place case.  For in-place, you must have
1586
 *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
1587
 *          result: the copy puts pixs1 image data in pixs2, and
1588
 *          the rasterop is then between pixs2 and pixs2 (a no-op).
1589
 * </pre>
1590
 */
1591
PIX *
1592
pixAnd(PIX  *pixd,
1593
       PIX  *pixs1,
1594
       PIX  *pixs2)
1595
0
{
1596
0
    if (!pixs1)
1597
0
        return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1598
0
    if (!pixs2)
1599
0
        return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1600
0
    if (pixd == pixs2)
1601
0
        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1602
0
    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1603
0
        return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1604
1605
#if  EQUAL_SIZE_WARNING
1606
    if (!pixSizesEqual(pixs1, pixs2))
1607
        L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1608
#endif  /* EQUAL_SIZE_WARNING */
1609
1610
        /* Prepare pixd to be a copy of pixs1 */
1611
0
    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1612
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1613
1614
        /* src1 & src2 --> dest */
1615
0
    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1616
0
                PIX_SRC & PIX_DST, pixs2, 0, 0);
1617
1618
0
    return pixd;
1619
0
}
1620
1621
1622
/*!
1623
 * \brief   pixXor()
1624
 *
1625
 * \param[in]   pixd    [optional]; this can be null, equal to pixs1,
1626
 *                      different from pixs1
1627
 * \param[in]   pixs1   can be == pixd
1628
 * \param[in]   pixs2   must be != pixd
1629
 * \return  pixd always
1630
 *
1631
 * <pre>
1632
 * Notes:
1633
 *      (1) This gives the XOR of two images with equal depth,
1634
 *          aligning them to the the UL corner.  pixs1 and pixs2
1635
 *          need not have the same width and height.
1636
 *      (2) There are 3 cases:
1637
 *            (a) pixd == null,   (src1 ^ src2) --> new pixd
1638
 *            (b) pixd == pixs1,  (src1 ^ src2) --> src1  (in-place)
1639
 *            (c) pixd != pixs1,  (src1 ^ src2) --> input pixd
1640
 *      (3) For clarity, if the case is known, use these patterns:
1641
 *            (a) pixd = pixXor(NULL, pixs1, pixs2);
1642
 *            (b) pixXor(pixs1, pixs1, pixs2);
1643
 *            (c) pixXor(pixd, pixs1, pixs2);
1644
 *      (4) The size of the result is determined by pixs1.
1645
 *      (5) The depths of pixs1 and pixs2 must be equal.
1646
 *      (6) Note carefully that the order of pixs1 and pixs2 only matters
1647
 *          for the in-place case.  For in-place, you must have
1648
 *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
1649
 *          result: the copy puts pixs1 image data in pixs2, and
1650
 *          the rasterop is then between pixs2 and pixs2 (a no-op).
1651
 * </pre>
1652
 */
1653
PIX *
1654
pixXor(PIX  *pixd,
1655
       PIX  *pixs1,
1656
       PIX  *pixs2)
1657
0
{
1658
0
    if (!pixs1)
1659
0
        return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1660
0
    if (!pixs2)
1661
0
        return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1662
0
    if (pixd == pixs2)
1663
0
        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", __func__, pixd);
1664
0
    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1665
0
        return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1666
1667
#if  EQUAL_SIZE_WARNING
1668
    if (!pixSizesEqual(pixs1, pixs2))
1669
        L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1670
#endif  /* EQUAL_SIZE_WARNING */
1671
1672
        /* Prepare pixd to be a copy of pixs1 */
1673
0
    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
1674
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, pixd);
1675
1676
        /* src1 ^ src2 --> dest */
1677
0
    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
1678
0
                PIX_SRC ^ PIX_DST, pixs2, 0, 0);
1679
1680
0
    return pixd;
1681
0
}
1682
1683
1684
/*!
1685
 * \brief   pixSubtract()
1686
 *
1687
 * \param[in]   pixd    [optional]; this can be null, equal to pixs1,
1688
 *                      equal to pixs2, or different from both pixs1 and pixs2
1689
 * \param[in]   pixs1   can be == pixd
1690
 * \param[in]   pixs2   can be == pixd
1691
 * \return  pixd always
1692
 *
1693
 * <pre>
1694
 * Notes:
1695
 *      (1) This gives the set subtraction of two images with equal depth,
1696
 *          aligning them to the the UL corner.  pixs1 and pixs2
1697
 *          need not have the same width and height.
1698
 *      (2) Source pixs2 is always subtracted from source pixs1.
1699
 *          The result is
1700
 *                  pixs1 \ pixs2 = pixs1 & (~pixs2)
1701
 *      (3) There are 4 cases:
1702
 *            (a) pixd == null,   (src1 - src2) --> new pixd
1703
 *            (b) pixd == pixs1,  (src1 - src2) --> src1  (in-place)
1704
 *            (c) pixd == pixs2,  (src1 - src2) --> src2  (in-place)
1705
 *            (d) pixd != pixs1 && pixd != pixs2),
1706
 *                                 (src1 - src2) --> input pixd
1707
 *      (4) For clarity, if the case is known, use these patterns:
1708
 *            (a) pixd = pixSubtract(NULL, pixs1, pixs2);
1709
 *            (b) pixSubtract(pixs1, pixs1, pixs2);
1710
 *            (c) pixSubtract(pixs2, pixs1, pixs2);
1711
 *            (d) pixSubtract(pixd, pixs1, pixs2);
1712
 *      (5) The size of the result is determined by pixs1.
1713
 *      (6) The depths of pixs1 and pixs2 must be equal.
1714
 * </pre>
1715
 */
1716
PIX *
1717
pixSubtract(PIX  *pixd,
1718
            PIX  *pixs1,
1719
            PIX  *pixs2)
1720
0
{
1721
0
l_int32  w, h;
1722
1723
0
    if (!pixs1)
1724
0
        return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd);
1725
0
    if (!pixs2)
1726
0
        return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd);
1727
0
    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
1728
0
        return (PIX *)ERROR_PTR("depths of pixs* unequal", __func__, pixd);
1729
1730
#if  EQUAL_SIZE_WARNING
1731
    if (!pixSizesEqual(pixs1, pixs2))
1732
        L_WARNING("pixs1 and pixs2 not equal sizes\n", __func__);
1733
#endif  /* EQUAL_SIZE_WARNING */
1734
1735
0
    pixGetDimensions(pixs1, &w, &h, NULL);
1736
0
    if (!pixd) {
1737
0
        pixd = pixCopy(NULL, pixs1);
1738
0
        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1739
0
            pixs2, 0, 0);   /* src1 & (~src2)  */
1740
0
    } else if (pixd == pixs1) {
1741
0
        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1742
0
            pixs2, 0, 0);   /* src1 & (~src2)  */
1743
0
    } else if (pixd == pixs2) {
1744
0
        pixRasterop(pixd, 0, 0, w, h, PIX_NOT(PIX_DST) & PIX_SRC,
1745
0
            pixs1, 0, 0);   /* src1 & (~src2)  */
1746
0
    } else  { /* pixd != pixs1 && pixd != pixs2 */
1747
0
        pixCopy(pixd, pixs1);  /* sizes pixd to pixs1 if unequal */
1748
0
        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
1749
0
            pixs2, 0, 0);   /* src1 & (~src2)  */
1750
0
    }
1751
1752
0
    return pixd;
1753
0
}
1754
1755
1756
/*-------------------------------------------------------------*
1757
 *                         Pixel counting                      *
1758
 *-------------------------------------------------------------*/
1759
/*!
1760
 * \brief   pixZero()
1761
 *
1762
 * \param[in]    pix     all depths; colormap OK
1763
 * \param[out]   pempty  1 if all bits in image data field are 0; 0 otherwise
1764
 * \return  0 if OK; 1 on error
1765
 *
1766
 * <pre>
1767
 * Notes:
1768
 *      (1) For a binary image, if there are no fg (black) pixels, empty = 1.
1769
 *      (2) For a grayscale image, if all pixels are black (0), empty = 1.
1770
 *      (3) For an RGB image, if all 4 components in every pixel is 0
1771
 *          (i.e. opaque black), empty = 1.
1772
 *      (4) For a colormapped image, pixel values are 0.  The colormap
1773
 *          is ignored.
1774
 * </pre>
1775
 */
1776
l_ok
1777
pixZero(PIX      *pix,
1778
        l_int32  *pempty)
1779
253
{
1780
253
l_int32    w, h, wpl, i, j, fullwords, endbits;
1781
253
l_uint32   endmask;
1782
253
l_uint32  *data, *line;
1783
1784
253
    if (!pempty)
1785
0
        return ERROR_INT("&empty not defined", __func__, 1);
1786
253
    *pempty = 1;
1787
253
    if (!pix)
1788
0
        return ERROR_INT("pix not defined", __func__, 1);
1789
1790
253
    w = pixGetWidth(pix) * pixGetDepth(pix);  /* in bits */
1791
253
    h = pixGetHeight(pix);
1792
253
    wpl = pixGetWpl(pix);
1793
253
    data = pixGetData(pix);
1794
253
    fullwords = w / 32;
1795
253
    endbits = w & 31;
1796
253
    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
1797
1798
2.50k
    for (i = 0; i < h; i++) {
1799
2.38k
        line = data + wpl * i;
1800
5.72k
        for (j = 0; j < fullwords; j++)
1801
3.40k
            if (*line++) {
1802
66
                *pempty = 0;
1803
66
                return 0;
1804
66
            }
1805
2.31k
        if (endbits) {
1806
1.83k
            if (*line & endmask) {
1807
63
                *pempty = 0;
1808
63
                return 0;
1809
63
            }
1810
1.83k
        }
1811
2.31k
    }
1812
1813
124
    return 0;
1814
253
}
1815
1816
1817
/*!
1818
 * \brief   pixForegroundFraction()
1819
 *
1820
 * \param[in]    pix      1 bpp
1821
 * \param[out]   pfract   fraction of ON pixels
1822
 * \return  0 if OK; 1 on error
1823
 */
1824
l_ok
1825
pixForegroundFraction(PIX        *pix,
1826
                      l_float32  *pfract)
1827
0
{
1828
0
l_int32  w, h, count;
1829
1830
0
    if (!pfract)
1831
0
        return ERROR_INT("&fract not defined", __func__, 1);
1832
0
    *pfract = 0.0;
1833
0
    if (!pix || pixGetDepth(pix) != 1)
1834
0
        return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
1835
1836
0
    pixCountPixels(pix, &count, NULL);
1837
0
    pixGetDimensions(pix, &w, &h, NULL);
1838
0
    *pfract = (l_float32)count / (l_float32)(w * h);
1839
0
    return 0;
1840
0
}
1841
1842
1843
/*!
1844
 * \brief   pixaCountPixels()
1845
 *
1846
 * \param[in]    pixa    array of 1 bpp pix
1847
 * \return  na of ON pixels in each pix, or NULL on error
1848
 */
1849
NUMA *
1850
pixaCountPixels(PIXA  *pixa)
1851
0
{
1852
0
l_int32   d, i, n, count;
1853
0
l_int32  *tab;
1854
0
NUMA     *na;
1855
0
PIX      *pix;
1856
1857
0
    if (!pixa)
1858
0
        return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
1859
1860
0
    if ((n = pixaGetCount(pixa)) == 0)
1861
0
        return numaCreate(1);
1862
1863
0
    pix = pixaGetPix(pixa, 0, L_CLONE);
1864
0
    d = pixGetDepth(pix);
1865
0
    pixDestroy(&pix);
1866
0
    if (d != 1)
1867
0
        return (NUMA *)ERROR_PTR("pixa not 1 bpp", __func__, NULL);
1868
1869
0
    if ((na = numaCreate(n)) == NULL)
1870
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
1871
0
    tab = makePixelSumTab8();
1872
0
    for (i = 0; i < n; i++) {
1873
0
        pix = pixaGetPix(pixa, i, L_CLONE);
1874
0
        pixCountPixels(pix, &count, tab);
1875
0
        numaAddNumber(na, count);
1876
0
        pixDestroy(&pix);
1877
0
    }
1878
1879
0
    LEPT_FREE(tab);
1880
0
    return na;
1881
0
}
1882
1883
1884
/*!
1885
 * \brief   pixCountPixels()
1886
 *
1887
 * \param[in]    pixs     1 bpp
1888
 * \param[out]   pcount   count of ON pixels
1889
 * \param[in]    tab8     [optional] 8-bit pixel lookup table
1890
 * \return  0 if OK; 1 on error
1891
 */
1892
l_ok
1893
pixCountPixels(PIX      *pixs,
1894
               l_int32  *pcount,
1895
               l_int32  *tab8)
1896
0
{
1897
0
l_uint32   endmask;
1898
0
l_int32    w, h, wpl, i, j;
1899
0
l_int32    fullwords, endbits, sum;
1900
0
l_int32   *tab;
1901
0
l_uint32  *data;
1902
1903
0
    if (!pcount)
1904
0
        return ERROR_INT("&count not defined", __func__, 1);
1905
0
    *pcount = 0;
1906
0
    if (!pixs || pixGetDepth(pixs) != 1)
1907
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1908
1909
0
    tab = (tab8) ? tab8 : makePixelSumTab8();
1910
0
    pixGetDimensions(pixs, &w, &h, NULL);
1911
0
    wpl = pixGetWpl(pixs);
1912
0
    data = pixGetData(pixs);
1913
0
    fullwords = w >> 5;
1914
0
    endbits = w & 31;
1915
0
    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
1916
1917
0
    sum = 0;
1918
0
    for (i = 0; i < h; i++, data += wpl) {
1919
0
        for (j = 0; j < fullwords; j++) {
1920
0
            l_uint32 word = data[j];
1921
0
            if (word) {
1922
0
                sum += tab[word & 0xff] +
1923
0
                       tab[(word >> 8) & 0xff] +
1924
0
                       tab[(word >> 16) & 0xff] +
1925
0
                       tab[(word >> 24) & 0xff];
1926
0
            }
1927
0
        }
1928
0
        if (endbits) {
1929
0
            l_uint32 word = data[j] & endmask;
1930
0
            if (word) {
1931
0
                sum += tab[word & 0xff] +
1932
0
                       tab[(word >> 8) & 0xff] +
1933
0
                       tab[(word >> 16) & 0xff] +
1934
0
                       tab[(word >> 24) & 0xff];
1935
0
            }
1936
0
        }
1937
0
    }
1938
0
    *pcount = sum;
1939
1940
0
    if (!tab8) LEPT_FREE(tab);
1941
0
    return 0;
1942
0
}
1943
1944
1945
/*!
1946
 * \brief   pixCountPixelsInRect()
1947
 *
1948
 * \param[in]    pixs     1 bpp
1949
 * \param[in]    box      (can be null)
1950
 * \param[out]   pcount   count of ON pixels
1951
 * \param[in]    tab8     [optional] 8-bit pixel lookup table
1952
 * \return  0 if OK; 1 on error
1953
 */
1954
l_ok
1955
pixCountPixelsInRect(PIX      *pixs,
1956
                     BOX      *box,
1957
                     l_int32  *pcount,
1958
                     l_int32  *tab8)
1959
0
{
1960
0
l_int32  w, h, bx, by, bw, bh;
1961
0
BOX     *box1;
1962
0
PIX     *pix1;
1963
1964
0
    if (!pcount)
1965
0
        return ERROR_INT("&count not defined", __func__, 1);
1966
0
    *pcount = 0;
1967
0
    if (!pixs || pixGetDepth(pixs) != 1)
1968
0
        return ERROR_INT("pixs not defined or not 1 bpp", __func__, 1);
1969
1970
0
    if (box) {
1971
0
        pixGetDimensions(pixs, &w, &h, NULL);
1972
0
        if ((box1 = boxClipToRectangle(box, w, h)) == NULL)
1973
0
            return ERROR_INT("box1 not made", __func__, 1);
1974
0
        boxGetGeometry(box1, &bx, &by, &bw, &bh);
1975
0
        pix1 = pixCreate(bw, bh, 1);
1976
0
        pixRasterop(pix1, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
1977
0
        pixCountPixels(pix1, pcount, tab8);
1978
0
        pixDestroy(&pix1);
1979
0
        boxDestroy(&box1);
1980
0
    } else {
1981
0
        pixCountPixels(pixs, pcount, tab8);
1982
0
    }
1983
1984
0
    return 0;
1985
0
}
1986
1987
1988
/*!
1989
 * \brief   pixCountByRow()
1990
 *
1991
 * \param[in]   pix   1 bpp
1992
 * \param[in]   box   [optional] clipping box for count; can be null
1993
 * \return  na of number of ON pixels by row, or NULL on error
1994
 *
1995
 * <pre>
1996
 * Notes:
1997
 *      (1) To resample for a bin size different from 1, use
1998
 *          numaUniformSampling() on the result of this function.
1999
 * </pre>
2000
 */
2001
NUMA *
2002
pixCountByRow(PIX      *pix,
2003
              BOX      *box)
2004
0
{
2005
0
l_int32    i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
2006
0
l_uint32  *line, *data;
2007
0
NUMA      *na;
2008
2009
0
    if (!pix || pixGetDepth(pix) != 1)
2010
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2011
0
    if (!box)
2012
0
        return pixCountPixelsByRow(pix, NULL);
2013
2014
0
    pixGetDimensions(pix, &w, &h, NULL);
2015
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2016
0
                                 &bw, &bh) == 1)
2017
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2018
2019
0
    if ((na = numaCreate(bh)) == NULL)
2020
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2021
0
    numaSetParameters(na, ystart, 1);
2022
0
    data = pixGetData(pix);
2023
0
    wpl = pixGetWpl(pix);
2024
0
    for (i = ystart; i < yend; i++) {
2025
0
        count = 0;
2026
0
        line = data + i * wpl;
2027
0
        for (j = xstart; j < xend; j++) {
2028
0
            if (GET_DATA_BIT(line, j))
2029
0
                count++;
2030
0
        }
2031
0
        numaAddNumber(na, count);
2032
0
    }
2033
2034
0
    return na;
2035
0
}
2036
2037
2038
/*!
2039
 * \brief   pixCountByColumn()
2040
 *
2041
 * \param[in]   pix   1 bpp
2042
 * \param[in]   box   [optional] clipping box for count; can be null
2043
 * \return  na of number of ON pixels by column, or NULL on error
2044
 *
2045
 * <pre>
2046
 * Notes:
2047
 *      (1) To resample for a bin size different from 1, use
2048
 *          numaUniformSampling() on the result of this function.
2049
 * </pre>
2050
 */
2051
NUMA *
2052
pixCountByColumn(PIX      *pix,
2053
                 BOX      *box)
2054
0
{
2055
0
l_int32    i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
2056
0
l_uint32  *line, *data;
2057
0
NUMA      *na;
2058
2059
0
    if (!pix || pixGetDepth(pix) != 1)
2060
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2061
0
    if (!box)
2062
0
        return pixCountPixelsByColumn(pix);
2063
2064
0
    pixGetDimensions(pix, &w, &h, NULL);
2065
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2066
0
                                 &bw, &bh) == 1)
2067
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2068
2069
0
    if ((na = numaCreate(bw)) == NULL)
2070
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2071
0
    numaSetParameters(na, xstart, 1);
2072
0
    data = pixGetData(pix);
2073
0
    wpl = pixGetWpl(pix);
2074
0
    for (j = xstart; j < xend; j++) {
2075
0
        count = 0;
2076
0
        for (i = ystart; i < yend; i++) {
2077
0
            line = data + i * wpl;
2078
0
            if (GET_DATA_BIT(line, j))
2079
0
                count++;
2080
0
        }
2081
0
        numaAddNumber(na, count);
2082
0
    }
2083
2084
0
    return na;
2085
0
}
2086
2087
2088
/*!
2089
 * \brief   pixCountPixelsByRow()
2090
 *
2091
 * \param[in]   pix   1 bpp
2092
 * \param[in]   tab8  [optional] 8-bit pixel lookup table
2093
 * \return  na of counts, or NULL on error
2094
 */
2095
NUMA *
2096
pixCountPixelsByRow(PIX      *pix,
2097
                    l_int32  *tab8)
2098
0
{
2099
0
l_int32   h, i, count;
2100
0
l_int32  *tab;
2101
0
NUMA     *na;
2102
2103
0
    if (!pix || pixGetDepth(pix) != 1)
2104
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2105
2106
0
    h = pixGetHeight(pix);
2107
0
    if ((na = numaCreate(h)) == NULL)
2108
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2109
2110
0
    tab = (tab8) ? tab8 : makePixelSumTab8();
2111
0
    for (i = 0; i < h; i++) {
2112
0
        pixCountPixelsInRow(pix, i, &count, tab);
2113
0
        numaAddNumber(na, count);
2114
0
    }
2115
2116
0
    if (!tab8) LEPT_FREE(tab);
2117
0
    return na;
2118
0
}
2119
2120
2121
/*!
2122
 * \brief   pixCountPixelsByColumn()
2123
 *
2124
 * \param[in]   pix   1 bpp
2125
 * \return  na of counts in each column, or NULL on error
2126
 */
2127
NUMA *
2128
pixCountPixelsByColumn(PIX  *pix)
2129
0
{
2130
0
l_int32     i, j, w, h, wpl;
2131
0
l_uint32   *line, *data;
2132
0
l_float32  *array;
2133
0
NUMA       *na;
2134
2135
0
    if (!pix || pixGetDepth(pix) != 1)
2136
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2137
2138
0
    pixGetDimensions(pix, &w, &h, NULL);
2139
0
    if ((na = numaCreate(w)) == NULL)
2140
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2141
0
    numaSetCount(na, w);
2142
0
    array = numaGetFArray(na, L_NOCOPY);
2143
0
    data = pixGetData(pix);
2144
0
    wpl = pixGetWpl(pix);
2145
0
    for (i = 0; i < h; i++) {
2146
0
        line = data + wpl * i;
2147
0
        for (j = 0; j < w; j++) {
2148
0
            if (GET_DATA_BIT(line, j))
2149
0
                array[j] += 1.0;
2150
0
        }
2151
0
    }
2152
2153
0
    return na;
2154
0
}
2155
2156
2157
/*!
2158
 * \brief   pixCountPixelsInRow()
2159
 *
2160
 * \param[in]    pix     1 bpp
2161
 * \param[in]    row     number
2162
 * \param[out]   pcount  sum of ON pixels in raster line
2163
 * \param[in]    tab8    [optional] 8-bit pixel lookup table
2164
 * \return  0 if OK; 1 on error
2165
 */
2166
l_ok
2167
pixCountPixelsInRow(PIX      *pix,
2168
                    l_int32   row,
2169
                    l_int32  *pcount,
2170
                    l_int32  *tab8)
2171
0
{
2172
0
l_uint32   word, endmask;
2173
0
l_int32    j, w, h, wpl;
2174
0
l_int32    fullwords, endbits, sum;
2175
0
l_int32   *tab;
2176
0
l_uint32  *line;
2177
2178
0
    if (!pcount)
2179
0
        return ERROR_INT("&count not defined", __func__, 1);
2180
0
    *pcount = 0;
2181
0
    if (!pix || pixGetDepth(pix) != 1)
2182
0
        return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
2183
2184
0
    pixGetDimensions(pix, &w, &h, NULL);
2185
0
    if (row < 0 || row >= h)
2186
0
        return ERROR_INT("row out of bounds", __func__, 1);
2187
0
    wpl = pixGetWpl(pix);
2188
0
    line = pixGetData(pix) + row * wpl;
2189
0
    fullwords = w >> 5;
2190
0
    endbits = w & 31;
2191
0
    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
2192
2193
0
    tab = (tab8) ? tab8 : makePixelSumTab8();
2194
0
    sum = 0;
2195
0
    for (j = 0; j < fullwords; j++) {
2196
0
        word = line[j];
2197
0
        if (word) {
2198
0
            sum += tab[word & 0xff] +
2199
0
                   tab[(word >> 8) & 0xff] +
2200
0
                   tab[(word >> 16) & 0xff] +
2201
0
                   tab[(word >> 24) & 0xff];
2202
0
        }
2203
0
    }
2204
0
    if (endbits) {
2205
0
        word = line[j] & endmask;
2206
0
        if (word) {
2207
0
            sum += tab[word & 0xff] +
2208
0
                   tab[(word >> 8) & 0xff] +
2209
0
                   tab[(word >> 16) & 0xff] +
2210
0
                   tab[(word >> 24) & 0xff];
2211
0
        }
2212
0
    }
2213
0
    *pcount = sum;
2214
2215
0
    if (!tab8) LEPT_FREE(tab);
2216
0
    return 0;
2217
0
}
2218
2219
2220
/*!
2221
 * \brief   pixGetMomentByColumn()
2222
 *
2223
 * \param[in]   pix     1 bpp
2224
 * \param[in]   order   of moment, either 1 or 2
2225
 * \return  na of first moment of fg pixels, by column, or NULL on error
2226
 */
2227
NUMA *
2228
pixGetMomentByColumn(PIX     *pix,
2229
                     l_int32  order)
2230
0
{
2231
0
l_int32     i, j, w, h, wpl;
2232
0
l_uint32   *line, *data;
2233
0
l_float32  *array;
2234
0
NUMA       *na;
2235
2236
0
    if (!pix || pixGetDepth(pix) != 1)
2237
0
        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
2238
0
    if (order != 1 && order != 2)
2239
0
        return (NUMA *)ERROR_PTR("order of moment not 1 or 2", __func__, NULL);
2240
2241
0
    pixGetDimensions(pix, &w, &h, NULL);
2242
0
    if ((na = numaCreate(w)) == NULL)
2243
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2244
0
    numaSetCount(na, w);
2245
0
    array = numaGetFArray(na, L_NOCOPY);
2246
0
    data = pixGetData(pix);
2247
0
    wpl = pixGetWpl(pix);
2248
0
    for (i = 0; i < h; i++) {
2249
0
        line = data + wpl * i;
2250
0
        for (j = 0; j < w; j++) {
2251
0
            if (GET_DATA_BIT(line, j)) {
2252
0
                if (order == 1)
2253
0
                    array[j] += i;
2254
0
                else  /* order == 2 */
2255
0
                    array[j] += i * i;
2256
0
            }
2257
0
        }
2258
0
    }
2259
2260
0
    return na;
2261
0
}
2262
2263
2264
/*!
2265
 * \brief   pixThresholdPixelSum()
2266
 *
2267
 * \param[in]    pix      1 bpp
2268
 * \param[in]    thresh   threshold
2269
 * \param[out]   pabove   1 if above threshold;
2270
 *                        0 if equal to or less than threshold
2271
 * \param[in]    tab8     [optional] 8-bit pixel lookup table
2272
 * \return  0 if OK; 1 on error
2273
 *
2274
 * <pre>
2275
 * Notes:
2276
 *      (1) This sums the ON pixels and returns immediately if the count
2277
 *          goes above threshold.  It is therefore more efficient
2278
 *          for matching images (by running this function on the xor of
2279
 *          the 2 images) than using pixCountPixels(), which counts all
2280
 *          pixels before returning.
2281
 * </pre>
2282
 */
2283
l_ok
2284
pixThresholdPixelSum(PIX      *pix,
2285
                     l_int32   thresh,
2286
                     l_int32  *pabove,
2287
                     l_int32  *tab8)
2288
0
{
2289
0
l_uint32   word, endmask;
2290
0
l_int32   *tab;
2291
0
l_int32    w, h, wpl, i, j;
2292
0
l_int32    fullwords, endbits, sum;
2293
0
l_uint32  *line, *data;
2294
2295
0
    if (!pabove)
2296
0
        return ERROR_INT("&above not defined", __func__, 1);
2297
0
    *pabove = 0;
2298
0
    if (!pix || pixGetDepth(pix) != 1)
2299
0
        return ERROR_INT("pix not defined or not 1 bpp", __func__, 1);
2300
2301
0
    tab = (tab8) ? tab8 : makePixelSumTab8();
2302
0
    pixGetDimensions(pix, &w, &h, NULL);
2303
0
    wpl = pixGetWpl(pix);
2304
0
    data = pixGetData(pix);
2305
0
    fullwords = w >> 5;
2306
0
    endbits = w & 31;
2307
0
    endmask = 0xffffffff << (32 - endbits);
2308
2309
0
    sum = 0;
2310
0
    for (i = 0; i < h; i++) {
2311
0
        line = data + wpl * i;
2312
0
        for (j = 0; j < fullwords; j++) {
2313
0
            word = line[j];
2314
0
            if (word) {
2315
0
                sum += tab[word & 0xff] +
2316
0
                       tab[(word >> 8) & 0xff] +
2317
0
                       tab[(word >> 16) & 0xff] +
2318
0
                       tab[(word >> 24) & 0xff];
2319
0
            }
2320
0
        }
2321
0
        if (endbits) {
2322
0
            word = line[j] & endmask;
2323
0
            if (word) {
2324
0
                sum += tab[word & 0xff] +
2325
0
                       tab[(word >> 8) & 0xff] +
2326
0
                       tab[(word >> 16) & 0xff] +
2327
0
                       tab[(word >> 24) & 0xff];
2328
0
            }
2329
0
        }
2330
0
        if (sum > thresh) {
2331
0
            *pabove = 1;
2332
0
            if (!tab8) LEPT_FREE(tab);
2333
0
            return 0;
2334
0
        }
2335
0
    }
2336
2337
0
    if (!tab8) LEPT_FREE(tab);
2338
0
    return 0;
2339
0
}
2340
2341
2342
/*!
2343
 * \brief   makePixelSumTab8()
2344
 *
2345
 * \return  table of 256 l_int32.
2346
 *
2347
 * <pre>
2348
 * Notes:
2349
 *      (1) This table of integers gives the number of 1 bits
2350
 *          in the 8 bit index.
2351
 * </pre>
2352
 */
2353
l_int32 *
2354
makePixelSumTab8(void)
2355
0
{
2356
0
l_uint8   byte;
2357
0
l_int32   i;
2358
0
l_int32  *tab;
2359
2360
0
    tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2361
0
    for (i = 0; i < 256; i++) {
2362
0
        byte = (l_uint8)i;
2363
0
        tab[i] = (byte & 0x1) +
2364
0
                 ((byte >> 1) & 0x1) +
2365
0
                 ((byte >> 2) & 0x1) +
2366
0
                 ((byte >> 3) & 0x1) +
2367
0
                 ((byte >> 4) & 0x1) +
2368
0
                 ((byte >> 5) & 0x1) +
2369
0
                 ((byte >> 6) & 0x1) +
2370
0
                 ((byte >> 7) & 0x1);
2371
0
    }
2372
0
    return tab;
2373
0
}
2374
2375
2376
/*!
2377
 * \brief   makePixelCentroidTab8()
2378
 *
2379
 * \return  table of 256 l_int32.
2380
 *
2381
 * <pre>
2382
 * Notes:
2383
 *      (1) This table of integers gives the centroid weight of the 1 bits
2384
 *          in the 8 bit index.  In other words, if sumtab is obtained by
2385
 *          makePixelSumTab8, and centroidtab is obtained by
2386
 *          makePixelCentroidTab8, then, for 1 <= i <= 255,
2387
 *          centroidtab[i] / (float)sumtab[i]
2388
 *          is the centroid of the 1 bits in the 8-bit index i, where the
2389
 *          MSB is considered to have position 0 and the LSB is considered
2390
 *          to have position 7.
2391
 * </pre>
2392
 */
2393
l_int32 *
2394
makePixelCentroidTab8(void)
2395
0
{
2396
0
l_int32   i;
2397
0
l_int32  *tab;
2398
2399
0
    tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
2400
0
    tab[0] = 0;
2401
0
    tab[1] = 7;
2402
0
    for (i = 2; i < 4; i++) {
2403
0
        tab[i] = tab[i - 2] + 6;
2404
0
    }
2405
0
    for (i = 4; i < 8; i++) {
2406
0
        tab[i] = tab[i - 4] + 5;
2407
0
    }
2408
0
    for (i = 8; i < 16; i++) {
2409
0
        tab[i] = tab[i - 8] + 4;
2410
0
    }
2411
0
    for (i = 16; i < 32; i++) {
2412
0
        tab[i] = tab[i - 16] + 3;
2413
0
    }
2414
0
    for (i = 32; i < 64; i++) {
2415
0
        tab[i] = tab[i - 32] + 2;
2416
0
    }
2417
0
    for (i = 64; i < 128; i++) {
2418
0
        tab[i] = tab[i - 64] + 1;
2419
0
    }
2420
0
    for (i = 128; i < 256; i++) {
2421
0
        tab[i] = tab[i - 128];
2422
0
    }
2423
0
    return tab;
2424
0
}
2425
2426
2427
/*-------------------------------------------------------------*
2428
 *             Average of pixel values in gray images          *
2429
 *-------------------------------------------------------------*/
2430
/*!
2431
 * \brief   pixAverageByRow()
2432
 *
2433
 * \param[in]   pix    8 or 16 bpp; no colormap
2434
 * \param[in]   box    [optional] clipping box for sum; can be null
2435
 * \param[in]   type   L_WHITE_IS_MAX, L_BLACK_IS_MAX
2436
 * \return  na of pixel averages by row, or NULL on error
2437
 *
2438
 * <pre>
2439
 * Notes:
2440
 *      (1) To resample for a bin size different from 1, use
2441
 *          numaUniformSampling() on the result of this function.
2442
 *      (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
2443
 *          value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
2444
 * </pre>
2445
 */
2446
NUMA *
2447
pixAverageByRow(PIX     *pix,
2448
                BOX     *box,
2449
                l_int32  type)
2450
0
{
2451
0
l_int32    i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
2452
0
l_uint32  *line, *data;
2453
0
l_float64  norm, sum;
2454
0
NUMA      *na;
2455
2456
0
    if (!pix)
2457
0
        return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2458
0
    pixGetDimensions(pix, &w, &h, &d);
2459
0
    if (d != 8 && d != 16)
2460
0
        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2461
0
    if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
2462
0
        return (NUMA *)ERROR_PTR("invalid type", __func__, NULL);
2463
0
    if (pixGetColormap(pix) != NULL)
2464
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2465
2466
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2467
0
                                 &bw, &bh) == 1)
2468
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2469
2470
0
    norm = 1. / (l_float32)bw;
2471
0
    if ((na = numaCreate(bh)) == NULL)
2472
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2473
0
    numaSetParameters(na, ystart, 1);
2474
0
    data = pixGetData(pix);
2475
0
    wpl = pixGetWpl(pix);
2476
0
    for (i = ystart; i < yend; i++) {
2477
0
        sum = 0.0;
2478
0
        line = data + i * wpl;
2479
0
        if (d == 8) {
2480
0
            for (j = xstart; j < xend; j++)
2481
0
                sum += GET_DATA_BYTE(line, j);
2482
0
            if (type == L_BLACK_IS_MAX)
2483
0
                sum = bw * 255 - sum;
2484
0
        } else {  /* d == 16 */
2485
0
            for (j = xstart; j < xend; j++)
2486
0
                sum += GET_DATA_TWO_BYTES(line, j);
2487
0
            if (type == L_BLACK_IS_MAX)
2488
0
                sum = bw * 0xffff - sum;
2489
0
        }
2490
0
        numaAddNumber(na, (l_float32)(norm * sum));
2491
0
    }
2492
2493
0
    return na;
2494
0
}
2495
2496
2497
/*!
2498
 * \brief   pixAverageByColumn()
2499
 *
2500
 * \param[in]   pix   8 or 16 bpp; no colormap
2501
 * \param[in]   box   [optional] clipping box for sum; can be null
2502
 * \param[in]   type  L_WHITE_IS_MAX, L_BLACK_IS_MAX
2503
 * \return  na of pixel averages by column, or NULL on error
2504
 *
2505
 * <pre>
2506
 * Notes:
2507
 *      (1) To resample for a bin size different from 1, use
2508
 *          numaUniformSampling() on the result of this function.
2509
 *      (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
2510
 *          value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
2511
 * </pre>
2512
 */
2513
NUMA *
2514
pixAverageByColumn(PIX     *pix,
2515
                   BOX     *box,
2516
                   l_int32  type)
2517
0
{
2518
0
l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
2519
0
l_uint32   *line, *data;
2520
0
l_float32   norm, sum;
2521
0
NUMA       *na;
2522
2523
0
    if (!pix)
2524
0
        return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2525
0
    pixGetDimensions(pix, &w, &h, &d);
2526
2527
0
    if (d != 8 && d != 16)
2528
0
        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2529
0
    if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
2530
0
        return (NUMA *)ERROR_PTR("invalid type", __func__, NULL);
2531
0
    if (pixGetColormap(pix) != NULL)
2532
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2533
2534
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2535
0
                                 &bw, &bh) == 1)
2536
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2537
2538
0
    if ((na = numaCreate(bw)) == NULL)
2539
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2540
0
    numaSetParameters(na, xstart, 1);
2541
0
    norm = 1.f / (l_float32)bh;
2542
0
    data = pixGetData(pix);
2543
0
    wpl = pixGetWpl(pix);
2544
0
    for (j = xstart; j < xend; j++) {
2545
0
        sum = 0.0;
2546
0
        if (d == 8) {
2547
0
            for (i = ystart; i < yend; i++) {
2548
0
                line = data + i * wpl;
2549
0
                sum += GET_DATA_BYTE(line, j);
2550
0
            }
2551
0
            if (type == L_BLACK_IS_MAX)
2552
0
                sum = bh * 255 - sum;
2553
0
        } else {  /* d == 16 */
2554
0
            for (i = ystart; i < yend; i++) {
2555
0
                line = data + i * wpl;
2556
0
                sum += GET_DATA_TWO_BYTES(line, j);
2557
0
            }
2558
0
            if (type == L_BLACK_IS_MAX)
2559
0
                sum = bh * 0xffff - sum;
2560
0
        }
2561
0
        numaAddNumber(na, (l_float32)(norm * sum));
2562
0
    }
2563
2564
0
    return na;
2565
0
}
2566
2567
2568
/*!
2569
 * \brief   pixAverageInRect()
2570
 *
2571
 * \param[in]    pixs     1, 2, 4, 8 bpp; not cmapped
2572
 * \param[in]    pixm     [optional] 1 bpp mask; if null, use all pixels
2573
 * \param[in]    box      [optional] if null, use entire image
2574
 * \param[in]    minval   ignore values less than this
2575
 * \param[in]    maxval   ignore values greater than this
2576
 * \param[in]    subsamp  subsample factor: integer; use 1 for all pixels
2577
 * \param[out]   pave     average of pixel values under consideration
2578
 * \return  0 if OK; 1 on error; 2 if all pixels are filtered out
2579
 *
2580
 * <pre>
2581
 * Notes:
2582
 *      (1) The average is computed with 4 optional filters: a rectangle,
2583
 *          a mask, a contiguous set of range values, and subsampling.
2584
 *          In practice you might use only one or two of these.
2585
 *      (2) The mask %pixm is a blocking mask: only count pixels in the bg.
2586
 *          If it exists, alignment is assumed at UL corner and computation
2587
 *          is over the minimum intersection of %pixs and %pixm.
2588
 *          If you want the average of pixels under the mask fg, invert it.
2589
 *      (3) Set the range limits %minval = 0 and %maxval = 255 to use
2590
 *          all non-masked pixels (regardless of value) in the average.
2591
 *      (4) If no pixels are used in the averaging, the returned average
2592
 *          value is 0 and the function returns 2.  This is not an error,
2593
 *          but it says to disregard the returned average value.
2594
 *      (5) For example, to average all pixels in a given clipping rect %box,
2595
 *              pixAverageInRect(pixs, NULL, box, 0, 255, 1, &aveval);
2596
 * </pre>
2597
 */
2598
l_ok
2599
pixAverageInRect(PIX        *pixs,
2600
                 PIX        *pixm,
2601
                 BOX        *box,
2602
                 l_int32     minval,
2603
                 l_int32     maxval,
2604
                 l_int32     subsamp,
2605
                 l_float32  *pave)
2606
0
{
2607
0
l_int32    w, h, d, wpls, wm, hm, dm, wplm, val, count;
2608
0
l_int32    i, j, xstart, xend, ystart, yend;
2609
0
l_uint32  *datas, *datam = NULL, *lines, *linem = NULL;
2610
0
l_float64  sum;
2611
2612
0
    if (!pave)
2613
0
        return ERROR_INT("&ave not defined", __func__, 1);
2614
0
    *pave = 0;
2615
0
    if (!pixs)
2616
0
        return ERROR_INT("pixs not defined", __func__, 1);
2617
0
    if (pixGetColormap(pixs) != NULL)
2618
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
2619
0
    pixGetDimensions(pixs, &w, &h, &d);
2620
0
    if (d != 1 && d != 2 && d != 4 && d != 8)
2621
0
        return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", __func__, 1);
2622
0
    if (pixm) {
2623
0
        pixGetDimensions(pixm, &wm, &hm, &dm);
2624
0
        if (dm != 1)
2625
0
            return ERROR_INT("pixm not 1 bpp", __func__, 1);
2626
0
        w = L_MIN(w, wm);
2627
0
        h = L_MIN(h, hm);
2628
0
    }
2629
0
    if (subsamp < 1)
2630
0
        return ERROR_INT("subsamp must be >= 1", __func__, 1);
2631
2632
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2633
0
                                 NULL, NULL) == 1)
2634
0
        return ERROR_INT("invalid clipping box", __func__, 1);
2635
2636
0
    datas = pixGetData(pixs);
2637
0
    wpls = pixGetWpl(pixs);
2638
0
    if (pixm) {
2639
0
        datam = pixGetData(pixm);
2640
0
        wplm = pixGetWpl(pixm);
2641
0
    }
2642
0
    sum = 0.0;
2643
0
    count = 0;
2644
0
    for (i = ystart; i < yend; i += subsamp) {
2645
0
        lines = datas + i * wpls;
2646
0
        if (pixm)
2647
0
            linem = datam + i * wplm;
2648
0
        for (j = xstart; j < xend; j += subsamp) {
2649
0
            if (pixm && (GET_DATA_BIT(linem, j) == 1))
2650
0
                continue;
2651
0
            if (d == 1)
2652
0
                val = GET_DATA_BIT(lines, j);
2653
0
            else if (d == 2)
2654
0
                val = GET_DATA_DIBIT(lines, j);
2655
0
            else if (d == 4)
2656
0
                val = GET_DATA_QBIT(lines, j);
2657
0
            else  /* d == 8 */
2658
0
                val = GET_DATA_BYTE(lines, j);
2659
0
            if (val >= minval && val <= maxval) {
2660
0
                sum += val;
2661
0
                count++;
2662
0
            }
2663
0
        }
2664
0
    }
2665
2666
0
    if (count == 0)
2667
0
        return 2;  /* not an error; don't use the average value (0.0) */
2668
0
    *pave = sum / (l_float32)count;
2669
0
    return 0;
2670
0
}
2671
2672
2673
/*-------------------------------------------------------------*
2674
 *             Average of pixel values in RGB images           *
2675
 *-------------------------------------------------------------*/
2676
/*!
2677
 * \brief   pixAverageInRectRGB()
2678
 *
2679
 * \param[in]    pixs     rgb; not cmapped
2680
 * \param[in]    pixm     [optional] 1 bpp mask; if null, use all pixels
2681
 * \param[in]    box      [optional] if null, use entire image
2682
 * \param[in]    subsamp  subsample factor: integer; use 1 for all pixels
2683
 * \param[out]   pave     average color of pixel values under consideration,
2684
 *                        in format 0xrrggbb00.
2685
 * \return  0 if OK; 1 on error; 2 if all pixels are filtered out
2686
 *
2687
 * <pre>
2688
 * Notes:
2689
 *      (1) The average is computed with 3 optional filters: a rectangle,
2690
 *          a mask, and subsampling.
2691
 *          In practice you might use only one or two of these.
2692
 *      (2) The mask %pixm is a blocking mask: only count pixels in the bg.
2693
 *          If it exists, alignment is assumed at UL corner and computation
2694
 *          is over the minimum intersection of %pixs and %pixm.
2695
 *          If you want the average of pixels under the mask fg, invert it.
2696
 *      (3) If no pixels are used in the averaging, the returned average
2697
 *          value is 0 and the function returns 2.  This is not an error,
2698
 *          but it says to disregard the returned average value.
2699
 *      (4) For example, to average all pixels in a given clipping rect %box,
2700
 *              pixAverageInRectRGB(pixs, NULL, box, 1, &aveval);
2701
 * </pre>
2702
 */
2703
l_ok
2704
pixAverageInRectRGB(PIX       *pixs,
2705
                    PIX       *pixm,
2706
                    BOX       *box,
2707
                    l_int32    subsamp,
2708
                    l_uint32  *pave)
2709
0
{
2710
0
l_int32    w, h, wpls, wm, hm, dm, wplm, i, j, xstart, xend, ystart, yend;
2711
0
l_int32    rval, gval, bval, rave, gave, bave, count;
2712
0
l_uint32  *datas, *datam = NULL, *lines, *linem = NULL;
2713
0
l_uint32   pixel;
2714
0
l_float64  rsum, gsum, bsum;
2715
2716
0
    if (!pave)
2717
0
        return ERROR_INT("&ave not defined", __func__, 1);
2718
0
    *pave = 0;
2719
0
    if (!pixs || pixGetDepth(pixs) != 32)
2720
0
        return ERROR_INT("pixs undefined or not 32 bpp", __func__, 1);
2721
0
    pixGetDimensions(pixs, &w, &h, NULL);
2722
0
    if (pixm) {
2723
0
        pixGetDimensions(pixm, &wm, &hm, &dm);
2724
0
        if (dm != 1)
2725
0
            return ERROR_INT("pixm not 1 bpp", __func__, 1);
2726
0
        w = L_MIN(w, wm);
2727
0
        h = L_MIN(h, hm);
2728
0
    }
2729
0
    if (subsamp < 1)
2730
0
        return ERROR_INT("subsamp must be >= 1", __func__, 1);
2731
2732
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2733
0
                                 NULL, NULL) == 1)
2734
0
        return ERROR_INT("invalid clipping box", __func__, 1);
2735
2736
0
    datas = pixGetData(pixs);
2737
0
    wpls = pixGetWpl(pixs);
2738
0
    if (pixm) {
2739
0
        datam = pixGetData(pixm);
2740
0
        wplm = pixGetWpl(pixm);
2741
0
    }
2742
0
    rsum = gsum = bsum = 0.0;
2743
0
    count = 0;
2744
0
    for (i = ystart; i < yend; i += subsamp) {
2745
0
        lines = datas + i * wpls;
2746
0
        if (pixm)
2747
0
            linem = datam + i * wplm;
2748
0
        for (j = xstart; j < xend; j += subsamp) {
2749
0
            if (pixm && (GET_DATA_BIT(linem, j) == 1))
2750
0
                continue;
2751
0
            pixel = *(lines + j);
2752
0
            extractRGBValues(pixel, &rval, &gval, &bval);
2753
0
            rsum += rval;
2754
0
            gsum += gval;
2755
0
            bsum += bval;
2756
0
            count++;
2757
0
        }
2758
0
    }
2759
2760
0
    if (count == 0)
2761
0
        return 2;  /* not an error */
2762
0
    rave = (l_uint32)(rsum / (l_float64)count);
2763
0
    gave = (l_uint32)(gsum / (l_float64)count);
2764
0
    bave = (l_uint32)(bsum / (l_float64)count);
2765
0
    composeRGBPixel(rave, gave, bave, pave);
2766
0
    return 0;
2767
0
}
2768
2769
2770
/*------------------------------------------------------------------*
2771
 *               Variance of pixel values in gray images            *
2772
 *------------------------------------------------------------------*/
2773
/*!
2774
 * \brief   pixVarianceByRow()
2775
 *
2776
 * \param[in]   pix   8 or 16 bpp; no colormap
2777
 * \param[in]   box   [optional] clipping box for variance; can be null
2778
 * \return  na of rmsdev by row, or NULL on error
2779
 *
2780
 * <pre>
2781
 * Notes:
2782
 *      (1) To resample for a bin size different from 1, use
2783
 *          numaUniformSampling() on the result of this function.
2784
 *      (2) We are actually computing the RMS deviation in each row.
2785
 *          This is the square root of the variance.
2786
 * </pre>
2787
 */
2788
NUMA *
2789
pixVarianceByRow(PIX     *pix,
2790
                 BOX     *box)
2791
0
{
2792
0
l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
2793
0
l_uint32   *line, *data;
2794
0
l_float64   sum1, sum2, norm, ave, var, rootvar;
2795
0
NUMA       *na;
2796
2797
0
    if (!pix)
2798
0
        return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2799
0
    pixGetDimensions(pix, &w, &h, &d);
2800
0
    if (d != 8 && d != 16)
2801
0
        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2802
0
    if (pixGetColormap(pix) != NULL)
2803
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2804
2805
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2806
0
                                 &bw, &bh) == 1)
2807
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2808
2809
0
    if ((na = numaCreate(bh)) == NULL)
2810
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2811
0
    numaSetParameters(na, ystart, 1);
2812
0
    norm = 1. / (l_float32)bw;
2813
0
    data = pixGetData(pix);
2814
0
    wpl = pixGetWpl(pix);
2815
0
    for (i = ystart; i < yend; i++) {
2816
0
        sum1 = sum2 = 0.0;
2817
0
        line = data + i * wpl;
2818
0
        for (j = xstart; j < xend; j++) {
2819
0
            if (d == 8)
2820
0
                val = GET_DATA_BYTE(line, j);
2821
0
            else  /* d == 16 */
2822
0
                val = GET_DATA_TWO_BYTES(line, j);
2823
0
            sum1 += val;
2824
0
            sum2 += (l_float64)(val) * val;
2825
0
        }
2826
0
        ave = norm * sum1;
2827
0
        var = norm * sum2 - ave * ave;
2828
0
        rootvar = sqrt(var);
2829
0
        numaAddNumber(na, (l_float32)rootvar);
2830
0
    }
2831
2832
0
    return na;
2833
0
}
2834
2835
2836
/*!
2837
 * \brief   pixVarianceByColumn()
2838
 *
2839
 * \param[in]   pix   8 or 16 bpp; no colormap
2840
 * \param[in]   box   [optional] clipping box for variance; can be null
2841
 * \return  na of rmsdev by column, or NULL on error
2842
 *
2843
 * <pre>
2844
 * Notes:
2845
 *      (1) To resample for a bin size different from 1, use
2846
 *          numaUniformSampling() on the result of this function.
2847
 *      (2) We are actually computing the RMS deviation in each row.
2848
 *          This is the square root of the variance.
2849
 * </pre>
2850
 */
2851
NUMA *
2852
pixVarianceByColumn(PIX     *pix,
2853
                    BOX     *box)
2854
0
{
2855
0
l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
2856
0
l_uint32   *line, *data;
2857
0
l_float64   sum1, sum2, norm, ave, var, rootvar;
2858
0
NUMA       *na;
2859
2860
0
    if (!pix)
2861
0
        return (NUMA *)ERROR_PTR("pix not defined", __func__, NULL);
2862
0
    pixGetDimensions(pix, &w, &h, &d);
2863
0
    if (d != 8 && d != 16)
2864
0
        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", __func__, NULL);
2865
0
    if (pixGetColormap(pix) != NULL)
2866
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2867
2868
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2869
0
                                 &bw, &bh) == 1)
2870
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
2871
2872
0
    if ((na = numaCreate(bw)) == NULL)
2873
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
2874
0
    numaSetParameters(na, xstart, 1);
2875
0
    norm = 1. / (l_float32)bh;
2876
0
    data = pixGetData(pix);
2877
0
    wpl = pixGetWpl(pix);
2878
0
    for (j = xstart; j < xend; j++) {
2879
0
        sum1 = sum2 = 0.0;
2880
0
        for (i = ystart; i < yend; i++) {
2881
0
            line = data + wpl * i;
2882
0
            if (d == 8)
2883
0
                val = GET_DATA_BYTE(line, j);
2884
0
            else  /* d == 16 */
2885
0
                val = GET_DATA_TWO_BYTES(line, j);
2886
0
            sum1 += val;
2887
0
            sum2 += (l_float64)(val) * val;
2888
0
        }
2889
0
        ave = norm * sum1;
2890
0
        var = norm * sum2 - ave * ave;
2891
0
        rootvar = sqrt(var);
2892
0
        numaAddNumber(na, (l_float32)rootvar);
2893
0
    }
2894
2895
0
    return na;
2896
0
}
2897
2898
2899
/*!
2900
 * \brief   pixVarianceInRect()
2901
 *
2902
 * \param[in]    pix       1, 2, 4, 8 bpp; not cmapped
2903
 * \param[in]    box       [optional] if null, use entire image
2904
 * \param[out]   prootvar  sqrt variance of pixel values in region
2905
 * \return  0 if OK; 1 on error
2906
 */
2907
l_ok
2908
pixVarianceInRect(PIX        *pix,
2909
                  BOX        *box,
2910
                  l_float32  *prootvar)
2911
0
{
2912
0
l_int32    w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val;
2913
0
l_uint32  *data, *line;
2914
0
l_float64  sum1, sum2, norm, ave, var;
2915
2916
0
    if (!prootvar)
2917
0
        return ERROR_INT("&rootvar not defined", __func__, 1);
2918
0
    *prootvar = 0.0;
2919
0
    if (!pix)
2920
0
        return ERROR_INT("pix not defined", __func__, 1);
2921
0
    pixGetDimensions(pix, &w, &h, &d);
2922
0
    if (d != 1 && d != 2 && d != 4 && d != 8)
2923
0
        return ERROR_INT("pix not 1, 2, 4 or 8 bpp", __func__, 1);
2924
0
    if (pixGetColormap(pix) != NULL)
2925
0
        return ERROR_INT("pix is colormapped", __func__, 1);
2926
2927
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2928
0
                                 &bw, &bh) == 1)
2929
0
        return ERROR_INT("invalid clipping box", __func__, 1);
2930
2931
0
    wpl = pixGetWpl(pix);
2932
0
    data = pixGetData(pix);
2933
0
    sum1 = sum2 = 0.0;
2934
0
    for (i = ystart; i < yend; i++) {
2935
0
        line = data + i * wpl;
2936
0
        for (j = xstart; j < xend; j++) {
2937
0
            if (d == 1) {
2938
0
                val = GET_DATA_BIT(line, j);
2939
0
                sum1 += val;
2940
0
                sum2 += (l_float64)(val) * val;
2941
0
            } else if (d == 2) {
2942
0
                val = GET_DATA_DIBIT(line, j);
2943
0
                sum1 += val;
2944
0
                sum2 += (l_float64)(val) * val;
2945
0
            } else if (d == 4) {
2946
0
                val = GET_DATA_QBIT(line, j);
2947
0
                sum1 += val;
2948
0
                sum2 += (l_float64)(val) * val;
2949
0
            } else {  /* d == 8 */
2950
0
                val = GET_DATA_BYTE(line, j);
2951
0
                sum1 += val;
2952
0
                sum2 += (l_float64)(val) * val;
2953
0
            }
2954
0
        }
2955
0
    }
2956
0
    norm = 1.0 / ((l_float64)(bw) * bh);
2957
0
    ave = norm * sum1;
2958
0
    var = norm * sum2 - ave * ave;
2959
0
    *prootvar = (l_float32)sqrt(var);
2960
0
    return 0;
2961
0
}
2962
2963
2964
/*---------------------------------------------------------------------*
2965
 *    Average of absolute value of pixel differences in gray images    *
2966
 *---------------------------------------------------------------------*/
2967
/*!
2968
 * \brief   pixAbsDiffByRow()
2969
 *
2970
 * \param[in]   pix   8 bpp; no colormap
2971
 * \param[in]   box   [optional] clipping box for region; can be null
2972
 * \return  na of abs val pixel difference averages by row, or NULL on error
2973
 *
2974
 * <pre>
2975
 * Notes:
2976
 *      (1) This is an average over differences of adjacent pixels along
2977
 *          each row.
2978
 *      (2) To resample for a bin size different from 1, use
2979
 *          numaUniformSampling() on the result of this function.
2980
 * </pre>
2981
 */
2982
NUMA *
2983
pixAbsDiffByRow(PIX  *pix,
2984
                BOX  *box)
2985
0
{
2986
0
l_int32    i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
2987
0
l_uint32  *line, *data;
2988
0
l_float64  norm, sum;
2989
0
NUMA      *na;
2990
2991
0
    if (!pix || pixGetDepth(pix) != 8)
2992
0
        return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", __func__, NULL);
2993
0
    if (pixGetColormap(pix) != NULL)
2994
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
2995
2996
0
    pixGetDimensions(pix, &w, &h, NULL);
2997
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
2998
0
                                 &bw, &bh) == 1)
2999
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
3000
0
    if (bw < 2)
3001
0
        return (NUMA *)ERROR_PTR("row width must be >= 2", __func__, NULL);
3002
3003
0
    norm = 1. / (l_float32)(bw - 1);
3004
0
    if ((na = numaCreate(bh)) == NULL)
3005
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
3006
0
    numaSetParameters(na, ystart, 1);
3007
0
    data = pixGetData(pix);
3008
0
    wpl = pixGetWpl(pix);
3009
0
    for (i = ystart; i < yend; i++) {
3010
0
        sum = 0.0;
3011
0
        line = data + i * wpl;
3012
0
        val0 = GET_DATA_BYTE(line, xstart);
3013
0
        for (j = xstart + 1; j < xend; j++) {
3014
0
            val1 = GET_DATA_BYTE(line, j);
3015
0
            sum += L_ABS(val1 - val0);
3016
0
            val0 = val1;
3017
0
        }
3018
0
        numaAddNumber(na, (l_float32)(norm * sum));
3019
0
    }
3020
3021
0
    return na;
3022
0
}
3023
3024
3025
/*!
3026
 * \brief   pixAbsDiffByColumn()
3027
 *
3028
 * \param[in]   pix   8 bpp; no colormap
3029
 * \param[in]   box   [optional] clipping box for region; can be null
3030
 * \return  na of abs val pixel difference averages by column,
3031
 *              or NULL on error
3032
 *
3033
 * <pre>
3034
 * Notes:
3035
 *      (1) This is an average over differences of adjacent pixels along
3036
 *          each column.
3037
 *      (2) To resample for a bin size different from 1, use
3038
 *          numaUniformSampling() on the result of this function.
3039
 * </pre>
3040
 */
3041
NUMA *
3042
pixAbsDiffByColumn(PIX  *pix,
3043
                   BOX  *box)
3044
0
{
3045
0
l_int32    i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
3046
0
l_uint32  *line, *data;
3047
0
l_float64  norm, sum;
3048
0
NUMA      *na;
3049
3050
0
    if (!pix || pixGetDepth(pix) != 8)
3051
0
        return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", __func__, NULL);
3052
0
    if (pixGetColormap(pix) != NULL)
3053
0
        return (NUMA *)ERROR_PTR("pix colormapped", __func__, NULL);
3054
3055
0
    pixGetDimensions(pix, &w, &h, NULL);
3056
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
3057
0
                                 &bw, &bh) == 1)
3058
0
        return (NUMA *)ERROR_PTR("invalid clipping box", __func__, NULL);
3059
0
    if (bh < 2)
3060
0
        return (NUMA *)ERROR_PTR("column height must be >= 2", __func__, NULL);
3061
3062
0
    norm = 1. / (l_float32)(bh - 1);
3063
0
    if ((na = numaCreate(bw)) == NULL)
3064
0
        return (NUMA *)ERROR_PTR("na not made", __func__, NULL);
3065
0
    numaSetParameters(na, xstart, 1);
3066
0
    data = pixGetData(pix);
3067
0
    wpl = pixGetWpl(pix);
3068
0
    for (j = xstart; j < xend; j++) {
3069
0
        sum = 0.0;
3070
0
        line = data + ystart * wpl;
3071
0
        val0 = GET_DATA_BYTE(line, j);
3072
0
        for (i = ystart + 1; i < yend; i++) {
3073
0
            line = data + i * wpl;
3074
0
            val1 = GET_DATA_BYTE(line, j);
3075
0
            sum += L_ABS(val1 - val0);
3076
0
            val0 = val1;
3077
0
        }
3078
0
        numaAddNumber(na, (l_float32)(norm * sum));
3079
0
    }
3080
3081
0
    return na;
3082
0
}
3083
3084
3085
/*!
3086
 * \brief   pixAbsDiffInRect()
3087
 *
3088
 * \param[in]   pix       8 bpp; not cmapped
3089
 * \param[in]   box       [optional] if null, use entire image
3090
 * \param[in]   dir       differences along L_HORIZONTAL_LINE or L_VERTICAL_LINE
3091
 * \param[out]  pabsdiff  average of abs diff pixel values in region
3092
 * \return  0 if OK; 1 on error
3093
 *
3094
 * <pre>
3095
 * Notes:
3096
 *      (1) This gives the average over the abs val of differences of
3097
 *          adjacent pixels values, along either each
3098
 *             row:     dir == L_HORIZONTAL_LINE
3099
 *             column:  dir == L_VERTICAL_LINE
3100
 * </pre>
3101
 */
3102
l_ok
3103
pixAbsDiffInRect(PIX        *pix,
3104
                 BOX        *box,
3105
                 l_int32     dir,
3106
                 l_float32  *pabsdiff)
3107
0
{
3108
0
l_int32    w, h, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val0, val1;
3109
0
l_uint32  *data, *line;
3110
0
l_float64  norm, sum;
3111
3112
0
    if (!pabsdiff)
3113
0
        return ERROR_INT("&absdiff not defined", __func__, 1);
3114
0
    *pabsdiff = 0.0;
3115
0
    if (!pix || pixGetDepth(pix) != 8)
3116
0
        return ERROR_INT("pix undefined or not 8 bpp", __func__, 1);
3117
0
    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
3118
0
        return ERROR_INT("invalid direction", __func__, 1);
3119
0
    if (pixGetColormap(pix) != NULL)
3120
0
        return ERROR_INT("pix is colormapped", __func__, 1);
3121
3122
0
    pixGetDimensions(pix, &w, &h, NULL);
3123
0
    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
3124
0
                                 &bw, &bh) == 1)
3125
0
        return ERROR_INT("invalid clipping box", __func__, 1);
3126
3127
0
    wpl = pixGetWpl(pix);
3128
0
    data = pixGetData(pix);
3129
0
    if (dir == L_HORIZONTAL_LINE) {
3130
0
        norm = 1. / (l_float32)(bh * (bw - 1));
3131
0
        sum = 0.0;
3132
0
        for (i = ystart; i < yend; i++) {
3133
0
            line = data + i * wpl;
3134
0
            val0 = GET_DATA_BYTE(line, xstart);
3135
0
            for (j = xstart + 1; j < xend; j++) {
3136
0
                val1 = GET_DATA_BYTE(line, j);
3137
0
                sum += L_ABS(val1 - val0);
3138
0
                val0 = val1;
3139
0
            }
3140
0
        }
3141
0
    } else {  /* vertical line */
3142
0
        norm = 1. / (l_float32)(bw * (bh - 1));
3143
0
        sum = 0.0;
3144
0
        for (j = xstart; j < xend; j++) {
3145
0
            line = data + ystart * wpl;
3146
0
            val0 = GET_DATA_BYTE(line, j);
3147
0
            for (i = ystart + 1; i < yend; i++) {
3148
0
                line = data + i * wpl;
3149
0
                val1 = GET_DATA_BYTE(line, j);
3150
0
                sum += L_ABS(val1 - val0);
3151
0
                val0 = val1;
3152
0
            }
3153
0
        }
3154
0
    }
3155
0
    *pabsdiff = (l_float32)(norm * sum);
3156
0
    return 0;
3157
0
}
3158
3159
3160
/*!
3161
 * \brief   pixAbsDiffOnLine()
3162
 *
3163
 * \param[in]    pix        8 bpp; not cmapped
3164
 * \param[in]    x1, y1     first point; x1 <= x2, y1 <= y2
3165
 * \param[in]    x2, y2     first point
3166
 * \param[out]   pabsdiff   average of abs diff pixel values on line
3167
 * \return  0 if OK; 1 on error
3168
 *
3169
 * <pre>
3170
 * Notes:
3171
 *      (1) This gives the average over the abs val of differences of
3172
 *          adjacent pixels values, along a line that is either horizontal
3173
 *          or vertical.
3174
 *      (2) If horizontal, require x1 < x2; if vertical, require y1 < y2.
3175
 * </pre>
3176
 */
3177
l_ok
3178
pixAbsDiffOnLine(PIX        *pix,
3179
                 l_int32     x1,
3180
                 l_int32     y1,
3181
                 l_int32     x2,
3182
                 l_int32     y2,
3183
                 l_float32  *pabsdiff)
3184
0
{
3185
0
l_int32    w, h, i, j, dir, size, sum;
3186
0
l_uint32   val0, val1;
3187
3188
0
    if (!pabsdiff)
3189
0
        return ERROR_INT("&absdiff not defined", __func__, 1);
3190
0
    *pabsdiff = 0.0;
3191
0
    if (!pix || pixGetDepth(pix) != 8)
3192
0
        return ERROR_INT("pix undefined or not 8 bpp", __func__, 1);
3193
0
    if (y1 == y2) {
3194
0
        dir = L_HORIZONTAL_LINE;
3195
0
    } else if (x1 == x2) {
3196
0
        dir = L_VERTICAL_LINE;
3197
0
    } else {
3198
0
        return ERROR_INT("line is neither horiz nor vert", __func__, 1);
3199
0
    }
3200
0
    if (pixGetColormap(pix) != NULL)
3201
0
        return ERROR_INT("pix is colormapped", __func__, 1);
3202
3203
0
    pixGetDimensions(pix, &w, &h, NULL);
3204
0
    sum = 0;
3205
0
    if (dir == L_HORIZONTAL_LINE) {
3206
0
        x1 = L_MAX(x1, 0);
3207
0
        x2 = L_MIN(x2, w - 1);
3208
0
        if (x1 >= x2)
3209
0
            return ERROR_INT("x1 >= x2", __func__, 1);
3210
0
        size = x2 - x1;
3211
0
        pixGetPixel(pix, x1, y1, &val0);
3212
0
        for (j = x1 + 1; j <= x2; j++) {
3213
0
            pixGetPixel(pix, j, y1, &val1);
3214
0
            sum += L_ABS((l_int32)val1 - (l_int32)val0);
3215
0
            val0 = val1;
3216
0
        }
3217
0
    } else {  /* vertical */
3218
0
        y1 = L_MAX(y1, 0);
3219
0
        y2 = L_MIN(y2, h - 1);
3220
0
        if (y1 >= y2)
3221
0
            return ERROR_INT("y1 >= y2", __func__, 1);
3222
0
        size = y2 - y1;
3223
0
        pixGetPixel(pix, x1, y1, &val0);
3224
0
        for (i = y1 + 1; i <= y2; i++) {
3225
0
            pixGetPixel(pix, x1, i, &val1);
3226
0
            sum += L_ABS((l_int32)val1 - (l_int32)val0);
3227
0
            val0 = val1;
3228
0
        }
3229
0
    }
3230
0
    *pabsdiff = (l_float32)sum / (l_float32)size;
3231
0
    return 0;
3232
0
}
3233
3234
3235
/*-------------------------------------------------------------*
3236
 *              Count of pixels with specific value            *
3237
 *-------------------------------------------------------------*/
3238
/*!
3239
 * \brief   pixCountArbInRect()
3240
 *
3241
 * \param[in]    pixs     1,2,4,8 bpp; can be colormapped
3242
 * \param[in]    box      [optional] over which count is made;
3243
 *                        use entire image if NULL
3244
 * \param[in]    val      pixel value to count
3245
 * \param[in]    factor   subsampling factor; integer >= 1
3246
 * \param[out]   pcount   count; estimate it if factor > 1
3247
 * \return  na histogram, or NULL on error
3248
 *
3249
 * <pre>
3250
 * Notes:
3251
 *      (1) If pixs is cmapped, %val is compared to the colormap index;
3252
 *          otherwise, %val is compared to the grayscale value.
3253
 *      (2) Set the subsampling %factor > 1 to reduce the amount of computation.
3254
 *          If %factor > 1, multiply the count by %factor * %factor.
3255
 * </pre>
3256
 */
3257
l_int32
3258
pixCountArbInRect(PIX      *pixs,
3259
                  BOX      *box,
3260
                  l_int32   val,
3261
                  l_int32   factor,
3262
                  l_int32  *pcount)
3263
0
{
3264
0
l_int32    i, j, bx, by, bw, bh, w, h, d, wpl, pixval;
3265
0
l_uint32  *data, *line;
3266
3267
0
    if (!pcount)
3268
0
        return ERROR_INT("&count not defined", __func__, 1);
3269
0
    *pcount = 0;
3270
0
    if (!pixs)
3271
0
        return ERROR_INT("pixs not defined", __func__, 1);
3272
0
    d = pixGetDepth(pixs);
3273
0
    if (d != 1 && d != 2 && d != 4 && d != 8)
3274
0
        return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", __func__, 1);
3275
0
    if (val < 0)
3276
0
        return ERROR_INT("val < 0", __func__, 1);
3277
0
    if (val > (1 << d) - 1) {
3278
0
        L_ERROR("invalid val = %d for depth %d\n", __func__, val, d);
3279
0
        return 1;
3280
0
    }
3281
0
    if (factor < 1)
3282
0
        return ERROR_INT("sampling factor < 1", __func__, 1);
3283
3284
0
    pixGetDimensions(pixs, &w, &h, NULL);
3285
0
    data = pixGetData(pixs);
3286
0
    wpl = pixGetWpl(pixs);
3287
0
    if (!box) {
3288
0
        for (i = 0; i < h; i += factor) {
3289
0
            line = data + i * wpl;
3290
0
            for (j = 0; j < w; j += factor) {
3291
0
                if (d == 8) {
3292
0
                    pixval = GET_DATA_BYTE(line, j);
3293
0
                } else if (d == 1) {
3294
0
                    pixval = GET_DATA_BIT(line, j);
3295
0
                } else if (d == 2) {
3296
0
                    pixval = GET_DATA_DIBIT(line, j);
3297
0
                } else  /* d == 4 */  {
3298
0
                    pixval = GET_DATA_QBIT(line, j);
3299
0
                }
3300
0
                if (pixval == val) (*pcount)++;
3301
0
            }
3302
0
        }
3303
0
    } else {
3304
0
        boxGetGeometry(box, &bx, &by, &bw, &bh);
3305
0
        for (i = 0; i < bh; i += factor) {
3306
0
            if (by + i < 0 || by + i >= h) continue;
3307
0
            line = data + (by + i) * wpl;
3308
0
            for (j = 0; j < bw; j += factor) {
3309
0
                if (bx + j < 0 || bx + j >= w) continue;
3310
0
                if (d == 8) {
3311
0
                    pixval = GET_DATA_BYTE(line, bx + j);
3312
0
                } else if (d == 1) {
3313
0
                    pixval = GET_DATA_BIT(line, bx + j);
3314
0
                } else if (d == 2) {
3315
0
                    pixval = GET_DATA_DIBIT(line, bx + j);
3316
0
                } else  /* d == 4 */  {
3317
0
                    pixval = GET_DATA_QBIT(line, bx + j);
3318
0
                }
3319
0
                if (pixval == val) (*pcount)++;
3320
0
            }
3321
0
        }
3322
0
    }
3323
3324
0
    if (factor > 1)  /* assume pixel color is randomly distributed */
3325
0
        *pcount = *pcount * factor * factor;
3326
0
    return 0;
3327
0
}
3328
3329
3330
/*-------------------------------------------------------------*
3331
 *              Mirrored tiling of a smaller image             *
3332
 *-------------------------------------------------------------*/
3333
/*!
3334
 * \brief   pixMirroredTiling()
3335
 *
3336
 * \param[in]   pixs   8 or 32 bpp, small tile; to be replicated
3337
 * \param[in]   w, h   dimensions of output pix
3338
 * \return  pixd usually larger pix, mirror-tiled with pixs,
3339
 *              or NULL on error
3340
 *
3341
 * <pre>
3342
 * Notes:
3343
 *      (1) This uses mirrored tiling, where each row alternates
3344
 *          with LR flips and every column alternates with TB
3345
 *          flips, such that the result is a tiling with identical
3346
 *          2 x 2 tiles, each of which is composed of these transforms:
3347
 *                  -----------------
3348
 *                  | 1    |  LR    |
3349
 *                  -----------------
3350
 *                  | TB   |  LR/TB |
3351
 *                  -----------------
3352
 * </pre>
3353
 */
3354
PIX *
3355
pixMirroredTiling(PIX     *pixs,
3356
                  l_int32  w,
3357
                  l_int32  h)
3358
0
{
3359
0
l_int32   wt, ht, d, i, j, nx, ny;
3360
0
PIX      *pixd, *pixsfx, *pixsfy, *pixsfxy, *pix;
3361
3362
0
    if (!pixs)
3363
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
3364
0
    pixGetDimensions(pixs, &wt, &ht, &d);
3365
0
    if (wt <= 0 || ht <= 0)
3366
0
        return (PIX *)ERROR_PTR("pixs size illegal", __func__, NULL);
3367
0
    if (d != 8 && d != 32)
3368
0
        return (PIX *)ERROR_PTR("depth not 32 bpp", __func__, NULL);
3369
3370
0
    if ((pixd = pixCreate(w, h, d)) == NULL)
3371
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
3372
0
    pixCopySpp(pixd, pixs);
3373
3374
0
    nx = (w + wt - 1) / wt;
3375
0
    ny = (h + ht - 1) / ht;
3376
0
    pixsfx = pixFlipLR(NULL, pixs);
3377
0
    pixsfy = pixFlipTB(NULL, pixs);
3378
0
    pixsfxy = pixFlipTB(NULL, pixsfx);
3379
0
    for (i = 0; i < ny; i++) {
3380
0
        for (j = 0; j < nx; j++) {
3381
0
            pix = pixs;
3382
0
            if ((i & 1) && !(j & 1))
3383
0
                pix = pixsfy;
3384
0
            else if (!(i & 1) && (j & 1))
3385
0
                pix = pixsfx;
3386
0
            else if ((i & 1) && (j & 1))
3387
0
                pix = pixsfxy;
3388
0
            pixRasterop(pixd, j * wt, i * ht, wt, ht, PIX_SRC, pix, 0, 0);
3389
0
        }
3390
0
    }
3391
3392
0
    pixDestroy(&pixsfx);
3393
0
    pixDestroy(&pixsfy);
3394
0
    pixDestroy(&pixsfxy);
3395
0
    return pixd;
3396
0
}
3397
3398
3399
/*!
3400
 * \brief   pixFindRepCloseTile()
3401
 *
3402
 * \param[in]    pixs       32 bpp rgb
3403
 * \param[in]    box        region of pixs to search around
3404
 * \param[in]    searchdir  L_HORIZ or L_VERT; direction to search
3405
 * \param[in]    mindist    min distance of selected tile edge from box; >= 0
3406
 * \param[in]    tsize      tile size; > 1; even; typically ~50
3407
 * \param[in]    ntiles     number of tiles tested in each row/column
3408
 * \param[out]   pboxtile   region of best tile
3409
 * \param[in]    debug 1    for debug output
3410
 * \return  0 if OK, 1 on error
3411
 *
3412
 * <pre>
3413
 * Notes:
3414
 *      (1) This looks for one or two square tiles with conforming median
3415
 *          intensity and low variance, that is outside but near the input box.
3416
 *      (2) %mindist specifies the gap between the box and the
3417
 *          potential tiles.  The tiles are given an overlap of 50%.
3418
 *          %ntiles specifies the number of tiles that are tested
3419
 *          beyond %mindist for each row or column.
3420
 *      (3) For example, if %mindist = 20, %tilesize = 50 and %ntiles = 3,
3421
 *          a horizontal search to the right will have 3 tiles in each row,
3422
 *          with left edges at 20, 45 and 70 from the right edge of the
3423
 *          input %box.  The number of rows of tiles is determined by
3424
 *          the height of %box and %tsize, with the 50% overlap..
3425
 * </pre>
3426
 */
3427
l_ok
3428
pixFindRepCloseTile(PIX     *pixs,
3429
                    BOX     *box,
3430
                    l_int32  searchdir,
3431
                    l_int32  mindist,
3432
                    l_int32  tsize,
3433
                    l_int32  ntiles,
3434
                    BOX    **pboxtile,
3435
                    l_int32  debug)
3436
0
{
3437
0
l_int32    w, h, i, n, bestindex;
3438
0
l_float32  var_of_mean, median_of_mean, median_of_stdev, mean_val, stdev_val;
3439
0
l_float32  mindels, bestdelm, delm, dels, mean, stdev;
3440
0
BOXA      *boxa;
3441
0
NUMA      *namean, *nastdev;
3442
0
PIX       *pix, *pixg;
3443
0
PIXA      *pixa;
3444
3445
0
    if (!pboxtile)
3446
0
        return ERROR_INT("&boxtile not defined", __func__, 1);
3447
0
    *pboxtile = NULL;
3448
0
    if (!pixs)
3449
0
        return ERROR_INT("pixs not defined", __func__, 1);
3450
0
    if (!box)
3451
0
        return ERROR_INT("box not defined", __func__, 1);
3452
0
    if (searchdir != L_HORIZ && searchdir != L_VERT)
3453
0
        return ERROR_INT("invalid searchdir", __func__, 1);
3454
0
    if (mindist < 0)
3455
0
        return ERROR_INT("mindist must be >= 0", __func__, 1);
3456
0
    if (tsize < 2)
3457
0
        return ERROR_INT("tsize must be > 1", __func__, 1);
3458
0
    if (ntiles > 7) {
3459
0
        L_WARNING("ntiles = %d; larger than suggested max of 7\n",
3460
0
                  __func__, ntiles);
3461
0
    }
3462
3463
        /* Locate tile regions */
3464
0
    pixGetDimensions(pixs, &w, &h, NULL);
3465
0
    boxa = findTileRegionsForSearch(box, w, h, searchdir, mindist,
3466
0
                                    tsize, ntiles);
3467
0
    if (!boxa)
3468
0
        return ERROR_INT("no tiles found", __func__, 1);
3469
3470
        /* Generate the tiles and the mean and stdev of intensity */
3471
0
    pixa = pixClipRectangles(pixs, boxa);
3472
0
    n = pixaGetCount(pixa);
3473
0
    namean = numaCreate(n);
3474
0
    nastdev = numaCreate(n);
3475
0
    for (i = 0; i < n; i++) {
3476
0
        pix = pixaGetPix(pixa, i, L_CLONE);
3477
0
        pixg = pixConvertRGBToGray(pix, 0.33f, 0.34f, 0.33f);
3478
0
        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_MEAN_ABSVAL, &mean);
3479
0
        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_STANDARD_DEVIATION, &stdev);
3480
0
        numaAddNumber(namean, mean);
3481
0
        numaAddNumber(nastdev, stdev);
3482
0
        pixDestroy(&pix);
3483
0
        pixDestroy(&pixg);
3484
0
    }
3485
3486
        /* Find the median and variance of the averages.  We require
3487
         * the best tile to have a mean pixel intensity within a standard
3488
         * deviation of the median of mean intensities, and choose the
3489
         * tile in that set with the smallest stdev of pixel intensities
3490
         * (as a proxy for the tile with least visible structure).
3491
         * The median of the stdev is used, for debugging, as a normalizing
3492
         * factor for the stdev of intensities within a tile. */
3493
0
    numaGetStatsUsingHistogram(namean, 256, NULL, NULL, NULL, &var_of_mean,
3494
0
                               &median_of_mean, 0.0, NULL, NULL);
3495
0
    numaGetStatsUsingHistogram(nastdev, 256, NULL, NULL, NULL, NULL,
3496
0
                               &median_of_stdev, 0.0, NULL, NULL);
3497
0
    mindels = 1000.0;
3498
0
    bestdelm = 1000.0;
3499
0
    bestindex = 0;
3500
0
    for (i = 0; i < n; i++) {
3501
0
        numaGetFValue(namean, i, &mean_val);
3502
0
        numaGetFValue(nastdev, i, &stdev_val);
3503
0
        if (var_of_mean == 0.0) {  /* uniform color; any box will do */
3504
0
            delm = 0.0;  /* any value < 1.01 */
3505
0
            dels = 1.0;  /* n'importe quoi */
3506
0
        } else {
3507
0
            delm = L_ABS(mean_val - median_of_mean) / sqrt(var_of_mean);
3508
0
            dels = stdev_val / median_of_stdev;
3509
0
        }
3510
0
        if (delm < 1.01) {
3511
0
            if (dels < mindels) {
3512
0
                if (debug) {
3513
0
                    lept_stderr("i = %d, mean = %7.3f, delm = %7.3f,"
3514
0
                                " stdev = %7.3f, dels = %7.3f\n",
3515
0
                                i, mean_val, delm, stdev_val, dels);
3516
0
                }
3517
0
                mindels = dels;
3518
0
                bestdelm = delm;
3519
0
                bestindex = i;
3520
0
            }
3521
0
        }
3522
0
    }
3523
0
    *pboxtile = boxaGetBox(boxa, bestindex, L_COPY);
3524
3525
0
    if (debug) {
3526
0
        L_INFO("median of mean = %7.3f\n", __func__, median_of_mean);
3527
0
        L_INFO("standard dev of mean = %7.3f\n", __func__, sqrt(var_of_mean));
3528
0
        L_INFO("median of stdev = %7.3f\n", __func__, median_of_stdev);
3529
0
        L_INFO("best tile: index = %d\n", __func__, bestindex);
3530
0
        L_INFO("delta from median in units of stdev = %5.3f\n",
3531
0
               __func__, bestdelm);
3532
0
        L_INFO("stdev as fraction of median stdev = %5.3f\n",
3533
0
               __func__, mindels);
3534
0
    }
3535
3536
0
    numaDestroy(&namean);
3537
0
    numaDestroy(&nastdev);
3538
0
    pixaDestroy(&pixa);
3539
0
    boxaDestroy(&boxa);
3540
0
    return 0;
3541
0
}
3542
3543
3544
/*!
3545
 * \brief   findTileRegionsForSearch()
3546
 *
3547
 * \param[in]   box        region of Pix to search around
3548
 * \param[in]   w, h       dimensions of Pix
3549
 * \param[in]   searchdir  L_HORIZ or L_VERT; direction to search
3550
 * \param[in]   mindist    min distance of selected tile edge from box; >= 0
3551
 * \param[in]   tsize      tile size; > 1; even; typically ~50
3552
 * \param[in]   ntiles     number of tiles tested in each row/column
3553
 * \return  boxa if OK, or NULL on error
3554
 *
3555
 * <pre>
3556
 * Notes:
3557
 *      (1) See calling function pixfindRepCloseTile().
3558
 * </pre>
3559
 */
3560
static BOXA *
3561
findTileRegionsForSearch(BOX     *box,
3562
                         l_int32  w,
3563
                         l_int32  h,
3564
                         l_int32  searchdir,
3565
                         l_int32  mindist,
3566
                         l_int32  tsize,
3567
                         l_int32  ntiles)
3568
0
{
3569
0
l_int32  bx, by, bw, bh, left, right, top, bot, i, j, nrows, ncols;
3570
0
l_int32  x0, y0, x, y, w_avail, w_needed, h_avail, h_needed, t_avail;
3571
0
BOX     *box1;
3572
0
BOXA    *boxa;
3573
3574
0
    if (!box)
3575
0
        return (BOXA *)ERROR_PTR("box not defined", __func__, NULL);
3576
0
    if (ntiles == 0)
3577
0
        return (BOXA *)ERROR_PTR("no tiles requested", __func__, NULL);
3578
3579
0
    boxGetGeometry(box, &bx, &by, &bw, &bh);
3580
0
    if (searchdir == L_HORIZ) {
3581
            /* Find the tile parameters for the search.  Note that the
3582
             * tiles are overlapping by 50% in each direction. */
3583
0
        left = bx;   /* distance to left of box */
3584
0
        right = w - bx - bw + 1;   /* distance to right of box */
3585
0
        w_avail = L_MAX(left, right) - mindist;
3586
0
        if (tsize & 1) tsize++;  /* be sure it's even */
3587
0
        if (w_avail < tsize) {
3588
0
            L_ERROR("tsize = %d, w_avail = %d\n", __func__, tsize, w_avail);
3589
0
            return NULL;
3590
0
        }
3591
0
        w_needed = tsize + (ntiles - 1) * (tsize / 2);
3592
0
        if (w_needed > w_avail) {
3593
0
            t_avail = 1 + 2 * (w_avail - tsize) / tsize;
3594
0
            L_WARNING("ntiles = %d; room for only %d\n", __func__,
3595
0
                      ntiles, t_avail);
3596
0
            ntiles = t_avail;
3597
0
            w_needed = tsize + (ntiles - 1) * (tsize / 2);
3598
0
        }
3599
0
        nrows = L_MAX(1, 1 + 2 * (bh - tsize) / tsize);
3600
3601
            /* Generate the tile regions to search */
3602
0
        boxa = boxaCreate(0);
3603
0
        if (left > right)  /* search to left */
3604
0
            x0 = bx - w_needed;
3605
0
        else  /* search to right */
3606
0
            x0 = bx + bw + mindist;
3607
0
        for (i = 0; i < nrows; i++) {
3608
0
            y = by + i * tsize / 2;
3609
0
            for (j = 0; j < ntiles; j++) {
3610
0
                x = x0 + j * tsize / 2;
3611
0
                box1 = boxCreate(x, y, tsize, tsize);
3612
0
                boxaAddBox(boxa, box1, L_INSERT);
3613
0
            }
3614
0
        }
3615
0
    } else {  /* L_VERT */
3616
            /* Find the tile parameters for the search */
3617
0
        top = by;   /* distance above box */
3618
0
        bot = h - by - bh + 1;   /* distance below box */
3619
0
        h_avail = L_MAX(top, bot) - mindist;
3620
0
        if (h_avail < tsize) {
3621
0
            L_ERROR("tsize = %d, h_avail = %d\n", __func__, tsize, h_avail);
3622
0
            return NULL;
3623
0
        }
3624
0
        h_needed = tsize + (ntiles - 1) * (tsize / 2);
3625
0
        if (h_needed > h_avail) {
3626
0
            t_avail = 1 + 2 * (h_avail - tsize) / tsize;
3627
0
            L_WARNING("ntiles = %d; room for only %d\n", __func__,
3628
0
                      ntiles, t_avail);
3629
0
            ntiles = t_avail;
3630
0
            h_needed = tsize + (ntiles - 1) * (tsize / 2);
3631
0
        }
3632
0
        ncols = L_MAX(1, 1 + 2 * (bw - tsize) / tsize);
3633
3634
            /* Generate the tile regions to search */
3635
0
        boxa = boxaCreate(0);
3636
0
        if (top > bot)  /* search above */
3637
0
            y0 = by - h_needed;
3638
0
        else  /* search below */
3639
0
            y0 = by + bh + mindist;
3640
0
        for (j = 0; j < ncols; j++) {
3641
0
            x = bx + j * tsize / 2;
3642
0
            for (i = 0; i < ntiles; i++) {
3643
0
                y = y0 + i * tsize / 2;
3644
0
                box1 = boxCreate(x, y, tsize, tsize);
3645
0
                boxaAddBox(boxa, box1, L_INSERT);
3646
0
            }
3647
0
        }
3648
0
    }
3649
0
    return boxa;
3650
0
}