Mercurial > hgrepos > Python2 > PyMuPDF
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 |
