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  pixafunc2.c
29  * <pre>
30  *
31  *      Pixa display (render into a pix)
32  *           PIX      *pixaDisplay()
33  *           PIX      *pixaDisplayOnColor()
34  *           PIX      *pixaDisplayRandomCmap()
35  *           PIX      *pixaDisplayLinearly()
36  *           PIX      *pixaDisplayOnLattice()
37  *           PIX      *pixaDisplayUnsplit()
38  *           PIX      *pixaDisplayTiled()
39  *           PIX      *pixaDisplayTiledInRows()
40  *           PIX      *pixaDisplayTiledInColumns()
41  *           PIX      *pixaDisplayTiledAndScaled()
42  *           PIX      *pixaDisplayTiledWithText()
43  *           PIX      *pixaDisplayTiledByIndex()
44  *
45  *      Pixaa display (render into a pix)
46  *           PIX      *pixaaDisplay()
47  *           PIX      *pixaaDisplayByPixa()
48  *           PIXA     *pixaaDisplayTiledAndScaled()
49  *
50  *      Conversion of all pix to specified type (e.g., depth)
51  *           PIXA     *pixaConvertTo1()
52  *           PIXA     *pixaConvertTo8()
53  *           PIXA     *pixaConvertTo8Colormap()
54  *           PIXA     *pixaConvertTo32()
55  *
56  *      Pixa constrained selection and pdf generation
57  *           PIXA     *pixaConstrainedSelect()
58  *           l_int32   pixaSelectToPdf()
59  *
60  *      Pixa display into multiple tiles
61  *           PIXA     *pixaDisplayMultiTiled()
62  *
63  *      Split pixa into files
64  *           l_int32   pixaSplitIntoFiles()
65  *
66  *      Tile N-Up
67  *           l_int32   convertToNUpFiles()
68  *           PIXA     *convertToNUpPixa()
69  *           PIXA     *pixaConvertToNUpPixa()
70  *
71  *      Render two pixa side-by-side for comparison                   *
72  *           l_int32   pixaCompareInPdf()
73  *
74  *  We give twelve pixaDisplay*() methods for tiling a pixa in a pix.
75  *  Some work for 1 bpp input; others for any input depth.
76  *  Some give an output depth that depends on the input depth;
77  *  others give a different output depth or allow you to choose it.
78  *  Some use a boxes to determine where each pix goes; others tile
79  *  onto a regular lattice; others tile onto an irregular lattice;
80  *  one uses an associated index array to determine which column
81  *  each pix goes into.
82  *
83  *  Here is a brief description of what the pixa display functions do.
84  *
85  *    pixaDisplay()
86  *        This uses the boxes in the pixa to lay out each pix.  This
87  *        can be used to reconstruct a pix that has been broken into
88  *        components, if the boxes represents the positions of the
89  *        components in the original image.
90  *    pixaDisplayOnColor()
91  *        pixaDisplay() with choice of background color.
92  *    pixaDisplayRandomCmap()
93  *        This also uses the boxes to lay out each pix.  However, it creates
94  *        a colormapped dest, where each 1 bpp pix is given a randomly
95  *        generated color (up to 256 are used).
96  *    pixaDisplayLinearly()
97  *        This puts each pix, sequentially, in a line, either horizontally
98  *        or vertically.
99  *    pixaDisplayOnLattice()
100  *        This puts each pix, sequentially, onto a regular lattice,
101  *        omitting any pix that are too big for the lattice size.
102  *        This is useful, for example, to store bitmapped fonts,
103  *        where all the characters are stored in a single image.
104  *    pixaDisplayUnsplit()
105  *        This lays out a mosaic of tiles (the pix in the pixa) that
106  *        are all of equal size.  (Don't use this for unequal sized pix!)
107  *        For example, it can be used to invert the action of
108  *        pixaSplitPix().
109  *    pixaDisplayTiled()
110  *        Like pixaDisplayOnLattice(), this places each pix on a regular
111  *        lattice, but here the lattice size is determined by the
112  *        largest component, and no components are omitted.  This is
113  *        dangerous if there are thousands of small components and
114  *        one or more very large one, because the size of the resulting
115  *        pix can be huge!
116  *    pixaDisplayTiledInRows()
117  *        This puts each pix down in a series of rows, where the upper
118  *        edges of each pix in a row are aligned and there is a uniform
119  *        spacing between the pix.  The height of each row is determined
120  *        by the tallest pix that was put in the row.  This function
121  *        is a reasonably efficient way to pack the subimages.
122  *        A boxa of the locations of each input pix is stored in the output.
123  *    pixaDisplayTiledInColumns()
124  *        This puts each pix down in a series of rows, each row having
125  *        a specified number of pix.  The upper edges of each pix in a
126  *        row are aligned and there is a uniform spacing between the pix.
127  *        The height of each row is determined by the tallest pix that
128  *        was put in the row.  A boxa of the locations of each input
129  *        pix is stored in the output.
130  *    pixaDisplayTiledAndScaled()
131  *        This scales each pix to a given width and output depth, and then
132  *        tiles them in rows with a given number placed in each row.
133  *        This is useful for presenting a sequence of images that can be
134  *        at different resolutions, but which are derived from the same
135  *        initial image.
136  *    pixaDisplayTiledWithText()
137  *        This is a version of pixaDisplayTiledInRows() that prints, below
138  *        each pix, the text in the pix text field.  It renders a pixa
139  *        to an image with white background that does not exceed a
140  *        given value in width.
141  *    pixaDisplayTiledByIndex()
142  *        This scales each pix to a given width and output depth,
143  *        and then tiles them in columns corresponding to the value
144  *        in an associated numa.  All pix with the same index value are
145  *        rendered in the same column.  Text in the pix text field are
146  *        rendered below the pix.
147  * </pre>
148  */
149 
150 #include <string.h>
151 #include <math.h>   /* for sqrt() */
152 #include "allheaders.h"
153 
154 
155 /*---------------------------------------------------------------------*
156  *                               Pixa Display                          *
157  *---------------------------------------------------------------------*/
158 /*!
159  * \brief   pixaDisplay()
160  *
161  * \param[in]    pixa
162  * \param[in]    w, h    if set to 0, the size is determined from the
163  *                       bounding box of the components in pixa
164  * \return  pix, or NULL on error
165  *
166  * <pre>
167  * Notes:
168  *      (1) This uses the boxes to place each pix in the rendered composite.
169  *      (2) Set w = h = 0 to use the b.b. of the components to determine
170  *          the size of the returned pix.
171  *      (3) Uses the first pix in pixa to determine the depth.
172  *      (4) The background is written "white".  On 1 bpp, each successive
173  *          pix is "painted" (adding foreground), whereas for grayscale
174  *          or color each successive pix is blitted with just the src.
175  *      (5) If the pixa is empty, returns an empty 1 bpp pix.
176  * </pre>
177  */
178 PIX *
pixaDisplay(PIXA * pixa,l_int32 w,l_int32 h)179 pixaDisplay(PIXA    *pixa,
180             l_int32  w,
181             l_int32  h)
182 {
183 l_int32  i, n, d, xb, yb, wb, hb, res;
184 BOXA    *boxa;
185 PIX     *pix1, *pixd;
186 
187     PROCNAME("pixaDisplay");
188 
189     if (!pixa)
190         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
191 
192     n = pixaGetCount(pixa);
193     if (n == 0 && w == 0 && h == 0)
194         return (PIX *)ERROR_PTR("no components; no size", procName, NULL);
195     if (n == 0) {
196         L_WARNING("no components; returning empty 1 bpp pix\n", procName);
197         return pixCreate(w, h, 1);
198     }
199 
200         /* If w and h not input, determine the minimum size required
201          * to contain the origin and all c.c. */
202     if (w == 0 || h == 0) {
203         boxa = pixaGetBoxa(pixa, L_CLONE);
204         boxaGetExtent(boxa, &w, &h, NULL);
205         boxaDestroy(&boxa);
206         if (w == 0 || h == 0)
207             return (PIX *)ERROR_PTR("no associated boxa", procName, NULL);
208     }
209 
210         /* Use the first pix in pixa to determine depth and resolution  */
211     pix1 = pixaGetPix(pixa, 0, L_CLONE);
212     d = pixGetDepth(pix1);
213     res = pixGetXRes(pix1);
214     pixDestroy(&pix1);
215 
216     if ((pixd = pixCreate(w, h, d)) == NULL)
217         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
218     pixSetResolution(pixd, res, res);
219     if (d > 1)
220         pixSetAll(pixd);
221     for (i = 0; i < n; i++) {
222         if (pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb)) {
223             L_WARNING("no box found!\n", procName);
224             continue;
225         }
226         pix1 = pixaGetPix(pixa, i, L_CLONE);
227         if (d == 1)
228             pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix1, 0, 0);
229         else
230             pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pix1, 0, 0);
231         pixDestroy(&pix1);
232     }
233 
234     return pixd;
235 }
236 
237 
238 /*!
239  * \brief   pixaDisplayOnColor()
240  *
241  * \param[in]    pixa
242  * \param[in]    w, h     if set to 0, the size is determined from the
243  *                        bounding box of the components in pixa
244  * \param[in]    bgcolor  background color to use
245  * \return  pix, or NULL on error
246  *
247  * <pre>
248  * Notes:
249  *      (1) This uses the boxes to place each pix in the rendered composite.
250  *      (2) Set w = h = 0 to use the b.b. of the components to determine
251  *          the size of the returned pix.
252  *      (3) If any pix in %pixa are colormapped, or if the pix have
253  *          different depths, it returns a 32 bpp pix.  Otherwise,
254  *          the depth of the returned pixa equals that of the pix in %pixa.
255  *      (4) If the pixa is empty, return null.
256  * </pre>
257  */
258 PIX *
pixaDisplayOnColor(PIXA * pixa,l_int32 w,l_int32 h,l_uint32 bgcolor)259 pixaDisplayOnColor(PIXA     *pixa,
260                    l_int32   w,
261                    l_int32   h,
262                    l_uint32  bgcolor)
263 {
264 l_int32  i, n, xb, yb, wb, hb, hascmap, maxdepth, same, res;
265 BOXA    *boxa;
266 PIX     *pix1, *pix2, *pixd;
267 PIXA    *pixat;
268 
269     PROCNAME("pixaDisplayOnColor");
270 
271     if (!pixa)
272         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
273     if ((n = pixaGetCount(pixa)) == 0)
274         return (PIX *)ERROR_PTR("no components", procName, NULL);
275 
276         /* If w and h are not input, determine the minimum size
277          * required to contain the origin and all c.c. */
278     if (w == 0 || h == 0) {
279         boxa = pixaGetBoxa(pixa, L_CLONE);
280         boxaGetExtent(boxa, &w, &h, NULL);
281         boxaDestroy(&boxa);
282     }
283 
284         /* If any pix have colormaps, or if they have different depths,
285          * generate rgb */
286     pixaAnyColormaps(pixa, &hascmap);
287     pixaGetDepthInfo(pixa, &maxdepth, &same);
288     if (hascmap || !same) {
289         maxdepth = 32;
290         pixat = pixaCreate(n);
291         for (i = 0; i < n; i++) {
292             pix1 = pixaGetPix(pixa, i, L_CLONE);
293             pix2 = pixConvertTo32(pix1);
294             pixaAddPix(pixat, pix2, L_INSERT);
295             pixDestroy(&pix1);
296         }
297     } else {
298         pixat = pixaCopy(pixa, L_CLONE);
299     }
300 
301         /* Make the output pix and set the background color */
302     if ((pixd = pixCreate(w, h, maxdepth)) == NULL) {
303         pixaDestroy(&pixat);
304         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
305     }
306     if ((maxdepth == 1 && bgcolor > 0) ||
307         (maxdepth == 2 && bgcolor >= 0x3) ||
308         (maxdepth == 4 && bgcolor >= 0xf) ||
309         (maxdepth == 8 && bgcolor >= 0xff) ||
310         (maxdepth == 16 && bgcolor >= 0xffff) ||
311         (maxdepth == 32 && bgcolor >= 0xffffff00)) {
312         pixSetAll(pixd);
313     } else if (bgcolor > 0) {
314         pixSetAllArbitrary(pixd, bgcolor);
315     }
316 
317         /* Blit each pix into its place */
318     for (i = 0; i < n; i++) {
319         if (pixaGetBoxGeometry(pixat, i, &xb, &yb, &wb, &hb)) {
320             L_WARNING("no box found!\n", procName);
321             continue;
322         }
323         pix1 = pixaGetPix(pixat, i, L_CLONE);
324         if (i == 0) res = pixGetXRes(pix1);
325         pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pix1, 0, 0);
326         pixDestroy(&pix1);
327     }
328     pixSetResolution(pixd, res, res);
329 
330     pixaDestroy(&pixat);
331     return pixd;
332 }
333 
334 
335 /*!
336  * \brief   pixaDisplayRandomCmap()
337  *
338  * \param[in]    pixa    1 bpp regions, with boxa delineating those regions
339  * \param[in]    w, h    if set to 0, the size is determined from the
340  *                       bounding box of the components in pixa
341  * \return  pix   8 bpp, cmapped, with random colors assigned to each region,
342  *                or NULL on error.
343  *
344  * <pre>
345  * Notes:
346  *      (1) This uses the boxes to place each pix in the rendered composite.
347  *          The fg of each pix in %pixa, such as a single connected
348  *          component or a line of text, is given a random color.
349  *      (2) By default, the background color is black (cmap index 0).
350  *          This can be changed by pixcmapResetColor()
351  * </pre>
352  */
353 PIX *
pixaDisplayRandomCmap(PIXA * pixa,l_int32 w,l_int32 h)354 pixaDisplayRandomCmap(PIXA    *pixa,
355                       l_int32  w,
356                       l_int32  h)
357 {
358 l_int32   i, n, same, maxd, index, xb, yb, wb, hb, res;
359 BOXA     *boxa;
360 PIX      *pixs, *pix1, *pixd;
361 PIXCMAP  *cmap;
362 
363     PROCNAME("pixaDisplayRandomCmap");
364 
365     if (!pixa)
366         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
367 
368     if ((n = pixaGetCount(pixa)) == 0)
369         return (PIX *)ERROR_PTR("no components", procName, NULL);
370     pixaVerifyDepth(pixa, &same, &maxd);
371     if (maxd > 1)
372         return (PIX *)ERROR_PTR("not all components are 1 bpp", procName, NULL);
373 
374         /* If w and h are not input, determine the minimum size required
375          * to contain the origin and all c.c. */
376     if (w == 0 || h == 0) {
377         boxa = pixaGetBoxa(pixa, L_CLONE);
378         boxaGetExtent(boxa, &w, &h, NULL);
379         boxaDestroy(&boxa);
380     }
381 
382         /* Set up an 8 bpp dest pix, with a colormap with 254 random colors */
383     if ((pixd = pixCreate(w, h, 8)) == NULL)
384         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
385     cmap = pixcmapCreateRandom(8, 1, 1);
386     pixSetColormap(pixd, cmap);
387 
388         /* Color each component and blit it in */
389     for (i = 0; i < n; i++) {
390         index = 1 + (i % 254);
391         pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb);
392         pixs = pixaGetPix(pixa, i, L_CLONE);
393         if (i == 0) res = pixGetXRes(pixs);
394         pix1 = pixConvert1To8(NULL, pixs, 0, index);
395         pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix1, 0, 0);
396         pixDestroy(&pixs);
397         pixDestroy(&pix1);
398     }
399 
400     pixSetResolution(pixd, res, res);
401     return pixd;
402 }
403 
404 
405 /*!
406  * \brief   pixaDisplayLinearly()
407  *
408  * \param[in]    pixas
409  * \param[in]    direction    L_HORIZ or L_VERT
410  * \param[in]    scalefactor  applied to every pix; use 1.0 for no scaling
411  * \param[in]    background   0 for white, 1 for black; this is the color
412  *                            of the spacing between the images
413  * \param[in]    spacing      between images, and on outside
414  * \param[in]    border       width of black border added to each image;
415  *                            use 0 for no border
416  * \param[out]   pboxa        [optional] location of images in output pix
417  * \return  pix of composite images, or NULL on error
418  *
419  * <pre>
420  * Notes:
421  *      (1) This puts each pix, sequentially, in a line, either horizontally
422  *          or vertically.
423  *      (2) If any pix has a colormap, all pix are rendered in rgb.
424  *      (3) The boxa gives the location of each image.
425  * </pre>
426  */
427 PIX *
pixaDisplayLinearly(PIXA * pixas,l_int32 direction,l_float32 scalefactor,l_int32 background,l_int32 spacing,l_int32 border,BOXA ** pboxa)428 pixaDisplayLinearly(PIXA      *pixas,
429                     l_int32    direction,
430                     l_float32  scalefactor,
431                     l_int32    background,  /* not used */
432                     l_int32    spacing,
433                     l_int32    border,
434                     BOXA     **pboxa)
435 {
436 l_int32  i, n, x, y, w, h, size, depth, bordval;
437 BOX     *box;
438 PIX     *pix1, *pix2, *pix3, *pixd;
439 PIXA    *pixa1, *pixa2;
440 
441     PROCNAME("pixaDisplayLinearly");
442 
443     if (pboxa) *pboxa = NULL;
444     if (!pixas)
445         return (PIX *)ERROR_PTR("pixas not defined", procName, NULL);
446     if (direction != L_HORIZ && direction != L_VERT)
447         return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
448 
449         /* Make sure all pix are at the same depth */
450     pixa1 = pixaConvertToSameDepth(pixas);
451     pixaGetDepthInfo(pixa1, &depth, NULL);
452 
453         /* Scale and add border if requested */
454     n = pixaGetCount(pixa1);
455     pixa2 = pixaCreate(n);
456     bordval = (depth == 1) ? 1 : 0;
457     size = (n - 1) * spacing;
458     x = y = 0;
459     for (i = 0; i < n; i++) {
460         if ((pix1 = pixaGetPix(pixa1, i, L_CLONE)) == NULL) {
461             L_WARNING("missing pix at index %d\n", procName, i);
462             continue;
463         }
464 
465         if (scalefactor != 1.0)
466             pix2 = pixScale(pix1, scalefactor, scalefactor);
467         else
468             pix2 = pixClone(pix1);
469         if (border)
470             pix3 = pixAddBorder(pix2, border, bordval);
471         else
472             pix3 = pixClone(pix2);
473 
474         pixGetDimensions(pix3, &w, &h, NULL);
475         box = boxCreate(x, y, w, h);
476         if (direction == L_HORIZ) {
477             size += w;
478             x += w + spacing;
479         } else {  /* vertical */
480             size += h;
481             y += h + spacing;
482         }
483         pixaAddPix(pixa2, pix3, L_INSERT);
484         pixaAddBox(pixa2, box, L_INSERT);
485         pixDestroy(&pix1);
486         pixDestroy(&pix2);
487     }
488     pixd = pixaDisplay(pixa2, 0, 0);
489 
490     if (pboxa)
491         *pboxa = pixaGetBoxa(pixa2, L_COPY);
492     pixaDestroy(&pixa1);
493     pixaDestroy(&pixa2);
494     return pixd;
495 }
496 
497 
498 /*!
499  * \brief   pixaDisplayOnLattice()
500  *
501  * \param[in]    pixa
502  * \param[in]    cellw    lattice cell width
503  * \param[in]    cellh    lattice cell height
504  * \param[out]   pncols   [optional] number of columns in output lattice
505  * \param[out]   pboxa    [optional] location of images in lattice
506  * \return  pix of composite images, or NULL on error
507  *
508  * <pre>
509  * Notes:
510  *      (1) This places each pix on sequentially on a regular lattice
511  *          in the rendered composite.  If a pix is too large to fit in the
512  *          allocated lattice space, it is not rendered.
513  *      (2) If any pix has a colormap, all pix are rendered in rgb.
514  *      (3) This is useful when putting bitmaps of components,
515  *          such as characters, into a single image.
516  *      (4) The boxa gives the location of each image.  The UL corner
517  *          of each image is on a lattice cell corner.  Omitted images
518  *          (due to size) are assigned an invalid width and height of 0.
519  * </pre>
520  */
521 PIX *
pixaDisplayOnLattice(PIXA * pixa,l_int32 cellw,l_int32 cellh,l_int32 * pncols,BOXA ** pboxa)522 pixaDisplayOnLattice(PIXA     *pixa,
523                      l_int32   cellw,
524                      l_int32   cellh,
525                      l_int32  *pncols,
526                      BOXA    **pboxa)
527 {
528 l_int32  n, nw, nh, w, h, d, wt, ht, res;
529 l_int32  index, i, j, hascmap;
530 BOX     *box;
531 BOXA    *boxa;
532 PIX     *pix1, *pix2, *pixd;
533 PIXA    *pixa1;
534 
535     PROCNAME("pixaDisplayOnLattice");
536 
537     if (pncols) *pncols = 0;
538     if (pboxa) *pboxa = NULL;
539     if (!pixa)
540         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
541 
542         /* If any pix have colormaps, generate rgb */
543     if ((n = pixaGetCount(pixa)) == 0)
544         return (PIX *)ERROR_PTR("no components", procName, NULL);
545     pix1 = pixaGetPix(pixa, 0, L_CLONE);
546     res = pixGetXRes(pix1);
547     pixDestroy(&pix1);
548     pixaAnyColormaps(pixa, &hascmap);
549     if (hascmap) {
550         pixa1 = pixaCreate(n);
551         for (i = 0; i < n; i++) {
552             pix1 = pixaGetPix(pixa, i, L_CLONE);
553             pix2 = pixConvertTo32(pix1);
554             pixaAddPix(pixa1, pix2, L_INSERT);
555             pixDestroy(&pix1);
556         }
557     } else {
558         pixa1 = pixaCopy(pixa, L_CLONE);
559     }
560     boxa = boxaCreate(n);
561 
562         /* Have number of rows and columns approximately equal */
563     nw = (l_int32)sqrt((l_float64)n);
564     nh = (n + nw - 1) / nw;
565     w = cellw * nw;
566     h = cellh * nh;
567 
568         /* Use the first pix in pixa to determine the output depth.  */
569     pixaGetPixDimensions(pixa1, 0, NULL, NULL, &d);
570     if ((pixd = pixCreate(w, h, d)) == NULL) {
571         pixaDestroy(&pixa1);
572         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
573     }
574     pixSetBlackOrWhite(pixd, L_SET_WHITE);
575     pixSetResolution(pixd, res, res);
576 
577         /* Tile the output */
578     index = 0;
579     for (i = 0; i < nh; i++) {
580         for (j = 0; j < nw && index < n; j++, index++) {
581             pix1 = pixaGetPix(pixa1, index, L_CLONE);
582             pixGetDimensions(pix1, &wt, &ht, NULL);
583             if (wt > cellw || ht > cellh) {
584                 L_INFO("pix(%d) omitted; size %dx%x\n", procName, index,
585                        wt, ht);
586                 box = boxCreate(0, 0, 0, 0);
587                 boxaAddBox(boxa, box, L_INSERT);
588                 pixDestroy(&pix1);
589                 continue;
590             }
591             pixRasterop(pixd, j * cellw, i * cellh, wt, ht,
592                         PIX_SRC, pix1, 0, 0);
593             box = boxCreate(j * cellw, i * cellh, wt, ht);
594             boxaAddBox(boxa, box, L_INSERT);
595             pixDestroy(&pix1);
596         }
597     }
598 
599     if (pncols) *pncols = nw;
600     if (pboxa)
601         *pboxa = boxa;
602     else
603         boxaDestroy(&boxa);
604     pixaDestroy(&pixa1);
605     return pixd;
606 }
607 
608 
609 /*!
610  * \brief   pixaDisplayUnsplit()
611  *
612  * \param[in]    pixa
613  * \param[in]    nx           number of mosaic cells horizontally
614  * \param[in]    ny           number of mosaic cells vertically
615  * \param[in]    borderwidth  of added border on all sides
616  * \param[in]    bordercolor  in our RGBA format: 0xrrggbbaa
617  * \return  pix of tiled images, or NULL on error
618  *
619  * <pre>
620  * Notes:
621  *      (1) This is a logical inverse of pixaSplitPix().  It
622  *          constructs a pix from a mosaic of tiles, all of equal size.
623  *      (2) For added generality, a border of arbitrary color can
624  *          be added to each of the tiles.
625  *      (3) In use, pixa will typically have either been generated
626  *          from pixaSplitPix() or will derived from a pixa that
627  *          was so generated.
628  *      (4) All pix in the pixa must be of equal depth, and, if
629  *          colormapped, have the same colormap.
630  * </pre>
631  */
632 PIX *
pixaDisplayUnsplit(PIXA * pixa,l_int32 nx,l_int32 ny,l_int32 borderwidth,l_uint32 bordercolor)633 pixaDisplayUnsplit(PIXA     *pixa,
634                    l_int32   nx,
635                    l_int32   ny,
636                    l_int32   borderwidth,
637                    l_uint32  bordercolor)
638 {
639 l_int32  w, h, d, wt, ht;
640 l_int32  i, j, k, x, y, n;
641 PIX     *pix1, *pixd;
642 
643     PROCNAME("pixaDisplayUnsplit");
644 
645     if (!pixa)
646         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
647     if (nx <= 0 || ny <= 0)
648         return (PIX *)ERROR_PTR("nx and ny must be > 0", procName, NULL);
649     if ((n = pixaGetCount(pixa)) == 0)
650         return (PIX *)ERROR_PTR("no components", procName, NULL);
651     if (n != nx * ny)
652         return (PIX *)ERROR_PTR("n != nx * ny", procName, NULL);
653     borderwidth = L_MAX(0, borderwidth);
654 
655     pixaGetPixDimensions(pixa, 0, &wt, &ht, &d);
656     w = nx * (wt + 2 * borderwidth);
657     h = ny * (ht + 2 * borderwidth);
658 
659     if ((pixd = pixCreate(w, h, d)) == NULL)
660         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
661     pix1 = pixaGetPix(pixa, 0, L_CLONE);
662     pixCopyColormap(pixd, pix1);
663     pixDestroy(&pix1);
664     if (borderwidth > 0)
665         pixSetAllArbitrary(pixd, bordercolor);
666 
667     y = borderwidth;
668     for (i = 0, k = 0; i < ny; i++) {
669         x = borderwidth;
670         for (j = 0; j < nx; j++, k++) {
671             pix1 = pixaGetPix(pixa, k, L_CLONE);
672             pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix1, 0, 0);
673             pixDestroy(&pix1);
674             x += wt + 2 * borderwidth;
675         }
676         y += ht + 2 * borderwidth;
677     }
678 
679     return pixd;
680 }
681 
682 
683 /*!
684  * \brief   pixaDisplayTiled()
685  *
686  * \param[in]    pixa
687  * \param[in]    maxwidth     of output image
688  * \param[in]    background   0 for white, 1 for black
689  * \param[in]    spacing
690  * \return  pix of tiled images, or NULL on error
691  *
692  * <pre>
693  * Notes:
694  *      (1) This renders a pixa to a single image of width not to
695  *          exceed maxwidth, with background color either white or black,
696  *          and with each subimage spaced on a regular lattice.
697  *      (2) The lattice size is determined from the largest width and height,
698  *          separately, of all pix in the pixa.
699  *      (3) All pix in the pixa must be of equal depth.
700  *      (4) If any pix has a colormap, all pix are rendered in rgb.
701  *      (5) Careful: because no components are omitted, this is
702  *          dangerous if there are thousands of small components and
703  *          one or more very large one, because the size of the
704  *          resulting pix can be huge!
705  * </pre>
706  */
707 PIX *
pixaDisplayTiled(PIXA * pixa,l_int32 maxwidth,l_int32 background,l_int32 spacing)708 pixaDisplayTiled(PIXA    *pixa,
709                  l_int32  maxwidth,
710                  l_int32  background,
711                  l_int32  spacing)
712 {
713 l_int32  wmax, hmax, wd, hd, d, hascmap, res, same;
714 l_int32  i, j, n, ni, ncols, nrows;
715 l_int32  ystart, xstart, wt, ht;
716 PIX     *pix1, *pix2, *pixd;
717 PIXA    *pixa1;
718 
719     PROCNAME("pixaDisplayTiled");
720 
721     if (!pixa)
722         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
723 
724         /* If any pix have colormaps, generate rgb */
725     if ((n = pixaGetCount(pixa)) == 0)
726         return (PIX *)ERROR_PTR("no components", procName, NULL);
727     pixaAnyColormaps(pixa, &hascmap);
728     if (hascmap) {
729         pixa1 = pixaCreate(n);
730         for (i = 0; i < n; i++) {
731             pix1 = pixaGetPix(pixa, i, L_CLONE);
732             pix2 = pixConvertTo32(pix1);
733             pixaAddPix(pixa1, pix2, L_INSERT);
734             pixDestroy(&pix1);
735         }
736     } else {
737         pixa1 = pixaCopy(pixa, L_CLONE);
738     }
739 
740         /* Find the max dimensions and depth subimages */
741     pixaGetDepthInfo(pixa1, &d, &same);
742     if (!same) {
743         pixaDestroy(&pixa1);
744         return (PIX *)ERROR_PTR("depths not equal", procName, NULL);
745     }
746     pixaSizeRange(pixa1, NULL, NULL, &wmax, &hmax);
747 
748         /* Get the number of rows and columns and the output image size */
749     spacing = L_MAX(spacing, 0);
750     ncols = (l_int32)((l_float32)(maxwidth - spacing) /
751                       (l_float32)(wmax + spacing));
752     ncols = L_MAX(ncols, 1);
753     nrows = (n + ncols - 1) / ncols;
754     wd = wmax * ncols + spacing * (ncols + 1);
755     hd = hmax * nrows + spacing * (nrows + 1);
756     if ((pixd = pixCreate(wd, hd, d)) == NULL) {
757         pixaDestroy(&pixa1);
758         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
759     }
760 
761         /* Reset the background color if necessary */
762     if ((background == 1 && d == 1) || (background == 0 && d != 1))
763         pixSetAll(pixd);
764 
765         /* Blit the images to the dest */
766     for (i = 0, ni = 0; i < nrows; i++) {
767         ystart = spacing + i * (hmax + spacing);
768         for (j = 0; j < ncols && ni < n; j++, ni++) {
769             xstart = spacing + j * (wmax + spacing);
770             pix1 = pixaGetPix(pixa1, ni, L_CLONE);
771             if (ni == 0) res = pixGetXRes(pix1);
772             pixGetDimensions(pix1, &wt, &ht, NULL);
773             pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix1, 0, 0);
774             pixDestroy(&pix1);
775         }
776     }
777     pixSetResolution(pixd, res, res);
778 
779     pixaDestroy(&pixa1);
780     return pixd;
781 }
782 
783 
784 /*!
785  * \brief   pixaDisplayTiledInRows()
786  *
787  * \param[in]    pixa
788  * \param[in]    outdepth     output depth: 1, 8 or 32 bpp
789  * \param[in]    maxwidth     of output image
790  * \param[in]    scalefactor  applied to every pix; use 1.0 for no scaling
791  * \param[in]    background   0 for white, 1 for black; this is the color
792  *                            of the spacing between the images
793  * \param[in]    spacing      between images, and on outside
794  * \param[in]    border       width of black border added to each image;
795  *                            use 0 for no border
796  * \return  pixd of tiled images, or NULL on error
797  *
798  * <pre>
799  * Notes:
800  *      (1) This renders a pixa to a single image of width not to
801  *          exceed maxwidth, with background color either white or black,
802  *          and with each row tiled such that the top of each pix is
803  *          aligned and separated by 'spacing' from the next one.
804  *          A black border can be added to each pix.
805  *      (2) All pix are converted to outdepth; existing colormaps are removed.
806  *      (3) This does a reasonably spacewise-efficient job of laying
807  *          out the individual pix images into a tiled composite.
808  *      (4) A serialized boxa giving the location in pixd of each input
809  *          pix (without added border) is stored in the text string of pixd.
810  *          This allows, e.g., regeneration of a pixa from pixd, using
811  *          pixaCreateFromBoxa().  If there is no scaling and the depth of
812  *          each input pix in the pixa is the same, this tiling operation
813  *          can be inverted using the boxa (except for loss of text in
814  *          each of the input pix):
815  *            pix1 = pixaDisplayTiledInRows(pixa1, 1, 1500, 1.0, 0, 30, 0);
816  *            char *boxatxt = pixGetText(pix1);
817  *            boxa1 = boxaReadMem((l_uint8 *)boxatxt, strlen(boxatxt));
818  *            pixa2 = pixaCreateFromBoxa(pix1, boxa1, NULL);
819  * </pre>
820  */
821 PIX *
pixaDisplayTiledInRows(PIXA * pixa,l_int32 outdepth,l_int32 maxwidth,l_float32 scalefactor,l_int32 background,l_int32 spacing,l_int32 border)822 pixaDisplayTiledInRows(PIXA      *pixa,
823                        l_int32    outdepth,
824                        l_int32    maxwidth,
825                        l_float32  scalefactor,
826                        l_int32    background,
827                        l_int32    spacing,
828                        l_int32    border)
829 {
830 l_int32   h;  /* cumulative height over all the rows */
831 l_int32   w;  /* cumulative height in the current row */
832 l_int32   bordval, wtry, wt, ht;
833 l_int32   irow;  /* index of current pix in current row */
834 l_int32   wmaxrow;  /* width of the largest row */
835 l_int32   maxh;  /* max height in row */
836 l_int32   i, j, index, n, x, y, nrows, ninrow, res;
837 size_t    size;
838 l_uint8  *data;
839 BOXA     *boxa;
840 NUMA     *nainrow;  /* number of pix in the row */
841 NUMA     *namaxh;  /* height of max pix in the row */
842 PIX      *pix, *pixn, *pix1, *pixd;
843 PIXA     *pixan;
844 
845     PROCNAME("pixaDisplayTiledInRows");
846 
847     if (!pixa)
848         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
849     if (outdepth != 1 && outdepth != 8 && outdepth != 32)
850         return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
851     if (border < 0)
852         border = 0;
853     if (scalefactor <= 0.0) scalefactor = 1.0;
854 
855     if ((n = pixaGetCount(pixa)) == 0)
856         return (PIX *)ERROR_PTR("no components", procName, NULL);
857 
858         /* Normalize depths, scale, remove colormaps; optionally add border */
859     pixan = pixaCreate(n);
860     bordval = (outdepth == 1) ? 1 : 0;
861     for (i = 0; i < n; i++) {
862         if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
863             continue;
864 
865         if (outdepth == 1)
866             pixn = pixConvertTo1(pix, 128);
867         else if (outdepth == 8)
868             pixn = pixConvertTo8(pix, FALSE);
869         else  /* outdepth == 32 */
870             pixn = pixConvertTo32(pix);
871         pixDestroy(&pix);
872 
873         if (scalefactor != 1.0)
874             pix1 = pixScale(pixn, scalefactor, scalefactor);
875         else
876             pix1 = pixClone(pixn);
877         if (border)
878             pixd = pixAddBorder(pix1, border, bordval);
879         else
880             pixd = pixClone(pix1);
881         pixDestroy(&pixn);
882         pixDestroy(&pix1);
883 
884         pixaAddPix(pixan, pixd, L_INSERT);
885     }
886     if (pixaGetCount(pixan) != n) {
887         n = pixaGetCount(pixan);
888         L_WARNING("only got %d components\n", procName, n);
889         if (n == 0) {
890             pixaDestroy(&pixan);
891             return (PIX *)ERROR_PTR("no components", procName, NULL);
892         }
893     }
894 
895         /* Compute parameters for layout */
896     nainrow = numaCreate(0);
897     namaxh = numaCreate(0);
898     wmaxrow = 0;
899     w = h = spacing;
900     maxh = 0;  /* max height in row */
901     for (i = 0, irow = 0; i < n; i++, irow++) {
902         pixaGetPixDimensions(pixan, i, &wt, &ht, NULL);
903         wtry = w + wt + spacing;
904         if (wtry > maxwidth) {  /* end the current row and start next one */
905             numaAddNumber(nainrow, irow);
906             numaAddNumber(namaxh, maxh);
907             wmaxrow = L_MAX(wmaxrow, w);
908             h += maxh + spacing;
909             irow = 0;
910             w = wt + 2 * spacing;
911             maxh = ht;
912         } else {
913             w = wtry;
914             maxh = L_MAX(maxh, ht);
915         }
916     }
917 
918         /* Enter the parameters for the last row */
919     numaAddNumber(nainrow, irow);
920     numaAddNumber(namaxh, maxh);
921     wmaxrow = L_MAX(wmaxrow, w);
922     h += maxh + spacing;
923 
924     if ((pixd = pixCreate(wmaxrow, h, outdepth)) == NULL) {
925         numaDestroy(&nainrow);
926         numaDestroy(&namaxh);
927         pixaDestroy(&pixan);
928         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
929     }
930 
931         /* Reset the background color if necessary */
932     if ((background == 1 && outdepth == 1) ||
933         (background == 0 && outdepth != 1))
934         pixSetAll(pixd);
935 
936         /* Blit the images to the dest, and save the boxa identifying
937          * the image regions that do not include the borders. */
938     nrows = numaGetCount(nainrow);
939     y = spacing;
940     boxa = boxaCreate(n);
941     for (i = 0, index = 0; i < nrows; i++) {  /* over rows */
942         numaGetIValue(nainrow, i, &ninrow);
943         numaGetIValue(namaxh, i, &maxh);
944         x = spacing;
945         for (j = 0; j < ninrow; j++, index++) {   /* over pix in row */
946             pix = pixaGetPix(pixan, index, L_CLONE);
947             if (index == 0) {
948                 res = pixGetXRes(pix);
949                 pixSetResolution(pixd, res, res);
950             }
951             pixGetDimensions(pix, &wt, &ht, NULL);
952             boxaAddBox(boxa, boxCreate(x + border, y + border,
953                 wt - 2 * border, ht - 2 *border), L_INSERT);
954             pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix, 0, 0);
955             pixDestroy(&pix);
956             x += wt + spacing;
957         }
958         y += maxh + spacing;
959     }
960     boxaWriteMem(&data, &size, boxa);
961     pixSetText(pixd, (char *)data);  /* data is ascii */
962     LEPT_FREE(data);
963     boxaDestroy(&boxa);
964 
965     numaDestroy(&nainrow);
966     numaDestroy(&namaxh);
967     pixaDestroy(&pixan);
968     return pixd;
969 }
970 
971 
972 /*!
973  * \brief   pixaDisplayTiledInColumns()
974  *
975  * \param[in]    pixas
976  * \param[in]    nx           number of columns in output image
977  * \param[in]    scalefactor  applied to every pix; use 1.0 for no scaling
978  * \param[in]    spacing      between images, and on outside
979  * \param[in]    border       width of black border added to each image;
980  *                            use 0 for no border
981  * \return  pixd of tiled images, or NULL on error
982  *
983  * <pre>
984  * Notes:
985  *      (1) This renders a pixa to a single image with &nx columns of
986  *          subimages.  The background color is white, and each row
987  *          is tiled such that the top of each pix is aligned and
988  *          each pix is separated by 'spacing' from the next one.
989  *          A black border can be added to each pix.
990  *      (2) The output depth is determined by the largest depth
991  *          required by the pix in the pixa.  Colormaps are removed.
992  *      (3) A serialized boxa giving the location in pixd of each input
993  *          pix (without added border) is stored in the text string of pixd.
994  *          This allows, e.g., regeneration of a pixa from pixd, using
995  *          pixaCreateFromBoxa().  If there is no scaling and the depth of
996  *          each input pix in the pixa is the same, this tiling operation
997  *          can be inverted using the boxa (except for loss of text in
998  *          each of the input pix):
999  *            pix1 = pixaDisplayTiledInColumns(pixa1, 3, 1.0, 0, 30, 2);
1000  *            char *boxatxt = pixGetText(pix1);
1001  *            boxa1 = boxaReadMem((l_uint8 *)boxatxt, strlen(boxatxt));
1002  *            pixa2 = pixaCreateFromBoxa(pix1, boxa1, NULL);
1003  * </pre>
1004  */
1005 PIX *
pixaDisplayTiledInColumns(PIXA * pixas,l_int32 nx,l_float32 scalefactor,l_int32 spacing,l_int32 border)1006 pixaDisplayTiledInColumns(PIXA      *pixas,
1007                           l_int32    nx,
1008                           l_float32  scalefactor,
1009                           l_int32    spacing,
1010                           l_int32    border)
1011 {
1012 l_int32   i, j, index, n, x, y, nrows, wb, hb, w, h, maxd, maxh, bordval, res;
1013 size_t    size;
1014 l_uint8  *data;
1015 BOX      *box;
1016 BOXA     *boxa;
1017 PIX      *pix1, *pix2, *pix3, *pixd;
1018 PIXA     *pixa1, *pixa2;
1019 
1020     PROCNAME("pixaDisplayTiledInColumns");
1021 
1022     if (!pixas)
1023         return (PIX *)ERROR_PTR("pixas not defined", procName, NULL);
1024     if (border < 0)
1025         border = 0;
1026     if (scalefactor <= 0.0) scalefactor = 1.0;
1027 
1028     if ((n = pixaGetCount(pixas)) == 0)
1029         return (PIX *)ERROR_PTR("no components", procName, NULL);
1030 
1031         /* Convert to same depth, if necessary */
1032     pixa1 = pixaConvertToSameDepth(pixas);
1033     pixaGetDepthInfo(pixa1, &maxd, NULL);
1034 
1035         /* Scale and optionally add border */
1036     pixa2 = pixaCreate(n);
1037     bordval = (maxd == 1) ? 1 : 0;
1038     for (i = 0; i < n; i++) {
1039         if ((pix1 = pixaGetPix(pixa1, i, L_CLONE)) == NULL)
1040             continue;
1041         if (scalefactor != 1.0)
1042             pix2 = pixScale(pix1, scalefactor, scalefactor);
1043         else
1044             pix2 = pixClone(pix1);
1045         if (border)
1046             pix3 = pixAddBorder(pix2, border, bordval);
1047         else
1048             pix3 = pixClone(pix2);
1049         if (i == 0) res = pixGetXRes(pix3);
1050         pixaAddPix(pixa2, pix3, L_INSERT);
1051         pixDestroy(&pix1);
1052         pixDestroy(&pix2);
1053     }
1054     pixaDestroy(&pixa1);
1055     if (pixaGetCount(pixa2) != n) {
1056         n = pixaGetCount(pixa2);
1057         L_WARNING("only got %d components\n", procName, n);
1058         if (n == 0) {
1059             pixaDestroy(&pixa2);
1060             return (PIX *)ERROR_PTR("no components", procName, NULL);
1061         }
1062     }
1063 
1064         /* Compute layout parameters and save as a boxa */
1065     boxa = boxaCreate(n);
1066     nrows = (n + nx - 1) / nx;
1067     y = spacing;
1068     for (i = 0, index = 0; i < nrows; i++) {
1069         x = spacing;
1070         maxh = 0;
1071         for (j = 0; j < nx && index < n; j++) {
1072             pixaGetPixDimensions(pixa2, index, &wb, &hb, NULL);
1073             box = boxCreate(x, y, wb, hb);
1074             boxaAddBox(boxa, box, L_INSERT);
1075             maxh = L_MAX(maxh, hb + spacing);
1076             x += wb + spacing;
1077             index++;
1078         }
1079         y += maxh;
1080     }
1081     pixaSetBoxa(pixa2, boxa, L_INSERT);
1082 
1083         /* Render the output pix */
1084     boxaGetExtent(boxa, &w, &h, NULL);
1085     pixd = pixaDisplay(pixa2, w + spacing, h + spacing);
1086     pixSetResolution(pixd, res, res);
1087 
1088         /* Save the boxa in the text field of the output pix */
1089     boxaWriteMem(&data, &size, boxa);
1090     pixSetText(pixd, (char *)data);  /* data is ascii */
1091     LEPT_FREE(data);
1092 
1093     pixaDestroy(&pixa2);
1094     return pixd;
1095 }
1096 
1097 
1098 /*!
1099  * \brief   pixaDisplayTiledAndScaled()
1100  *
1101  * \param[in]    pixa
1102  * \param[in]    outdepth    output depth: 1, 8 or 32 bpp
1103  * \param[in]    tilewidth   each pix is scaled to this width
1104  * \param[in]    ncols       number of tiles in each row
1105  * \param[in]    background  0 for white, 1 for black; this is the color
1106  *                           of the spacing between the images
1107  * \param[in]    spacing     between images, and on outside
1108  * \param[in]    border      width of additional black border on each image;
1109  *                           use 0 for no border
1110  * \return  pix of tiled images, or NULL on error
1111  *
1112  * <pre>
1113  * Notes:
1114  *      (1) This can be used to tile a number of renderings of
1115  *          an image that are at different scales and depths.
1116  *      (2) Each image, after scaling and optionally adding the
1117  *          black border, has width 'tilewidth'.  Thus, the border does
1118  *          not affect the spacing between the image tiles.  The
1119  *          maximum allowed border width is tilewidth / 5.
1120  * </pre>
1121  */
1122 PIX *
pixaDisplayTiledAndScaled(PIXA * pixa,l_int32 outdepth,l_int32 tilewidth,l_int32 ncols,l_int32 background,l_int32 spacing,l_int32 border)1123 pixaDisplayTiledAndScaled(PIXA    *pixa,
1124                           l_int32  outdepth,
1125                           l_int32  tilewidth,
1126                           l_int32  ncols,
1127                           l_int32  background,
1128                           l_int32  spacing,
1129                           l_int32  border)
1130 {
1131 l_int32    x, y, w, h, wd, hd, d, res;
1132 l_int32    i, n, nrows, maxht, ninrow, irow, bordval;
1133 l_int32   *rowht;
1134 l_float32  scalefact;
1135 PIX       *pix, *pixn, *pix1, *pixb, *pixd;
1136 PIXA      *pixan;
1137 
1138     PROCNAME("pixaDisplayTiledAndScaled");
1139 
1140     if (!pixa)
1141         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
1142     if (outdepth != 1 && outdepth != 8 && outdepth != 32)
1143         return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
1144     if (ncols <= 0)
1145         return (PIX *)ERROR_PTR("ncols must be > 0", procName, NULL);
1146     if (border < 0 || border > tilewidth / 5)
1147         border = 0;
1148 
1149     if ((n = pixaGetCount(pixa)) == 0)
1150         return (PIX *)ERROR_PTR("no components", procName, NULL);
1151 
1152         /* Normalize scale and depth for each pix; optionally add border */
1153     pixan = pixaCreate(n);
1154     bordval = (outdepth == 1) ? 1 : 0;
1155     for (i = 0; i < n; i++) {
1156         if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
1157             continue;
1158 
1159         pixGetDimensions(pix, &w, &h, &d);
1160         scalefact = (l_float32)(tilewidth - 2 * border) / (l_float32)w;
1161         if (d == 1 && outdepth > 1 && scalefact < 1.0)
1162             pix1 = pixScaleToGray(pix, scalefact);
1163         else
1164             pix1 = pixScale(pix, scalefact, scalefact);
1165 
1166         if (outdepth == 1)
1167             pixn = pixConvertTo1(pix1, 128);
1168         else if (outdepth == 8)
1169             pixn = pixConvertTo8(pix1, FALSE);
1170         else  /* outdepth == 32 */
1171             pixn = pixConvertTo32(pix1);
1172         pixDestroy(&pix1);
1173 
1174         if (border)
1175             pixb = pixAddBorder(pixn, border, bordval);
1176         else
1177             pixb = pixClone(pixn);
1178 
1179         pixaAddPix(pixan, pixb, L_INSERT);
1180         pixDestroy(&pix);
1181         pixDestroy(&pixn);
1182     }
1183     if ((n = pixaGetCount(pixan)) == 0) { /* should not have changed! */
1184         pixaDestroy(&pixan);
1185         return (PIX *)ERROR_PTR("no components", procName, NULL);
1186     }
1187 
1188         /* Determine the size of each row and of pixd */
1189     wd = tilewidth * ncols + spacing * (ncols + 1);
1190     nrows = (n + ncols - 1) / ncols;
1191     if ((rowht = (l_int32 *)LEPT_CALLOC(nrows, sizeof(l_int32))) == NULL) {
1192         pixaDestroy(&pixan);
1193         return (PIX *)ERROR_PTR("rowht array not made", procName, NULL);
1194     }
1195     maxht = 0;
1196     ninrow = 0;
1197     irow = 0;
1198     for (i = 0; i < n; i++) {
1199         pix = pixaGetPix(pixan, i, L_CLONE);
1200         ninrow++;
1201         pixGetDimensions(pix, &w, &h, NULL);
1202         maxht = L_MAX(h, maxht);
1203         if (ninrow == ncols) {
1204             rowht[irow] = maxht;
1205             maxht = ninrow = 0;  /* reset */
1206             irow++;
1207         }
1208         pixDestroy(&pix);
1209     }
1210     if (ninrow > 0) {   /* last fencepost */
1211         rowht[irow] = maxht;
1212         irow++;  /* total number of rows */
1213     }
1214     nrows = irow;
1215     hd = spacing * (nrows + 1);
1216     for (i = 0; i < nrows; i++)
1217         hd += rowht[i];
1218 
1219     pixd = pixCreate(wd, hd, outdepth);
1220     if ((background == 1 && outdepth == 1) ||
1221         (background == 0 && outdepth != 1))
1222         pixSetAll(pixd);
1223 
1224         /* Now blit images to pixd */
1225     x = y = spacing;
1226     irow = 0;
1227     for (i = 0; i < n; i++) {
1228         pix = pixaGetPix(pixan, i, L_CLONE);
1229         if (i == 0) {
1230             res = pixGetXRes(pix);
1231             pixSetResolution(pixd, res, res);
1232         }
1233         pixGetDimensions(pix, &w, &h, NULL);
1234         if (i && ((i % ncols) == 0)) {  /* start new row */
1235             x = spacing;
1236             y += spacing + rowht[irow];
1237             irow++;
1238         }
1239         pixRasterop(pixd, x, y, w, h, PIX_SRC, pix, 0, 0);
1240         x += tilewidth + spacing;
1241         pixDestroy(&pix);
1242     }
1243 
1244     pixaDestroy(&pixan);
1245     LEPT_FREE(rowht);
1246     return pixd;
1247 }
1248 
1249 
1250 /*!
1251  * \brief   pixaDisplayTiledWithText()
1252  *
1253  * \param[in]    pixa
1254  * \param[in]    maxwidth     of output image
1255  * \param[in]    scalefactor  applied to every pix; use 1.0 for no scaling
1256  * \param[in]    spacing      between images, and on outside
1257  * \param[in]    border       width of black border added to each image;
1258  *                            use 0 for no border
1259  * \param[in]    fontsize     4, 6, ... 20
1260  * \param[in]    textcolor    0xrrggbb00
1261  * \return  pixd of tiled images, or NULL on error
1262  *
1263  * <pre>
1264  * Notes:
1265  *      (1) This is a version of pixaDisplayTiledInRows() that prints, below
1266  *          each pix, the text in the pix text field.  Up to 127 chars
1267  *          of text in the pix text field are rendered below each pix.
1268  *      (2) It renders a pixa to a single image of width not to
1269  *          exceed %maxwidth, with white background color, with each row
1270  *          tiled such that the top of each pix is aligned and separated
1271  *          by %spacing from the next one.
1272  *      (3) All pix are converted to 32 bpp.
1273  *      (4) This does a reasonably spacewise-efficient job of laying
1274  *          out the individual pix images into a tiled composite.
1275  * </pre>
1276  */
1277 PIX *
pixaDisplayTiledWithText(PIXA * pixa,l_int32 maxwidth,l_float32 scalefactor,l_int32 spacing,l_int32 border,l_int32 fontsize,l_uint32 textcolor)1278 pixaDisplayTiledWithText(PIXA      *pixa,
1279                          l_int32    maxwidth,
1280                          l_float32  scalefactor,
1281                          l_int32    spacing,
1282                          l_int32    border,
1283                          l_int32    fontsize,
1284                          l_uint32   textcolor)
1285 {
1286 char      buf[128];
1287 char     *textstr;
1288 l_int32   i, n, maxw;
1289 L_BMF    *bmf;
1290 PIX      *pix1, *pix2, *pix3, *pix4, *pixd;
1291 PIXA     *pixad;
1292 
1293     PROCNAME("pixaDisplayTiledWithText");
1294 
1295     if (!pixa)
1296         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
1297     if ((n = pixaGetCount(pixa)) == 0)
1298         return (PIX *)ERROR_PTR("no components", procName, NULL);
1299     if (maxwidth <= 0)
1300         return (PIX *)ERROR_PTR("invalid maxwidth", procName, NULL);
1301     if (border < 0)
1302         border = 0;
1303     if (scalefactor <= 0.0) {
1304         L_WARNING("invalid scalefactor; setting to 1.0\n", procName);
1305         scalefactor = 1.0;
1306     }
1307     if (fontsize < 4 || fontsize > 20 || (fontsize & 1)) {
1308         l_int32 fsize = L_MAX(L_MIN(fontsize, 20), 4);
1309         if (fsize & 1) fsize--;
1310         L_WARNING("changed fontsize from %d to %d\n", procName,
1311                   fontsize, fsize);
1312         fontsize = fsize;
1313     }
1314 
1315         /* Be sure the width can accommodate a single column of images */
1316     pixaSizeRange(pixa, NULL, NULL, &maxw, NULL);
1317     maxwidth = L_MAX(maxwidth, scalefactor * (maxw + 2 * spacing + 2 * border));
1318 
1319     bmf = bmfCreate(NULL, fontsize);
1320     pixad = pixaCreate(n);
1321     for (i = 0; i < n; i++) {
1322         pix1 = pixaGetPix(pixa, i, L_CLONE);
1323         pix2 = pixConvertTo32(pix1);
1324         pix3 = pixAddBorderGeneral(pix2, spacing, spacing, spacing,
1325                                    spacing, 0xffffff00);
1326         textstr = pixGetText(pix1);
1327         if (textstr && strlen(textstr) > 0) {
1328             snprintf(buf, sizeof(buf), "%s", textstr);
1329             pix4 = pixAddSingleTextblock(pix3, bmf, buf, textcolor,
1330                                      L_ADD_BELOW, NULL);
1331         } else {
1332             pix4 = pixClone(pix3);
1333         }
1334         pixaAddPix(pixad, pix4, L_INSERT);
1335         pixDestroy(&pix1);
1336         pixDestroy(&pix2);
1337         pixDestroy(&pix3);
1338     }
1339     bmfDestroy(&bmf);
1340 
1341     pixd = pixaDisplayTiledInRows(pixad, 32, maxwidth, scalefactor,
1342                                   0, 10, border);
1343     pixaDestroy(&pixad);
1344     return pixd;
1345 }
1346 
1347 
1348 /*!
1349  * \brief   pixaDisplayTiledByIndex()
1350  *
1351  * \param[in]    pixa
1352  * \param[in]    na         numa with indices corresponding to the pix in pixa
1353  * \param[in]    width      each pix is scaled to this width
1354  * \param[in]    spacing    between images, and on outside
1355  * \param[in]    border     width of black border added to each image;
1356  *                          use 0 for no border
1357  * \param[in]    fontsize   4, 6, ... 20
1358  * \param[in]    textcolor  0xrrggbb00
1359  * \return  pixd of tiled images, or NULL on error
1360  *
1361  * <pre>
1362  * Notes:
1363  *      (1) This renders a pixa to a single image with white
1364  *          background color, where the pix are placed in columns
1365  *          given by the index value in the numa.  Each pix
1366  *          is separated by %spacing from the adjacent ones, and
1367  *          an optional border is placed around them.
1368  *      (2) Up to 127 chars of text in the pix text field are rendered
1369  *          below each pix.  Use newlines in the text field to write
1370  *          the text in multiple lines that fit within the pix width.
1371  *      (3) To avoid having empty columns, if there are N different
1372  *          index values, they should be in [0 ... N-1].
1373  *      (4) All pix are converted to 32 bpp.
1374  * </pre>
1375  */
1376 PIX *
pixaDisplayTiledByIndex(PIXA * pixa,NUMA * na,l_int32 width,l_int32 spacing,l_int32 border,l_int32 fontsize,l_uint32 textcolor)1377 pixaDisplayTiledByIndex(PIXA     *pixa,
1378                         NUMA     *na,
1379                         l_int32   width,
1380                         l_int32   spacing,
1381                         l_int32   border,
1382                         l_int32   fontsize,
1383                         l_uint32  textcolor)
1384 {
1385 char      buf[128];
1386 char     *textstr;
1387 l_int32    i, n, x, y, w, h, yval, index;
1388 l_float32  maxindex;
1389 L_BMF     *bmf;
1390 BOX       *box;
1391 NUMA      *nay;  /* top of the next pix to add in that column */
1392 PIX       *pix1, *pix2, *pix3, *pix4, *pix5, *pixd;
1393 PIXA      *pixad;
1394 
1395     PROCNAME("pixaDisplayTiledByIndex");
1396 
1397     if (!pixa)
1398         return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
1399     if (!na)
1400         return (PIX *)ERROR_PTR("na not defined", procName, NULL);
1401     if ((n = pixaGetCount(pixa)) == 0)
1402         return (PIX *)ERROR_PTR("no pixa components", procName, NULL);
1403     if (n != numaGetCount(na))
1404         return (PIX *)ERROR_PTR("pixa and na counts differ", procName, NULL);
1405     if (width <= 0)
1406         return (PIX *)ERROR_PTR("invalid width", procName, NULL);
1407     if (width < 20)
1408         L_WARNING("very small width: %d\n", procName, width);
1409     if (border < 0)
1410         border = 0;
1411     if (fontsize < 4 || fontsize > 20 || (fontsize & 1)) {
1412         l_int32 fsize = L_MAX(L_MIN(fontsize, 20), 4);
1413         if (fsize & 1) fsize--;
1414         L_WARNING("changed fontsize from %d to %d\n", procName,
1415                   fontsize, fsize);
1416         fontsize = fsize;
1417     }
1418 
1419         /* The pix will be rendered in the order they occupy in pixa. */
1420     bmf = bmfCreate(NULL, fontsize);
1421     pixad = pixaCreate(n);
1422     numaGetMax(na, &maxindex, NULL);
1423     nay = numaMakeConstant(spacing, lept_roundftoi(maxindex) + 1);
1424     for (i = 0; i < n; i++) {
1425         numaGetIValue(na, i, &index);
1426         numaGetIValue(nay, index, &yval);
1427         pix1 = pixaGetPix(pixa, i, L_CLONE);
1428         pix2 = pixConvertTo32(pix1);
1429         pix3 = pixScaleToSize(pix2, width, 0);
1430         pix4 = pixAddBorderGeneral(pix3, border, border, border, border, 0);
1431         textstr = pixGetText(pix1);
1432         if (textstr && strlen(textstr) > 0) {
1433             snprintf(buf, sizeof(buf), "%s", textstr);
1434             pix5 = pixAddTextlines(pix4, bmf, textstr, textcolor, L_ADD_BELOW);
1435         } else {
1436             pix5 = pixClone(pix4);
1437         }
1438         pixaAddPix(pixad, pix5, L_INSERT);
1439         x = spacing + border + index * (2 * border + width + spacing);
1440         y = yval;
1441         pixGetDimensions(pix5, &w, &h, NULL);
1442         yval += h + spacing;
1443         numaSetValue(nay, index, yval);
1444         box = boxCreate(x, y, w, h);
1445         pixaAddBox(pixad, box, L_INSERT);
1446         pixDestroy(&pix1);
1447         pixDestroy(&pix2);
1448         pixDestroy(&pix3);
1449         pixDestroy(&pix4);
1450     }
1451     numaDestroy(&nay);
1452     bmfDestroy(&bmf);
1453 
1454     pixd = pixaDisplay(pixad, 0, 0);
1455     pixaDestroy(&pixad);
1456     return pixd;
1457 }
1458 
1459 
1460 
1461 /*---------------------------------------------------------------------*
1462  *                              Pixaa Display                          *
1463  *---------------------------------------------------------------------*/
1464 /*!
1465  * \brief   pixaaDisplay()
1466  *
1467  * \param[in]    paa
1468  * \param[in]    w, h   if set to 0, the size is determined from the
1469  *                      bounding box of the components in pixa
1470  * \return  pix, or NULL on error
1471  *
1472  * <pre>
1473  * Notes:
1474  *      (1) Each pix of the paa is displayed at the location given by
1475  *          its box, translated by the box of the containing pixa
1476  *          if it exists.
1477  * </pre>
1478  */
1479 PIX *
pixaaDisplay(PIXAA * paa,l_int32 w,l_int32 h)1480 pixaaDisplay(PIXAA   *paa,
1481              l_int32  w,
1482              l_int32  h)
1483 {
1484 l_int32  i, j, n, nbox, na, d, wmax, hmax, x, y, xb, yb, wb, hb;
1485 BOXA    *boxa1;  /* top-level boxa */
1486 BOXA    *boxa;
1487 PIX     *pix1, *pixd;
1488 PIXA    *pixa;
1489 
1490     PROCNAME("pixaaDisplay");
1491 
1492     if (!paa)
1493         return (PIX *)ERROR_PTR("paa not defined", procName, NULL);
1494 
1495     n = pixaaGetCount(paa, NULL);
1496     if (n == 0)
1497         return (PIX *)ERROR_PTR("no components", procName, NULL);
1498 
1499         /* If w and h not input, determine the minimum size required
1500          * to contain the origin and all c.c. */
1501     boxa1 = pixaaGetBoxa(paa, L_CLONE);
1502     nbox = boxaGetCount(boxa1);
1503     if (w == 0 || h == 0) {
1504         if (nbox == n) {
1505             boxaGetExtent(boxa1, &w, &h, NULL);
1506         } else {  /* have to use the lower-level boxa for each pixa */
1507             wmax = hmax = 0;
1508             for (i = 0; i < n; i++) {
1509                 pixa = pixaaGetPixa(paa, i, L_CLONE);
1510                 boxa = pixaGetBoxa(pixa, L_CLONE);
1511                 boxaGetExtent(boxa, &w, &h, NULL);
1512                 wmax = L_MAX(wmax, w);
1513                 hmax = L_MAX(hmax, h);
1514                 pixaDestroy(&pixa);
1515                 boxaDestroy(&boxa);
1516             }
1517             w = wmax;
1518             h = hmax;
1519         }
1520     }
1521 
1522         /* Get depth from first pix */
1523     pixa = pixaaGetPixa(paa, 0, L_CLONE);
1524     pix1 = pixaGetPix(pixa, 0, L_CLONE);
1525     d = pixGetDepth(pix1);
1526     pixaDestroy(&pixa);
1527     pixDestroy(&pix1);
1528 
1529     if ((pixd = pixCreate(w, h, d)) == NULL) {
1530         boxaDestroy(&boxa1);
1531         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
1532     }
1533 
1534     x = y = 0;
1535     for (i = 0; i < n; i++) {
1536         pixa = pixaaGetPixa(paa, i, L_CLONE);
1537         if (nbox == n)
1538             boxaGetBoxGeometry(boxa1, i, &x, &y, NULL, NULL);
1539         na = pixaGetCount(pixa);
1540         for (j = 0; j < na; j++) {
1541             pixaGetBoxGeometry(pixa, j, &xb, &yb, &wb, &hb);
1542             pix1 = pixaGetPix(pixa, j, L_CLONE);
1543             pixRasterop(pixd, x + xb, y + yb, wb, hb, PIX_PAINT, pix1, 0, 0);
1544             pixDestroy(&pix1);
1545         }
1546         pixaDestroy(&pixa);
1547     }
1548     boxaDestroy(&boxa1);
1549 
1550     return pixd;
1551 }
1552 
1553 
1554 /*!
1555  * \brief   pixaaDisplayByPixa()
1556  *
1557  * \param[in]    paa     with pix that may have different depths
1558  * \param[in]    xspace  between pix in pixa
1559  * \param[in]    yspace  between pixa
1560  * \param[in]    maxw    max width of output pix
1561  * \return  pixd, or NULL on error
1562  *
1563  * <pre>
1564  * Notes:
1565  *      (1) Displays each pixa on a line (or set of lines),
1566  *          in order from top to bottom.  Within each pixa,
1567  *          the pix are displayed in order from left to right.
1568  *      (2) The sizes and depths of each pix can differ.  The output pix
1569  *          has a depth equal to the max depth of all the pix.
1570  *      (3) This ignores the boxa of the paa.
1571  * </pre>
1572  */
1573 PIX *
pixaaDisplayByPixa(PIXAA * paa,l_int32 xspace,l_int32 yspace,l_int32 maxw)1574 pixaaDisplayByPixa(PIXAA   *paa,
1575                    l_int32  xspace,
1576                    l_int32  yspace,
1577                    l_int32  maxw)
1578 {
1579 l_int32   i, j, npixa, npix, same, use_maxw, x, y, w, h, hindex;
1580 l_int32   maxwidth, maxd, width, lmaxh, lmaxw;
1581 l_int32  *harray;
1582 NUMA     *nah;
1583 PIX      *pix, *pix1, *pixd;
1584 PIXA     *pixa;
1585 
1586     PROCNAME("pixaaDisplayByPixa");
1587 
1588     if (!paa)
1589         return (PIX *)ERROR_PTR("paa not defined", procName, NULL);
1590 
1591     if ((npixa = pixaaGetCount(paa, NULL)) == 0)
1592         return (PIX *)ERROR_PTR("no components", procName, NULL);
1593     pixaaVerifyDepth(paa, &same, &maxd);
1594     if (!same && maxd < 8)
1595         return (PIX *)ERROR_PTR("depths differ; max < 8", procName, NULL);
1596 
1597         /* Be sure the widest box fits in the output pix */
1598     pixaaSizeRange(paa, NULL, NULL, &maxwidth, NULL);
1599     if (maxwidth > maxw) {
1600         L_WARNING("maxwidth > maxw; using maxwidth\n", procName);
1601         maxw = maxwidth;
1602     }
1603 
1604         /* Get size of output pix.  The width is the minimum of the
1605          * maxw and the largest pixa line width.  The height is whatever
1606          * it needs to be to accommodate all pixa. */
1607     lmaxw = 0;  /* widest line found */
1608     use_maxw = FALSE;
1609     nah = numaCreate(0);  /* store height of each line */
1610     y = yspace;
1611     for (i = 0; i < npixa; i++) {
1612         pixa = pixaaGetPixa(paa, i, L_CLONE);
1613         npix = pixaGetCount(pixa);
1614         if (npix == 0) {
1615             pixaDestroy(&pixa);
1616             continue;
1617         }
1618         x = xspace;
1619         lmaxh = 0;  /* max height found in the line */
1620         for (j = 0; j < npix; j++) {
1621             pix = pixaGetPix(pixa, j, L_CLONE);
1622             pixGetDimensions(pix, &w, &h, NULL);
1623             if (x + w >= maxw) {  /* start new line */
1624                 x = xspace;
1625                 y += lmaxh + yspace;
1626                 numaAddNumber(nah, lmaxh);
1627                 lmaxh = 0;
1628                 use_maxw = TRUE;
1629             }
1630             x += w + xspace;
1631             lmaxh = L_MAX(h, lmaxh);
1632             lmaxw = L_MAX(lmaxw, x);
1633             pixDestroy(&pix);
1634         }
1635         y += lmaxh + yspace;
1636         numaAddNumber(nah, lmaxh);
1637         pixaDestroy(&pixa);
1638     }
1639     width = (use_maxw) ? maxw : lmaxw;
1640 
1641     if ((pixd = pixCreate(width, y, maxd)) == NULL) {
1642         numaDestroy(&nah);
1643         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
1644     }
1645 
1646         /* Now layout the pix by pixa */
1647     y = yspace;
1648     harray = numaGetIArray(nah);
1649     hindex = 0;
1650     for (i = 0; i < npixa; i++) {
1651         x = xspace;
1652         pixa = pixaaGetPixa(paa, i, L_CLONE);
1653         npix = pixaGetCount(pixa);
1654         if (npix == 0) {
1655             pixaDestroy(&pixa);
1656             continue;
1657         }
1658         for (j = 0; j < npix; j++) {
1659             pix = pixaGetPix(pixa, j, L_CLONE);
1660             if (pixGetDepth(pix) != maxd) {
1661                 if (maxd == 8)
1662                      pix1 = pixConvertTo8(pix, 0);
1663                 else  /* 32 bpp */
1664                      pix1 = pixConvertTo32(pix);
1665             } else {
1666                 pix1 = pixClone(pix);
1667             }
1668             pixGetDimensions(pix1, &w, &h, NULL);
1669             if (x + w >= maxw) {  /* start new line */
1670                 x = xspace;
1671                 y += harray[hindex++] + yspace;
1672             }
1673             pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix1, 0, 0);
1674             pixDestroy(&pix);
1675             pixDestroy(&pix1);
1676             x += w + xspace;
1677         }
1678         y += harray[hindex++] + yspace;
1679         pixaDestroy(&pixa);
1680     }
1681     LEPT_FREE(harray);
1682 
1683     numaDestroy(&nah);
1684     return pixd;
1685 }
1686 
1687 
1688 /*!
1689  * \brief   pixaaDisplayTiledAndScaled()
1690  *
1691  * \param[in]    paa
1692  * \param[in]    outdepth    output depth: 1, 8 or 32 bpp
1693  * \param[in]    tilewidth   each pix is scaled to this width
1694  * \param[in]    ncols       number of tiles in each row
1695  * \param[in]    background  0 for white, 1 for black; this is the color
1696  *                           of the spacing between the images
1697  * \param[in]    spacing     between images, and on outside
1698  * \param[in]    border      width of additional black border on each image;
1699  *                           use 0 for no border
1700  * \return  pixa of tiled images, one image for each pixa in
1701  *                    the paa, or NULL on error
1702  *
1703  * <pre>
1704  * Notes:
1705  *      (1) For each pixa, this generates from all the pix a
1706  *          tiled/scaled output pix, and puts it in the output pixa.
1707  *      (2) See comments in pixaDisplayTiledAndScaled().
1708  * </pre>
1709  */
1710 PIXA *
pixaaDisplayTiledAndScaled(PIXAA * paa,l_int32 outdepth,l_int32 tilewidth,l_int32 ncols,l_int32 background,l_int32 spacing,l_int32 border)1711 pixaaDisplayTiledAndScaled(PIXAA   *paa,
1712                            l_int32  outdepth,
1713                            l_int32  tilewidth,
1714                            l_int32  ncols,
1715                            l_int32  background,
1716                            l_int32  spacing,
1717                            l_int32  border)
1718 {
1719 l_int32  i, n;
1720 PIX     *pix;
1721 PIXA    *pixa, *pixad;
1722 
1723     PROCNAME("pixaaDisplayTiledAndScaled");
1724 
1725     if (!paa)
1726         return (PIXA *)ERROR_PTR("paa not defined", procName, NULL);
1727     if (outdepth != 1 && outdepth != 8 && outdepth != 32)
1728         return (PIXA *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
1729     if (ncols <= 0)
1730         return (PIXA *)ERROR_PTR("ncols must be > 0", procName, NULL);
1731     if (border < 0 || border > tilewidth / 5)
1732         border = 0;
1733 
1734     if ((n = pixaaGetCount(paa, NULL)) == 0)
1735         return (PIXA *)ERROR_PTR("no components", procName, NULL);
1736 
1737     pixad = pixaCreate(n);
1738     for (i = 0; i < n; i++) {
1739         pixa = pixaaGetPixa(paa, i, L_CLONE);
1740         pix = pixaDisplayTiledAndScaled(pixa, outdepth, tilewidth, ncols,
1741                                         background, spacing, border);
1742         pixaAddPix(pixad, pix, L_INSERT);
1743         pixaDestroy(&pixa);
1744     }
1745 
1746     return pixad;
1747 }
1748 
1749 
1750 /*---------------------------------------------------------------------*
1751  *         Conversion of all pix to specified type (e.g., depth)       *
1752  *---------------------------------------------------------------------*/
1753 /*!
1754  * \brief   pixaConvertTo1()
1755  *
1756  * \param[in]    pixas
1757  * \param[in]    thresh    threshold for final binarization from 8 bpp gray
1758  * \return  pixad, or NULL on error
1759  */
1760 PIXA *
pixaConvertTo1(PIXA * pixas,l_int32 thresh)1761 pixaConvertTo1(PIXA    *pixas,
1762                l_int32  thresh)
1763 {
1764 l_int32  i, n;
1765 BOXA    *boxa;
1766 PIX     *pix1, *pix2;
1767 PIXA    *pixad;
1768 
1769     PROCNAME("pixaConvertTo1");
1770 
1771     if (!pixas)
1772         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
1773 
1774     n = pixaGetCount(pixas);
1775     pixad = pixaCreate(n);
1776     for (i = 0; i < n; i++) {
1777         pix1 = pixaGetPix(pixas, i, L_CLONE);
1778         pix2 = pixConvertTo1(pix1, thresh);
1779         pixaAddPix(pixad, pix2, L_INSERT);
1780         pixDestroy(&pix1);
1781     }
1782 
1783     boxa = pixaGetBoxa(pixas, L_COPY);
1784     pixaSetBoxa(pixad, boxa, L_INSERT);
1785     return pixad;
1786 }
1787 
1788 
1789 /*!
1790  * \brief   pixaConvertTo8()
1791  *
1792  * \param[in]    pixas
1793  * \param[in]    cmapflag   1 to give pixd a colormap; 0 otherwise
1794  * \return  pixad each pix is 8 bpp, or NULL on error
1795  *
1796  * <pre>
1797  * Notes:
1798  *      (1) See notes for pixConvertTo8(), applied to each pix in pixas.
1799  * </pre>
1800  */
1801 PIXA *
pixaConvertTo8(PIXA * pixas,l_int32 cmapflag)1802 pixaConvertTo8(PIXA    *pixas,
1803                l_int32  cmapflag)
1804 {
1805 l_int32  i, n;
1806 BOXA    *boxa;
1807 PIX     *pix1, *pix2;
1808 PIXA    *pixad;
1809 
1810     PROCNAME("pixaConvertTo8");
1811 
1812     if (!pixas)
1813         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
1814 
1815     n = pixaGetCount(pixas);
1816     pixad = pixaCreate(n);
1817     for (i = 0; i < n; i++) {
1818         pix1 = pixaGetPix(pixas, i, L_CLONE);
1819         pix2 = pixConvertTo8(pix1, cmapflag);
1820         pixaAddPix(pixad, pix2, L_INSERT);
1821         pixDestroy(&pix1);
1822     }
1823 
1824     boxa = pixaGetBoxa(pixas, L_COPY);
1825     pixaSetBoxa(pixad, boxa, L_INSERT);
1826     return pixad;
1827 }
1828 
1829 
1830 /*!
1831  * \brief   pixaConvertTo8Colormap()
1832  *
1833  * \param[in]    pixas
1834  * \param[in]    dither   1 to dither if necessary; 0 otherwise
1835  * \return  pixad each pix is 8 bpp, or NULL on error
1836  *
1837  * <pre>
1838  * Notes:
1839  *      (1) See notes for pixConvertTo8Colormap(), applied to each pix in pixas.
1840  * </pre>
1841  */
1842 PIXA *
pixaConvertTo8Colormap(PIXA * pixas,l_int32 dither)1843 pixaConvertTo8Colormap(PIXA    *pixas,
1844                        l_int32  dither)
1845 {
1846 l_int32  i, n;
1847 BOXA    *boxa;
1848 PIX     *pix1, *pix2;
1849 PIXA    *pixad;
1850 
1851     PROCNAME("pixaConvertTo8Colormap");
1852 
1853     if (!pixas)
1854         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
1855 
1856     n = pixaGetCount(pixas);
1857     pixad = pixaCreate(n);
1858     for (i = 0; i < n; i++) {
1859         pix1 = pixaGetPix(pixas, i, L_CLONE);
1860         pix2 = pixConvertTo8Colormap(pix1, dither);
1861         pixaAddPix(pixad, pix2, L_INSERT);
1862         pixDestroy(&pix1);
1863     }
1864 
1865     boxa = pixaGetBoxa(pixas, L_COPY);
1866     pixaSetBoxa(pixad, boxa, L_INSERT);
1867     return pixad;
1868 }
1869 
1870 
1871 /*!
1872  * \brief   pixaConvertTo32()
1873  *
1874  * \param[in]    pixas
1875  * \return  pixad 32 bpp rgb, or NULL on error
1876  *
1877  * <pre>
1878  * Notes:
1879  *      (1) See notes for pixConvertTo32(), applied to each pix in pixas.
1880  *      (2) This can be used to allow 1 bpp pix in a pixa to be displayed
1881  *          with color.
1882  * </pre>
1883  */
1884 PIXA *
pixaConvertTo32(PIXA * pixas)1885 pixaConvertTo32(PIXA  *pixas)
1886 {
1887 l_int32  i, n;
1888 BOXA    *boxa;
1889 PIX     *pix1, *pix2;
1890 PIXA    *pixad;
1891 
1892     PROCNAME("pixaConvertTo32");
1893 
1894     if (!pixas)
1895         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
1896 
1897     n = pixaGetCount(pixas);
1898     pixad = pixaCreate(n);
1899     for (i = 0; i < n; i++) {
1900         pix1 = pixaGetPix(pixas, i, L_CLONE);
1901         pix2 = pixConvertTo32(pix1);
1902         pixaAddPix(pixad, pix2, L_INSERT);
1903         pixDestroy(&pix1);
1904     }
1905 
1906     boxa = pixaGetBoxa(pixas, L_COPY);
1907     pixaSetBoxa(pixad, boxa, L_INSERT);
1908     return pixad;
1909 }
1910 
1911 
1912 /*---------------------------------------------------------------------*
1913  *                        Pixa constrained selection                   *
1914  *---------------------------------------------------------------------*/
1915 /*!
1916  * \brief   pixaConstrainedSelect()
1917  *
1918  * \param[in]    pixas
1919  * \param[in]    first      first index to choose; >= 0
1920  * \param[in]    last       biggest possible index to reach;
1921  *                          use -1 to go to the end; otherwise, last >= first
1922  * \param[in]    nmax       maximum number of pix to select; > 0
1923  * \param[in]    use_pairs  1 = select pairs of adjacent pix;
1924  *                          0 = select individual pix
1925  * \param[in]    copyflag   L_COPY, L_CLONE
1926  * \return  pixad if OK, NULL on error
1927  *
1928  * <pre>
1929  * Notes:
1930  *     (1) See notes in genConstrainedNumaInRange() for how selection
1931  *         is made.
1932  *     (2) This returns a selection of the pix in the input pixa.
1933  *     (3) Use copyflag == L_COPY if you don't want changes in the pix
1934  *         in the returned pixa to affect those in the input pixa.
1935  * </pre>
1936  */
1937 PIXA *
pixaConstrainedSelect(PIXA * pixas,l_int32 first,l_int32 last,l_int32 nmax,l_int32 use_pairs,l_int32 copyflag)1938 pixaConstrainedSelect(PIXA    *pixas,
1939                       l_int32  first,
1940                       l_int32  last,
1941                       l_int32  nmax,
1942                       l_int32  use_pairs,
1943                       l_int32  copyflag)
1944 {
1945 l_int32  i, n, nselect, index;
1946 NUMA    *na;
1947 PIX     *pix1;
1948 PIXA    *pixad;
1949 
1950     PROCNAME("pixaConstrainedSelect");
1951 
1952     if (!pixas)
1953         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
1954     n = pixaGetCount(pixas);
1955     first = L_MAX(0, first);
1956     last = (last < 0) ? n - 1 : L_MIN(n - 1, last);
1957     if (last < first)
1958         return (PIXA *)ERROR_PTR("last < first!", procName, NULL);
1959     if (nmax < 1)
1960         return (PIXA *)ERROR_PTR("nmax < 1!", procName, NULL);
1961 
1962     na = genConstrainedNumaInRange(first, last, nmax, use_pairs);
1963     nselect = numaGetCount(na);
1964     pixad = pixaCreate(nselect);
1965     for (i = 0; i < nselect; i++) {
1966         numaGetIValue(na, i, &index);
1967         pix1 = pixaGetPix(pixas, index, copyflag);
1968         pixaAddPix(pixad, pix1, L_INSERT);
1969     }
1970     numaDestroy(&na);
1971     return pixad;
1972 }
1973 
1974 
1975 /*!
1976  * \brief   pixaSelectToPdf()
1977  *
1978  * \param[in]    pixas
1979  * \param[in]    first     first index to choose; >= 0
1980  * \param[in]    last      biggest possible index to reach;
1981  *                         use -1 to go to the end; otherwise, last >= first
1982  * \param[in]    res       override the resolution of each input image, in ppi;
1983  *                         use 0 to respect the resolution embedded in the input
1984  * \param[in]    scalefactor   scaling factor applied to each image; > 0.0
1985  * \param[in]    type      encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
1986  *                         L_FLATE_ENCODE, or 0 for default
1987  * \param[in]    quality   used for JPEG only; 0 for default (75)
1988  * \param[in]    color     of numbers added to each image (e.g., 0xff000000)
1989  * \param[in]    fontsize  to print number below each image.  The valid set
1990  *                         is {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.
1991  * \param[in]    fileout   pdf file of all images
1992  * \return  0 if OK, 1 on error
1993  *
1994  * <pre>
1995  * Notes:
1996  *      (1) This writes a pdf of the selected images from %pixas, one to
1997  *          a page.  They are optionally scaled and annotated with the
1998  *          index printed to the left of the image.
1999  *      (2) If the input images are 1 bpp and you want the numbers to be
2000  *          in color, first promote each pix to 8 bpp with a colormap:
2001  *                pixa1 = pixaConvertTo8(pixas, 1);
2002  *          and then call this function with the specified color
2003  * </pre>
2004  */
2005 l_int32
pixaSelectToPdf(PIXA * pixas,l_int32 first,l_int32 last,l_int32 res,l_float32 scalefactor,l_int32 type,l_int32 quality,l_uint32 color,l_int32 fontsize,const char * fileout)2006 pixaSelectToPdf(PIXA        *pixas,
2007                 l_int32      first,
2008                 l_int32      last,
2009                 l_int32      res,
2010                 l_float32    scalefactor,
2011                 l_int32      type,
2012                 l_int32      quality,
2013                 l_uint32     color,
2014                 l_int32      fontsize,
2015                 const char  *fileout)
2016 {
2017 l_int32  n;
2018 L_BMF   *bmf;
2019 NUMA    *na;
2020 PIXA    *pixa1, *pixa2;
2021 
2022     PROCNAME("pixaSelectToPdf");
2023 
2024     if (!pixas)
2025         return ERROR_INT("pixas not defined", procName, 1);
2026     if (type < 0 || type > L_FLATE_ENCODE) {
2027         L_WARNING("invalid compression type; using default\n", procName);
2028         type = 0;
2029     }
2030     if (!fileout)
2031         return ERROR_INT("fileout not defined", procName, 1);
2032 
2033         /* Select from given range */
2034     n = pixaGetCount(pixas);
2035     first = L_MAX(0, first);
2036     last = (last < 0) ? n - 1 : L_MIN(n - 1, last);
2037     if (first > last) {
2038         L_ERROR("first = %d > last = %d\n", procName, first, last);
2039         return 1;
2040     }
2041     pixa1 = pixaSelectRange(pixas, first, last, L_CLONE);
2042 
2043         /* Optionally add index numbers */
2044     bmf = (fontsize <= 0) ? NULL : bmfCreate(NULL, fontsize);
2045     if (bmf) {
2046         na = numaMakeSequence(first, 1.0, last - first + 1);
2047         pixa2 = pixaAddTextNumber(pixa1, bmf, na, color, L_ADD_LEFT);
2048         numaDestroy(&na);
2049     } else {
2050         pixa2 = pixaCopy(pixa1, L_CLONE);
2051     }
2052     pixaDestroy(&pixa1);
2053     bmfDestroy(&bmf);
2054 
2055     pixaConvertToPdf(pixa2, res, scalefactor, type, quality, NULL, fileout);
2056     pixaDestroy(&pixa2);
2057     return 0;
2058 }
2059 
2060 
2061 /*---------------------------------------------------------------------*
2062  *                     Pixa display into multiple tiles                *
2063  *---------------------------------------------------------------------*/
2064 /*!
2065  * \brief   pixaDisplayMultiTiled()
2066  *
2067  * \param[in]    pixas
2068  * \param[in]    nx, ny       in [1, ... 50], tiling factors in each direction
2069  * \param[in]    maxw, maxh   max sizes to keep
2070  * \param[in]    scalefactor  scale each image by this
2071  * \param[in]    spacing      between images, and on outside
2072  * \param[in]    border       width of additional black border on each image;
2073  *                            use 0 for no border
2074  * \return  pixad if OK, NULL on error
2075  *
2076  * <pre>
2077  * Notes:
2078  *      (1) Each set of %nx * %ny images is optionally scaled and saved
2079  *          into a new pix, and then aggregated.
2080  *      (2) Set %maxw = %maxh = 0 if you want to include all pix from %pixs.
2081  *      (3) This is useful for generating a pdf from the output pixa, where
2082  *          each page is a tile of (%nx * %ny) images from the input pixa.
2083  * </pre>
2084  */
2085 PIXA *
pixaDisplayMultiTiled(PIXA * pixas,l_int32 nx,l_int32 ny,l_int32 maxw,l_int32 maxh,l_float32 scalefactor,l_int32 spacing,l_int32 border)2086 pixaDisplayMultiTiled(PIXA      *pixas,
2087                       l_int32    nx,
2088                       l_int32    ny,
2089                       l_int32    maxw,
2090                       l_int32    maxh,
2091                       l_float32  scalefactor,
2092                       l_int32    spacing,
2093                       l_int32    border)
2094 {
2095 l_int32  n, i, j, ntile, nout, index;
2096 PIX     *pix1, *pix2;
2097 PIXA    *pixa1, *pixa2, *pixad;
2098 
2099     PROCNAME("pixaDisplayMultiTiled");
2100 
2101     if (!pixas)
2102         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
2103     if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
2104         return (PIXA *)ERROR_PTR("invalid tiling factor(s)", procName, NULL);
2105     if ((n = pixaGetCount(pixas)) == 0)
2106         return (PIXA *)ERROR_PTR("pixas is empty", procName, NULL);
2107 
2108         /* Filter out large ones if requested */
2109     if (maxw == 0 && maxh == 0) {
2110         pixa1 = pixaCopy(pixas, L_CLONE);
2111     } else {
2112         maxw = (maxw == 0) ? 1000000 : maxw;
2113         maxh = (maxh == 0) ? 1000000 : maxh;
2114         pixa1 = pixaSelectBySize(pixas, maxw, maxh, L_SELECT_IF_BOTH,
2115                                  L_SELECT_IF_LTE, NULL);
2116         n = pixaGetCount(pixa1);
2117     }
2118 
2119     ntile = nx * ny;
2120     nout = L_MAX(1, (n + ntile - 1) / ntile);
2121     pixad = pixaCreate(nout);
2122     for (i = 0, index = 0; i < nout; i++) {  /* over tiles */
2123         pixa2 = pixaCreate(ntile);
2124         for (j = 0; j < ntile && index < n; j++, index++) {
2125             pix1 = pixaGetPix(pixa1, index, L_COPY);
2126             pixaAddPix(pixa2, pix1, L_INSERT);
2127         }
2128         pix2 = pixaDisplayTiledInColumns(pixa2, nx, scalefactor, spacing,
2129                                          border);
2130         pixaAddPix(pixad, pix2, L_INSERT);
2131         pixaDestroy(&pixa2);
2132     }
2133     pixaDestroy(&pixa1);
2134 
2135     return pixad;
2136 }
2137 
2138 
2139 /*---------------------------------------------------------------------*
2140  *                       Split pixa into files                         *
2141  *---------------------------------------------------------------------*/
2142 /*!
2143  * \brief   pixaSplitIntoFiles()
2144  *
2145  * \param[in]    pixas
2146  * \param[in]    nsplit       split pixas into this number of pixa; >= 2
2147  * \param[in]    scale        scalefactor applied to each pix
2148  * \param[in]    outwidth     the maxwidth parameter of tiled images
2149  *                            for write_pix
2150  * \param[in]    write_pixa  1 to write the split pixa as separate files
2151  * \param[in]    write_pix   1 to write tiled images of the split pixa
2152  * \param[in]    write_pdf   1 to write pdfs of the split pixa
2153  * \return  0 if OK, 1 on error
2154  *
2155  * <pre>
2156  * Notes:
2157  *      (1) For each requested output, %nsplit files are written into
2158  *          directory /tmp/lept/split/.
2159  *      (2) This is useful when a pixa is so large that the images
2160  *          are not conveniently displayed as a single tiled image at
2161  *          full resolution.
2162  * </pre>
2163  */
2164 l_int32
pixaSplitIntoFiles(PIXA * pixas,l_int32 nsplit,l_float32 scale,l_int32 outwidth,l_int32 write_pixa,l_int32 write_pix,l_int32 write_pdf)2165 pixaSplitIntoFiles(PIXA      *pixas,
2166                    l_int32    nsplit,
2167                    l_float32  scale,
2168                    l_int32    outwidth,
2169                    l_int32    write_pixa,
2170                    l_int32    write_pix,
2171                    l_int32    write_pdf)
2172 {
2173 char     buf[64];
2174 l_int32  i, j, index, n, nt;
2175 PIX     *pix1, *pix2;
2176 PIXA    *pixa1;
2177 
2178     PROCNAME("pixaSplitIntoFiles");
2179 
2180     if (!pixas)
2181         return ERROR_INT("pixas not defined", procName, 1);
2182     if (nsplit <= 1)
2183         return ERROR_INT("nsplit must be >= 2", procName, 1);
2184     if ((nt = pixaGetCount(pixas)) == 0)
2185         return ERROR_INT("pixas is empty", procName, 1);
2186     if (!write_pixa && !write_pix && !write_pdf)
2187         return ERROR_INT("no output is requested", procName, 1);
2188 
2189     lept_mkdir("lept/split");
2190     n = (nt + nsplit - 1) / nsplit;
2191     fprintf(stderr, "nt = %d, n = %d, nsplit = %d\n", nt, n, nsplit);
2192     for (i = 0, index = 0; i < nsplit; i++) {
2193         pixa1 = pixaCreate(n);
2194         for (j = 0; j < n && index < nt; j++, index++) {
2195             pix1 = pixaGetPix(pixas, index, L_CLONE);
2196             pix2 = pixScale(pix1, scale, scale);
2197             pixaAddPix(pixa1, pix2, L_INSERT);
2198             pixDestroy(&pix1);
2199         }
2200         if (write_pixa) {
2201             snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.pa", i + 1);
2202             pixaWriteDebug(buf, pixa1);
2203         }
2204         if (write_pix) {
2205             snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.tif", i + 1);
2206             pix1 = pixaDisplayTiledInRows(pixa1, 1, outwidth, 1.0, 0, 20, 2);
2207             pixWriteDebug(buf, pix1, IFF_TIFF_G4);
2208             pixDestroy(&pix1);
2209         }
2210         if (write_pdf) {
2211             snprintf(buf, sizeof(buf), "/tmp/lept/split/split%d.pdf", i + 1);
2212             pixaConvertToPdf(pixa1, 0, 1.0, L_G4_ENCODE, 0, buf, buf);
2213         }
2214         pixaDestroy(&pixa1);
2215     }
2216 
2217     return 0;
2218 }
2219 
2220 
2221 /*---------------------------------------------------------------------*
2222  *                               Tile N-Up                             *
2223  *---------------------------------------------------------------------*/
2224 /*!
2225  * \brief   convertToNUpFiles()
2226  *
2227  * \param[in]    dir        full path to directory of images
2228  * \param[in]    substr     [optional] can be null
2229  * \param[in]    nx, ny     in [1, ... 50], tiling factors in each direction
2230  * \param[in]    tw         target width, in pixels; must be >= 20
2231  * \param[in]    spacing    between images, and on outside
2232  * \param[in]    border     width of additional black border on each image;
2233  *                          use 0 for no border
2234  * \param[in]    fontsize   to print tail of filename with image.  Valid set is
2235  *                          {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.
2236  * \param[in]    outdir     subdirectory of /tmp to put N-up tiled images
2237  * \return  0 if OK, 1 on error
2238  *
2239  * <pre>
2240  * Notes:
2241  *      (1) Each set of %nx * %ny images is scaled and tiled into a single
2242  *          image, that is written out to %outdir.
2243  *      (2) All images in each %nx * %ny set are scaled to the same
2244  *          width, %tw.  This is typically used when all images are
2245  *          roughly the same size.
2246  *      (3) This is useful for generating a pdf from the set of input
2247  *          files, where each page is a tile of (%nx * %ny) input images.
2248  *          Typical values for %nx and %ny are in the range [2 ... 5].
2249  *      (4) If %fontsize != 0, each image has the tail of its filename
2250  *          rendered below it.
2251  * </pre>
2252  */
2253 l_int32
convertToNUpFiles(const char * dir,const char * substr,l_int32 nx,l_int32 ny,l_int32 tw,l_int32 spacing,l_int32 border,l_int32 fontsize,const char * outdir)2254 convertToNUpFiles(const char  *dir,
2255                   const char  *substr,
2256                   l_int32      nx,
2257                   l_int32      ny,
2258                   l_int32      tw,
2259                   l_int32      spacing,
2260                   l_int32      border,
2261                   l_int32      fontsize,
2262                   const char  *outdir)
2263 {
2264 l_int32  d, format;
2265 char     rootpath[256];
2266 PIXA    *pixa;
2267 
2268     PROCNAME("convertToNUpFiles");
2269 
2270     if (!dir)
2271         return ERROR_INT("dir not defined", procName, 1);
2272     if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
2273         return ERROR_INT("invalid tiling N-factor", procName, 1);
2274     if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
2275         return ERROR_INT("invalid fontsize", procName, 1);
2276     if (!outdir)
2277         return ERROR_INT("outdir not defined", procName, 1);
2278 
2279     pixa = convertToNUpPixa(dir, substr, nx, ny, tw, spacing, border,
2280                             fontsize);
2281     if (!pixa)
2282         return ERROR_INT("pixa not made", procName, 1);
2283 
2284     lept_rmdir(outdir);
2285     lept_mkdir(outdir);
2286     pixaGetRenderingDepth(pixa, &d);
2287     format = (d == 1) ? IFF_TIFF_G4 : IFF_JFIF_JPEG;
2288     makeTempDirname(rootpath, 256, outdir);
2289     modifyTrailingSlash(rootpath, 256, L_ADD_TRAIL_SLASH);
2290     pixaWriteFiles(rootpath, pixa, format);
2291     pixaDestroy(&pixa);
2292     return 0;
2293 }
2294 
2295 
2296 /*!
2297  * \brief   convertToNUpPixa()
2298  *
2299  * \param[in]    dir       full path to directory of images
2300  * \param[in]    substr    [optional] can be null
2301  * \param[in]    nx, ny    in [1, ... 50], tiling factors in each direction
2302  * \param[in]    tw        target width, in pixels; must be >= 20
2303  * \param[in]    spacing   between images, and on outside
2304  * \param[in]    border    width of additional black border on each image;
2305  *                         use 0 for no border
2306  * \param[in]    fontsize  to print tail of filename with image.  Valid set is
2307  *                         {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.
2308  * \return  pixad, or NULL on error
2309  *
2310  * <pre>
2311  * Notes:
2312  *      (1) See notes for convertToNUpFiles()
2313  * </pre>
2314  */
2315 PIXA *
convertToNUpPixa(const char * dir,const char * substr,l_int32 nx,l_int32 ny,l_int32 tw,l_int32 spacing,l_int32 border,l_int32 fontsize)2316 convertToNUpPixa(const char  *dir,
2317                  const char  *substr,
2318                  l_int32      nx,
2319                  l_int32      ny,
2320                  l_int32      tw,
2321                  l_int32      spacing,
2322                  l_int32      border,
2323                  l_int32      fontsize)
2324 {
2325 l_int32  i, n;
2326 char    *fname, *tail;
2327 PIXA    *pixa1, *pixa2;
2328 SARRAY  *sa1, *sa2;
2329 
2330     PROCNAME("convertToNUpPixa");
2331 
2332     if (!dir)
2333         return (PIXA *)ERROR_PTR("dir not defined", procName, NULL);
2334     if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
2335         return (PIXA *)ERROR_PTR("invalid tiling N-factor", procName, NULL);
2336     if (tw < 20)
2337         return (PIXA *)ERROR_PTR("tw must be >= 20", procName, NULL);
2338     if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
2339         return (PIXA *)ERROR_PTR("invalid fontsize", procName, NULL);
2340 
2341     sa1 = getSortedPathnamesInDirectory(dir, substr, 0, 0);
2342     pixa1 = pixaReadFilesSA(sa1);
2343     n = sarrayGetCount(sa1);
2344     sa2 = sarrayCreate(n);
2345     for (i = 0; i < n; i++) {
2346         fname = sarrayGetString(sa1, i, L_NOCOPY);
2347         splitPathAtDirectory(fname, NULL, &tail);
2348         sarrayAddString(sa2, tail, L_INSERT);
2349     }
2350     sarrayDestroy(&sa1);
2351     pixa2 = pixaConvertToNUpPixa(pixa1, sa2, nx, ny, tw, spacing,
2352                                  border, fontsize);
2353     pixaDestroy(&pixa1);
2354     sarrayDestroy(&sa2);
2355     return pixa2;
2356 }
2357 
2358 
2359 /*!
2360  * \brief   pixaConvertToNUpPixa()
2361  *
2362  * \param[in]    pixas
2363  * \param[in]    sa        [optional] array of strings associated with each pix
2364  * \param[in]    nx, ny    in [1, ... 50], tiling factors in each direction
2365  * \param[in]    tw        target width, in pixels; must be >= 20
2366  * \param[in]    spacing   between images, and on outside
2367  * \param[in]    border    width of additional black border on each image;
2368  *                         use 0 for no border
2369  * \param[in]    fontsize  to print string with each image.  Valid set is
2370  *                         {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.
2371  * \return  pixad, or NULL on error
2372  *
2373  * <pre>
2374  * Notes:
2375  *      (1) This takes an input pixa and an optional array of strings, and
2376  *          generates a pixa of NUp tiles from the input, labeled with
2377  *          the strings if they exist and %fontsize != 0.
2378  *      (2) See notes for convertToNUpFiles()
2379  * </pre>
2380  */
2381 PIXA *
pixaConvertToNUpPixa(PIXA * pixas,SARRAY * sa,l_int32 nx,l_int32 ny,l_int32 tw,l_int32 spacing,l_int32 border,l_int32 fontsize)2382 pixaConvertToNUpPixa(PIXA    *pixas,
2383                      SARRAY  *sa,
2384                      l_int32  nx,
2385                      l_int32  ny,
2386                      l_int32  tw,
2387                      l_int32  spacing,
2388                      l_int32  border,
2389                      l_int32  fontsize)
2390 {
2391 l_int32    i, j, k, nt, n2, nout, d;
2392 char      *str;
2393 L_BMF     *bmf;
2394 PIX       *pix1, *pix2, *pix3, *pix4;
2395 PIXA      *pixa1, *pixad;
2396 
2397     PROCNAME("pixaConvertToNUpPixa");
2398 
2399     if (!pixas)
2400         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
2401     if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
2402         return (PIXA *)ERROR_PTR("invalid tiling N-factor", procName, NULL);
2403     if (tw < 20)
2404         return (PIXA *)ERROR_PTR("tw must be >= 20", procName, NULL);
2405     if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
2406         return (PIXA *)ERROR_PTR("invalid fontsize", procName, NULL);
2407 
2408     nt = pixaGetCount(pixas);
2409     if (sa && (sarrayGetCount(sa) != nt)) {
2410         L_WARNING("pixa size %d not equal to sarray size %d\n", procName,
2411                   nt, sarrayGetCount(sa));
2412     }
2413 
2414     n2 = nx * ny;
2415     nout = (nt + n2 - 1) / n2;
2416     pixad = pixaCreate(nout);
2417     bmf = (fontsize == 0) ? NULL : bmfCreate(NULL, fontsize);
2418     for (i = 0, j = 0; i < nout; i++) {
2419         pixa1 = pixaCreate(n2);
2420         for (k = 0; k < n2 && j < nt; j++, k++) {
2421             pix1 = pixaGetPix(pixas, j, L_CLONE);
2422             pix2 = pixScaleToSize(pix1, tw, 0);  /* all images have width tw */
2423             if (bmf && sa) {
2424                 str = sarrayGetString(sa, j, L_NOCOPY);
2425                 pix3 = pixAddTextlines(pix2, bmf, str, 0xff000000,
2426                                        L_ADD_BELOW);
2427             } else {
2428                 pix3 = pixClone(pix2);
2429             }
2430             pixaAddPix(pixa1, pix3, L_INSERT);
2431             pixDestroy(&pix1);
2432             pixDestroy(&pix2);
2433         }
2434         if (pixaGetCount(pixa1) == 0) {  /* probably won't happen */
2435             pixaDestroy(&pixa1);
2436             continue;
2437         }
2438 
2439             /* Add 2 * border to image width to prevent scaling */
2440         pixaGetRenderingDepth(pixa1, &d);
2441         pix4 = pixaDisplayTiledAndScaled(pixa1, d, tw + 2 * border, nx, 0,
2442                                          spacing, border);
2443         pixaAddPix(pixad, pix4, L_INSERT);
2444         pixaDestroy(&pixa1);
2445     }
2446 
2447     bmfDestroy(&bmf);
2448     return pixad;
2449 }
2450 
2451 
2452 /*---------------------------------------------------------------------*
2453  *            Render two pixa side-by-side for comparison              *
2454  *---------------------------------------------------------------------*/
2455 /*!
2456  * \brief   pixaCompareInPdf()
2457  *
2458  * \param[in]    pixa1
2459  * \param[in]    pixa2
2460  * \param[in]    nx, ny     in [1, ... 20], tiling factors in each direction
2461  * \param[in]    tw         target width, in pixels; must be >= 20
2462  * \param[in]    spacing    between images, and on outside
2463  * \param[in]    border     width of additional black border on each image
2464  *                          and on each pair; use 0 for no border
2465  * \param[in]    fontsize   to print index of each pair of images.  Valid set
2466  *                          is {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.
2467  * \param[in]    fileout    output pdf file
2468  * \return  0 if OK, 1 on error
2469  *
2470  * <pre>
2471  * Notes:
2472  *      (1) This takes two pixa and renders them interleaved, side-by-side
2473  *          in a pdf.  A warning is issued if the input pixa arrays
2474  *          have different lengths.
2475  *      (2) %nx and %ny specify how many side-by-side pairs are displayed
2476  *          on each pdf page.  For example, if %nx = 1 and %ny = 2, then
2477  *          two pairs are shown, one above the other, on each page.
2478  *      (3) The input pix are scaled to a target width of %tw, and
2479  *          then paired with optional %spacing between and optional
2480  *          black border of width %border.
2481  *      (4) After a pixa is generated of these tiled images, it is
2482  *          written to %fileout as a pdf.
2483  *      (5) Typical numbers for the input parameters are:
2484  *            %nx = small integer (1 - 4)
2485  *            %ny = 2 * %nx
2486  *            %tw = 200 - 500 pixels
2487  *            %spacing = 10
2488  *            %border = 2
2489  *            %fontsize = 10
2490  *      (6) If %fontsize != 0, the index of the pix pair in their pixa
2491  *          is printed out below each pair.
2492  * </pre>
2493  */
2494 l_int32
pixaCompareInPdf(PIXA * pixa1,PIXA * pixa2,l_int32 nx,l_int32 ny,l_int32 tw,l_int32 spacing,l_int32 border,l_int32 fontsize,const char * fileout)2495 pixaCompareInPdf(PIXA        *pixa1,
2496                  PIXA        *pixa2,
2497                  l_int32      nx,
2498                  l_int32      ny,
2499                  l_int32      tw,
2500                  l_int32      spacing,
2501                  l_int32      border,
2502                  l_int32      fontsize,
2503                  const char  *fileout)
2504 {
2505 l_int32  n1, n2, npairs;
2506 PIXA    *pixa3, *pixa4, *pixa5;
2507 SARRAY  *sa;
2508 
2509     PROCNAME("pixaCompareInPdf");
2510 
2511     if (!pixa1 || !pixa2)
2512         return ERROR_INT("pixa1 and pixa2 not both defined", procName, 1);
2513     if (nx < 1 || ny < 1 || nx > 20 || ny > 20)
2514         return ERROR_INT("invalid tiling factors", procName, 1);
2515     if (tw < 20)
2516         return ERROR_INT("invalid tw; tw must be >= 20", procName, 1);
2517     if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
2518         return ERROR_INT("invalid fontsize", procName, 1);
2519     if (!fileout)
2520         return ERROR_INT("fileout not defined", procName, 1);
2521     n1 = pixaGetCount(pixa1);
2522     n2 = pixaGetCount(pixa2);
2523     if (n1 == 0 || n2 == 0)
2524         return ERROR_INT("at least one pixa is empty", procName, 1);
2525     if (n1 != n2)
2526         L_WARNING("sizes (%d, %d) differ; using the minimum in interleave\n",
2527                   procName, n1, n2);
2528 
2529         /* Interleave the input pixa */
2530     if ((pixa3 = pixaInterleave(pixa1, pixa2, L_CLONE)) == NULL)
2531         return ERROR_INT("pixa3 not made", procName, 1);
2532 
2533         /* Scale the images if necessary and pair them up side/by/side */
2534     pixa4 = pixaConvertToNUpPixa(pixa3, NULL, 2, 1, tw, spacing, border, 0);
2535     pixaDestroy(&pixa3);
2536 
2537         /* Label the pairs and mosaic into pages without further scaling */
2538     npairs = pixaGetCount(pixa4);
2539     sa = (fontsize > 0) ? sarrayGenerateIntegers(npairs) : NULL;
2540     pixa5 = pixaConvertToNUpPixa(pixa4, sa, nx, ny,
2541                                  2 * tw + 4 * border + spacing,
2542                                  spacing, border, fontsize);
2543     pixaDestroy(&pixa4);
2544     sarrayDestroy(&sa);
2545 
2546         /* Output as pdf without scaling */
2547     pixaConvertToPdf(pixa5, 0, 1.0, 0, 0, NULL, fileout);
2548     pixaDestroy(&pixa5);
2549     return 0;
2550 }
2551 
2552 
2553