Coverage Report

Created: 2025-06-13 07:15

/src/leptonica/src/boxfunc3.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  boxfunc3.c
29
 * <pre>
30
 *
31
 *      Boxa/Boxaa painting into pix
32
 *           PIX             *pixMaskConnComp()
33
 *           PIX             *pixMaskBoxa()
34
 *           PIX             *pixPaintBoxa()
35
 *           PIX             *pixSetBlackOrWhiteBoxa()
36
 *           PIX             *pixPaintBoxaRandom()
37
 *           PIX             *pixBlendBoxaRandom()
38
 *           PIX             *pixDrawBoxa()
39
 *           PIX             *pixDrawBoxaRandom()
40
 *           PIX             *boxaaDisplay()
41
 *           PIXA            *pixaDisplayBoxaa()
42
 *
43
 *      Split mask components into Boxa
44
 *           BOXA            *pixSplitIntoBoxa()
45
 *           BOXA            *pixSplitComponentIntoBoxa()
46
 *           static l_int32   pixSearchForRectangle()
47
 *
48
 *      Represent horizontal or vertical mosaic strips
49
 *           BOXA            *makeMosaicStrips()
50
 *
51
 *      Comparison between boxa
52
 *           l_int32          boxaCompareRegions()
53
 *
54
 *      Reliable selection of a single large box
55
 *           BOX             *pixSelectLargeULComp()
56
 *           BOX             *boxaSelectLargeULBox()
57
 *
58
 *  See summary in pixPaintBoxa() of various ways to paint and draw
59
 *  boxes on images.
60
 * </pre>
61
 */
62
63
#ifdef HAVE_CONFIG_H
64
#include <config_auto.h>
65
#endif  /* HAVE_CONFIG_H */
66
67
#include "allheaders.h"
68
69
static l_int32 pixSearchForRectangle(PIX *pixs, BOX *boxs, l_int32 minsum,
70
                                     l_int32 skipdist, l_int32 delta,
71
                                     l_int32 maxbg, l_int32 sideflag,
72
                                     BOXA *boxat, NUMA *nascore);
73
74
#ifndef NO_CONSOLE_IO
75
#define  DEBUG_SPLIT     0
76
#endif  /* ~NO_CONSOLE_IO */
77
78
/*---------------------------------------------------------------------*
79
 *                     Boxa/Boxaa painting into Pix                    *
80
 *---------------------------------------------------------------------*/
81
/*!
82
 * \brief   pixMaskConnComp()
83
 *
84
 * \param[in]    pixs           1 bpp
85
 * \param[in]    connectivity   4 or 8
86
 * \param[out]   pboxa          [optional] bounding boxes of c.c.
87
 * \return  pixd 1 bpp mask over the c.c., or NULL on error
88
 *
89
 * <pre>
90
 * Notes:
91
 *      (1) This generates a mask image with ON pixels over the
92
 *          b.b. of the c.c. in pixs.  If there are no ON pixels in pixs,
93
 *          pixd will also have no ON pixels.
94
 * </pre>
95
 */
96
PIX *
97
pixMaskConnComp(PIX     *pixs,
98
                l_int32  connectivity,
99
                BOXA   **pboxa)
100
0
{
101
0
BOXA  *boxa;
102
0
PIX   *pixd;
103
104
0
    if (pboxa) *pboxa = NULL;
105
0
    if (!pixs || pixGetDepth(pixs) != 1)
106
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
107
0
    if (connectivity != 4 && connectivity != 8)
108
0
        return (PIX *)ERROR_PTR("connectivity not 4 or 8", __func__, NULL);
109
110
0
    boxa = pixConnComp(pixs, NULL, connectivity);
111
0
    pixd = pixCreateTemplate(pixs);
112
0
    if (boxaGetCount(boxa) != 0)
113
0
        pixMaskBoxa(pixd, pixd, boxa, L_SET_PIXELS);
114
0
    if (pboxa)
115
0
        *pboxa = boxa;
116
0
    else
117
0
        boxaDestroy(&boxa);
118
0
    return pixd;
119
0
}
120
121
122
/*!
123
 * \brief   pixMaskBoxa()
124
 *
125
 * \param[in]    pixd    [optional] may be NULL
126
 * \param[in]    pixs    any depth; not cmapped
127
 * \param[in]    boxa    of boxes, to paint
128
 * \param[in]    op      L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS
129
 * \return  pixd with masking op over the boxes, or NULL on error
130
 *
131
 * <pre>
132
 * Notes:
133
 *      (1) This can be used with:
134
 *              pixd = NULL  (makes a new pixd)
135
 *              pixd = pixs  (in-place)
136
 *      (2) If pixd == NULL, this first makes a copy of pixs, and then
137
 *          bit-twiddles over the boxes.  Otherwise, it operates directly
138
 *          on pixs.
139
 *      (3) This simple function is typically used with 1 bpp images.
140
 *          It uses the 1-image rasterop function, rasteropUniLow(),
141
 *          to set, clear or flip the pixels in pixd.
142
 *      (4) If you want to generate a 1 bpp mask of ON pixels from the boxes
143
 *          in a Boxa, in a pix of size (w,h):
144
 *              pix = pixCreate(w, h, 1);
145
 *              pixMaskBoxa(pix, pix, boxa, L_SET_PIXELS);
146
 * </pre>
147
 */
148
PIX *
149
pixMaskBoxa(PIX     *pixd,
150
            PIX     *pixs,
151
            BOXA    *boxa,
152
            l_int32  op)
153
0
{
154
0
l_int32  i, n, x, y, w, h;
155
0
BOX     *box;
156
157
0
    if (!pixs)
158
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
159
0
    if (pixGetColormap(pixs))
160
0
        return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL);
161
0
    if (pixd && (pixd != pixs))
162
0
        return (PIX *)ERROR_PTR("if pixd, must be in-place", __func__, NULL);
163
0
    if (!boxa)
164
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
165
0
    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
166
0
        return (PIX *)ERROR_PTR("invalid op", __func__, NULL);
167
168
0
    pixd = pixCopy(pixd, pixs);
169
0
    if ((n = boxaGetCount(boxa)) == 0) {
170
0
        L_WARNING("no boxes to mask\n", __func__);
171
0
        return pixd;
172
0
    }
173
174
0
    for (i = 0; i < n; i++) {
175
0
        box = boxaGetBox(boxa, i, L_CLONE);
176
0
        boxGetGeometry(box, &x, &y, &w, &h);
177
0
        if (op == L_SET_PIXELS)
178
0
            pixRasterop(pixd, x, y, w, h, PIX_SET, NULL, 0, 0);
179
0
        else if (op == L_CLEAR_PIXELS)
180
0
            pixRasterop(pixd, x, y, w, h, PIX_CLR, NULL, 0, 0);
181
0
        else  /* op == L_FLIP_PIXELS */
182
0
            pixRasterop(pixd, x, y, w, h, PIX_NOT(PIX_DST), NULL, 0, 0);
183
0
        boxDestroy(&box);
184
0
    }
185
186
0
    return pixd;
187
0
}
188
189
190
/*!
191
 * \brief   pixPaintBoxa()
192
 *
193
 * \param[in]    pixs    any depth, can be cmapped
194
 * \param[in]    boxa    of boxes, to paint
195
 * \param[in]    val     rgba color to paint
196
 * \return  pixd with painted boxes, or NULL on error
197
 *
198
 * <pre>
199
 * Notes:
200
 *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
201
 *          and the boxa is painted using a colormap; otherwise,
202
 *          it is converted to 32 bpp rgb.
203
 *      (2) There are several ways to display a box on an image:
204
 *            * Paint it as a solid color
205
 *            * Draw the outline
206
 *            * Blend the outline or region with the existing image
207
 *          We provide painting and drawing here; blending is in blend.c.
208
 *          When painting or drawing, the result can be either a
209
 *          cmapped image or an rgb image.  The dest will be cmapped
210
 *          if the src is either 1 bpp or has a cmap that is not full.
211
 *          To force RGB output, use pixConvertTo8(pixs, FALSE)
212
 *          before calling any of these paint and draw functions.
213
 * </pre>
214
 */
215
PIX *
216
pixPaintBoxa(PIX      *pixs,
217
             BOXA     *boxa,
218
             l_uint32  val)
219
0
{
220
0
l_int32   i, n, d, rval, gval, bval, newindex;
221
0
l_int32   mapvacancy;   /* true only if cmap and not full */
222
0
BOX      *box;
223
0
PIX      *pixd;
224
0
PIXCMAP  *cmap;
225
226
0
    if (!pixs)
227
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
228
0
    if (!boxa)
229
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
230
231
0
    if ((n = boxaGetCount(boxa)) == 0) {
232
0
        L_WARNING("no boxes to paint; returning a copy\n", __func__);
233
0
        return pixCopy(NULL, pixs);
234
0
    }
235
236
0
    mapvacancy = FALSE;
237
0
    if ((cmap = pixGetColormap(pixs)) != NULL) {
238
0
        if (pixcmapGetCount(cmap) < 256)
239
0
            mapvacancy = TRUE;
240
0
    }
241
0
    if (pixGetDepth(pixs) == 1 || mapvacancy)
242
0
        pixd = pixConvertTo8(pixs, TRUE);
243
0
    else
244
0
        pixd = pixConvertTo32(pixs);
245
0
    if (!pixd)
246
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
247
248
0
    d = pixGetDepth(pixd);
249
0
    if (d == 8) {  /* colormapped */
250
0
        cmap = pixGetColormap(pixd);
251
0
        extractRGBValues(val, &rval, &gval, &bval);
252
0
        if (pixcmapAddNewColor(cmap, rval, gval, bval, &newindex)) {
253
0
            pixDestroy(&pixd);
254
0
            return (PIX *)ERROR_PTR("cmap full; can't add", __func__, NULL);
255
0
        }
256
0
    }
257
258
0
    for (i = 0; i < n; i++) {
259
0
        box = boxaGetBox(boxa, i, L_CLONE);
260
0
        if (d == 8)
261
0
            pixSetInRectArbitrary(pixd, box, newindex);
262
0
        else
263
0
            pixSetInRectArbitrary(pixd, box, val);
264
0
        boxDestroy(&box);
265
0
    }
266
267
0
    return pixd;
268
0
}
269
270
271
/*!
272
 * \brief   pixSetBlackOrWhiteBoxa()
273
 *
274
 * \param[in]    pixs    any depth, can be cmapped
275
 * \param[in]    boxa    [optional] of boxes, to clear or set
276
 * \param[in]    op      L_SET_BLACK, L_SET_WHITE
277
 * \return  pixd with boxes filled with white or black, or NULL on error
278
 */
279
PIX *
280
pixSetBlackOrWhiteBoxa(PIX     *pixs,
281
                       BOXA    *boxa,
282
                       l_int32  op)
283
0
{
284
0
l_int32   i, n, d, index;
285
0
l_uint32  color;
286
0
BOX      *box;
287
0
PIX      *pixd;
288
0
PIXCMAP  *cmap;
289
290
0
    if (!pixs)
291
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
292
0
    if (!boxa)
293
0
        return pixCopy(NULL, pixs);
294
0
    if ((n = boxaGetCount(boxa)) == 0)
295
0
        return pixCopy(NULL, pixs);
296
297
0
    pixd = pixCopy(NULL, pixs);
298
0
    d = pixGetDepth(pixd);
299
0
    if (d == 1) {
300
0
        for (i = 0; i < n; i++) {
301
0
            box = boxaGetBox(boxa, i, L_CLONE);
302
0
            if (op == L_SET_WHITE)
303
0
                pixClearInRect(pixd, box);
304
0
            else
305
0
                pixSetInRect(pixd, box);
306
0
            boxDestroy(&box);
307
0
        }
308
0
        return pixd;
309
0
    }
310
311
0
    cmap = pixGetColormap(pixs);
312
0
    if (cmap) {
313
0
        color = (op == L_SET_WHITE) ? 1 : 0;
314
0
        pixcmapAddBlackOrWhite(cmap, color, &index);
315
0
    } else if (d == 8) {
316
0
        color = (op == L_SET_WHITE) ? 0xff : 0x0;
317
0
    } else if (d == 32) {
318
0
        color = (op == L_SET_WHITE) ? 0xffffff00 : 0x0;
319
0
    } else if (d == 2) {
320
0
        color = (op == L_SET_WHITE) ? 0x3 : 0x0;
321
0
    } else if (d == 4) {
322
0
        color = (op == L_SET_WHITE) ? 0xf : 0x0;
323
0
    } else if (d == 16) {
324
0
        color = (op == L_SET_WHITE) ? 0xffff : 0x0;
325
0
    } else {
326
0
        pixDestroy(&pixd);
327
0
        return (PIX *)ERROR_PTR("invalid depth", __func__, NULL);
328
0
    }
329
330
0
    for (i = 0; i < n; i++) {
331
0
        box = boxaGetBox(boxa, i, L_CLONE);
332
0
        if (cmap)
333
0
            pixSetInRectArbitrary(pixd, box, index);
334
0
        else
335
0
            pixSetInRectArbitrary(pixd, box, color);
336
0
        boxDestroy(&box);
337
0
    }
338
339
0
    return pixd;
340
0
}
341
342
343
/*!
344
 * \brief   pixPaintBoxaRandom()
345
 *
346
 * \param[in]    pixs    any depth, can be cmapped
347
 * \param[in]    boxa    of boxes, to paint
348
 * \return  pixd with painted boxes, or NULL on error
349
 *
350
 * <pre>
351
 * Notes:
352
 *      (1) If pixs is 1 bpp, we paint the boxa using a colormap;
353
 *          otherwise, we convert to 32 bpp.
354
 *      (2) We use up to 254 different colors for painting the regions.
355
 *      (3) If boxes overlap, the later ones paint over earlier ones.
356
 * </pre>
357
 */
358
PIX *
359
pixPaintBoxaRandom(PIX   *pixs,
360
                   BOXA  *boxa)
361
0
{
362
0
l_int32   i, n, d, rval, gval, bval, index;
363
0
l_uint32  val;
364
0
BOX      *box;
365
0
PIX      *pixd;
366
0
PIXCMAP  *cmap;
367
368
0
    if (!pixs)
369
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
370
0
    if (!boxa)
371
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
372
373
0
    if ((n = boxaGetCount(boxa)) == 0) {
374
0
        L_WARNING("no boxes to paint; returning a copy\n", __func__);
375
0
        return pixCopy(NULL, pixs);
376
0
    }
377
378
0
    if (pixGetDepth(pixs) == 1)
379
0
        pixd = pixConvert1To8(NULL, pixs, 255, 0);
380
0
    else
381
0
        pixd = pixConvertTo32(pixs);
382
0
    if (!pixd)
383
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
384
385
0
    cmap = pixcmapCreateRandom(8, 1, 1);
386
0
    d = pixGetDepth(pixd);  /* either 8 or 32 */
387
0
    if (d == 8)  /* colormapped */
388
0
        pixSetColormap(pixd, cmap);
389
390
0
    for (i = 0; i < n; i++) {
391
0
        box = boxaGetBox(boxa, i, L_CLONE);
392
0
        index = 1 + (i % 254);
393
0
        if (d == 8) {
394
0
            pixSetInRectArbitrary(pixd, box, index);
395
0
        } else {  /* d == 32 */
396
0
            pixcmapGetColor(cmap, index, &rval, &gval, &bval);
397
0
            composeRGBPixel(rval, gval, bval, &val);
398
0
            pixSetInRectArbitrary(pixd, box, val);
399
0
        }
400
0
        boxDestroy(&box);
401
0
    }
402
403
0
    if (d == 32)
404
0
        pixcmapDestroy(&cmap);
405
0
    return pixd;
406
0
}
407
408
409
/*!
410
 * \brief   pixBlendBoxaRandom()
411
 *
412
 * \param[in]    pixs    any depth; can be cmapped
413
 * \param[in]    boxa    of boxes, to blend/paint
414
 * \param[in]    fract   of box color to use
415
 * \return  pixd 32 bpp, with blend/painted boxes, or NULL on error
416
 *
417
 * <pre>
418
 * Notes:
419
 *      (1) pixs is converted to 32 bpp.
420
 *      (2) This differs from pixPaintBoxaRandom(), in that the
421
 *          colors here are blended with the color of pixs.
422
 *      (3) We use up to 254 different colors for painting the regions.
423
 *      (4) If boxes overlap, the final color depends only on the last
424
 *          rect that is used.
425
 * </pre>
426
 */
427
PIX *
428
pixBlendBoxaRandom(PIX       *pixs,
429
                   BOXA      *boxa,
430
                   l_float32  fract)
431
0
{
432
0
l_int32   i, n, rval, gval, bval, index;
433
0
l_uint32  val;
434
0
BOX      *box;
435
0
PIX      *pixd;
436
0
PIXCMAP  *cmap;
437
438
0
    if (!pixs)
439
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
440
0
    if (!boxa)
441
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
442
0
    if (fract < 0.0 || fract > 1.0) {
443
0
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__);
444
0
        fract = 0.5;
445
0
    }
446
447
0
    if ((n = boxaGetCount(boxa)) == 0) {
448
0
        L_WARNING("no boxes to paint; returning a copy\n", __func__);
449
0
        return pixCopy(NULL, pixs);
450
0
    }
451
452
0
    if ((pixd = pixConvertTo32(pixs)) == NULL)
453
0
        return (PIX *)ERROR_PTR("pixd not defined", __func__, NULL);
454
455
0
    cmap = pixcmapCreateRandom(8, 1, 1);
456
0
    for (i = 0; i < n; i++) {
457
0
        box = boxaGetBox(boxa, i, L_CLONE);
458
0
        index = 1 + (i % 254);
459
0
        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
460
0
        composeRGBPixel(rval, gval, bval, &val);
461
0
        pixBlendInRect(pixd, box, val, fract);
462
0
        boxDestroy(&box);
463
0
    }
464
465
0
    pixcmapDestroy(&cmap);
466
0
    return pixd;
467
0
}
468
469
470
/*!
471
 * \brief   pixDrawBoxa()
472
 *
473
 * \param[in]    pixs    any depth; can be cmapped
474
 * \param[in]    boxa    of boxes, to draw
475
 * \param[in]    width   of lines
476
 * \param[in]    val     rgba color to draw
477
 * \return  pixd with outlines of boxes added, or NULL on error
478
 *
479
 * <pre>
480
 * Notes:
481
 *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
482
 *          and the boxa is drawn using a colormap; otherwise,
483
 *          it is converted to 32 bpp rgb.
484
 * </pre>
485
 */
486
PIX *
487
pixDrawBoxa(PIX      *pixs,
488
            BOXA     *boxa,
489
            l_int32   width,
490
            l_uint32  val)
491
0
{
492
0
l_int32   rval, gval, bval, newindex;
493
0
l_int32   mapvacancy;   /* true only if cmap and not full */
494
0
PIX      *pixd;
495
0
PIXCMAP  *cmap;
496
497
0
    if (!pixs)
498
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
499
0
    if (!boxa)
500
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
501
0
    if (width < 1)
502
0
        return (PIX *)ERROR_PTR("width must be >= 1", __func__, NULL);
503
504
0
    if (boxaGetCount(boxa) == 0) {
505
0
        L_WARNING("no boxes to draw; returning a copy\n", __func__);
506
0
        return pixCopy(NULL, pixs);
507
0
    }
508
509
0
    mapvacancy = FALSE;
510
0
    if ((cmap = pixGetColormap(pixs)) != NULL) {
511
0
        if (pixcmapGetCount(cmap) < 256)
512
0
            mapvacancy = TRUE;
513
0
    }
514
0
    if (pixGetDepth(pixs) == 1 || mapvacancy)
515
0
        pixd = pixConvertTo8(pixs, TRUE);
516
0
    else
517
0
        pixd = pixConvertTo32(pixs);
518
0
    if (!pixd)
519
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
520
521
0
    extractRGBValues(val, &rval, &gval, &bval);
522
0
    if (pixGetDepth(pixd) == 8) {  /* colormapped */
523
0
        cmap = pixGetColormap(pixd);
524
0
        pixcmapAddNewColor(cmap, rval, gval, bval, &newindex);
525
0
    }
526
527
0
    pixRenderBoxaArb(pixd, boxa, width, rval, gval, bval);
528
0
    return pixd;
529
0
}
530
531
532
/*!
533
 * \brief   pixDrawBoxaRandom()
534
 *
535
 * \param[in]    pixs     any depth, can be cmapped
536
 * \param[in]    boxa     of boxes, to draw
537
 * \param[in]    width    thickness of line
538
 * \return  pixd with box outlines drawn, or NULL on error
539
 *
540
 * <pre>
541
 * Notes:
542
 *      (1) If pixs is 1 bpp, we draw the boxa using a colormap;
543
 *          otherwise, we convert to 32 bpp.
544
 *      (2) We use up to 254 different colors for drawing the boxes.
545
 *      (3) If boxes overlap, the later ones draw over earlier ones.
546
 * </pre>
547
 */
548
PIX *
549
pixDrawBoxaRandom(PIX     *pixs,
550
                  BOXA    *boxa,
551
                  l_int32  width)
552
0
{
553
0
l_int32   i, n, rval, gval, bval, index;
554
0
BOX      *box;
555
0
PIX      *pixd;
556
0
PIXCMAP  *cmap;
557
0
PTAA     *ptaa;
558
559
0
    if (!pixs)
560
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
561
0
    if (!boxa)
562
0
        return (PIX *)ERROR_PTR("boxa not defined", __func__, NULL);
563
0
    if (width < 1)
564
0
        return (PIX *)ERROR_PTR("width must be >= 1", __func__, NULL);
565
566
0
    if ((n = boxaGetCount(boxa)) == 0) {
567
0
        L_WARNING("no boxes to draw; returning a copy\n", __func__);
568
0
        return pixCopy(NULL, pixs);
569
0
    }
570
571
        /* Input depth = 1 bpp; generate cmapped output */
572
0
    if (pixGetDepth(pixs) == 1) {
573
0
        ptaa = generatePtaaBoxa(boxa);
574
0
        pixd = pixRenderRandomCmapPtaa(pixs, ptaa, 1, width, 1);
575
0
        ptaaDestroy(&ptaa);
576
0
        return pixd;
577
0
    }
578
579
        /* Generate rgb output */
580
0
    pixd = pixConvertTo32(pixs);
581
0
    cmap = pixcmapCreateRandom(8, 1, 1);
582
0
    for (i = 0; i < n; i++) {
583
0
        box = boxaGetBox(boxa, i, L_CLONE);
584
0
        index = 1 + (i % 254);
585
0
        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
586
0
        pixRenderBoxArb(pixd, box, width, rval, gval, bval);
587
0
        boxDestroy(&box);
588
0
    }
589
0
    pixcmapDestroy(&cmap);
590
0
    return pixd;
591
0
}
592
593
594
/*!
595
 * \brief   boxaaDisplay()
596
 *
597
 * \param[in]    pixs     [optional] 1 bpp
598
 * \param[in]    baa      boxaa, typically from a 2d sort
599
 * \param[in]    linewba  line width to display outline of each boxa
600
 * \param[in]    linewb   line width to display outline of each box
601
 * \param[in]    colorba  color to display boxa
602
 * \param[in]    colorb   color to display box
603
 * \param[in]    w    width of output pix; use 0 if determined by %pixs or %baa
604
 * \param[in]    h    height of output pix; use 0 if determined by %pixs or %baa
605
 * \return  0 if OK, 1 on error
606
 *
607
 * <pre>
608
 * Notes:
609
 *      (1) If %pixs exists, this renders the boxes over an 8 bpp version
610
 *          of it.  Otherwise, it renders the boxes over an empty image
611
 *          with a white background.
612
 *      (2) If %pixs exists, the dimensions of %pixd are the same,
613
 *          and input values of %w and %h are ignored.
614
 *          If %pixs is NULL, the dimensions of %pixd are determined by
615
 *            - %w and %h if both are > 0, or
616
 *            - the minimum size required using all boxes in %baa.
617
 *
618
 * </pre>
619
 */
620
PIX *
621
boxaaDisplay(PIX      *pixs,
622
             BOXAA    *baa,
623
             l_int32   linewba,
624
             l_int32   linewb,
625
             l_uint32  colorba,
626
             l_uint32  colorb,
627
             l_int32   w,
628
             l_int32   h)
629
0
{
630
0
l_int32   i, j, n, m, rbox, gbox, bbox, rboxa, gboxa, bboxa;
631
0
BOX      *box;
632
0
BOXA     *boxa;
633
0
PIX      *pixd;
634
0
PIXCMAP  *cmap;
635
636
0
    if (!baa)
637
0
        return (PIX *)ERROR_PTR("baa not defined", __func__, NULL);
638
639
0
    if (w <= 0 || h <= 0) {
640
0
        if (pixs)
641
0
            pixGetDimensions(pixs, &w, &h, NULL);
642
0
        else
643
0
            boxaaGetExtent(baa, &w, &h, NULL, NULL);
644
0
    }
645
646
0
    if (pixs) {
647
0
        pixd = pixConvertTo8(pixs, 1);
648
0
        cmap = pixGetColormap(pixd);
649
0
    } else {
650
0
        pixd = pixCreate(w, h, 8);
651
0
        cmap = pixcmapCreate(8);
652
0
        pixSetColormap(pixd, cmap);
653
0
        pixcmapAddColor(cmap, 255, 255, 255);
654
0
    }
655
0
    extractRGBValues(colorb, &rbox, &gbox, &bbox);
656
0
    extractRGBValues(colorba, &rboxa, &gboxa, &bboxa);
657
0
    pixcmapAddColor(cmap, rbox, gbox, bbox);
658
0
    pixcmapAddColor(cmap, rboxa, gboxa, bboxa);
659
660
0
    n = boxaaGetCount(baa);
661
0
    for (i = 0; i < n; i++) {
662
0
        boxa = boxaaGetBoxa(baa, i, L_CLONE);
663
0
        boxaGetExtent(boxa, NULL, NULL, &box);
664
0
        pixRenderBoxArb(pixd, box, linewba, rboxa, gboxa, bboxa);
665
0
        boxDestroy(&box);
666
0
        m = boxaGetCount(boxa);
667
0
        for (j = 0; j < m; j++) {
668
0
            box = boxaGetBox(boxa, j, L_CLONE);
669
0
            pixRenderBoxArb(pixd, box, linewb, rbox, gbox, bbox);
670
0
            boxDestroy(&box);
671
0
        }
672
0
        boxaDestroy(&boxa);
673
0
    }
674
675
0
    return pixd;
676
0
}
677
678
679
/*!
680
 * \brief   pixaDisplayBoxaa()
681
 *
682
 * \param[in]    pixas       any depth, can be cmapped
683
 * \param[in]    baa         boxes to draw on input pixa
684
 * \param[in]    colorflag   L_DRAW_RED, L_DRAW_GREEN, etc
685
 * \param[in]    width       thickness of lines
686
 * \return  pixa with box outlines drawn on each pix, or NULL on error
687
 *
688
 * <pre>
689
 * Notes:
690
 *      (1) All pix in %pixas that are not rgb are converted to rgb.
691
 *      (2) Each boxa in %baa contains boxes that will be drawn on
692
 *          the corresponding pix in %pixas.
693
 *      (3) The color of the boxes drawn on each pix are selected with
694
 *          %colorflag:
695
 *            * For red, green or blue: use L_DRAW_RED, etc.
696
 *            * For sequential r, g, b: use L_DRAW_RGB
697
 *            * For random colors: use L_DRAW_RANDOM
698
 * </pre>
699
 */
700
PIXA *
701
pixaDisplayBoxaa(PIXA    *pixas,
702
                 BOXAA   *baa,
703
                 l_int32  colorflag,
704
                 l_int32  width)
705
0
{
706
0
l_int32    i, j, nba, n, nbox, rval, gval, bval;
707
0
l_uint32   color;
708
0
l_uint32   colors[255];
709
0
BOXA      *boxa;
710
0
BOX       *box;
711
0
PIX       *pix;
712
0
PIXA      *pixad;
713
714
0
    if (!pixas)
715
0
        return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL);
716
0
    if (!baa)
717
0
        return (PIXA *)ERROR_PTR("baa not defined", __func__, NULL);
718
0
    if (width < 1)
719
0
        return (PIXA *)ERROR_PTR("width must be >= 1", __func__, NULL);
720
0
    if ((nba = boxaaGetCount(baa)) < 1)
721
0
        return (PIXA *)ERROR_PTR("no boxa in baa", __func__, NULL);
722
0
    if ((n = pixaGetCount(pixas)) == 0)
723
0
        return (PIXA *)ERROR_PTR("no pix in pixas", __func__, NULL);
724
0
    if (n != nba)
725
0
        return (PIXA *)ERROR_PTR("num pix != num boxa", __func__, NULL);
726
0
    if (colorflag == L_DRAW_RED)
727
0
        color = 0xff000000;
728
0
    else if (colorflag == L_DRAW_GREEN)
729
0
        color = 0x00ff0000;
730
0
    else if (colorflag == L_DRAW_BLUE)
731
0
        color = 0x0000ff00;
732
0
    else if (colorflag == L_DRAW_RGB)
733
0
        color = 0x000000ff;
734
0
    else if (colorflag == L_DRAW_RANDOM)
735
0
        color = 0x00000000;
736
0
    else
737
0
        return (PIXA *)ERROR_PTR("invalid colorflag", __func__, NULL);
738
739
0
    if (colorflag == L_DRAW_RED || colorflag == L_DRAW_GREEN ||
740
0
        colorflag == L_DRAW_BLUE) {
741
0
        for (i = 0; i < 255; i++)
742
0
            colors[i] = color;
743
0
    } else if (colorflag == L_DRAW_RGB) {
744
0
        for (i = 0; i < 255; i++) {
745
0
            if (i % 3 == L_DRAW_RED)
746
0
                colors[i] = 0xff000000;
747
0
            else if (i % 3 == L_DRAW_GREEN)
748
0
                colors[i] = 0x00ff0000;
749
0
            else  /* i % 3 == L_DRAW_BLUE) */
750
0
                colors[i] = 0x0000ff00;
751
0
        }
752
0
    } else if (colorflag == L_DRAW_RANDOM) {
753
0
        for (i = 0; i < 255; i++) {
754
0
            rval = (l_uint32)rand() & 0xff;
755
0
            gval = (l_uint32)rand() & 0xff;
756
0
            bval = (l_uint32)rand() & 0xff;
757
0
            composeRGBPixel(rval, gval, bval, &colors[i]);
758
0
        }
759
0
    }
760
761
0
    pixad = pixaCreate(n);
762
0
    for (i = 0; i < n; i++) {
763
0
        pix = pixaGetPix(pixas, i, L_COPY);
764
0
        boxa = boxaaGetBoxa(baa, i, L_CLONE);
765
0
        nbox = boxaGetCount(boxa);
766
0
        for (j = 0; j < nbox; j++) {
767
0
            box = boxaGetBox(boxa, j, L_CLONE);
768
0
            extractRGBValues(colors[j % 255], &rval, &gval, &bval);
769
0
            pixRenderBoxArb(pix, box, width, rval, gval, bval);
770
0
            boxDestroy(&box);
771
0
        }
772
0
        boxaDestroy(&boxa);
773
0
        pixaAddPix(pixad, pix, L_INSERT);
774
0
    }
775
776
0
    return pixad;
777
0
}
778
779
780
/*---------------------------------------------------------------------*
781
 *                   Split mask components into Boxa                   *
782
 *---------------------------------------------------------------------*/
783
/*!
784
 * \brief   pixSplitIntoBoxa()
785
 *
786
 * \param[in]    pixs       1 bpp
787
 * \param[in]    minsum     minimum pixels to trigger propagation
788
 * \param[in]    skipdist   distance before computing sum for propagation
789
 * \param[in]    delta      difference required to stop propagation
790
 * \param[in]    maxbg      maximum number of allowed bg pixels in ref scan
791
 * \param[in]    maxcomps   use 0 for unlimited number of subdivided components
792
 * \param[in]    remainder  set to 1 to get b.b. of remaining stuff
793
 * \return  boxa of rectangles covering the fg of pixs, or NULL on error
794
 *
795
 * <pre>
796
 * Notes:
797
 *      (1) This generates a boxa of rectangles that covers
798
 *          the fg of a mask.  For each 8-connected component in pixs,
799
 *          it does a greedy partitioning, choosing the largest
800
 *          rectangle found from each of the four directions at each iter.
801
 *          See pixSplitComponentIntoBoxa() for details.
802
 *      (2) The input parameters give some flexibility for boundary
803
 *          noise.  The resulting set of rectangles may cover some
804
 *          bg pixels.
805
 *      (3) This should be used when there are a small number of
806
 *          mask components, each of which has sides that are close
807
 *          to horizontal and vertical.  The input parameters %delta
808
 *          and %maxbg determine whether or not holes in the mask are covered.
809
 *      (4) The parameter %maxcomps gives the maximum number of allowed
810
 *          rectangles extracted from any single connected component.
811
 *          Use 0 if no limit is to be applied.
812
 *      (5) The flag %remainder specifies whether we take a final bounding
813
 *          box for anything left after the maximum number of allowed
814
 *          rectangle is extracted.
815
 * </pre>
816
 */
817
BOXA *
818
pixSplitIntoBoxa(PIX     *pixs,
819
                 l_int32  minsum,
820
                 l_int32  skipdist,
821
                 l_int32  delta,
822
                 l_int32  maxbg,
823
                 l_int32  maxcomps,
824
                 l_int32  remainder)
825
0
{
826
0
l_int32  i, n;
827
0
BOX     *box;
828
0
BOXA    *boxa, *boxas, *boxad;
829
0
PIX     *pix;
830
0
PIXA    *pixas;
831
832
0
    if (!pixs || pixGetDepth(pixs) != 1)
833
0
        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
834
835
0
    boxas = pixConnComp(pixs, &pixas, 8);
836
0
    n = boxaGetCount(boxas);
837
0
    boxad = boxaCreate(0);
838
0
    for (i = 0; i < n; i++) {
839
0
        pix = pixaGetPix(pixas, i, L_CLONE);
840
0
        box = boxaGetBox(boxas, i, L_CLONE);
841
0
        boxa = pixSplitComponentIntoBoxa(pix, box, minsum, skipdist,
842
0
                                         delta, maxbg, maxcomps, remainder);
843
0
        boxaJoin(boxad, boxa, 0, -1);
844
0
        pixDestroy(&pix);
845
0
        boxDestroy(&box);
846
0
        boxaDestroy(&boxa);
847
0
    }
848
849
0
    pixaDestroy(&pixas);
850
0
    boxaDestroy(&boxas);
851
0
    return boxad;
852
0
}
853
854
855
/*!
856
 * \brief   pixSplitComponentIntoBoxa()
857
 *
858
 * \param[in]    pix        1 bpp
859
 * \param[in]    box        [optional] location of pix w/rt an origin
860
 * \param[in]    minsum     minimum pixels to trigger propagation
861
 * \param[in]    skipdist   distance before computing sum for propagation
862
 * \param[in]    delta      difference required to stop propagation
863
 * \param[in]    maxbg      maximum number of allowed bg pixels in ref scan
864
 * \param[in]    maxcomps   use 0 for unlimited number of subdivided components
865
 * \param[in]    remainder  set to 1 to get b.b. of remaining stuff
866
 * \return  boxa of rectangles covering the fg of pix, or NULL on error
867
 *
868
 * <pre>
869
 * Notes:
870
 *      (1) This generates a boxa of rectangles that covers
871
 *          the fg of a mask.  It does so by a greedy partitioning of
872
 *          the mask, choosing the largest rectangle found from
873
 *          each of the four directions at each step.
874
 *      (2) The input parameters give some flexibility for boundary
875
 *          noise.  The resulting set of rectangles must cover all
876
 *          the fg pixels and, in addition, may cover some bg pixels.
877
 *          Using small input parameters on a noiseless mask (i.e., one
878
 *          that has only large vertical and horizontal edges) will
879
 *          result in a proper covering of only the fg pixels of the mask.
880
 *      (3) The input is assumed to be a single connected component, that
881
 *          may have holes.  From each side, sweep inward, counting
882
 *          the pixels.  If the count becomes greater than %minsum,
883
 *          and we have moved forward a further amount %skipdist,
884
 *          record that count ('countref'), but don't accept if the scan
885
 *          contains more than %maxbg bg pixels.  Continue the scan
886
 *          until we reach a count that differs from countref by at
887
 *          least %delta, at which point the propagation stops.  The box
888
 *          swept out gets a score, which is the sum of fg pixels
889
 *          minus a penalty.  The penalty is the number of bg pixels
890
 *          in the box.  This is done from all four sides, and the
891
 *          side with the largest score is saved as a rectangle.
892
 *          The process repeats until there is either no rectangle
893
 *          left, or there is one that can't be captured from any
894
 *          direction.  For the latter case, we simply accept the
895
 *          last rectangle.
896
 *      (4) The input box is only used to specify the location of
897
 *          the UL corner of pix, with respect to an origin that
898
 *          typically represents the UL corner of an underlying image,
899
 *          of which pix is one component.  If %box is null,
900
 *          the UL corner is taken to be (0, 0).
901
 *      (5) The parameter %maxcomps gives the maximum number of allowed
902
 *          rectangles extracted from any single connected component.
903
 *          Use 0 if no limit is to be applied.
904
 *      (6) The flag %remainder specifies whether we take a final bounding
905
 *          box for anything left after the maximum number of allowed
906
 *          rectangle is extracted.
907
 *      (7) So if %maxcomps > 0, it specifies that we want no more than
908
 *          the first %maxcomps rectangles that satisfy the input
909
 *          criteria.  After this, we can get a final rectangle that
910
 *          bounds everything left over by setting %remainder == 1.
911
 *          If %remainder == 0, we only get rectangles that satisfy
912
 *          the input criteria.
913
 *      (8) It should be noted that the removal of rectangles can
914
 *          break the original c.c. into several c.c.
915
 *      (9) Summing up:
916
 *            * If %maxcomp == 0, the splitting proceeds as far as possible.
917
 *            * If %maxcomp > 0, the splitting stops when %maxcomps are
918
 *                found, or earlier if no more components can be selected.
919
 *            * If %remainder == 1 and components remain that cannot be
920
 *                selected, they are returned as a single final rectangle;
921
 *                otherwise, they are ignored.
922
 * </pre>
923
 */
924
BOXA *
925
pixSplitComponentIntoBoxa(PIX     *pix,
926
                          BOX     *box,
927
                          l_int32  minsum,
928
                          l_int32  skipdist,
929
                          l_int32  delta,
930
                          l_int32  maxbg,
931
                          l_int32  maxcomps,
932
                          l_int32  remainder)
933
0
{
934
0
l_int32  i, w, h, boxx, boxy, bx, by, bw, bh, maxdir, maxscore;
935
0
l_int32  iter;
936
0
BOX     *boxs;  /* shrinks as rectangular regions are removed */
937
0
BOX     *boxt1, *boxt2, *boxt3;
938
0
BOXA    *boxat;  /* stores rectangle data for each side in an iteration */
939
0
BOXA    *boxad;
940
0
NUMA    *nascore, *nas;
941
0
PIX     *pixs;
942
943
0
    if (!pix || pixGetDepth(pix) != 1)
944
0
        return (BOXA *)ERROR_PTR("pix undefined or not 1 bpp", __func__, NULL);
945
946
0
    pixs = pixCopy(NULL, pix);
947
0
    pixGetDimensions(pixs, &w, &h, NULL);
948
0
    if (box)
949
0
        boxGetGeometry(box, &boxx, &boxy, NULL, NULL);
950
0
    else
951
0
        boxx = boxy = 0;
952
0
    boxs = boxCreate(0, 0, w, h);
953
0
    boxad = boxaCreate(0);
954
955
0
    iter = 0;
956
0
    while (boxs != NULL) {
957
0
        boxGetGeometry(boxs, &bx, &by, &bw, &bh);
958
0
        boxat = boxaCreate(4);  /* potential rectangular regions */
959
0
        nascore = numaCreate(4);
960
0
        for (i = 0; i < 4; i++) {
961
0
            pixSearchForRectangle(pixs, boxs, minsum, skipdist, delta, maxbg,
962
0
                                  i, boxat, nascore);
963
0
        }
964
0
        nas = numaGetSortIndex(nascore, L_SORT_DECREASING);
965
0
        numaGetIValue(nas, 0, &maxdir);
966
0
        numaGetIValue(nascore, maxdir, &maxscore);
967
#if  DEBUG_SPLIT
968
        lept_stderr("Iteration: %d\n", iter);
969
        boxPrintStreamInfo(stderr, boxs);
970
        boxaWriteStderr(boxat);
971
        lept_stderr("\nmaxdir = %d, maxscore = %d\n\n", maxdir, maxscore);
972
#endif  /* DEBUG_SPLIT */
973
0
        if (maxscore > 0) {  /* accept this */
974
0
            boxt1 = boxaGetBox(boxat, maxdir, L_CLONE);
975
0
            boxt2 = boxTransform(boxt1, boxx, boxy, 1.0, 1.0);
976
0
            boxaAddBox(boxad, boxt2, L_INSERT);
977
0
            pixClearInRect(pixs, boxt1);
978
0
            boxDestroy(&boxt1);
979
0
            pixClipBoxToForeground(pixs, boxs, NULL, &boxt3);
980
0
            boxDestroy(&boxs);
981
0
            boxs = boxt3;
982
0
            if (boxs) {
983
0
                boxGetGeometry(boxs, NULL, NULL, &bw, &bh);
984
0
                if (bw < 2 || bh < 2)
985
0
                    boxDestroy(&boxs);  /* we're done */
986
0
            }
987
0
        } else {  /* no more valid rectangles can be found */
988
0
            if (remainder == 1) {  /* save the last box */
989
0
                boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0);
990
0
                boxaAddBox(boxad, boxt1, L_INSERT);
991
0
            }
992
0
            boxDestroy(&boxs);  /* we're done */
993
0
        }
994
0
        boxaDestroy(&boxat);
995
0
        numaDestroy(&nascore);
996
0
        numaDestroy(&nas);
997
998
0
        iter++;
999
0
        if ((iter == maxcomps) && boxs) {
1000
0
            if (remainder == 1) {  /* save the last box */
1001
0
                boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0);
1002
0
                boxaAddBox(boxad, boxt1, L_INSERT);
1003
0
            }
1004
0
            boxDestroy(&boxs);  /* we're done */
1005
0
        }
1006
0
    }
1007
1008
0
    pixDestroy(&pixs);
1009
0
    return boxad;
1010
0
}
1011
1012
1013
/*!
1014
 * \brief   pixSearchForRectangle()
1015
 *
1016
 * \param[in]    pixs       1 bpp
1017
 * \param[in]    boxs       current region to investigate
1018
 * \param[in]    minsum     minimum pixels to trigger propagation
1019
 * \param[in]    skipdist   distance before computing sum for propagation
1020
 * \param[in]    delta      difference required to stop propagation
1021
 * \param[in]    maxbg      maximum number of allowed bg pixels in ref scan
1022
 * \param[in]    sideflag   side to search from
1023
 * \param[in]    boxat      add result of rectangular region found here
1024
 * \param[in]    nascore    add score for this rectangle here
1025
 * \return  0 if OK, 1 on error
1026
 *
1027
 * <pre>
1028
 * Notes:
1029
 *      (1) See pixSplitComponentIntoBoxa() for an explanation of the algorithm.
1030
 *          This does the sweep from a single side.  For each iteration
1031
 *          in pixSplitComponentIntoBoxa(), this will be called 4 times,
1032
 *          for %sideflag = {0, 1, 2, 3}.
1033
 *      (2) If a valid rectangle is not found, add a score of 0 and
1034
 *          input a minimum box.
1035
 * </pre>
1036
 */
1037
static l_int32
1038
pixSearchForRectangle(PIX     *pixs,
1039
                      BOX     *boxs,
1040
                      l_int32  minsum,
1041
                      l_int32  skipdist,
1042
                      l_int32  delta,
1043
                      l_int32  maxbg,
1044
                      l_int32  sideflag,
1045
                      BOXA    *boxat,
1046
                      NUMA    *nascore)
1047
0
{
1048
0
l_int32  bx, by, bw, bh, width, height, setref, atref;
1049
0
l_int32  minincol, maxincol, mininrow, maxinrow, minval, maxval, bgref;
1050
0
l_int32  x, y, x0, y0, xref, yref, colsum, rowsum, score, countref, diff;
1051
0
void   **lines1;
1052
0
BOX     *boxr;
1053
1054
0
    if (!pixs || pixGetDepth(pixs) != 1)
1055
0
        return ERROR_INT("pixs undefined or not 1 bpp", __func__, 1);
1056
0
    if (!boxs)
1057
0
        return ERROR_INT("boxs not defined", __func__, 1);
1058
0
    if (!boxat)
1059
0
        return ERROR_INT("boxat not defined", __func__, 1);
1060
0
    if (!nascore)
1061
0
        return ERROR_INT("nascore not defined", __func__, 1);
1062
1063
0
    lines1 = pixGetLinePtrs(pixs, NULL);
1064
0
    boxGetGeometry(boxs, &bx, &by, &bw, &bh);
1065
0
    boxr = NULL;
1066
0
    setref = 0;
1067
0
    atref = 0;
1068
0
    maxval = 0;
1069
0
    minval = 100000;
1070
0
    score = 0;  /* sum of all (fg - bg) pixels seen in the scan */
1071
0
    xref = yref = 100000;  /* init to impossibly big number */
1072
0
    if (sideflag == L_FROM_LEFT) {
1073
0
        for (x = bx; x < bx + bw; x++) {
1074
0
            colsum = 0;
1075
0
            maxincol = 0;
1076
0
            minincol = 100000;
1077
0
            for (y = by; y < by + bh; y++) {
1078
0
                if (GET_DATA_BIT(lines1[y], x)) {
1079
0
                    colsum++;
1080
0
                    if (y > maxincol) maxincol = y;
1081
0
                    if (y < minincol) minincol = y;
1082
0
                }
1083
0
            }
1084
0
            score += colsum;
1085
1086
                /* Enough fg to sweep out a rectangle? */
1087
0
            if (!setref && colsum >= minsum) {
1088
0
                setref = 1;
1089
0
                xref = x + 10;
1090
0
                if (xref >= bx + bw)
1091
0
                    goto failure;
1092
0
            }
1093
1094
                /* Reached the reference line; save the count;
1095
                 * if there is too much bg, the rectangle is invalid. */
1096
0
            if (setref && x == xref) {
1097
0
                atref = 1;
1098
0
                countref = colsum;
1099
0
                bgref = maxincol - minincol + 1 - countref;
1100
0
                if (bgref > maxbg)
1101
0
                    goto failure;
1102
0
            }
1103
1104
                /* Have we left the rectangle?  If so, save it along
1105
                 * with the score. */
1106
0
            if (atref) {
1107
0
                diff = L_ABS(colsum - countref);
1108
0
                if (diff >= delta || x == bx + bw - 1) {
1109
0
                    height = maxval - minval + 1;
1110
0
                    width = x - bx;
1111
0
                    if (x == bx + bw - 1) width = x - bx + 1;
1112
0
                    boxr = boxCreate(bx, minval, width, height);
1113
0
                    score = 2 * score - width * height;
1114
0
                    goto success;
1115
0
                }
1116
0
            }
1117
0
            maxval = L_MAX(maxval, maxincol);
1118
0
            minval = L_MIN(minval, minincol);
1119
0
        }
1120
0
        goto failure;
1121
0
    } else if (sideflag == L_FROM_RIGHT) {
1122
0
        for (x = bx + bw - 1; x >= bx; x--) {
1123
0
            colsum = 0;
1124
0
            maxincol = 0;
1125
0
            minincol = 100000;
1126
0
            for (y = by; y < by + bh; y++) {
1127
0
                if (GET_DATA_BIT(lines1[y], x)) {
1128
0
                    colsum++;
1129
0
                    if (y > maxincol) maxincol = y;
1130
0
                    if (y < minincol) minincol = y;
1131
0
                }
1132
0
            }
1133
0
            score += colsum;
1134
0
            if (!setref && colsum >= minsum) {
1135
0
                setref = 1;
1136
0
                xref = x - 10;
1137
0
                if (xref < bx)
1138
0
                    goto failure;
1139
0
            }
1140
0
            if (setref && x == xref) {
1141
0
                atref = 1;
1142
0
                countref = colsum;
1143
0
                bgref = maxincol - minincol + 1 - countref;
1144
0
                if (bgref > maxbg)
1145
0
                    goto failure;
1146
0
            }
1147
0
            if (atref) {
1148
0
                diff = L_ABS(colsum - countref);
1149
0
                if (diff >= delta || x == bx) {
1150
0
                    height = maxval - minval + 1;
1151
0
                    x0 = x + 1;
1152
0
                    if (x == bx) x0 = x;
1153
0
                    width = bx + bw - x0;
1154
0
                    boxr = boxCreate(x0, minval, width, height);
1155
0
                    score = 2 * score - width * height;
1156
0
                    goto success;
1157
0
                }
1158
0
            }
1159
0
            maxval = L_MAX(maxval, maxincol);
1160
0
            minval = L_MIN(minval, minincol);
1161
0
        }
1162
0
        goto failure;
1163
0
    } else if (sideflag == L_FROM_TOP) {
1164
0
        for (y = by; y < by + bh; y++) {
1165
0
            rowsum = 0;
1166
0
            maxinrow = 0;
1167
0
            mininrow = 100000;
1168
0
            for (x = bx; x < bx + bw; x++) {
1169
0
                if (GET_DATA_BIT(lines1[y], x)) {
1170
0
                    rowsum++;
1171
0
                    if (x > maxinrow) maxinrow = x;
1172
0
                    if (x < mininrow) mininrow = x;
1173
0
                }
1174
0
            }
1175
0
            score += rowsum;
1176
0
            if (!setref && rowsum >= minsum) {
1177
0
                setref = 1;
1178
0
                yref = y + 10;
1179
0
                if (yref >= by + bh)
1180
0
                    goto failure;
1181
0
            }
1182
0
            if (setref && y == yref) {
1183
0
                atref = 1;
1184
0
                countref = rowsum;
1185
0
                bgref = maxinrow - mininrow + 1 - countref;
1186
0
                if (bgref > maxbg)
1187
0
                    goto failure;
1188
0
            }
1189
0
            if (atref) {
1190
0
                diff = L_ABS(rowsum - countref);
1191
0
                if (diff >= delta || y == by + bh - 1) {
1192
0
                    width = maxval - minval + 1;
1193
0
                    height = y - by;
1194
0
                    if (y == by + bh - 1) height = y - by + 1;
1195
0
                    boxr = boxCreate(minval, by, width, height);
1196
0
                    score = 2 * score - width * height;
1197
0
                    goto success;
1198
0
                }
1199
0
            }
1200
0
            maxval = L_MAX(maxval, maxinrow);
1201
0
            minval = L_MIN(minval, mininrow);
1202
0
        }
1203
0
        goto failure;
1204
0
    } else if (sideflag == L_FROM_BOT) {
1205
0
        for (y = by + bh - 1; y >= by; y--) {
1206
0
            rowsum = 0;
1207
0
            maxinrow = 0;
1208
0
            mininrow = 100000;
1209
0
            for (x = bx; x < bx + bw; x++) {
1210
0
                if (GET_DATA_BIT(lines1[y], x)) {
1211
0
                    rowsum++;
1212
0
                    if (x > maxinrow) maxinrow = x;
1213
0
                    if (x < mininrow) mininrow = x;
1214
0
                }
1215
0
            }
1216
0
            score += rowsum;
1217
0
            if (!setref && rowsum >= minsum) {
1218
0
                setref = 1;
1219
0
                yref = y - 10;
1220
0
                if (yref < by)
1221
0
                    goto failure;
1222
0
            }
1223
0
            if (setref && y == yref) {
1224
0
                atref = 1;
1225
0
                countref = rowsum;
1226
0
                bgref = maxinrow - mininrow + 1 - countref;
1227
0
                if (bgref > maxbg)
1228
0
                    goto failure;
1229
0
            }
1230
0
            if (atref) {
1231
0
                diff = L_ABS(rowsum - countref);
1232
0
                if (diff >= delta || y == by) {
1233
0
                    width = maxval - minval + 1;
1234
0
                    y0 = y + 1;
1235
0
                    if (y == by) y0 = y;
1236
0
                    height = by + bh - y0;
1237
0
                    boxr = boxCreate(minval, y0, width, height);
1238
0
                    score = 2 * score - width * height;
1239
0
                    goto success;
1240
0
                }
1241
0
            }
1242
0
            maxval = L_MAX(maxval, maxinrow);
1243
0
            minval = L_MIN(minval, mininrow);
1244
0
        }
1245
0
        goto failure;
1246
0
    }
1247
1248
0
failure:
1249
0
    numaAddNumber(nascore, 0);
1250
0
    boxaAddBox(boxat, boxCreate(0, 0, 1, 1), L_INSERT);  /* min box */
1251
0
    LEPT_FREE(lines1);
1252
0
    return 0;
1253
1254
0
success:
1255
0
    numaAddNumber(nascore, score);
1256
0
    boxaAddBox(boxat, boxr, L_INSERT);
1257
0
    LEPT_FREE(lines1);
1258
0
    return 0;
1259
0
}
1260
1261
1262
/*---------------------------------------------------------------------*
1263
 *             Represent horizontal or vertical mosaic strips          *
1264
 *---------------------------------------------------------------------*/
1265
/*!
1266
 * \brief   makeMosaicStrips()
1267
 *
1268
 * \param[in]    w, h
1269
 * \param[in]    direction    L_SCAN_HORIZONTAL or L_SCAN_VERTICAL
1270
 * \param[in]    size         of strips in the scan direction
1271
 * \return  boxa, or NULL on error
1272
 *
1273
 * <pre>
1274
 * Notes:
1275
 *      (1) For example, this can be used to generate a pixa of
1276
 *          vertical strips of width 10 from an image, using:
1277
 *             pixGetDimensions(pix, &w, &h, NULL);
1278
 *             boxa = makeMosaicStrips(w, h, L_SCAN_HORIZONTAL, 10);
1279
 *             pixa = pixClipRectangles(pix, boxa);
1280
 *          All strips except the last will be the same width.  The
1281
 *          last strip will have width w % 10.
1282
 * </pre>
1283
 */
1284
BOXA *
1285
makeMosaicStrips(l_int32  w,
1286
                 l_int32  h,
1287
                 l_int32  direction,
1288
                 l_int32  size)
1289
0
{
1290
0
l_int32  i, nstrips, extra;
1291
0
BOX     *box;
1292
0
BOXA    *boxa;
1293
1294
0
    if (w < 1 || h < 1)
1295
0
        return (BOXA *)ERROR_PTR("invalid w or h", __func__, NULL);
1296
0
    if (direction != L_SCAN_HORIZONTAL && direction != L_SCAN_VERTICAL)
1297
0
        return (BOXA *)ERROR_PTR("invalid direction", __func__, NULL);
1298
0
    if (size < 1)
1299
0
        return (BOXA *)ERROR_PTR("size < 1", __func__, NULL);
1300
1301
0
    boxa = boxaCreate(0);
1302
0
    if (direction == L_SCAN_HORIZONTAL) {
1303
0
        nstrips = w / size;
1304
0
        for (i = 0; i < nstrips; i++) {
1305
0
            box = boxCreate(i * size, 0, size, h);
1306
0
            boxaAddBox(boxa, box, L_INSERT);
1307
0
        }
1308
0
        if ((extra = w % size) > 0) {
1309
0
            box = boxCreate(nstrips * size, 0, extra, h);
1310
0
            boxaAddBox(boxa, box, L_INSERT);
1311
0
        }
1312
0
    } else {
1313
0
        nstrips = h / size;
1314
0
        for (i = 0; i < nstrips; i++) {
1315
0
            box = boxCreate(0, i * size, w, size);
1316
0
            boxaAddBox(boxa, box, L_INSERT);
1317
0
        }
1318
0
        if ((extra = h % size) > 0) {
1319
0
            box = boxCreate(0, nstrips * size, w, extra);
1320
0
            boxaAddBox(boxa, box, L_INSERT);
1321
0
        }
1322
0
    }
1323
0
    return boxa;
1324
0
}
1325
1326
1327
/*---------------------------------------------------------------------*
1328
 *                        Comparison between boxa                      *
1329
 *---------------------------------------------------------------------*/
1330
/*!
1331
 * \brief   boxaCompareRegions()
1332
 *
1333
 * \param[in]    boxa1, boxa2
1334
 * \param[in]    areathresh  minimum area of boxes to be considered
1335
 * \param[out]   pnsame      true if same number of boxes
1336
 * \param[out]   pdiffarea   fractional difference in total area
1337
 * \param[out]   pdiffxor    [optional] fractional difference in xor of regions
1338
 * \param[out]   ppixdb      [optional] debug pix showing two boxa
1339
 * \return  0 if OK, 1 on error
1340
 *
1341
 * <pre>
1342
 * Notes:
1343
 *      (1) This takes 2 boxa, removes all boxes smaller than a given area,
1344
 *          and compares the remaining boxes between the boxa.
1345
 *      (2) The area threshold is introduced to help remove noise from
1346
 *          small components.  Any box with a smaller value of w * h
1347
 *          will be removed from consideration.
1348
 *      (3) The xor difference is the most stringent test, requiring alignment
1349
 *          of the corresponding boxes.  It is also more computationally
1350
 *          intensive and is optionally returned.  Alignment is to the
1351
 *          UL corner of each region containing all boxes, as given by
1352
 *          boxaGetExtent().
1353
 *      (4) Both fractional differences are with respect to the total
1354
 *          area in the two boxa.  They range from 0.0 to 1.0.
1355
 *          A perfect match has value 0.0.  If both boxa are empty,
1356
 *          we return 0.0; if one is empty we return 1.0.
1357
 *      (5) An example input might be the rectangular regions of a
1358
 *          segmentation mask for text or images from two pages.
1359
 * </pre>
1360
 */
1361
l_ok
1362
boxaCompareRegions(BOXA       *boxa1,
1363
                   BOXA       *boxa2,
1364
                   l_int32     areathresh,
1365
                   l_int32    *pnsame,
1366
                   l_float32  *pdiffarea,
1367
                   l_float32  *pdiffxor,
1368
                   PIX       **ppixdb)
1369
0
{
1370
0
l_int32   w, h, x3, y3, w3, h3, x4, y4, w4, h4, n3, n4, area1, area2;
1371
0
l_int32   count3, count4, countxor;
1372
0
l_int32  *tab;
1373
0
BOX      *box3, *box4;
1374
0
BOXA     *boxa3, *boxa4, *boxa3t, *boxa4t;
1375
0
PIX      *pix1, *pix2, *pix3, *pix4, *pix5;
1376
0
PIXA     *pixa;
1377
1378
0
    if (pdiffxor) *pdiffxor = 1.0;
1379
0
    if (ppixdb) *ppixdb = NULL;
1380
0
    if (pnsame) *pnsame = FALSE;
1381
0
    if (pdiffarea) *pdiffarea = 1.0;
1382
0
    if (!boxa1 || !boxa2)
1383
0
        return ERROR_INT("boxa1 and boxa2 not both defined", __func__, 1);
1384
0
    if (!pnsame)
1385
0
        return ERROR_INT("&nsame not defined", __func__, 1);
1386
0
    if (!pdiffarea)
1387
0
        return ERROR_INT("&diffarea not defined", __func__, 1);
1388
1389
0
    boxa3 = boxaSelectByArea(boxa1, areathresh, L_SELECT_IF_GTE, NULL);
1390
0
    boxa4 = boxaSelectByArea(boxa2, areathresh, L_SELECT_IF_GTE, NULL);
1391
0
    n3 = boxaGetCount(boxa3);
1392
0
    n4 = boxaGetCount(boxa4);
1393
0
    if (n3 == n4)
1394
0
        *pnsame = TRUE;
1395
1396
        /* There are no boxes in one or both */
1397
0
    if (n3 == 0 || n4 == 0) {
1398
0
        boxaDestroy(&boxa3);
1399
0
        boxaDestroy(&boxa4);
1400
0
        if (n3 == 0 && n4 == 0) { /* they are both empty: we say they are the
1401
                                   * same; otherwise, they differ maximally
1402
                                   * and retain the default value. */
1403
0
            *pdiffarea = 0.0;
1404
0
            if (pdiffxor) *pdiffxor = 0.0;
1405
0
        }
1406
0
        return 0;
1407
0
    }
1408
1409
        /* There are boxes in both */
1410
0
    boxaGetArea(boxa3, &area1);
1411
0
    boxaGetArea(boxa4, &area2);
1412
0
    *pdiffarea = (l_float32)L_ABS(area1 - area2) / (l_float32)(area1 + area2);
1413
0
    if (!pdiffxor) {
1414
0
        boxaDestroy(&boxa3);
1415
0
        boxaDestroy(&boxa4);
1416
0
        return 0;
1417
0
    }
1418
1419
        /* The easiest way to get the xor of aligned boxes is to work
1420
         * with images of each boxa.  This is done by translating each
1421
         * boxa so that the UL corner of the region that includes all
1422
         * boxes in the boxa is placed at the origin of each pix. */
1423
0
    boxaGetExtent(boxa3, &w, &h, &box3);
1424
0
    boxaGetExtent(boxa4, &w, &h, &box4);
1425
0
    boxGetGeometry(box3, &x3, &y3, &w3, &h3);
1426
0
    boxGetGeometry(box4, &x4, &y4, &w4, &h4);
1427
0
    boxa3t = boxaTransform(boxa3, -x3, -y3, 1.0, 1.0);
1428
0
    boxa4t = boxaTransform(boxa4, -x4, -y4, 1.0, 1.0);
1429
0
    w = L_MAX(x3 + w3, x4 + w4);
1430
0
    h = L_MAX(y3 + h3, y4 + h4);
1431
0
    pix3 = pixCreate(w, h, 1);  /* use the max to keep everything in the xor */
1432
0
    pix4 = pixCreate(w, h, 1);
1433
0
    pixMaskBoxa(pix3, pix3, boxa3t, L_SET_PIXELS);
1434
0
    pixMaskBoxa(pix4, pix4, boxa4t, L_SET_PIXELS);
1435
0
    tab = makePixelSumTab8();
1436
0
    pixCountPixels(pix3, &count3, tab);
1437
0
    pixCountPixels(pix4, &count4, tab);
1438
0
    pix5 = pixXor(NULL, pix3, pix4);
1439
0
    pixCountPixels(pix5, &countxor, tab);
1440
0
    LEPT_FREE(tab);
1441
0
    *pdiffxor = (l_float32)countxor / (l_float32)(count3 + count4);
1442
1443
0
    if (ppixdb) {
1444
0
        pixa = pixaCreate(2);
1445
0
        pix1 = pixCreate(w, h, 32);
1446
0
        pixSetAll(pix1);
1447
0
        pixRenderHashBoxaBlend(pix1, boxa3, 5, 1, L_POS_SLOPE_LINE, 2,
1448
0
                               255, 0, 0, 0.5);
1449
0
        pixRenderHashBoxaBlend(pix1, boxa4, 5, 1, L_NEG_SLOPE_LINE, 2,
1450
0
                               0, 255, 0, 0.5);
1451
0
        pixaAddPix(pixa, pix1, L_INSERT);
1452
0
        pix2 = pixCreate(w, h, 32);
1453
0
        pixPaintThroughMask(pix2, pix3, x3, y3, 0xff000000);
1454
0
        pixPaintThroughMask(pix2, pix4, x4, y4, 0x00ff0000);
1455
0
        pixAnd(pix3, pix3, pix4);
1456
0
        pixPaintThroughMask(pix2, pix3, x3, y3, 0x0000ff00);
1457
0
        pixaAddPix(pixa, pix2, L_INSERT);
1458
0
        *ppixdb = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2);
1459
0
        pixaDestroy(&pixa);
1460
0
    }
1461
1462
0
    boxDestroy(&box3);
1463
0
    boxDestroy(&box4);
1464
0
    boxaDestroy(&boxa3);
1465
0
    boxaDestroy(&boxa3t);
1466
0
    boxaDestroy(&boxa4);
1467
0
    boxaDestroy(&boxa4t);
1468
0
    pixDestroy(&pix3);
1469
0
    pixDestroy(&pix4);
1470
0
    pixDestroy(&pix5);
1471
0
    return 0;
1472
0
}
1473
1474
1475
/*---------------------------------------------------------------------*
1476
 *                Reliable selection of a single large box             *
1477
 *---------------------------------------------------------------------*/
1478
/*!
1479
 * \brief   pixSelectLargeULComp()
1480
 *
1481
 * \param[in]    pixs           1 bpp
1482
 * \param[in]    areaslop       fraction near but less than 1.0
1483
 * \param[in]    yslop          number of pixels in y direction
1484
 * \param[in]    connectivity   4 or 8
1485
 * \return  box, or NULL on error
1486
 *
1487
 * <pre>
1488
 * Notes:
1489
 *      (1) This selects a box near the top (first) and left (second)
1490
 *          of the image, from the set of all boxes that have
1491
 *                area >= %areaslop * (area of biggest box),
1492
 *          where %areaslop is some fraction; say ~ 0.9.
1493
 *      (2) For all boxes satisfying the above condition, select
1494
 *          the left-most box that is within %yslop (say, 20) pixels
1495
 *          of the box nearest the top.
1496
 *      (3) This can be used to reliably select a specific one of
1497
 *          the largest regions in an image, for applications where
1498
 *          there are expected to be small variations in region size
1499
 *          and location.
1500
 *      (4) See boxSelectLargeULBox() for implementation details.
1501
 * </pre>
1502
 */
1503
BOX *
1504
pixSelectLargeULComp(PIX       *pixs,
1505
                     l_float32  areaslop,
1506
                     l_int32    yslop,
1507
                     l_int32    connectivity)
1508
0
{
1509
0
BOX   *box;
1510
0
BOXA  *boxa1;
1511
1512
0
    if (!pixs)
1513
0
        return (BOX *)ERROR_PTR("pixs not defined", __func__, NULL);
1514
0
    if (areaslop < 0.0 || areaslop > 1.0)
1515
0
        return (BOX *)ERROR_PTR("invalid value for areaslop", __func__, NULL);
1516
0
    yslop = L_MAX(0, yslop);
1517
1518
0
    boxa1 = pixConnCompBB(pixs, connectivity);
1519
0
    if (boxaGetCount(boxa1) == 0) {
1520
0
        boxaDestroy(&boxa1);
1521
0
        return NULL;
1522
0
    }
1523
0
    box = boxaSelectLargeULBox(boxa1, areaslop, yslop);
1524
0
    boxaDestroy(&boxa1);
1525
0
    return box;
1526
0
}
1527
1528
1529
/*!
1530
 * \brief   boxaSelectLargeULBox()
1531
 *
1532
 * \param[in]    boxas      1 bpp
1533
 * \param[in]    areaslop   fraction near but less than 1.0
1534
 * \param[in]    yslop      number of pixels in y direction
1535
 * \return  box, or NULL on error
1536
 *
1537
 * <pre>
1538
 * Notes:
1539
 *      (1) See usage notes in pixSelectLargeULComp().
1540
 * </pre>
1541
 */
1542
BOX *
1543
boxaSelectLargeULBox(BOXA      *boxas,
1544
                     l_float32  areaslop,
1545
                     l_int32    yslop)
1546
0
{
1547
0
l_int32    w, h, i, n, x1, y1, x2, y2, select;
1548
0
l_float32  area, max_area;
1549
0
BOX       *box;
1550
0
BOXA      *boxa1, *boxa2, *boxa3;
1551
1552
0
    if (!boxas)
1553
0
        return (BOX *)ERROR_PTR("boxas not defined", __func__, NULL);
1554
0
    if (boxaGetCount(boxas) == 0)
1555
0
        return (BOX *)ERROR_PTR("no boxes in boxas", __func__, NULL);
1556
0
    if (areaslop < 0.0 || areaslop > 1.0)
1557
0
        return (BOX *)ERROR_PTR("invalid value for areaslop", __func__, NULL);
1558
0
    yslop = L_MAX(0, yslop);
1559
1560
0
    boxa1 = boxaSort(boxas, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
1561
0
    boxa2 = boxaSort(boxa1, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
1562
0
    n = boxaGetCount(boxa2);
1563
0
    boxaGetBoxGeometry(boxa1, 0, NULL, NULL, &w, &h);  /* biggest box by area */
1564
0
    max_area = (l_float32)(w * h);
1565
1566
        /* boxa3 collects all boxes eligible by area, sorted top-down */
1567
0
    boxa3 = boxaCreate(4);
1568
0
    for (i = 0; i < n; i++) {
1569
0
        boxaGetBoxGeometry(boxa2, i, NULL, NULL, &w, &h);
1570
0
        area = (l_float32)(w * h);
1571
0
        if (area / max_area >= areaslop) {
1572
0
            box = boxaGetBox(boxa2, i, L_COPY);
1573
0
            boxaAddBox(boxa3, box, L_INSERT);
1574
0
        }
1575
0
    }
1576
1577
        /* Take the first (top-most box) unless the second (etc) has
1578
         * nearly the same y value but a smaller x value. */
1579
0
    n = boxaGetCount(boxa3);
1580
0
    boxaGetBoxGeometry(boxa3, 0, &x1, &y1, NULL, NULL);
1581
0
    select = 0;
1582
0
    for (i = 1; i < n; i++) {
1583
0
        boxaGetBoxGeometry(boxa3, i, &x2, &y2, NULL, NULL);
1584
0
        if (y2 - y1 < yslop && x2 < x1) {
1585
0
            select = i;
1586
0
            x1 = x2;  /* but always compare against y1 */
1587
0
        }
1588
0
    }
1589
1590
0
    box = boxaGetBox(boxa3, select, L_COPY);
1591
0
    boxaDestroy(&boxa1);
1592
0
    boxaDestroy(&boxa2);
1593
0
    boxaDestroy(&boxa3);
1594
0
    return box;
1595
0
}