comparison mupdf-source/thirdparty/leptonica/src/pixafunc2.c @ 2:b50eed0cc0ef upstream

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