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