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;
+}
+