Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/pixabasic.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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/pixabasic.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,3134 @@ +/*====================================================================* + - Copyright (C) 2001 Leptonica. All rights reserved. + - + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - 2. Redistributions in binary form must reproduce the above + - copyright notice, this list of conditions and the following + - disclaimer in the documentation and/or other materials + - provided with the distribution. + - + - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY + - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *====================================================================*/ + +/*! + * \file pixabasic.c + * <pre> + * + * Pixa creation, destruction, copying + * PIXA *pixaCreate() + * PIXA *pixaCreateFromPix() + * PIXA *pixaCreateFromBoxa() + * PIXA *pixaSplitPix() + * void pixaDestroy() + * PIXA *pixaCopy() + * + * Pixa addition + * l_int32 pixaAddPix() + * l_int32 pixaAddBox() + * static l_int32 pixaExtendArray() + * l_int32 pixaExtendArrayToSize() + * + * Pixa accessors + * l_int32 pixaGetCount() + * PIX *pixaGetPix() + * l_int32 pixaGetPixDimensions() + * BOXA *pixaGetBoxa() + * l_int32 pixaGetBoxaCount() + * BOX *pixaGetBox() + * l_int32 pixaGetBoxGeometry() + * l_int32 pixaSetBoxa() + * PIX **pixaGetPixArray() + * l_int32 pixaVerifyDepth() + * l_int32 pixaVerifyDimensions() + * l_int32 pixaIsFull() + * l_int32 pixaCountText() + * l_int32 pixaSetText() + * void ***pixaGetLinePtrs() + * + * Pixa output info + * l_int32 pixaWriteStreamInfo() + * + * Pixa array modifiers + * l_int32 pixaReplacePix() + * l_int32 pixaInsertPix() + * l_int32 pixaRemovePix() + * l_int32 pixaRemovePixAndSave() + * l_int32 pixaRemoveSelected() + * l_int32 pixaInitFull() + * l_int32 pixaClear() + * + * Pixa and Pixaa combination + * l_int32 pixaJoin() + * PIXA *pixaInterleave() + * l_int32 pixaaJoin() + * + * Pixaa creation, destruction + * PIXAA *pixaaCreate() + * PIXAA *pixaaCreateFromPixa() + * void pixaaDestroy() + * + * Pixaa addition + * l_int32 pixaaAddPixa() + * static l_int32 pixaaExtendArray() + * l_int32 pixaaAddPix() + * l_int32 pixaaAddBox() + * + * Pixaa accessors + * l_int32 pixaaGetCount() + * PIXA *pixaaGetPixa() + * BOXA *pixaaGetBoxa() + * PIX *pixaaGetPix() + * l_int32 pixaaVerifyDepth() + * l_int32 pixaaVerifyDimensions() + * l_int32 pixaaIsFull() + * + * Pixaa array modifiers + * l_int32 pixaaInitFull() + * l_int32 pixaaReplacePixa() + * l_int32 pixaaClear() + * l_int32 pixaaTruncate() + * + * Pixa serialized I/O (requires png support) + * PIXA *pixaRead() + * PIXA *pixaReadStream() + * PIXA *pixaReadMem() + * l_int32 pixaWriteDebug() + * l_int32 pixaWrite() + * l_int32 pixaWriteStream() + * l_int32 pixaWriteMem() + * PIXA *pixaReadBoth() + * + * Pixaa serialized I/O (requires png support) + * PIXAA *pixaaReadFromFiles() + * PIXAA *pixaaRead() + * PIXAA *pixaaReadStream() + * PIXAA *pixaaReadMem() + * l_int32 pixaaWrite() + * l_int32 pixaaWriteStream() + * l_int32 pixaaWriteMem() + * + * + * Important note on reference counting: + * Reference counting for the Pixa is analogous to that for the Boxa. + * See pix.h for details. pixaCopy() provides three possible modes + * of copy. The basic rule is that however a Pixa is obtained + * (e.g., from pixaCreate*(), pixaCopy(), or a Pixaa accessor), + * it is necessary to call pixaDestroy() on it. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include "allheaders.h" +#include "pix_internal.h" + + /* Bounds on array sizes */ +static const size_t MaxInitPtrArraySize = 100000; +static const size_t MaxPixaPtrArraySize = 5000000; +static const size_t MaxPixaaPtrArraySize = 1000000; +static const size_t InitialPtrArraySize = 20; /*!< n'importe quoi */ + + /* Static functions */ +static l_int32 pixaExtendArray(PIXA *pixa); +static l_int32 pixaaExtendArray(PIXAA *paa); + +/*---------------------------------------------------------------------* + * Pixa creation, destruction, copy * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaCreate() + * + * \param[in] n initial number of ptrs + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) This creates an empty boxa. + * </pre> + */ +PIXA * +pixaCreate(l_int32 n) +{ +PIXA *pixa; + + if (n <= 0 || n > MaxInitPtrArraySize) + n = InitialPtrArraySize; + + pixa = (PIXA *)LEPT_CALLOC(1, sizeof(PIXA)); + pixa->n = 0; + pixa->nalloc = n; + pixa->refcount = 1; + pixa->pix = (PIX **)LEPT_CALLOC(n, sizeof(PIX *)); + pixa->boxa = boxaCreate(n); + if (!pixa->pix || !pixa->boxa) { + pixaDestroy(&pixa); + return (PIXA *)ERROR_PTR("pix or boxa not made", __func__, NULL); + } + return pixa; +} + + +/*! + * \brief pixaCreateFromPix() + * + * \param[in] pixs with individual components on a lattice + * \param[in] n number of components + * \param[in] cellw width of each cell + * \param[in] cellh height of each cell + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) For bpp = 1, we truncate each retrieved pix to the ON + * pixels, which we assume for now start at (0,0) + * </pre> + */ +PIXA * +pixaCreateFromPix(PIX *pixs, + l_int32 n, + l_int32 cellw, + l_int32 cellh) +{ +l_int32 w, h, d, nw, nh, i, j, index; +PIX *pix1, *pix2; +PIXA *pixa; + + if (!pixs) + return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL); + if (n <= 0) + return (PIXA *)ERROR_PTR("n must be > 0", __func__, NULL); + + if ((pixa = pixaCreate(n)) == NULL) + return (PIXA *)ERROR_PTR("pixa not made", __func__, NULL); + pixGetDimensions(pixs, &w, &h, &d); + if ((pix1 = pixCreate(cellw, cellh, d)) == NULL) { + pixaDestroy(&pixa); + return (PIXA *)ERROR_PTR("pix1 not made", __func__, NULL); + } + + nw = (w + cellw - 1) / cellw; + nh = (h + cellh - 1) / cellh; + for (i = 0, index = 0; i < nh; i++) { + for (j = 0; j < nw && index < n; j++, index++) { + pixRasterop(pix1, 0, 0, cellw, cellh, PIX_SRC, pixs, + j * cellw, i * cellh); + if (d == 1 && !pixClipToForeground(pix1, &pix2, NULL)) + pixaAddPix(pixa, pix2, L_INSERT); + else + pixaAddPix(pixa, pix1, L_COPY); + } + } + + pixDestroy(&pix1); + return pixa; +} + + +/*! + * \brief pixaCreateFromBoxa() + * + * \param[in] pixs + * \param[in] boxa + * \param[in] start first box to use + * \param[in] num number of boxes; use 0 to go to the end + * \param[out] pcropwarn [optional] TRUE if the boxa extent + * is larger than pixs. + * \return pixad, or NULL on error + * + * <pre> + * Notes: + * (1) This simply extracts from pixs the region corresponding to each + * box in the boxa. To extract all the regions, set both %start + * and %num to 0. + * (2) The 5th arg is optional. If the extent of the boxa exceeds the + * size of the pixa, so that some boxes are either clipped + * or entirely outside the pix, a warning is returned as TRUE. + * (3) pixad will have only the properly clipped elements, and + * the internal boxa will be correct. + * </pre> + */ +PIXA * +pixaCreateFromBoxa(PIX *pixs, + BOXA *boxa, + l_int32 start, + l_int32 num, + l_int32 *pcropwarn) +{ +l_int32 i, n, end, w, h, wbox, hbox, cropwarn; +BOX *box, *boxc; +PIX *pixd; +PIXA *pixad; + + if (!pixs) + return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!boxa) + return (PIXA *)ERROR_PTR("boxa not defined", __func__, NULL); + if (num < 0) + return (PIXA *)ERROR_PTR("num must be >= 0", __func__, NULL); + + n = boxaGetCount(boxa); + end = (num == 0) ? n - 1 : L_MIN(start + num - 1, n - 1); + if ((pixad = pixaCreate(end - start + 1)) == NULL) + return (PIXA *)ERROR_PTR("pixad not made", __func__, NULL); + + boxaGetExtent(boxa, &wbox, &hbox, NULL); + pixGetDimensions(pixs, &w, &h, NULL); + cropwarn = FALSE; + if (wbox > w || hbox > h) + cropwarn = TRUE; + if (pcropwarn) + *pcropwarn = cropwarn; + + for (i = start; i <= end; i++) { + box = boxaGetBox(boxa, i, L_COPY); + if (cropwarn) { /* if box is outside pixs, pixd is NULL */ + pixd = pixClipRectangle(pixs, box, &boxc); /* may be NULL */ + if (pixd) { + pixaAddPix(pixad, pixd, L_INSERT); + pixaAddBox(pixad, boxc, L_INSERT); + } + boxDestroy(&box); + } else { + pixd = pixClipRectangle(pixs, box, NULL); + pixaAddPix(pixad, pixd, L_INSERT); + pixaAddBox(pixad, box, L_INSERT); + } + } + + return pixad; +} + + +/*! + * \brief pixaSplitPix() + * + * \param[in] pixs with individual components on a lattice + * \param[in] nx number of mosaic cells horizontally + * \param[in] ny number of mosaic cells vertically + * \param[in] borderwidth of added border on all sides + * \param[in] bordercolor in our RGBA format: 0xrrggbbaa + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) This is a variant on pixaCreateFromPix(), where we + * simply divide the image up into (approximately) equal + * subunits. If you want the subimages to have essentially + * the same aspect ratio as the input pix, use nx = ny. + * (2) If borderwidth is 0, we ignore the input bordercolor and + * redefine it to white. + * (3) The bordercolor is always used to initialize each tiled pix, + * so that if the src is clipped, the unblitted part will + * be this color. This avoids 1 pixel wide black stripes at the + * left and lower edges. + * </pre> + */ +PIXA * +pixaSplitPix(PIX *pixs, + l_int32 nx, + l_int32 ny, + l_int32 borderwidth, + l_uint32 bordercolor) +{ +l_int32 w, h, d, cellw, cellh, i, j; +PIX *pix1; +PIXA *pixa; + + if (!pixs) + return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL); + if (nx <= 0 || ny <= 0) + return (PIXA *)ERROR_PTR("nx and ny must be > 0", __func__, NULL); + borderwidth = L_MAX(0, borderwidth); + + if ((pixa = pixaCreate(nx * ny)) == NULL) + return (PIXA *)ERROR_PTR("pixa not made", __func__, NULL); + pixGetDimensions(pixs, &w, &h, &d); + cellw = (w + nx - 1) / nx; /* round up */ + cellh = (h + ny - 1) / ny; + + for (i = 0; i < ny; i++) { + for (j = 0; j < nx; j++) { + if ((pix1 = pixCreate(cellw + 2 * borderwidth, + cellh + 2 * borderwidth, d)) == NULL) { + pixaDestroy(&pixa); + return (PIXA *)ERROR_PTR("pix1 not made", __func__, NULL); + } + pixCopyColormap(pix1, pixs); + if (borderwidth == 0) { /* initialize full image to white */ + if (d == 1) + pixClearAll(pix1); + else + pixSetAll(pix1); + } else { + pixSetAllArbitrary(pix1, bordercolor); + } + pixRasterop(pix1, borderwidth, borderwidth, cellw, cellh, + PIX_SRC, pixs, j * cellw, i * cellh); + pixaAddPix(pixa, pix1, L_INSERT); + } + } + + return pixa; +} + + +/*! + * \brief pixaDestroy() + * + * \param[in,out] ppixa use ptr address so it will be nulled + * + * <pre> + * Notes: + * (1) Decrements the ref count and, if 0, destroys the pixa. + * (2) Always nulls the input ptr. + * </pre> + */ +void +pixaDestroy(PIXA **ppixa) +{ +l_int32 i; +PIXA *pixa; + + if (ppixa == NULL) { + L_WARNING("ptr address is NULL!\n", __func__); + return; + } + + if ((pixa = *ppixa) == NULL) + return; + + /* Decrement the refcount. If it is 0, destroy the pixa. */ + if (--pixa->refcount == 0) { + for (i = 0; i < pixa->n; i++) + pixDestroy(&pixa->pix[i]); + LEPT_FREE(pixa->pix); + boxaDestroy(&pixa->boxa); + LEPT_FREE(pixa); + } + + *ppixa = NULL; +} + + +/*! + * \brief pixaCopy() + * + * \param[in] pixa + * \param[in] copyflag see pix.h for details: + * L_COPY makes a new pixa and copies each pix and each box; + * L_CLONE gives a new ref-counted handle to the input pixa; + * L_COPY_CLONE makes a new pixa and inserts clones of + * all pix and boxes + * \return new pixa, or NULL on error + */ +PIXA * +pixaCopy(PIXA *pixa, + l_int32 copyflag) +{ +l_int32 i, nb; +BOX *boxc = NULL; +PIX *pixc; +PIXA *pixac; + + if (!pixa) + return (PIXA *)ERROR_PTR("pixa not defined", __func__, NULL); + + if (copyflag == L_CLONE) { + ++pixa->refcount; + return pixa; + } + + if (copyflag != L_COPY && copyflag != L_COPY_CLONE) + return (PIXA *)ERROR_PTR("invalid copyflag", __func__, NULL); + + if ((pixac = pixaCreate(pixa->n)) == NULL) + return (PIXA *)ERROR_PTR("pixac not made", __func__, NULL); + nb = pixaGetBoxaCount(pixa); + for (i = 0; i < pixa->n; i++) { + if (copyflag == L_COPY) { + pixc = pixaGetPix(pixa, i, L_COPY); + if (i < nb) boxc = pixaGetBox(pixa, i, L_COPY); + } else { /* copy-clone */ + pixc = pixaGetPix(pixa, i, L_CLONE); + if (i < nb) boxc = pixaGetBox(pixa, i, L_CLONE); + } + pixaAddPix(pixac, pixc, L_INSERT); + if (i < nb) pixaAddBox(pixac, boxc, L_INSERT); + } + + return pixac; +} + + + +/*---------------------------------------------------------------------* + * Pixa addition * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaAddPix() + * + * \param[in] pixa + * \param[in] pix to be added + * \param[in] copyflag L_INSERT, L_COPY, L_CLONE + * \return 0 if OK; 1 on error + */ +l_ok +pixaAddPix(PIXA *pixa, + PIX *pix, + l_int32 copyflag) +{ +l_int32 n; +PIX *pixc; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (!pix) + return ERROR_INT("pix not defined", __func__, 1); + + if (copyflag == L_INSERT) + pixc = pix; + else if (copyflag == L_COPY) + pixc = pixCopy(NULL, pix); + else if (copyflag == L_CLONE) + pixc = pixClone(pix); + else + return ERROR_INT("invalid copyflag", __func__, 1); + if (!pixc) + return ERROR_INT("pixc not made", __func__, 1); + + n = pixaGetCount(pixa); + if (n >= pixa->nalloc) { + if (pixaExtendArray(pixa)) { + if (copyflag != L_INSERT) + pixDestroy(&pixc); + return ERROR_INT("extension failed", __func__, 1); + } + } + + pixa->pix[n] = pixc; + pixa->n++; + return 0; +} + + +/*! + * \brief pixaAddBox() + * + * \param[in] pixa + * \param[in] box + * \param[in] copyflag L_INSERT, L_COPY, L_CLONE + * \return 0 if OK, 1 on error + */ +l_ok +pixaAddBox(PIXA *pixa, + BOX *box, + l_int32 copyflag) +{ + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE) + return ERROR_INT("invalid copyflag", __func__, 1); + + boxaAddBox(pixa->boxa, box, copyflag); + return 0; +} + + +/*! + * \brief pixaExtendArray() + * + * \param[in] pixa + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Doubles the size of the pixa and boxa ptr arrays. + * (2) The max number of pix in the array is 5 million. + * </pre> + */ +static l_int32 +pixaExtendArray(PIXA *pixa) +{ + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + return pixaExtendArrayToSize(pixa, 2 * pixa->nalloc); +} + + +/*! + * \brief pixaExtendArrayToSize() + * + * \param[in] pixa + * \param[in] size number of pix ptrs in new array + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) If necessary, reallocs new pixa and boxa ptrs arrays to %size. + * The pixa and boxa ptr arrays must always be equal in size. + * (2) The max number of pix ptrs is 5M. + * </pre> + */ +l_ok +pixaExtendArrayToSize(PIXA *pixa, + size_t size) +{ +size_t oldsize, newsize; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (pixa->nalloc > MaxPixaPtrArraySize) /* belt & suspenders */ + return ERROR_INT("pixa has too many ptrs", __func__, 1); + if (size > MaxPixaPtrArraySize) + return ERROR_INT("size > 5M ptrs; too large", __func__, 1); + if (size <= pixa->nalloc) { + L_INFO("size too small; no extension\n", __func__); + return 0; + } + + oldsize = pixa->nalloc * sizeof(PIX *); + newsize = size * sizeof(PIX *); + if ((pixa->pix = (PIX **)reallocNew((void **)&pixa->pix, + oldsize, newsize)) == NULL) + return ERROR_INT("new ptr array not returned", __func__, 1); + pixa->nalloc = size; + return boxaExtendArrayToSize(pixa->boxa, size); +} + + +/*---------------------------------------------------------------------* + * Pixa accessors * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaGetCount() + * + * \param[in] pixa + * \return count, or 0 if no pixa + */ +l_int32 +pixaGetCount(PIXA *pixa) +{ + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 0); + + return pixa->n; +} + + +/*! + * \brief pixaGetPix() + * + * \param[in] pixa + * \param[in] index to the index-th pix + * \param[in] accesstype L_COPY or L_CLONE + * \return pix, or NULL on error + */ +PIX * +pixaGetPix(PIXA *pixa, + l_int32 index, + l_int32 accesstype) +{ +PIX *pix; + + if (!pixa) + return (PIX *)ERROR_PTR("pixa not defined", __func__, NULL); + if (index < 0 || index >= pixa->n) + return (PIX *)ERROR_PTR("index not valid", __func__, NULL); + if ((pix = pixa->pix[index]) == NULL) { + L_ERROR("no pix at pixa[%d]\n", __func__, index); + return (PIX *)ERROR_PTR("pix not found!", __func__, NULL); + } + + if (accesstype == L_COPY) + return pixCopy(NULL, pix); + else if (accesstype == L_CLONE) + return pixClone(pix); + else + return (PIX *)ERROR_PTR("invalid accesstype", __func__, NULL); +} + + +/*! + * \brief pixaGetPixDimensions() + * + * \param[in] pixa + * \param[in] index to the index-th box + * \param[out] pw, ph, pd [optional] each can be null + * \return 0 if OK, 1 on error + */ +l_ok +pixaGetPixDimensions(PIXA *pixa, + l_int32 index, + l_int32 *pw, + l_int32 *ph, + l_int32 *pd) +{ +PIX *pix; + + if (pw) *pw = 0; + if (ph) *ph = 0; + if (pd) *pd = 0; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (index < 0 || index >= pixa->n) + return ERROR_INT("index not valid", __func__, 1); + + if ((pix = pixaGetPix(pixa, index, L_CLONE)) == NULL) + return ERROR_INT("pix not found!", __func__, 1); + pixGetDimensions(pix, pw, ph, pd); + pixDestroy(&pix); + return 0; +} + + +/*! + * \brief pixaGetBoxa() + * + * \param[in] pixa + * \param[in] accesstype L_COPY, L_CLONE, L_COPY_CLONE + * \return boxa, or NULL on error + */ +BOXA * +pixaGetBoxa(PIXA *pixa, + l_int32 accesstype) +{ + if (!pixa) + return (BOXA *)ERROR_PTR("pixa not defined", __func__, NULL); + if (!pixa->boxa) + return (BOXA *)ERROR_PTR("boxa not defined", __func__, NULL); + if (accesstype != L_COPY && accesstype != L_CLONE && + accesstype != L_COPY_CLONE) + return (BOXA *)ERROR_PTR("invalid accesstype", __func__, NULL); + + return boxaCopy(pixa->boxa, accesstype); +} + + +/*! + * \brief pixaGetBoxaCount() + * + * \param[in] pixa + * \return count, or 0 on error + */ +l_int32 +pixaGetBoxaCount(PIXA *pixa) +{ + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 0); + + return boxaGetCount(pixa->boxa); +} + + +/*! + * \brief pixaGetBox() + * + * \param[in] pixa + * \param[in] index to the index-th pix + * \param[in] accesstype L_COPY or L_CLONE + * \return box if null, not automatically an error, or NULL on error + * + * <pre> + * Notes: + * (1) There is always a boxa with a pixa, and it is initialized so + * that each box ptr is NULL. + * (2) In general, we expect that there is either a box associated + * with each pix, or no boxes at all in the boxa. + * (3) Having no boxes is thus not an automatic error. Whether it + * is an actual error is determined by the calling program. + * If the caller expects to get a box, it is an error; see, e.g., + * pixaGetBoxGeometry(). + * </pre> + */ +BOX * +pixaGetBox(PIXA *pixa, + l_int32 index, + l_int32 accesstype) +{ +BOX *box; + + if (!pixa) + return (BOX *)ERROR_PTR("pixa not defined", __func__, NULL); + if (!pixa->boxa) + return (BOX *)ERROR_PTR("boxa not defined", __func__, NULL); + if (index < 0 || index >= pixa->boxa->n) + return (BOX *)ERROR_PTR("index not valid", __func__, NULL); + if (accesstype != L_COPY && accesstype != L_CLONE) + return (BOX *)ERROR_PTR("invalid accesstype", __func__, NULL); + + box = pixa->boxa->box[index]; + if (box) { + if (accesstype == L_COPY) + return boxCopy(box); + else /* accesstype == L_CLONE */ + return boxClone(box); + } else { + return NULL; + } +} + + +/*! + * \brief pixaGetBoxGeometry() + * + * \param[in] pixa + * \param[in] index to the index-th box + * \param[out] px, py, pw, ph [optional] each can be null + * \return 0 if OK, 1 on error + */ +l_ok +pixaGetBoxGeometry(PIXA *pixa, + l_int32 index, + l_int32 *px, + l_int32 *py, + l_int32 *pw, + l_int32 *ph) +{ +BOX *box; + + if (px) *px = 0; + if (py) *py = 0; + if (pw) *pw = 0; + if (ph) *ph = 0; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (index < 0 || index >= pixa->n) + return ERROR_INT("index not valid", __func__, 1); + + if ((box = pixaGetBox(pixa, index, L_CLONE)) == NULL) + return ERROR_INT("box not found!", __func__, 1); + boxGetGeometry(box, px, py, pw, ph); + boxDestroy(&box); + return 0; +} + + +/*! + * \brief pixaSetBoxa() + * + * \param[in] pixa + * \param[in] boxa + * \param[in] accesstype L_INSERT, L_COPY, L_CLONE + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This destroys the existing boxa in the pixa. + * </pre> + */ +l_ok +pixaSetBoxa(PIXA *pixa, + BOXA *boxa, + l_int32 accesstype) +{ + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + if (accesstype != L_INSERT && accesstype != L_COPY && + accesstype != L_CLONE) + return ERROR_INT("invalid access type", __func__, 1); + + boxaDestroy(&pixa->boxa); + if (accesstype == L_INSERT) + pixa->boxa = boxa; + else + pixa->boxa = boxaCopy(boxa, accesstype); + + return 0; +} + + +/*! + * \brief pixaGetPixArray() + * + * \param[in] pixa + * \return pix array, or NULL on error + * + * <pre> + * Notes: + * (1) This returns a ptr to the actual array. The array is + * owned by the pixa, so it must not be destroyed. + * (2) The caller should always check if the return value is NULL + * before accessing any of the pix ptrs in this array! + * </pre> + */ +PIX ** +pixaGetPixArray(PIXA *pixa) +{ + if (!pixa) + return (PIX **)ERROR_PTR("pixa not defined", __func__, NULL); + + return pixa->pix; +} + + +/*! + * \brief pixaVerifyDepth() + * + * \param[in] pixa + * \param[out] psame 1 if depth is the same for all pix; 0 otherwise + * \param[out] pmaxd [optional] max depth of all pix + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) It is considered to be an error if there are no pix. + * </pre> + */ +l_ok +pixaVerifyDepth(PIXA *pixa, + l_int32 *psame, + l_int32 *pmaxd) +{ +l_int32 i, n, d, maxd, same; + + if (pmaxd) *pmaxd = 0; + if (!psame) + return ERROR_INT("psame not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if ((n = pixaGetCount(pixa)) == 0) + return ERROR_INT("no pix in pixa", __func__, 1); + + same = 1; + pixaGetPixDimensions(pixa, 0, NULL, NULL, &maxd); + for (i = 1; i < n; i++) { + if (pixaGetPixDimensions(pixa, i, NULL, NULL, &d)) + return ERROR_INT("pix depth not found", __func__, 1); + maxd = L_MAX(maxd, d); + if (d != maxd) + same = 0; + } + *psame = same; + if (pmaxd) *pmaxd = maxd; + return 0; +} + + +/*! + * \brief pixaVerifyDimensions() + * + * \param[in] pixa + * \param[out] psame 1 if dimensions are the same for all pix; 0 otherwise + * \param[out] pmaxw [optional] max width of all pix + * \param[out] pmaxh [optional] max height of all pix + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) It is considered to be an error if there are no pix. + * </pre> + */ +l_ok +pixaVerifyDimensions(PIXA *pixa, + l_int32 *psame, + l_int32 *pmaxw, + l_int32 *pmaxh) +{ +l_int32 i, n, w, h, maxw, maxh, same; + + if (pmaxw) *pmaxw = 0; + if (pmaxh) *pmaxh = 0; + if (!psame) + return ERROR_INT("psame not defined", __func__, 1); + *psame = 0; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if ((n = pixaGetCount(pixa)) == 0) + return ERROR_INT("no pix in pixa", __func__, 1); + + same = 1; + pixaGetPixDimensions(pixa, 0, &maxw, &maxh, NULL); + for (i = 1; i < n; i++) { + if (pixaGetPixDimensions(pixa, i, &w, &h, NULL)) + return ERROR_INT("pix dimensions not found", __func__, 1); + maxw = L_MAX(maxw, w); + maxh = L_MAX(maxh, h); + if (w != maxw || h != maxh) + same = 0; + } + *psame = same; + if (pmaxw) *pmaxw = maxw; + if (pmaxh) *pmaxh = maxh; + return 0; +} + + +/*! + * \brief pixaIsFull() + * + * \param[in] pixa + * \param[out] pfullpa [optional] 1 if pixa is full + * \param[out] pfullba [optional] 1 if boxa is full + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) A pixa is "full" if the array of pix is fully + * occupied from index 0 to index (pixa->n - 1). + * </pre> + */ +l_ok +pixaIsFull(PIXA *pixa, + l_int32 *pfullpa, + l_int32 *pfullba) +{ +l_int32 i, n, full; +BOXA *boxa; +PIX *pix; + + if (pfullpa) *pfullpa = 0; + if (pfullba) *pfullba = 0; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + if (pfullpa) { + full = 1; + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) { + full = 0; + break; + } + pixDestroy(&pix); + } + *pfullpa = full; + } + if (pfullba) { + boxa = pixaGetBoxa(pixa, L_CLONE); + boxaIsFull(boxa, pfullba); + boxaDestroy(&boxa); + } + return 0; +} + + +/*! + * \brief pixaCountText() + * + * \param[in] pixa + * \param[out] pntext number of pix with non-empty text strings + * \return 0 if OK, 1 on error. + * + * <pre> + * Notes: + * (1) All pix have non-empty text strings if the returned value %ntext + * equals the pixa count. + * </pre> + */ +l_ok +pixaCountText(PIXA *pixa, + l_int32 *pntext) +{ +char *text; +l_int32 i, n; +PIX *pix; + + if (!pntext) + return ERROR_INT("&ntext not defined", __func__, 1); + *pntext = 0; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) + continue; + text = pixGetText(pix); + if (text && strlen(text) > 0) + (*pntext)++; + pixDestroy(&pix); + } + + return 0; +} + + +/*! + * \brief pixaSetText() + * + * \param[in] pixa + * \param[in] text [optional] single text string, to insert in each pix + * \param[in] sa [optional] array of text strings, to insert in each pix + * \return 0 if OK, 1 on error. + * + * <pre> + * Notes: + * (1) To clear all the text fields, use %sa == NULL and %text == NULL. + * (2) Otherwise, this replaces all text fields with a copy of a string, + * either the same string or a string from %sa. + * (3) To set all the text fields to the same value %text, use %sa = NULL. + * (4) If %sa is defined, ignore %text and use the strings in %sa. + * %sa must have the same count as %pixa. + * </pre> + */ +l_ok +pixaSetText(PIXA *pixa, + const char *text, + SARRAY *sa) +{ +char *str; +l_int32 i, n; +PIX *pix; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + if (sa && (sarrayGetCount(sa) != n)) + return ERROR_INT("pixa and sa sizes differ", __func__, 1); + + if (!sa) { + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) + continue; + pixSetText(pix, text); + pixDestroy(&pix); + } + return 0; + } + + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) + continue; + str = sarrayGetString(sa, i, L_NOCOPY); + pixSetText(pix, str); + pixDestroy(&pix); + } + + return 0; +} + + +/*! + * \brief pixaGetLinePtrs() + * + * \param[in] pixa of pix that all have the same depth + * \param[out] psize [optional] number of pix in the pixa + * \return array of array of line ptrs, or NULL on error + * + * <pre> + * Notes: + * (1) See pixGetLinePtrs() for details. + * (2) It is best if all pix in the pixa are the same size. + * The size of each line ptr array is equal to the height + * of the pix that it refers to. + * (3) This is an array of arrays. To destroy it: + * for (i = 0; i < size; i++) + * LEPT_FREE(lineset[i]); + * LEPT_FREE(lineset); + * </pre> + */ +void *** +pixaGetLinePtrs(PIXA *pixa, + l_int32 *psize) +{ +l_int32 i, n, same; +void **lineptrs; +void ***lineset; +PIX *pix; + + if (psize) *psize = 0; + if (!pixa) + return (void ***)ERROR_PTR("pixa not defined", __func__, NULL); + pixaVerifyDepth(pixa, &same, NULL); + if (!same) + return (void ***)ERROR_PTR("pixa not all same depth", __func__, NULL); + n = pixaGetCount(pixa); + if (psize) *psize = n; + if ((lineset = (void ***)LEPT_CALLOC(n, sizeof(void **))) == NULL) + return (void ***)ERROR_PTR("lineset not made", __func__, NULL); + for (i = 0; i < n; i++) { + pix = pixaGetPix(pixa, i, L_CLONE); + lineptrs = pixGetLinePtrs(pix, NULL); + lineset[i] = lineptrs; + pixDestroy(&pix); + } + + return lineset; +} + + +/*---------------------------------------------------------------------* + * Pixa output info * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaWriteStreamInfo() + * + * \param[in] fp file stream + * \param[in] pixa + * \return 0 if OK, 1 on error. + * + * <pre> + * Notes: + * (1) For each pix in the pixa, write out the pix dimensions, spp, + * text string (if it exists), and cmap info. + * </pre> + */ +l_ok +pixaWriteStreamInfo(FILE *fp, + PIXA *pixa) +{ +char *text; +l_int32 i, n, w, h, d, spp, count, hastext; +PIX *pix; +PIXCMAP *cmap; + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) { + fprintf(fp, "%d: no pix at this index\n", i); + continue; + } + pixGetDimensions(pix, &w, &h, &d); + spp = pixGetSpp(pix); + text = pixGetText(pix); + hastext = (text && strlen(text) > 0); + if ((cmap = pixGetColormap(pix)) != NULL) + count = pixcmapGetCount(cmap); + fprintf(fp, "Pix %d: w = %d, h = %d, d = %d, spp = %d", + i, w, h, d, spp); + if (cmap) fprintf(fp, ", cmap(%d colors)", count); + if (hastext) fprintf(fp, ", text = %s", text); + fprintf(fp, "\n"); + pixDestroy(&pix); + } + + return 0; +} + + +/*---------------------------------------------------------------------* + * Pixa array modifiers * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaReplacePix() + * + * \param[in] pixa + * \param[in] index to the index-th pix + * \param[in] pix insert to replace existing one + * \param[in] box [optional] insert to replace existing + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) In-place replacement of one pix. + * (2) The previous pix at that location is destroyed. + * </pre> + */ +l_ok +pixaReplacePix(PIXA *pixa, + l_int32 index, + PIX *pix, + BOX *box) +{ +BOXA *boxa; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (index < 0 || index >= pixa->n) + return ERROR_INT("index not valid", __func__, 1); + if (!pix) + return ERROR_INT("pix not defined", __func__, 1); + + pixDestroy(&(pixa->pix[index])); + pixa->pix[index] = pix; + + if (box) { + boxa = pixa->boxa; + if (index > boxa->n) + return ERROR_INT("boxa index not valid", __func__, 1); + boxaReplaceBox(boxa, index, box); + } + + return 0; +} + + +/*! + * \brief pixaInsertPix() + * + * \param[in] pixa + * \param[in] index at which pix is to be inserted + * \param[in] pixs new pix to be inserted + * \param[in] box [optional] new box to be inserted + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This shifts pixa[i] --> pixa[i + 1] for all i >= index, + * and then inserts at pixa[index]. + * (2) To insert at the beginning of the array, set index = 0. + * (3) It should not be used repeatedly on large arrays, + * because the function is O(n). + * (4) To append a pix to a pixa, it's easier to use pixaAddPix(). + * </pre> + */ +l_ok +pixaInsertPix(PIXA *pixa, + l_int32 index, + PIX *pixs, + BOX *box) +{ +l_int32 i, n; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + n = pixaGetCount(pixa); + if (index < 0 || index > n) { + L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n); + return 1; + } + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + + if (n >= pixa->nalloc) { /* extend both ptr arrays */ + if (pixaExtendArray(pixa)) + return ERROR_INT("extension failed", __func__, 1); + if (boxaExtendArray(pixa->boxa)) + return ERROR_INT("extension failed", __func__, 1); + } + pixa->n++; + for (i = n; i > index; i--) + pixa->pix[i] = pixa->pix[i - 1]; + pixa->pix[index] = pixs; + + /* Optionally, insert the box */ + if (box) + boxaInsertBox(pixa->boxa, index, box); + return 0; +} + + +/*! + * \brief pixaRemovePix() + * + * \param[in] pixa + * \param[in] index of pix to be removed + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This shifts pixa[i] --> pixa[i - 1] for all i > index. + * (2) It should not be used repeatedly on large arrays, + * because the function is O(n). + * (3) The corresponding box is removed as well, if it exists. + * </pre> + */ +l_ok +pixaRemovePix(PIXA *pixa, + l_int32 index) +{ +l_int32 i, n, nbox; +BOXA *boxa; +PIX **array; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + n = pixaGetCount(pixa); + if (index < 0 || index >= n) { + L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n - 1); + return 1; + } + + /* Remove the pix */ + array = pixa->pix; + pixDestroy(&array[index]); + for (i = index + 1; i < n; i++) + array[i - 1] = array[i]; + array[n - 1] = NULL; + pixa->n--; + + /* Remove the box if it exists */ + boxa = pixa->boxa; + nbox = boxaGetCount(boxa); + if (index < nbox) + boxaRemoveBox(boxa, index); + + return 0; +} + + +/*! + * \brief pixaRemovePixAndSave() + * + * \param[in] pixa + * \param[in] index of pix to be removed + * \param[out] ppix [optional] removed pix + * \param[out] pbox [optional] removed box + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This shifts pixa[i] --> pixa[i - 1] for all i > index. + * (2) It should not be used repeatedly on large arrays, + * because the function is O(n). + * (3) The corresponding box is removed as well, if it exists. + * (4) The removed pix and box can either be retained or destroyed. + * </pre> + */ +l_ok +pixaRemovePixAndSave(PIXA *pixa, + l_int32 index, + PIX **ppix, + BOX **pbox) +{ +l_int32 i, n, nbox; +BOXA *boxa; +PIX **array; + + if (ppix) *ppix = NULL; + if (pbox) *pbox = NULL; + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + n = pixaGetCount(pixa); + if (index < 0 || index >= n) { + L_ERROR("index %d not in [0,...,%d]\n", __func__, index, n - 1); + return 1; + } + + /* Remove the pix */ + array = pixa->pix; + if (ppix) + *ppix = pixaGetPix(pixa, index, L_CLONE); + pixDestroy(&array[index]); + for (i = index + 1; i < n; i++) + array[i - 1] = array[i]; + array[n - 1] = NULL; + pixa->n--; + + /* Remove the box if it exists */ + boxa = pixa->boxa; + nbox = boxaGetCount(boxa); + if (index < nbox) + boxaRemoveBoxAndSave(boxa, index, pbox); + + return 0; +} + + +/*! + * \brief pixaRemoveSelected() + * + * \param[in] pixa + * \param[in] naindex numa of indices of pix to be removed + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This gives error messages for invalid indices + * </pre> + */ +l_ok +pixaRemoveSelected(PIXA *pixa, + NUMA *naindex) +{ +l_int32 i, n, index; +NUMA *na1; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (!naindex) + return ERROR_INT("naindex not defined", __func__, 1); + if ((n = numaGetCount(naindex)) == 0) + return ERROR_INT("naindex is empty", __func__, 1); + + /* Remove from highest indices first */ + na1 = numaSort(NULL, naindex, L_SORT_DECREASING); + for (i = 0; i < n; i++) { + numaGetIValue(na1, i, &index); + pixaRemovePix(pixa, index); + } + numaDestroy(&na1); + return 0; +} + + +/*! + * \brief pixaInitFull() + * + * \param[in] pixa typically empty + * \param[in] pix [optional] to be replicated to the entire pixa ptr array + * \param[in] box [optional] to be replicated to the entire boxa ptr array + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This initializes a pixa by filling up the entire pix ptr array + * with copies of %pix. If %pix == NULL, we use a tiny placeholder + * pix (w = h = d = 1). Any existing pix are destroyed. + * It also optionally fills the boxa with copies of %box. + * After this operation, the numbers of pix and (optionally) + * boxes are equal to the number of allocated ptrs. + * (2) Note that we use pixaReplacePix() instead of pixaInsertPix(). + * They both have the same effect when inserting into a NULL ptr + * in the pixa ptr array: + * (3) If the boxa is not initialized (i.e., filled with boxes), + * later insertion of boxes will cause an error, because the + * 'n' field is 0. + * (4) Example usage. This function is useful to prepare for a + * random insertion (or replacement) of pix into a pixa. + * To randomly insert pix into a pixa, without boxes, up to + * some index "max": + * Pixa *pixa = pixaCreate(max); + * pixaInitFull(pixa, NULL, NULL); + * An existing pixa with a smaller ptr array can also be reused: + * pixaExtendArrayToSize(pixa, max); + * pixaInitFull(pixa, NULL, NULL); + * The initialization allows the pixa to always be properly + * filled, even if all pix (and boxes) are not later replaced. + * </pre> + */ +l_ok +pixaInitFull(PIXA *pixa, + PIX *pix, + BOX *box) +{ +l_int32 i, n; +PIX *pix1; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixa->nalloc; + pixa->n = n; + for (i = 0; i < n; i++) { + if (pix) + pix1 = pixCopy(NULL, pix); + else + pix1 = pixCreate(1, 1, 1); + pixaReplacePix(pixa, i, pix1, NULL); + } + if (box) + boxaInitFull(pixa->boxa, box); + + return 0; +} + + +/*! + * \brief pixaClear() + * + * \param[in] pixa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This destroys all pix in the pixa, as well as + * all boxes in the boxa. The ptrs in the pix ptr array + * are all null'd. The number of allocated pix, n, is set to 0. + * </pre> + */ +l_ok +pixaClear(PIXA *pixa) +{ +l_int32 i, n; + + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + for (i = 0; i < n; i++) + pixDestroy(&pixa->pix[i]); + pixa->n = 0; + return boxaClear(pixa->boxa); +} + + +/*---------------------------------------------------------------------* + * Pixa and Pixaa combination * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaJoin() + * + * \param[in] pixad dest pixa; add to this one + * \param[in] pixas [optional] source pixa; add from this one + * \param[in] istart starting index in pixas + * \param[in] iend ending index in pixas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This appends a clone of each indicated pix in pixas to pixad + * (2) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (3) iend < 0 means 'read to the end' + * (4) If pixas is NULL or contains no pix, this is a no-op. + * </pre> + */ +l_ok +pixaJoin(PIXA *pixad, + PIXA *pixas, + l_int32 istart, + l_int32 iend) +{ +l_int32 i, n, nb; +BOXA *boxas, *boxad; +PIX *pix; + + if (!pixad) + return ERROR_INT("pixad not defined", __func__, 1); + if (!pixas || ((n = pixaGetCount(pixas)) == 0)) + return 0; + + if (istart < 0) + istart = 0; + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", __func__, 1); + + for (i = istart; i <= iend; i++) { + pix = pixaGetPix(pixas, i, L_CLONE); + pixaAddPix(pixad, pix, L_INSERT); + } + + boxas = pixaGetBoxa(pixas, L_CLONE); + boxad = pixaGetBoxa(pixad, L_CLONE); + nb = pixaGetBoxaCount(pixas); + iend = L_MIN(iend, nb - 1); + boxaJoin(boxad, boxas, istart, iend); + boxaDestroy(&boxas); /* just the clones */ + boxaDestroy(&boxad); + return 0; +} + + +/*! + * \brief pixaInterleave() + * + * \param[in] pixa1 first src pixa + * \param[in] pixa2 second src pixa + * \param[in] copyflag L_CLONE, L_COPY + * \return pixa interleaved from sources, or NULL on error. + * + * <pre> + * Notes: + * (1) %copyflag determines if the pix are copied or cloned. + * The boxes, if they exist, are copied. + * (2) If the two pixa have different sizes, a warning is issued, + * and the number of pairs returned is the minimum size. + * </pre> + */ +PIXA * +pixaInterleave(PIXA *pixa1, + PIXA *pixa2, + l_int32 copyflag) +{ +l_int32 i, n1, n2, n, nb1, nb2; +BOX *box; +PIX *pix; +PIXA *pixad; + + if (!pixa1) + return (PIXA *)ERROR_PTR("pixa1 not defined", __func__, NULL); + if (!pixa2) + return (PIXA *)ERROR_PTR("pixa2 not defined", __func__, NULL); + if (copyflag != L_COPY && copyflag != L_CLONE) + return (PIXA *)ERROR_PTR("invalid copyflag", __func__, NULL); + n1 = pixaGetCount(pixa1); + n2 = pixaGetCount(pixa2); + n = L_MIN(n1, n2); + if (n == 0) + return (PIXA *)ERROR_PTR("at least one input pixa is empty", + __func__, NULL); + if (n1 != n2) + L_WARNING("counts differ: %d != %d\n", __func__, n1, n2); + + pixad = pixaCreate(2 * n); + nb1 = pixaGetBoxaCount(pixa1); + nb2 = pixaGetBoxaCount(pixa2); + for (i = 0; i < n; i++) { + pix = pixaGetPix(pixa1, i, copyflag); + pixaAddPix(pixad, pix, L_INSERT); + if (i < nb1) { + box = pixaGetBox(pixa1, i, L_COPY); + pixaAddBox(pixad, box, L_INSERT); + } + pix = pixaGetPix(pixa2, i, copyflag); + pixaAddPix(pixad, pix, L_INSERT); + if (i < nb2) { + box = pixaGetBox(pixa2, i, L_COPY); + pixaAddBox(pixad, box, L_INSERT); + } + } + + return pixad; +} + + +/*! + * \brief pixaaJoin() + * + * \param[in] paad dest pixaa; add to this one + * \param[in] paas [optional] source pixaa; add from this one + * \param[in] istart starting index in pixaas + * \param[in] iend ending index in pixaas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This appends a clone of each indicated pixa in paas to pixaad + * (2) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (3) iend < 0 means 'read to the end' + * </pre> + */ +l_ok +pixaaJoin(PIXAA *paad, + PIXAA *paas, + l_int32 istart, + l_int32 iend) +{ +l_int32 i, n; +PIXA *pixa; + + if (!paad) + return ERROR_INT("pixaad not defined", __func__, 1); + if (!paas) + return 0; + + if (istart < 0) + istart = 0; + n = pixaaGetCount(paas, NULL); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", __func__, 1); + + for (i = istart; i <= iend; i++) { + pixa = pixaaGetPixa(paas, i, L_CLONE); + pixaaAddPixa(paad, pixa, L_INSERT); + } + + return 0; +} + + +/*---------------------------------------------------------------------* + * Pixaa creation and destruction * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaaCreate() + * + * \param[in] n initial number of pixa ptrs + * \return paa, or NULL on error + * + * <pre> + * Notes: + * (1) A pixaa provides a 2-level hierarchy of images. + * A common use is for segmentation masks, which are + * inexpensive to store in png format. + * (2) For example, suppose you want a mask for each textline + * in a two-column page. The textline masks for each column + * can be represented by a pixa, of which there are 2 in the pixaa. + * The boxes for the textline mask components within a column + * can have their origin referred to the column rather than the page. + * Then the boxa field can be used to represent the two box (regions) + * for the columns, and the (x,y) components of each box can + * be used to get the absolute position of the textlines on + * the page. + * </pre> + */ +PIXAA * +pixaaCreate(l_int32 n) +{ +PIXAA *paa; + + if (n <= 0 || n > MaxInitPtrArraySize) + n = InitialPtrArraySize; + + paa = (PIXAA *)LEPT_CALLOC(1, sizeof(PIXAA)); + paa->n = 0; + paa->nalloc = n; + if ((paa->pixa = (PIXA **)LEPT_CALLOC(n, sizeof(PIXA *))) == NULL) { + pixaaDestroy(&paa); + return (PIXAA *)ERROR_PTR("pixa ptrs not made", __func__, NULL); + } + paa->boxa = boxaCreate(n); + + return paa; +} + + +/*! + * \brief pixaaCreateFromPixa() + * + * \param[in] pixa + * \param[in] n number specifying subdivision of pixa + * \param[in] type L_CHOOSE_CONSECUTIVE, L_CHOOSE_SKIP_BY + * \param[in] copyflag L_CLONE, L_COPY + * \return paa, or NULL on error + * + * <pre> + * Notes: + * (1) This subdivides a pixa into a set of smaller pixa that + * are accumulated into a pixaa. + * (2) If type == L_CHOOSE_CONSECUTIVE, the first 'n' pix are + * put in a pixa and added to pixaa, then the next 'n', etc. + * If type == L_CHOOSE_SKIP_BY, the first pixa is made by + * aggregating pix[0], pix[n], pix[2*n], etc. + * (3) The copyflag specifies if each new pix is a copy or a clone. + * </pre> + */ +PIXAA * +pixaaCreateFromPixa(PIXA *pixa, + l_int32 n, + l_int32 type, + l_int32 copyflag) +{ +l_int32 count, i, j, npixa; +PIX *pix; +PIXA *pixat = NULL; +PIXAA *paa; + + if (!pixa) + return (PIXAA *)ERROR_PTR("pixa not defined", __func__, NULL); + count = pixaGetCount(pixa); + if (count == 0) + return (PIXAA *)ERROR_PTR("no pix in pixa", __func__, NULL); + if (n <= 0) + return (PIXAA *)ERROR_PTR("n must be > 0", __func__, NULL); + if (type != L_CHOOSE_CONSECUTIVE && type != L_CHOOSE_SKIP_BY) + return (PIXAA *)ERROR_PTR("invalid type", __func__, NULL); + if (copyflag != L_CLONE && copyflag != L_COPY) + return (PIXAA *)ERROR_PTR("invalid copyflag", __func__, NULL); + + if (type == L_CHOOSE_CONSECUTIVE) + npixa = (count + n - 1) / n; + else /* L_CHOOSE_SKIP_BY */ + npixa = L_MIN(n, count); + paa = pixaaCreate(npixa); + if (type == L_CHOOSE_CONSECUTIVE) { + for (i = 0; i < count; i++) { + if (i % n == 0) + pixat = pixaCreate(n); + pix = pixaGetPix(pixa, i, copyflag); + pixaAddPix(pixat, pix, L_INSERT); + if (i % n == n - 1) + pixaaAddPixa(paa, pixat, L_INSERT); + } + if (i % n != 0) + pixaaAddPixa(paa, pixat, L_INSERT); + } else { /* L_CHOOSE_SKIP_BY */ + for (i = 0; i < npixa; i++) { + pixat = pixaCreate(count / npixa + 1); + for (j = i; j < count; j += n) { + pix = pixaGetPix(pixa, j, copyflag); + pixaAddPix(pixat, pix, L_INSERT); + } + pixaaAddPixa(paa, pixat, L_INSERT); + } + } + + return paa; +} + + +/*! + * \brief pixaaDestroy() + * + * \param[in,out] ppaa use ptr address so it will be nulled + * \return void + */ +void +pixaaDestroy(PIXAA **ppaa) +{ +l_int32 i; +PIXAA *paa; + + if (ppaa == NULL) { + L_WARNING("ptr address is NULL!\n", __func__); + return; + } + + if ((paa = *ppaa) == NULL) + return; + + for (i = 0; i < paa->n; i++) + pixaDestroy(&paa->pixa[i]); + LEPT_FREE(paa->pixa); + boxaDestroy(&paa->boxa); + LEPT_FREE(paa); + *ppaa = NULL; +} + + +/*---------------------------------------------------------------------* + * Pixaa addition * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaaAddPixa() + * + * \param[in] paa + * \param[in] pixa to be added + * \param[in] copyflag: + * L_INSERT inserts the pixa directly; + * L_COPY makes a new pixa and copies each pix and each box; + * L_CLONE gives a new handle to the input pixa; + * L_COPY_CLONE makes a new pixa and inserts clones of + * all pix and boxes + * \return 0 if OK; 1 on error + */ +l_ok +pixaaAddPixa(PIXAA *paa, + PIXA *pixa, + l_int32 copyflag) +{ +l_int32 n; +PIXA *pixac; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + if (copyflag != L_INSERT && copyflag != L_COPY && + copyflag != L_CLONE && copyflag != L_COPY_CLONE) + return ERROR_INT("invalid copyflag", __func__, 1); + + if (copyflag == L_INSERT) { + pixac = pixa; + } else { + if ((pixac = pixaCopy(pixa, copyflag)) == NULL) + return ERROR_INT("pixac not made", __func__, 1); + } + + n = pixaaGetCount(paa, NULL); + if (n >= paa->nalloc) { + if (pixaaExtendArray(paa)) { + if (copyflag != L_INSERT) + pixaDestroy(&pixac); + return ERROR_INT("extension failed", __func__, 1); + } + } + paa->pixa[n] = pixac; + paa->n++; + return 0; +} + + +/*! + * \brief pixaaExtendArray() + * + * \param[in] paa + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) The max number of pixa ptrs is 1M. + * </pre> + */ +static l_int32 +pixaaExtendArray(PIXAA *paa) +{ +size_t oldsize, newsize; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (paa->nalloc > MaxPixaaPtrArraySize) /* belt & suspenders */ + return ERROR_INT("paa has too many ptrs", __func__, 1); + oldsize = paa->nalloc * sizeof(PIXA *); + newsize = 2 * oldsize; + if (newsize > 8 * MaxPixaaPtrArraySize) + return ERROR_INT("newsize > 8 MB; too large", __func__, 1); + + if ((paa->pixa = (PIXA **)reallocNew((void **)&paa->pixa, + oldsize, newsize)) == NULL) + return ERROR_INT("new ptr array not returned", __func__, 1); + + paa->nalloc *= 2; + return 0; +} + + +/*! + * \brief pixaaAddPix() + * + * \param[in] paa input paa + * \param[in] index index of pixa in paa + * \param[in] pix to be added + * \param[in] box [optional] to be added + * \param[in] copyflag L_INSERT, L_COPY, L_CLONE + * \return 0 if OK; 1 on error + */ +l_ok +pixaaAddPix(PIXAA *paa, + l_int32 index, + PIX *pix, + BOX *box, + l_int32 copyflag) +{ +PIXA *pixa; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (!pix) + return ERROR_INT("pix not defined", __func__, 1); + + if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL) + return ERROR_INT("pixa not found", __func__, 1); + pixaAddPix(pixa, pix, copyflag); + if (box) pixaAddBox(pixa, box, copyflag); + pixaDestroy(&pixa); + return 0; +} + + +/*! + * \brief pixaaAddBox() + * + * \param[in] paa + * \param[in] box + * \param[in] copyflag L_INSERT, L_COPY, L_CLONE + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The box can be used, for example, to hold the support region + * of a pixa that is being added to the pixaa. + * </pre> + */ +l_ok +pixaaAddBox(PIXAA *paa, + BOX *box, + l_int32 copyflag) +{ + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE) + return ERROR_INT("invalid copyflag", __func__, 1); + + boxaAddBox(paa->boxa, box, copyflag); + return 0; +} + + + +/*---------------------------------------------------------------------* + * Pixaa accessors * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaaGetCount() + * + * \param[in] paa + * \param[out] pna [optional] number of pix in each pixa + * \return count, or 0 if no pixaa + * + * <pre> + * Notes: + * (1) If paa is empty, a returned na will also be empty. + * </pre> + */ +l_int32 +pixaaGetCount(PIXAA *paa, + NUMA **pna) +{ +l_int32 i, n; +NUMA *na; +PIXA *pixa; + + if (pna) *pna = NULL; + if (!paa) + return ERROR_INT("paa not defined", __func__, 0); + + n = paa->n; + if (pna) { + if ((na = numaCreate(n)) == NULL) + return ERROR_INT("na not made", __func__, 0); + *pna = na; + for (i = 0; i < n; i++) { + pixa = pixaaGetPixa(paa, i, L_CLONE); + numaAddNumber(na, pixaGetCount(pixa)); + pixaDestroy(&pixa); + } + } + return n; +} + + +/*! + * \brief pixaaGetPixa() + * + * \param[in] paa + * \param[in] index to the index-th pixa + * \param[in] accesstype L_COPY, L_CLONE, L_COPY_CLONE + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) L_COPY makes a new pixa with a copy of every pix + * (2) L_CLONE just makes a new reference to the pixa, + * and bumps the counter. You would use this, for example, + * when you need to extract some data from a pix within a + * pixa within a pixaa. + * (3) L_COPY_CLONE makes a new pixa with a clone of every pix + * and box + * (4) In all cases, you must invoke pixaDestroy() on the returned pixa + * </pre> + */ +PIXA * +pixaaGetPixa(PIXAA *paa, + l_int32 index, + l_int32 accesstype) +{ +PIXA *pixa; + + if (!paa) + return (PIXA *)ERROR_PTR("paa not defined", __func__, NULL); + if (index < 0 || index >= paa->n) + return (PIXA *)ERROR_PTR("index not valid", __func__, NULL); + if (accesstype != L_COPY && accesstype != L_CLONE && + accesstype != L_COPY_CLONE) + return (PIXA *)ERROR_PTR("invalid accesstype", __func__, NULL); + + if ((pixa = paa->pixa[index]) == NULL) { /* shouldn't happen! */ + L_ERROR("missing pixa[%d]\n", __func__, index); + return (PIXA *)ERROR_PTR("pixa not found at index", __func__, NULL); + } + return pixaCopy(pixa, accesstype); +} + + +/*! + * \brief pixaaGetBoxa() + * + * \param[in] paa + * \param[in] accesstype L_COPY, L_CLONE + * \return boxa, or NULL on error + * + * <pre> + * Notes: + * (1) L_COPY returns a copy; L_CLONE returns a new reference to the boxa. + * (2) In both cases, invoke boxaDestroy() on the returned boxa. + * </pre> + */ +BOXA * +pixaaGetBoxa(PIXAA *paa, + l_int32 accesstype) +{ + if (!paa) + return (BOXA *)ERROR_PTR("paa not defined", __func__, NULL); + if (accesstype != L_COPY && accesstype != L_CLONE) + return (BOXA *)ERROR_PTR("invalid access type", __func__, NULL); + + return boxaCopy(paa->boxa, accesstype); +} + + +/*! + * \brief pixaaGetPix() + * + * \param[in] paa + * \param[in] index index into the pixa array in the pixaa + * \param[in] ipix index into the pix array in the pixa + * \param[in] accessflag L_COPY or L_CLONE + * \return pix, or NULL on error + */ +PIX * +pixaaGetPix(PIXAA *paa, + l_int32 index, + l_int32 ipix, + l_int32 accessflag) +{ +PIX *pix; +PIXA *pixa; + + if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL) + return (PIX *)ERROR_PTR("pixa not retrieved", __func__, NULL); + if ((pix = pixaGetPix(pixa, ipix, accessflag)) == NULL) + L_ERROR("pix not retrieved\n", __func__); + pixaDestroy(&pixa); + return pix; +} + + +/*! + * \brief pixaaVerifyDepth() + * + * \param[in] paa + * \param[out] psame 1 if all pix have the same depth; 0 otherwise + * \param[out] pmaxd [optional] max depth of all pix in pixaa + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) It is considered to be an error if any pixa have no pix. + * </pre> + */ +l_ok +pixaaVerifyDepth(PIXAA *paa, + l_int32 *psame, + l_int32 *pmaxd) +{ +l_int32 i, n, d, maxd, same, samed; +PIXA *pixa; + + if (pmaxd) *pmaxd = 0; + if (!psame) + return ERROR_INT("psame not defined", __func__, 1); + *psame = 0; + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if ((n = pixaaGetCount(paa, NULL)) == 0) + return ERROR_INT("no pixa in paa", __func__, 1); + + pixa = pixaaGetPixa(paa, 0, L_CLONE); + pixaVerifyDepth(pixa, &same, &maxd); /* init same, maxd with first pixa */ + pixaDestroy(&pixa); + for (i = 1; i < n; i++) { + pixa = pixaaGetPixa(paa, i, L_CLONE); + pixaVerifyDepth(pixa, &samed, &d); + pixaDestroy(&pixa); + maxd = L_MAX(maxd, d); + if (!samed || maxd != d) + same = 0; + } + *psame = same; + if (pmaxd) *pmaxd = maxd; + return 0; +} + + +/*! + * \brief pixaaVerifyDimensions() + * + * \param[in] paa + * \param[out] psame 1 if all pix have the same depth; 0 otherwise + * \param[out] pmaxw [optional] max width of all pix in pixaa + * \param[out] pmaxh [optional] max height of all pix in pixaa + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) It is considered to be an error if any pixa have no pix. + * </pre> + */ +l_ok +pixaaVerifyDimensions(PIXAA *paa, + l_int32 *psame, + l_int32 *pmaxw, + l_int32 *pmaxh) +{ +l_int32 i, n, w, h, maxw, maxh, same, same2; +PIXA *pixa; + + if (pmaxw) *pmaxw = 0; + if (pmaxh) *pmaxh = 0; + if (!psame) + return ERROR_INT("psame not defined", __func__, 1); + *psame = 0; + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if ((n = pixaaGetCount(paa, NULL)) == 0) + return ERROR_INT("no pixa in paa", __func__, 1); + + /* Init same; init maxw and maxh from first pixa */ + pixa = pixaaGetPixa(paa, 0, L_CLONE); + pixaVerifyDimensions(pixa, &same, &maxw, &maxh); + pixaDestroy(&pixa); + + for (i = 1; i < n; i++) { + pixa = pixaaGetPixa(paa, i, L_CLONE); + pixaVerifyDimensions(pixa, &same2, &w, &h); + pixaDestroy(&pixa); + maxw = L_MAX(maxw, w); + maxh = L_MAX(maxh, h); + if (!same2 || maxw != w || maxh != h) + same = 0; + } + *psame = same; + if (pmaxw) *pmaxw = maxw; + if (pmaxh) *pmaxh = maxh; + return 0; +} + + +/*! + * \brief pixaaIsFull() + * + * \param[in] paa + * \param[out] pfull 1 if all pixa in the paa have full pix arrays + * \return return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Does not require boxa associated with each pixa to be full. + * </pre> + */ +l_int32 +pixaaIsFull(PIXAA *paa, + l_int32 *pfull) +{ +l_int32 i, n, full; +PIXA *pixa; + + if (!pfull) + return ERROR_INT("&full not defined", __func__, 0); + *pfull = 0; + if (!paa) + return ERROR_INT("paa not defined", __func__, 0); + + n = pixaaGetCount(paa, NULL); + full = 1; + for (i = 0; i < n; i++) { + pixa = pixaaGetPixa(paa, i, L_CLONE); + pixaIsFull(pixa, &full, NULL); + pixaDestroy(&pixa); + if (!full) break; + } + *pfull = full; + return 0; +} + + +/*---------------------------------------------------------------------* + * Pixaa array modifiers * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaaInitFull() + * + * \param[in] paa typically empty + * \param[in] pixa to be replicated into the entire pixa ptr array + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This initializes a pixaa by filling up the entire pixa ptr array + * with copies of %pixa. Any existing pixa are destroyed. + * (2) Example usage. This function is useful to prepare for a + * random insertion (or replacement) of pixa into a pixaa. + * To randomly insert pixa into a pixaa, up to some index "max": + * Pixaa *paa = pixaaCreate(max); + * Pixa *pixa = pixaCreate(1); // if you want little memory + * pixaaInitFull(paa, pixa); // copy it to entire array + * pixaDestroy(&pixa); // no longer needed + * The initialization allows the pixaa to always be properly filled. + * </pre> + */ +l_ok +pixaaInitFull(PIXAA *paa, + PIXA *pixa) +{ +l_int32 i, n; +PIXA *pixat; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = paa->nalloc; + paa->n = n; + for (i = 0; i < n; i++) { + pixat = pixaCopy(pixa, L_COPY); + pixaaReplacePixa(paa, i, pixat); + } + + return 0; +} + + +/*! + * \brief pixaaReplacePixa() + * + * \param[in] paa + * \param[in] index to the index-th pixa + * \param[in] pixa insert to replace existing one + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This allows random insertion of a pixa into a pixaa, with + * destruction of any existing pixa at that location. + * The input pixa is now owned by the pixaa. + * (2) No other pixa in the array are affected. + * (3) The index must be within the allowed set. + * </pre> + */ +l_ok +pixaaReplacePixa(PIXAA *paa, + l_int32 index, + PIXA *pixa) +{ + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + if (index < 0 || index >= paa->n) + return ERROR_INT("index not valid", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + pixaDestroy(&(paa->pixa[index])); + paa->pixa[index] = pixa; + return 0; +} + + +/*! + * \brief pixaaClear() + * + * \param[in] paa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This destroys all pixa in the pixaa, and nulls the ptrs + * in the pixa ptr array. + * </pre> + */ +l_ok +pixaaClear(PIXAA *paa) +{ +l_int32 i, n; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + + n = pixaaGetCount(paa, NULL); + for (i = 0; i < n; i++) + pixaDestroy(&paa->pixa[i]); + paa->n = 0; + return 0; +} + + +/*! + * \brief pixaaTruncate() + * + * \param[in] paa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This identifies the largest index containing a pixa that + * has any pix within it, destroys all pixa above that index, + * and resets the count. + * </pre> + */ +l_ok +pixaaTruncate(PIXAA *paa) +{ +l_int32 i, n, np; +PIXA *pixa; + + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + + n = pixaaGetCount(paa, NULL); + for (i = n - 1; i >= 0; i--) { + pixa = pixaaGetPixa(paa, i, L_CLONE); + if (!pixa) { + paa->n--; + continue; + } + np = pixaGetCount(pixa); + pixaDestroy(&pixa); + if (np == 0) { + pixaDestroy(&paa->pixa[i]); + paa->n--; + } else { + break; + } + } + return 0; +} + + + +/*---------------------------------------------------------------------* + * Pixa serialized I/O * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaRead() + * + * \param[in] filename + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +PIXA * +pixaRead(const char *filename) +{ +FILE *fp; +PIXA *pixa; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return (PIXA *)ERROR_PTR("no libpng: can't read data", __func__, NULL); +#endif /* !HAVE_LIBPNG */ + + if (!filename) + return (PIXA *)ERROR_PTR("filename not defined", __func__, NULL); + + if ((fp = fopenReadStream(filename)) == NULL) + return (PIXA *)ERROR_PTR_1("stream not opened", + filename, __func__, NULL); + pixa = pixaReadStream(fp); + fclose(fp); + if (!pixa) + return (PIXA *)ERROR_PTR_1("pixa not read", + filename, __func__, NULL); + return pixa; +} + + +/*! + * \brief pixaReadStream() + * + * \param[in] fp file stream + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * (2) It is OK for the pixa to be empty. + * </pre> + */ +PIXA * +pixaReadStream(FILE *fp) +{ +l_int32 n, i, xres, yres, version; +l_int32 ignore; +BOXA *boxa; +PIX *pix; +PIXA *pixa; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return (PIXA *)ERROR_PTR("no libpng: can't read data", __func__, NULL); +#endif /* !HAVE_LIBPNG */ + + if (!fp) + return (PIXA *)ERROR_PTR("stream not defined", __func__, NULL); + + if (fscanf(fp, "\nPixa Version %d\n", &version) != 1) + return (PIXA *)ERROR_PTR("not a pixa file", __func__, NULL); + if (version != PIXA_VERSION_NUMBER) + return (PIXA *)ERROR_PTR("invalid pixa version", __func__, NULL); + if (fscanf(fp, "Number of pix = %d\n", &n) != 1) + return (PIXA *)ERROR_PTR("not a pixa file", __func__, NULL); + if (n < 0) + return (PIXA *)ERROR_PTR("num pix ptrs < 0", __func__, NULL); + if (n > MaxPixaPtrArraySize) + return (PIXA *)ERROR_PTR("too many pix ptrs", __func__, NULL); + if (n == 0) L_INFO("the pixa is empty\n", __func__); + + if ((boxa = boxaReadStream(fp)) == NULL) + return (PIXA *)ERROR_PTR("boxa not made", __func__, NULL); + if ((pixa = pixaCreate(n)) == NULL) { + boxaDestroy(&boxa); + return (PIXA *)ERROR_PTR("pixa not made", __func__, NULL); + } + boxaDestroy(&pixa->boxa); + pixa->boxa = boxa; + + for (i = 0; i < n; i++) { + if ((fscanf(fp, " pix[%d]: xres = %d, yres = %d\n", + &ignore, &xres, &yres)) != 3) { + pixaDestroy(&pixa); + return (PIXA *)ERROR_PTR("res reading error", __func__, NULL); + } + if ((pix = pixReadStreamPng(fp)) == NULL) { + pixaDestroy(&pixa); + return (PIXA *)ERROR_PTR("pix not read", __func__, NULL); + } + pixSetXRes(pix, xres); + pixSetYRes(pix, yres); + pixaAddPix(pixa, pix, L_INSERT); + } + return pixa; +} + + +/*! + * \brief pixaReadMem() + * + * \param[in] data of serialized pixa + * \param[in] size of data in bytes + * \return pixa, or NULL on error + */ +PIXA * +pixaReadMem(const l_uint8 *data, + size_t size) +{ +FILE *fp; +PIXA *pixa; + + if (!data) + return (PIXA *)ERROR_PTR("data not defined", __func__, NULL); + if ((fp = fopenReadFromMemory(data, size)) == NULL) + return (PIXA *)ERROR_PTR("stream not opened", __func__, NULL); + + pixa = pixaReadStream(fp); + fclose(fp); + if (!pixa) L_ERROR("pixa not read\n", __func__); + return pixa; +} + + +/*! + * \brief pixaWriteDebug() + * + * \param[in] fname + * \param[in] pixa + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) Debug version, intended for use in the library when writing + * to files in a temp directory with names that are compiled in. + * This is used instead of pixaWrite() for all such library calls. + * (2) The global variable LeptDebugOK defaults to 0, and can be set + * or cleared by the function setLeptDebugOK(). + * </pre> + */ +l_ok +pixaWriteDebug(const char *fname, + PIXA *pixa) +{ + if (LeptDebugOK) { + return pixaWrite(fname, pixa); + } else { + L_INFO("write to named temp file %s is disabled\n", __func__, fname); + return 0; + } +} + + +/*! + * \brief pixaWrite() + * + * \param[in] filename + * \param[in] pixa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +l_ok +pixaWrite(const char *filename, + PIXA *pixa) +{ +l_int32 ret; +FILE *fp; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return ERROR_INT("no libpng: can't write data", __func__, 1); +#endif /* !HAVE_LIBPNG */ + + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + if ((fp = fopenWriteStream(filename, "wb")) == NULL) + return ERROR_INT_1("stream not opened", filename, __func__, 1); + ret = pixaWriteStream(fp, pixa); + fclose(fp); + if (ret) + return ERROR_INT_1("pixa not written to stream", filename, __func__, 1); + return 0; +} + + +/*! + * \brief pixaWriteStream() + * + * \param[in] fp file stream opened for "wb" + * \param[in] pixa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +l_ok +pixaWriteStream(FILE *fp, + PIXA *pixa) +{ +l_int32 n, i; +PIX *pix; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return ERROR_INT("no libpng: can't write data", __func__, 1); +#endif /* !HAVE_LIBPNG */ + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + fprintf(fp, "\nPixa Version %d\n", PIXA_VERSION_NUMBER); + fprintf(fp, "Number of pix = %d\n", n); + boxaWriteStream(fp, pixa->boxa); + for (i = 0; i < n; i++) { + if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) + return ERROR_INT("pix not found", __func__, 1); + fprintf(fp, " pix[%d]: xres = %d, yres = %d\n", + i, pix->xres, pix->yres); + pixWriteStreamPng(fp, pix, 0.0); + pixDestroy(&pix); + } + return 0; +} + + +/*! + * \brief pixaWriteMem() + * + * \param[out] pdata data of serialized pixa + * \param[out] psize size of returned data + * \param[in] pixa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Serializes a pixa in memory and puts the result in a buffer. + * </pre> + */ +l_ok +pixaWriteMem(l_uint8 **pdata, + size_t *psize, + PIXA *pixa) +{ +l_int32 ret; +FILE *fp; + + if (pdata) *pdata = NULL; + if (psize) *psize = 0; + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + +#if HAVE_FMEMOPEN + if ((fp = open_memstream((char **)pdata, psize)) == NULL) + return ERROR_INT("stream not opened", __func__, 1); + ret = pixaWriteStream(fp, pixa); + fputc('\0', fp); + fclose(fp); + if (*psize > 0) *psize = *psize - 1; +#else + L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); + #ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #endif /* _WIN32 */ + ret = pixaWriteStream(fp, pixa); + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); + fclose(fp); +#endif /* HAVE_FMEMOPEN */ + return ret; +} + + +/*! + * \brief pixaReadBoth() + * + * \param[in] filename + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) This reads serialized files of either a pixa or a pixacomp, + * and returns a pixa in memory. It requires png and jpeg libraries. + * </pre> + */ +PIXA * +pixaReadBoth(const char *filename) +{ +char buf[32]; +char *sname; +PIXA *pixa; +PIXAC *pac; + + if (!filename) + return (PIXA *)ERROR_PTR("filename not defined", __func__, NULL); + + l_getStructStrFromFile(filename, L_STR_NAME, &sname); + if (!sname) + return (PIXA *)ERROR_PTR("struct name not found", __func__, NULL); + snprintf(buf, sizeof(buf), "%s", sname); + LEPT_FREE(sname); + + if (strcmp(buf, "Pixacomp") == 0) { + if ((pac = pixacompRead(filename)) == NULL) + return (PIXA *)ERROR_PTR("pac not made", __func__, NULL); + pixa = pixaCreateFromPixacomp(pac, L_COPY); + pixacompDestroy(&pac); + } else if (strcmp(buf, "Pixa") == 0) { + if ((pixa = pixaRead(filename)) == NULL) + return (PIXA *)ERROR_PTR("pixa not made", __func__, NULL); + } else { + return (PIXA *)ERROR_PTR("invalid file type", __func__, NULL); + } + return pixa; +} + + +/*---------------------------------------------------------------------* + * Pixaa serialized I/O * + *---------------------------------------------------------------------*/ +/*! + * \brief pixaaReadFromFiles() + * + * \param[in] dirname directory + * \param[in] substr [optional] substring filter on filenames; can be NULL + * \param[in] first 0-based + * \param[in] nfiles use 0 for everything from %first to the end + * \return paa, or NULL on error or if no pixa files are found. + * + * <pre> + * Notes: + * (1) The files must be serialized pixa files (e.g., *.pa) + * If some files cannot be read, warnings are issued. + * (2) Use %substr to filter filenames in the directory. If + * %substr == NULL, this takes all files. + * (3) After filtering, use %first and %nfiles to select + * a contiguous set of files, that have been lexically + * sorted in increasing order. + * </pre> + */ +PIXAA * +pixaaReadFromFiles(const char *dirname, + const char *substr, + l_int32 first, + l_int32 nfiles) +{ +char *fname; +l_int32 i, n; +PIXA *pixa; +PIXAA *paa; +SARRAY *sa; + + if (!dirname) + return (PIXAA *)ERROR_PTR("dirname not defined", __func__, NULL); + + sa = getSortedPathnamesInDirectory(dirname, substr, first, nfiles); + if (!sa || ((n = sarrayGetCount(sa)) == 0)) { + sarrayDestroy(&sa); + return (PIXAA *)ERROR_PTR("no pixa files found", __func__, NULL); + } + + paa = pixaaCreate(n); + for (i = 0; i < n; i++) { + fname = sarrayGetString(sa, i, L_NOCOPY); + if ((pixa = pixaRead(fname)) == NULL) { + L_ERROR("pixa not read for %d-th file", __func__, i); + continue; + } + pixaaAddPixa(paa, pixa, L_INSERT); + } + + sarrayDestroy(&sa); + return paa; +} + + +/*! + * \brief pixaaRead() + * + * \param[in] filename + * \return paa, or NULL on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +PIXAA * +pixaaRead(const char *filename) +{ +FILE *fp; +PIXAA *paa; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return (PIXAA *)ERROR_PTR("no libpng: can't read data", __func__, NULL); +#endif /* !HAVE_LIBPNG */ + + if (!filename) + return (PIXAA *)ERROR_PTR("filename not defined", __func__, NULL); + + if ((fp = fopenReadStream(filename)) == NULL) + return (PIXAA *)ERROR_PTR_1("stream not opened", + filename, __func__, NULL); + paa = pixaaReadStream(fp); + fclose(fp); + if (!paa) + return (PIXAA *)ERROR_PTR_1("paa not read", filename, __func__, NULL); + return paa; +} + + +/*! + * \brief pixaaReadStream() + * + * \param[in] fp file stream + * \return paa, or NULL on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * (2) It is OK for the pixaa to be empty. + * </pre> + */ +PIXAA * +pixaaReadStream(FILE *fp) +{ +l_int32 n, i, version; +l_int32 ignore; +BOXA *boxa; +PIXA *pixa; +PIXAA *paa; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return (PIXAA *)ERROR_PTR("no libpng: can't read data", __func__, NULL); +#endif /* !HAVE_LIBPNG */ + + if (!fp) + return (PIXAA *)ERROR_PTR("stream not defined", __func__, NULL); + + if (fscanf(fp, "\nPixaa Version %d\n", &version) != 1) + return (PIXAA *)ERROR_PTR("not a pixaa file", __func__, NULL); + if (version != PIXAA_VERSION_NUMBER) + return (PIXAA *)ERROR_PTR("invalid pixaa version", __func__, NULL); + if (fscanf(fp, "Number of pixa = %d\n", &n) != 1) + return (PIXAA *)ERROR_PTR("not a pixaa file", __func__, NULL); + if (n < 0) + return (PIXAA *)ERROR_PTR("num pixa ptrs < 0", __func__, NULL); + if (n > MaxPixaaPtrArraySize) + return (PIXAA *)ERROR_PTR("too many pixa ptrs", __func__, NULL); + if (n == 0) L_INFO("the pixaa is empty\n", __func__); + + if ((paa = pixaaCreate(n)) == NULL) + return (PIXAA *)ERROR_PTR("paa not made", __func__, NULL); + if ((boxa = boxaReadStream(fp)) == NULL) { + pixaaDestroy(&paa); + return (PIXAA *)ERROR_PTR("boxa not made", __func__, NULL); + } + boxaDestroy(&paa->boxa); + paa->boxa = boxa; + + for (i = 0; i < n; i++) { + if ((fscanf(fp, "\n\n --------------- pixa[%d] ---------------\n", + &ignore)) != 1) { + pixaaDestroy(&paa); + return (PIXAA *)ERROR_PTR("text reading", __func__, NULL); + } + if ((pixa = pixaReadStream(fp)) == NULL) { + pixaaDestroy(&paa); + return (PIXAA *)ERROR_PTR("pixa not read", __func__, NULL); + } + pixaaAddPixa(paa, pixa, L_INSERT); + } + + return paa; +} + + +/*! + * \brief pixaaReadMem() + * + * \param[in] data of serialized pixaa + * \param[in] size of data in bytes + * \return paa, or NULL on error + */ +PIXAA * +pixaaReadMem(const l_uint8 *data, + size_t size) +{ +FILE *fp; +PIXAA *paa; + + if (!data) + return (PIXAA *)ERROR_PTR("data not defined", __func__, NULL); + if ((fp = fopenReadFromMemory(data, size)) == NULL) + return (PIXAA *)ERROR_PTR("stream not opened", __func__, NULL); + + paa = pixaaReadStream(fp); + fclose(fp); + if (!paa) L_ERROR("paa not read\n", __func__); + return paa; +} + + +/*! + * \brief pixaaWrite() + * + * \param[in] filename + * \param[in] paa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +l_ok +pixaaWrite(const char *filename, + PIXAA *paa) +{ +l_int32 ret; +FILE *fp; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return ERROR_INT("no libpng: can't read data", __func__, 1); +#endif /* !HAVE_LIBPNG */ + + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + + if ((fp = fopenWriteStream(filename, "wb")) == NULL) + return ERROR_INT_1("stream not opened", filename, __func__, 1); + ret = pixaaWriteStream(fp, paa); + fclose(fp); + if (ret) + return ERROR_INT_1("paa not written to stream", filename, __func__, 1); + return 0; +} + + +/*! + * \brief pixaaWriteStream() + * + * \param[in] fp file stream opened for "wb" + * \param[in] paa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The pix are stored in the file as png. + * If the png library is not linked, this will fail. + * </pre> + */ +l_ok +pixaaWriteStream(FILE *fp, + PIXAA *paa) +{ +l_int32 n, i; +PIXA *pixa; + +#if !HAVE_LIBPNG /* defined in environ.h and config_auto.h */ + return ERROR_INT("no libpng: can't read data", __func__, 1); +#endif /* !HAVE_LIBPNG */ + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + + n = pixaaGetCount(paa, NULL); + fprintf(fp, "\nPixaa Version %d\n", PIXAA_VERSION_NUMBER); + fprintf(fp, "Number of pixa = %d\n", n); + boxaWriteStream(fp, paa->boxa); + for (i = 0; i < n; i++) { + if ((pixa = pixaaGetPixa(paa, i, L_CLONE)) == NULL) + return ERROR_INT("pixa not found", __func__, 1); + fprintf(fp, "\n\n --------------- pixa[%d] ---------------\n", i); + pixaWriteStream(fp, pixa); + pixaDestroy(&pixa); + } + return 0; +} + + +/*! + * \brief pixaaWriteMem() + * + * \param[out] pdata data of serialized pixaa + * \param[out] psize size of returned data + * \param[in] paa + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Serializes a pixaa in memory and puts the result in a buffer. + * </pre> + */ +l_ok +pixaaWriteMem(l_uint8 **pdata, + size_t *psize, + PIXAA *paa) +{ +l_int32 ret; +FILE *fp; + + if (pdata) *pdata = NULL; + if (psize) *psize = 0; + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1); + if (!paa) + return ERROR_INT("paa not defined", __func__, 1); + +#if HAVE_FMEMOPEN + if ((fp = open_memstream((char **)pdata, psize)) == NULL) + return ERROR_INT("stream not opened", __func__, 1); + ret = pixaaWriteStream(fp, paa); + fputc('\0', fp); + fclose(fp); + if (*psize > 0) *psize = *psize - 1; +#else + L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__); + #ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); + #endif /* _WIN32 */ + ret = pixaaWriteStream(fp, paa); + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); + fclose(fp); +#endif /* HAVE_FMEMOPEN */ + return ret; +} +
