text: /*====================================================================*
   -  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.
   *====================================================================*/
  
  /*
   *   pixabasic.c
   *
   *      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()
   *           l_int32   pixaChangeRefcount()
   *           PIX      *pixaGetPix()
   *           l_int32   pixaGetPixDimensions()
   *           PIX      *pixaGetBoxa()
   *           l_int32   pixaGetBoxaCount()
   *           BOX      *pixaGetBox()
   *           l_int32   pixaGetBoxGeometry()
   *           l_int32   pixaSetBoxa()
   *           PIX     **pixaGetPixArray()
   *           l_int32   pixaVerifyDepth()
   *           l_int32   pixaIsFull()
   *           l_int32   pixaCountText()
   *           void   ***pixaGetLinePtrs()
   *
   *      Pixa array modifiers
   *           l_int32   pixaReplacePix()
   *           l_int32   pixaInsertPix()
   *           l_int32   pixaRemovePix()
   *           l_int32   pixaRemovePixAndSave()
   *           l_int32   pixaInitFull()
   *           l_int32   pixaClear()
   *
   *      Pixa and Pixaa combination
   *           l_int32   pixaJoin()
   *           l_int32   pixaaJoin()
   *
   *      Pixaa creation, destruction
   *           PIXAA    *pixaaCreate()
   *           PIXAA    *pixaaCreateFromPixa()
   *           void      pixaaDestroy()
   *
   *      Pixaa addition
   *           l_int32   pixaaAddPixa()
   *           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   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()
   *           l_int32   pixaWrite()
   *           l_int32   pixaWriteStream()
   *
   *      Pixaa serialized I/O  (requires png support)
   *           PIXAA    *pixaaReadFromFiles()
   *           PIXAA    *pixaaRead()
   *           PIXAA    *pixaaReadStream()
   *           l_int32   pixaaWrite()
   *           l_int32   pixaaWriteStream()
   *
   *
   *   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.
   */
  
  #include <string.h>
  #include BLIK_OPENALPR_U_allheaders_h //original-code:"allheaders.h"
  
  static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
  
      /* Static functions */
  static l_int32 pixaExtendArray(PIXA  *pixa);
  
  
  /*---------------------------------------------------------------------*
   *                    Pixa creation, destruction, copy                 *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaCreate()
   *
   *      Input:  n  (initial number of ptrs)
   *      Return: pixa, or null on error
   */
  PIXA *
  pixaCreate(l_int32  n)
  {
  PIXA  *pixa;
  
      PROCNAME("pixaCreate");
  
      if (n <= 0)
          n = INITIAL_PTR_ARRAYSIZE;
  
      if ((pixa = (PIXA *)CALLOC(1, sizeof(PIXA))) == NULL)
          return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
      pixa->n = 0;
      pixa->nalloc = n;
      pixa->refcount = 1;
  
      if ((pixa->pix = (PIX **)CALLOC(n, sizeof(PIX *))) == NULL)
          return (PIXA *)ERROR_PTR("pix ptrs not made", procName, NULL);
      if ((pixa->boxa = boxaCreate(n)) == NULL)
          return (PIXA *)ERROR_PTR("boxa not made", procName, NULL);
  
      return pixa;
  }
  
  
  /*!
   *  pixaCreateFromPix()
   *
   *      Input:  pixs  (with individual components on a lattice)
   *              n   (number of components)
   *              cellw   (width of each cell)
   *              cellh   (height of each cell)
   *      Return: pixa, or null on error
   *
   *  Notes:
   *      (1) For bpp = 1, we truncate each retrieved pix to the ON
   *          pixels, which we assume for now start at (0,0)
   */
  PIXA *
  pixaCreateFromPix(PIX     *pixs,
                    l_int32  n,
                    l_int32  cellw,
                    l_int32  cellh)
  {
  l_int32  w, h, d, nw, nh, i, j, index;
  PIX     *pix, *pixt;
  PIXA    *pixa;
  
      PROCNAME("pixaCreateFromPix");
  
      if (!pixs)
          return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
      if (n <= 0)
          return (PIXA *)ERROR_PTR("n must be > 0", procName, NULL);
  
      if ((pixa = pixaCreate(n)) == NULL)
          return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
      pixGetDimensions(pixs, &w, &h, &d);
      if ((pixt = pixCreate(cellw, cellh, d)) == NULL)
          return (PIXA *)ERROR_PTR("pixt not made", procName, 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(pixt, 0, 0, cellw, cellh, PIX_SRC, pixs,
                     j * cellw, i * cellh);
              if (d == 1 && !pixClipToForeground(pixt, &pix, NULL))
                  pixaAddPix(pixa, pix, L_INSERT);
              else
                  pixaAddPix(pixa, pixt, L_COPY);
          }
      }
  
      pixDestroy(&pixt);
      return pixa;
  }
  
  
  /*!
   *  pixaCreateFromBoxa()
   *
   *      Input:  pixs
   *              boxa
   *              &cropwarn (<optional return> TRUE if the boxa extent
   *                         is larger than pixs.
   *      Return: pixad, or null on error
   *
   *  Notes:
   *      (1) This simply extracts from pixs the region corresponding to each
   *          box in the boxa.
   *      (2) The 3rd 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.
   */
  PIXA *
  pixaCreateFromBoxa(PIX      *pixs,
                     BOXA     *boxa,
                     l_int32  *pcropwarn)
  {
  l_int32  i, n, w, h, wbox, hbox, cropwarn;
  BOX     *box, *boxc;
  PIX     *pixd;
  PIXA    *pixad;
  
      PROCNAME("pixaCreateFromBoxa");
  
      if (!pixs)
          return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
      if (!boxa)
          return (PIXA *)ERROR_PTR("boxa not defined", procName, NULL);
  
      n = boxaGetCount(boxa);
      if ((pixad = pixaCreate(n)) == NULL)
          return (PIXA *)ERROR_PTR("pixad not made", procName, 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 = 0; i < n; 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;
  }
  
  
  /*!
   *  pixaSplitPix()
   *
   *      Input:  pixs  (with individual components on a lattice)
   *              nx   (number of mosaic cells horizontally)
   *              ny   (number of mosaic cells vertically)
   *              borderwidth  (of added border on all sides)
   *              bordercolor  (in our RGBA format: 0xrrggbbaa)
   *      Return: pixa, or null on error
   *
   *  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.
   */
  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     *pixt;
  PIXA    *pixa;
  
      PROCNAME("pixaSplitPix");
  
      if (!pixs)
          return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
      if (nx <= 0 || ny <= 0)
          return (PIXA *)ERROR_PTR("nx and ny must be > 0", procName, NULL);
      borderwidth = L_MAX(0, borderwidth);
  
      if ((pixa = pixaCreate(nx * ny)) == NULL)
          return (PIXA *)ERROR_PTR("pixa not made", procName, 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 ((pixt = pixCreate(cellw + 2 * borderwidth,
                                    cellh + 2 * borderwidth, d)) == NULL)
                  return (PIXA *)ERROR_PTR("pixt not made", procName, NULL);
              pixCopyColormap(pixt, pixs);
              if (borderwidth == 0) {  /* initialize full image to white */
                  if (d == 1)
                      pixClearAll(pixt);
                  else
                      pixSetAll(pixt);
              } else {
                  pixSetAllArbitrary(pixt, bordercolor);
              }
              pixRasterop(pixt, borderwidth, borderwidth, cellw, cellh,
                          PIX_SRC, pixs, j * cellw, i * cellh);
              pixaAddPix(pixa, pixt, L_INSERT);
          }
      }
  
      return pixa;
  }
  
  
  /*!
   *  pixaDestroy()
   *
   *      Input:  &pixa (<can be nulled>)
   *      Return: void
   *
   *  Notes:
   *      (1) Decrements the ref count and, if 0, destroys the pixa.
   *      (2) Always nulls the input ptr.
   */
  void
  pixaDestroy(PIXA  **ppixa)
  {
  l_int32  i;
  PIXA    *pixa;
  
      PROCNAME("pixaDestroy");
  
      if (ppixa == NULL) {
          L_WARNING("ptr address is NULL!\n", procName);
          return;
      }
  
      if ((pixa = *ppixa) == NULL)
          return;
  
          /* Decrement the refcount.  If it is 0, destroy the pixa. */
      pixaChangeRefcount(pixa, -1);
      if (pixa->refcount <= 0) {
          for (i = 0; i < pixa->n; i++)
              pixDestroy(&pixa->pix[i]);
          FREE(pixa->pix);
          boxaDestroy(&pixa->boxa);
          FREE(pixa);
      }
  
      *ppixa = NULL;
      return;
  }
  
  
  /*!
   *  pixaCopy()
   *
   *      Input:  pixas
   *              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;
  BOX     *boxc;
  PIX     *pixc;
  PIXA    *pixac;
  
      PROCNAME("pixaCopy");
  
      if (!pixa)
          return (PIXA *)ERROR_PTR("pixa not defined", procName, NULL);
  
      if (copyflag == L_CLONE) {
          pixaChangeRefcount(pixa, 1);
          return pixa;
      }
  
      if (copyflag != L_COPY && copyflag != L_COPY_CLONE)
          return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
  
      if ((pixac = pixaCreate(pixa->n)) == NULL)
          return (PIXA *)ERROR_PTR("pixac not made", procName, NULL);
      for (i = 0; i < pixa->n; i++) {
          if (copyflag == L_COPY) {
              pixc = pixaGetPix(pixa, i, L_COPY);
              boxc = pixaGetBox(pixa, i, L_COPY);
          } else {  /* copy-clone */
              pixc = pixaGetPix(pixa, i, L_CLONE);
              boxc = pixaGetBox(pixa, i, L_CLONE);
          }
          pixaAddPix(pixac, pixc, L_INSERT);
          pixaAddBox(pixac, boxc, L_INSERT);
      }
  
      return pixac;
  }
  
  
  
  /*---------------------------------------------------------------------*
   *                              Pixa addition                          *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaAddPix()
   *
   *      Input:  pixa
   *              pix  (to be added)
   *              copyflag (L_INSERT, L_COPY, L_CLONE)
   *      Return: 0 if OK; 1 on error
   */
  l_int32
  pixaAddPix(PIXA    *pixa,
             PIX     *pix,
             l_int32  copyflag)
  {
  l_int32  n;
  PIX     *pixc;
  
      PROCNAME("pixaAddPix");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 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", procName, 1);
      if (!pixc)
          return ERROR_INT("pixc not made", procName, 1);
  
      n = pixaGetCount(pixa);
      if (n >= pixa->nalloc)
          pixaExtendArray(pixa);
      pixa->pix[n] = pixc;
      pixa->n++;
  
      return 0;
  }
  
  
  /*!
   *  pixaAddBox()
   *
   *      Input:  pixa
   *              box
   *              copyflag (L_INSERT, L_COPY, L_CLONE)
   *      Return: 0 if OK, 1 on error
   */
  l_int32
  pixaAddBox(PIXA    *pixa,
             BOX     *box,
             l_int32  copyflag)
  {
      PROCNAME("pixaAddBox");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (!box)
          return ERROR_INT("box not defined", procName, 1);
      if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE)
          return ERROR_INT("invalid copyflag", procName, 1);
  
      boxaAddBox(pixa->boxa, box, copyflag);
      return 0;
  }
  
  
  /*!
   *  pixaExtendArray()
   *
   *      Input:  pixa
   *      Return: 0 if OK; 1 on error
   *
   *  Notes:
   *      (1) Doubles the size of the pixa and boxa ptr arrays.
   */
  static l_int32
  pixaExtendArray(PIXA  *pixa)
  {
      PROCNAME("pixaExtendArray");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      return pixaExtendArrayToSize(pixa, 2 * pixa->nalloc);
  }
  
  
  /*!
   *  pixaExtendArrayToSize()
   *
   *      Input:  pixa
   *      Return: 0 if OK; 1 on error
   *
   *  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.
   */
  l_int32
  pixaExtendArrayToSize(PIXA    *pixa,
                        l_int32  size)
  {
      PROCNAME("pixaExtendArrayToSize");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      if (size > pixa->nalloc) {
          if ((pixa->pix = (PIX **)reallocNew((void **)&pixa->pix,
                                   sizeof(PIX *) * pixa->nalloc,
                                   size * sizeof(PIX *))) == NULL)
              return ERROR_INT("new ptr array not returned", procName, 1);
          pixa->nalloc = size;
      }
      return boxaExtendArrayToSize(pixa->boxa, size);
  }
  
  
  /*---------------------------------------------------------------------*
   *                             Pixa accessors                          *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaGetCount()
   *
   *      Input:  pixa
   *      Return: count, or 0 if no pixa
   */
  l_int32
  pixaGetCount(PIXA  *pixa)
  {
      PROCNAME("pixaGetCount");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 0);
  
      return pixa->n;
  }
  
  
  /*!
   *  pixaChangeRefcount()
   *
   *      Input:  pixa
   *      Return: 0 if OK, 1 on error
   */
  l_int32
  pixaChangeRefcount(PIXA    *pixa,
                     l_int32  delta)
  {
      PROCNAME("pixaChangeRefcount");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      pixa->refcount += delta;
      return 0;
  }
  
  
  /*!
   *  pixaGetPix()
   *
   *      Input:  pixa
   *              index  (to the index-th pix)
   *              accesstype  (L_COPY or L_CLONE)
   *      Return: pix, or null on error
   */
  PIX *
  pixaGetPix(PIXA    *pixa,
             l_int32  index,
             l_int32  accesstype)
  {
  PIX  *pix;
  
      PROCNAME("pixaGetPix");
  
      if (!pixa)
          return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
      if (index < 0 || index >= pixa->n)
          return (PIX *)ERROR_PTR("index not valid", procName, NULL);
      if ((pix = pixa->pix[index]) == NULL) {
          L_ERROR("no pix at pixa[%d]\n", procName, index);
          return (PIX *)ERROR_PTR("pix not found!", procName, NULL);
      }
  
      if (accesstype == L_COPY)
          return pixCopy(NULL, pix);
      else if (accesstype == L_CLONE)
          return pixClone(pix);
      else
          return (PIX *)ERROR_PTR("invalid accesstype", procName, NULL);
  }
  
  
  /*!
   *  pixaGetPixDimensions()
   *
   *      Input:  pixa
   *              index  (to the index-th box)
   *              &w, &h, &d (<optional return>; each can be null)
   *      Return: 0 if OK, 1 on error
   */
  l_int32
  pixaGetPixDimensions(PIXA     *pixa,
                       l_int32   index,
                       l_int32  *pw,
                       l_int32  *ph,
                       l_int32  *pd)
  {
  PIX  *pix;
  
      PROCNAME("pixaGetPixDimensions");
  
      if (pw) *pw = 0;
      if (ph) *ph = 0;
      if (pd) *pd = 0;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (index < 0 || index >= pixa->n)
          return ERROR_INT("index not valid", procName, 1);
  
      if ((pix = pixaGetPix(pixa, index, L_CLONE)) == NULL)
          return ERROR_INT("pix not found!", procName, 1);
      pixGetDimensions(pix, pw, ph, pd);
      pixDestroy(&pix);
      return 0;
  }
  
  
  /*!
   *  pixaGetBoxa()
   *
   *      Input:  pixa
   *              accesstype  (L_COPY, L_CLONE, L_COPY_CLONE)
   *      Return: boxa, or null on error
   */
  BOXA *
  pixaGetBoxa(PIXA    *pixa,
              l_int32  accesstype)
  {
      PROCNAME("pixaGetBoxa");
  
      if (!pixa)
          return (BOXA *)ERROR_PTR("pixa not defined", procName, NULL);
      if (!pixa->boxa)
          return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
      if (accesstype != L_COPY && accesstype != L_CLONE &&
          accesstype != L_COPY_CLONE)
          return (BOXA *)ERROR_PTR("invalid accesstype", procName, NULL);
  
      return boxaCopy(pixa->boxa, accesstype);
  }
  
  
  /*!
   *  pixaGetBoxaCount()
   *
   *      Input:  pixa
   *      Return: count, or 0 on error
   */
  l_int32
  pixaGetBoxaCount(PIXA  *pixa)
  {
      PROCNAME("pixaGetBoxaCount");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 0);
  
      return boxaGetCount(pixa->boxa);
  }
  
  
  /*!
   *  pixaGetBox()
   *
   *      Input:  pixa
   *              index  (to the index-th pix)
   *              accesstype  (L_COPY or L_CLONE)
   *      Return: box (if null, not automatically an error), or null on error
   *
   *  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().
   */
  BOX *
  pixaGetBox(PIXA    *pixa,
             l_int32  index,
             l_int32  accesstype)
  {
  BOX  *box;
  
      PROCNAME("pixaGetBox");
  
      if (!pixa)
          return (BOX *)ERROR_PTR("pixa not defined", procName, NULL);
      if (!pixa->boxa)
          return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
      if (index < 0 || index >= pixa->boxa->n)
          return (BOX *)ERROR_PTR("index not valid", procName, NULL);
      if (accesstype != L_COPY && accesstype != L_CLONE)
          return (BOX *)ERROR_PTR("invalid accesstype", procName, 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;
      }
  }
  
  
  /*!
   *  pixaGetBoxGeometry()
   *
   *      Input:  pixa
   *              index  (to the index-th box)
   *              &x, &y, &w, &h (<optional return>; each can be null)
   *      Return: 0 if OK, 1 on error
   */
  l_int32
  pixaGetBoxGeometry(PIXA     *pixa,
                     l_int32   index,
                     l_int32  *px,
                     l_int32  *py,
                     l_int32  *pw,
                     l_int32  *ph)
  {
  BOX  *box;
  
      PROCNAME("pixaGetBoxGeometry");
  
      if (px) *px = 0;
      if (py) *py = 0;
      if (pw) *pw = 0;
      if (ph) *ph = 0;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (index < 0 || index >= pixa->n)
          return ERROR_INT("index not valid", procName, 1);
  
      if ((box = pixaGetBox(pixa, index, L_CLONE)) == NULL)
          return ERROR_INT("box not found!", procName, 1);
      boxGetGeometry(box, px, py, pw, ph);
      boxDestroy(&box);
      return 0;
  }
  
  
  /*!
   *  pixaSetBoxa()
   *
   *      Input:  pixa
   *              boxa
   *              accesstype  (L_INSERT, L_COPY, L_CLONE)
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) This destroys the existing boxa in the pixa.
   */
  l_int32
  pixaSetBoxa(PIXA    *pixa,
              BOXA    *boxa,
              l_int32  accesstype)
  {
      PROCNAME("pixaSetBoxa");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (!boxa)
          return ERROR_INT("boxa not defined", procName, 1);
      if (accesstype != L_INSERT && accesstype != L_COPY &&
          accesstype != L_CLONE)
          return ERROR_INT("invalid access type", procName, 1);
  
      boxaDestroy(&pixa->boxa);
      if (accesstype == L_INSERT)
          pixa->boxa = boxa;
      else
          pixa->boxa = boxaCopy(boxa, accesstype);
  
      return 0;
  }
  
  
  /*!
   *  pixaGetPixArray()
   *
   *      Input:  pixa
   *      Return: pix array, or null on error
   *
   *  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!
   */
  PIX **
  pixaGetPixArray(PIXA  *pixa)
  {
      PROCNAME("pixaGetPixArray");
  
      if (!pixa)
          return (PIX **)ERROR_PTR("pixa not defined", procName, NULL);
  
      return pixa->pix;
  }
  
  
  /*!
   *  pixaVerifyDepth()
   *
   *      Input:  pixa
   *              &maxdepth (<optional return> max depth of all pix)
   *      Return: depth (return 0 if they're not all the same, or on error)
   *
   *  Notes:
   *      (1) It is considered to be an error if there are no pix.
   */
  l_int32
  pixaVerifyDepth(PIXA     *pixa,
                  l_int32  *pmaxdepth)
  {
  l_int32  i, n, d, depth, maxdepth, same;
  
      PROCNAME("pixaVerifyDepth");
  
      if (pmaxdepth) *pmaxdepth = 0;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 0);
  
      depth = 0;
      n = pixaGetCount(pixa);
      maxdepth = 0;
      same = 1;
      for (i = 0; i < n; i++) {
          if (pixaGetPixDimensions(pixa, i, NULL, NULL, &d))
              return ERROR_INT("pix depth not found", procName, 0);
          maxdepth = L_MAX(maxdepth, d);
          if (i == 0)
              depth = d;
          else if (d != depth)
              same = 0;
      }
      if (pmaxdepth) *pmaxdepth = maxdepth;
      return (same == 1) ? depth : 0;
  }
  
  
  /*!
   *  pixaIsFull()
   *
   *      Input:  pixa
   *              &fullpa (<optional return> 1 if pixa is full)
   *              &fullba (<optional return> 1 if boxa is full)
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) A pixa is "full" if the array of pix is fully
   *          occupied from index 0 to index (pixa->n - 1).
   */
  l_int32
  pixaIsFull(PIXA     *pixa,
             l_int32  *pfullpa,
             l_int32  *pfullba)
  {
  l_int32  i, n, full;
  BOXA    *boxa;
  PIX     *pix;
  
      PROCNAME("pixaIsFull");
  
      if (pfullpa) *pfullpa = 0;
      if (pfullba) *pfullba = 0;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 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;
  }
  
  
  /*!
   *  pixaCountText()
   *
   *      Input:  pixa
   *              &ntext (<return> number of pix with non-empty text strings)
   *      Return: 0 if OK, 1 on error.
   *
   *  Notes:
   *      (1) All pix have non-empty text strings if the returned value @ntext
   *          equals the pixa count.
   */
  l_int32
  pixaCountText(PIXA     *pixa,
                l_int32  *pntext)
  {
  char    *text;
  l_int32  i, n;
  PIX     *pix;
  
      PROCNAME("pixaCountText");
  
      if (!pntext)
          return ERROR_INT("&ntext not defined", procName, 1);
      *pntext = 0;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 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;
  }
  
  
  /*!
   *  pixaGetLinePtrs()
   *
   *      Input:  pixa (of pix that all have the same depth)
   *              &size (<optional return> number of pix in the pixa)
   *      Return: array of array of line ptrs, or null on error
   *
   *  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++)
   *                FREE(lineset[i]);
   *            FREE(lineset);
   */
  void ***
  pixaGetLinePtrs(PIXA     *pixa,
                  l_int32  *psize)
  {
  l_int32  i, n;
  void   **lineptrs;
  void  ***lineset;
  PIX     *pix;
  
      PROCNAME("pixaGetLinePtrs");
  
      if (psize) *psize = 0;
      if (!pixa)
          return (void ***)ERROR_PTR("pixa not defined", procName, NULL);
      if (pixaVerifyDepth(pixa, NULL) == 0)
          return (void ***)ERROR_PTR("pixa not all same depth", procName, NULL);
      n = pixaGetCount(pixa);
      if (psize) *psize = n;
      if ((lineset = (void ***)CALLOC(n, sizeof(void **))) == NULL)
          return (void ***)ERROR_PTR("lineset not made", procName, 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 array modifiers                          *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaReplacePix()
   *
   *      Input:  pixa
   *              index  (to the index-th pix)
   *              pix (insert to replace existing one)
   *              box (<optional> insert to replace existing)
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) In-place replacement of one pix.
   *      (2) The previous pix at that location is destroyed.
   */
  l_int32
  pixaReplacePix(PIXA    *pixa,
                 l_int32  index,
                 PIX     *pix,
                 BOX     *box)
  {
  BOXA  *boxa;
  
      PROCNAME("pixaReplacePix");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (index < 0 || index >= pixa->n)
          return ERROR_INT("index not valid", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 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", procName, 1);
          boxaReplaceBox(boxa, index, box);
      }
  
      return 0;
  }
  
  
  /*!
   *  pixaInsertPix()
   *
   *      Input:  pixa
   *              index (at which pix is to be inserted)
   *              pixs (new pix to be inserted)
   *              box (<optional> new box to be inserted)
   *      Return: 0 if OK, 1 on error
   *
   *  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().
   */
  l_int32
  pixaInsertPix(PIXA    *pixa,
                l_int32  index,
                PIX     *pixs,
                BOX     *box)
  {
  l_int32  i, n;
  
      PROCNAME("pixaInsertPix");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      n = pixaGetCount(pixa);
      if (index < 0 || index > n)
          return ERROR_INT("index not in {0...n}", procName, 1);
      if (!pixs)
          return ERROR_INT("pixs not defined", procName, 1);
  
      if (n >= pixa->nalloc) {  /* extend both ptr arrays */
          pixaExtendArray(pixa);
          boxaExtendArray(pixa->boxa);
      }
      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;
  }
  
  
  /*!
   *  pixaRemovePix()
   *
   *      Input:  pixa
   *              index (of pix to be removed)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaRemovePix(PIXA    *pixa,
                l_int32  index)
  {
  l_int32  i, n, nbox;
  BOXA    *boxa;
  PIX    **array;
  
      PROCNAME("pixaRemovePix");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      n = pixaGetCount(pixa);
      if (index < 0 || index >= n)
          return ERROR_INT("index not in {0...n - 1}", procName, 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;
  }
  
  
  /*!
   *  pixaRemovePixAndSave()
   *
   *      Input:  pixa
   *              index (of pix to be removed)
   *              &pix (<optional return> removed pix)
   *              &box (<optional return> removed box)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaRemovePixAndSave(PIXA    *pixa,
                       l_int32  index,
                       PIX    **ppix,
                       BOX    **pbox)
  {
  l_int32  i, n, nbox;
  BOXA    *boxa;
  PIX    **array;
  
      PROCNAME("pixaRemovePixAndSave");
  
      if (ppix) *ppix = NULL;
      if (pbox) *pbox = NULL;
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      n = pixaGetCount(pixa);
      if (index < 0 || index >= n)
          return ERROR_INT("index not in {0...n - 1}", procName, 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;
  }
  
  
  /*!
   *  pixaInitFull()
   *
   *      Input:  pixa (typically empty)
   *              pix (<optional> to be replicated into the entire pixa ptr array)
   *              box (<optional> to be replicated into the entire boxa ptr array)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaInitFull(PIXA  *pixa,
               PIX   *pix,
               BOX   *box)
  {
  l_int32  i, n;
  PIX     *pixt;
  
      PROCNAME("pixaInitFull");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      n = pixa->nalloc;
      pixa->n = n;
      for (i = 0; i < n; i++) {
          if (pix)
              pixt = pixCopy(NULL, pix);
          else
              pixt = pixCreate(1, 1, 1);
          pixaReplacePix(pixa, i, pixt, NULL);
      }
      if (box)
          boxaInitFull(pixa->boxa, box);
  
      return 0;
  }
  
  
  /*!
   *  pixaClear()
   *
   *      Input:  pixa
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaClear(PIXA  *pixa)
  {
  l_int32  i, n;
  
      PROCNAME("pixaClear");
  
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 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                     *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaJoin()
   *
   *      Input:  pixad  (dest pixa; add to this one)
   *              pixas  (<optional> source pixa; add from this one)
   *              istart  (starting index in pixas)
   *              iend  (ending index in pixas; use -1 to cat all)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaJoin(PIXA    *pixad,
           PIXA    *pixas,
           l_int32  istart,
           l_int32  iend)
  {
  l_int32  i, n, nb;
  BOXA    *boxas, *boxad;
  PIX     *pix;
  
      PROCNAME("pixaJoin");
  
      if (!pixad)
          return ERROR_INT("pixad not defined", procName, 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", procName, 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;
  }
  
  
  /*!
   *  pixaaJoin()
   *
   *      Input:  paad  (dest pixaa; add to this one)
   *              paas  (<optional> source pixaa; add from this one)
   *              istart  (starting index in pixaas)
   *              iend  (ending index in pixaas; use -1 to cat all)
   *      Return: 0 if OK, 1 on error
   *
   *  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'
   */
  l_int32
  pixaaJoin(PIXAA   *paad,
            PIXAA   *paas,
            l_int32  istart,
            l_int32  iend)
  {
  l_int32  i, n;
  PIXA    *pixa;
  
      PROCNAME("pixaaJoin");
  
      if (!paad)
          return ERROR_INT("pixaad not defined", procName, 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", procName, 1);
  
      for (i = istart; i <= iend; i++) {
          pixa = pixaaGetPixa(paas, i, L_CLONE);
          pixaaAddPixa(paad, pixa, L_INSERT);
      }
  
      return 0;
  }
  
  
  /*---------------------------------------------------------------------*
   *                    Pixaa creation and destruction                   *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaaCreate()
   *
   *      Input:  n  (initial number of pixa ptrs)
   *      Return: paa, or null on error
   *
   *  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.
   */
  PIXAA *
  pixaaCreate(l_int32  n)
  {
  PIXAA  *paa;
  
      PROCNAME("pixaaCreate");
  
      if (n <= 0)
          n = INITIAL_PTR_ARRAYSIZE;
  
      if ((paa = (PIXAA *)CALLOC(1, sizeof(PIXAA))) == NULL)
          return (PIXAA *)ERROR_PTR("paa not made", procName, NULL);
      paa->n = 0;
      paa->nalloc = n;
  
      if ((paa->pixa = (PIXA **)CALLOC(n, sizeof(PIXA *))) == NULL) {
          pixaaDestroy(&paa);
          return (PIXAA *)ERROR_PTR("pixa ptrs not made", procName, NULL);
      }
      paa->boxa = boxaCreate(n);
  
      return paa;
  }
  
  
  /*!
   *  pixaaCreateFromPixa()
   *
   *      Input:  pixa
   *              n (number specifying subdivision of pixa)
   *              type (L_CHOOSE_CONSECUTIVE, L_CHOOSE_SKIP_BY)
   *              copyflag (L_CLONE, L_COPY)
   *      Return: paa, or null on error
   *
   *  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.
   */
  PIXAA *
  pixaaCreateFromPixa(PIXA    *pixa,
                      l_int32  n,
                      l_int32  type,
                      l_int32  copyflag)
  {
  l_int32  count, i, j, npixa;
  PIX     *pix;
  PIXA    *pixat;
  PIXAA   *paa;
  
      PROCNAME("pixaaCreateFromPixa");
  
      if (!pixa)
          return (PIXAA *)ERROR_PTR("pixa not defined", procName, NULL);
      count = pixaGetCount(pixa);
      if (count == 0)
          return (PIXAA *)ERROR_PTR("no pix in pixa", procName, NULL);
      if (n <= 0)
          return (PIXAA *)ERROR_PTR("n must be > 0", procName, NULL);
      if (type != L_CHOOSE_CONSECUTIVE && type != L_CHOOSE_SKIP_BY)
          return (PIXAA *)ERROR_PTR("invalid type", procName, NULL);
      if (copyflag != L_CLONE && copyflag != L_COPY)
          return (PIXAA *)ERROR_PTR("invalid copyflag", procName, 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;
  }
  
  
  /*!
   *  pixaaDestroy()
   *
   *      Input:  &paa <to be nulled>
   *      Return: void
   */
  void
  pixaaDestroy(PIXAA  **ppaa)
  {
  l_int32  i;
  PIXAA   *paa;
  
      PROCNAME("pixaaDestroy");
  
      if (ppaa == NULL) {
          L_WARNING("ptr address is NULL!\n", procName);
          return;
      }
  
      if ((paa = *ppaa) == NULL)
          return;
  
      for (i = 0; i < paa->n; i++)
          pixaDestroy(&paa->pixa[i]);
      FREE(paa->pixa);
      boxaDestroy(&paa->boxa);
  
      FREE(paa);
      *ppaa = NULL;
  
      return;
  }
  
  
  /*---------------------------------------------------------------------*
   *                             Pixaa addition                          *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaaAddPixa()
   *
   *      Input:  paa
   *              pixa  (to be added)
   *              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_int32
  pixaaAddPixa(PIXAA   *paa,
               PIXA    *pixa,
               l_int32  copyflag)
  {
  l_int32  n;
  PIXA    *pixac;
  
      PROCNAME("pixaaAddPixa");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
      if (copyflag != L_INSERT && copyflag != L_COPY &&
          copyflag != L_CLONE && copyflag != L_COPY_CLONE)
          return ERROR_INT("invalid copyflag", procName, 1);
  
      if (copyflag == L_INSERT) {
          pixac = pixa;
      } else {
          if ((pixac = pixaCopy(pixa, copyflag)) == NULL)
              return ERROR_INT("pixac not made", procName, 1);
      }
  
      n = pixaaGetCount(paa, NULL);
      if (n >= paa->nalloc)
          pixaaExtendArray(paa);
      paa->pixa[n] = pixac;
      paa->n++;
  
      return 0;
  }
  
  
  /*!
   *  pixaaExtendArray()
   *
   *      Input:  paa
   *      Return: 0 if OK; 1 on error
   */
  l_int32
  pixaaExtendArray(PIXAA  *paa)
  {
      PROCNAME("pixaaExtendArray");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
  
      if ((paa->pixa = (PIXA **)reallocNew((void **)&paa->pixa,
                               sizeof(PIXA *) * paa->nalloc,
                               2 * sizeof(PIXA *) * paa->nalloc)) == NULL)
          return ERROR_INT("new ptr array not returned", procName, 1);
  
      paa->nalloc = 2 * paa->nalloc;
      return 0;
  }
  
  
  /*!
   *  pixaaAddPix()
   *
   *      Input:  paa  (input paa)
   *              index (index of pixa in paa)
   *              pix (to be added)
   *              box (<optional> to be added)
   *              copyflag (L_INSERT, L_COPY, L_CLONE)
   *      Return: 0 if OK; 1 on error
   */
  l_int32
  pixaaAddPix(PIXAA   *paa,
              l_int32  index,
              PIX     *pix,
              BOX     *box,
              l_int32  copyflag)
  {
  PIXA  *pixa;
  
      PROCNAME("pixaaAddPix");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 1);
  
      if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL)
          return ERROR_INT("pixa not found", procName, 1);
      pixaAddPix(pixa, pix, copyflag);
      if (box) pixaAddBox(pixa, box, copyflag);
      pixaDestroy(&pixa);
      return 0;
  }
  
  
  /*!
   *  pixaaAddBox()
   *
   *      Input:  paa
   *              box
   *              copyflag (L_INSERT, L_COPY, L_CLONE)
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) The box can be used, for example, to hold the support region
   *          of a pixa that is being added to the pixaa.
   */
  l_int32
  pixaaAddBox(PIXAA   *paa,
              BOX     *box,
              l_int32  copyflag)
  {
      PROCNAME("pixaaAddBox");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
      if (!box)
          return ERROR_INT("box not defined", procName, 1);
      if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE)
          return ERROR_INT("invalid copyflag", procName, 1);
  
      boxaAddBox(paa->boxa, box, copyflag);
      return 0;
  }
  
  
  
  /*---------------------------------------------------------------------*
   *                            Pixaa accessors                          *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaaGetCount()
   *
   *      Input:  paa
   *              &na (<optional return> number of pix in each pixa)
   *      Return: count, or 0 if no pixaa
   *
   *  Notes:
   *      (1) If paa is empty, a returned na will also be empty.
   */
  l_int32
  pixaaGetCount(PIXAA  *paa,
                NUMA  **pna)
  {
  l_int32  i, n;
  NUMA    *na;
  PIXA    *pixa;
  
      PROCNAME("pixaaGetCount");
  
      if (pna) *pna = NULL;
      if (!paa)
          return ERROR_INT("paa not defined", procName, 0);
  
      n = paa->n;
      if (pna) {
          if ((na = numaCreate(n)) == NULL)
              return ERROR_INT("na not made", procName, 0);
          *pna = na;
          for (i = 0; i < n; i++) {
              pixa = pixaaGetPixa(paa, i, L_CLONE);
              numaAddNumber(na, pixaGetCount(pixa));
              pixaDestroy(&pixa);
          }
      }
      return n;
  }
  
  
  /*!
   *  pixaaGetPixa()
   *
   *      Input:  paa
   *              index  (to the index-th pixa)
   *              accesstype  (L_COPY, L_CLONE, L_COPY_CLONE)
   *      Return: pixa, or null on error
   *
   *  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
   */
  PIXA *
  pixaaGetPixa(PIXAA   *paa,
               l_int32  index,
               l_int32  accesstype)
  {
  PIXA  *pixa;
  
      PROCNAME("pixaaGetPixa");
  
      if (!paa)
          return (PIXA *)ERROR_PTR("paa not defined", procName, NULL);
      if (index < 0 || index >= paa->n)
          return (PIXA *)ERROR_PTR("index not valid", procName, NULL);
      if (accesstype != L_COPY && accesstype != L_CLONE &&
          accesstype != L_COPY_CLONE)
          return (PIXA *)ERROR_PTR("invalid accesstype", procName, NULL);
  
      if ((pixa = paa->pixa[index]) == NULL) {  /* shouldn't happen! */
          L_ERROR("missing pixa[%d]\n", procName, index);
          return (PIXA *)ERROR_PTR("pixa not found at index", procName, NULL);
      }
      return pixaCopy(pixa, accesstype);
  }
  
  
  /*!
   *  pixaaGetBoxa()
   *
   *      Input:  paa
   *              accesstype  (L_COPY, L_CLONE)
   *      Return: boxa, or null on error
   *
   *  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.
   */
  BOXA *
  pixaaGetBoxa(PIXAA   *paa,
               l_int32  accesstype)
  {
      PROCNAME("pixaaGetBoxa");
  
      if (!paa)
          return (BOXA *)ERROR_PTR("paa not defined", procName, NULL);
      if (accesstype != L_COPY && accesstype != L_CLONE)
          return (BOXA *)ERROR_PTR("invalid access type", procName, NULL);
  
      return boxaCopy(paa->boxa, accesstype);
  }
  
  
  /*!
   *  pixaaGetPix()
   *
   *      Input:  paa
   *              index  (index into the pixa array in the pixaa)
   *              ipix  (index into the pix array in the pixa)
   *              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;
  
      PROCNAME("pixaaGetPix");
  
      if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL)
          return (PIX *)ERROR_PTR("pixa not retrieved", procName, NULL);
      if ((pix = pixaGetPix(pixa, ipix, accessflag)) == NULL)
          L_ERROR("pix not retrieved\n", procName);
      pixaDestroy(&pixa);
      return pix;
  }
  
  
  /*!
   *  pixaaVerifyDepth()
   *
   *      Input:  paa
   *              &maxdepth (<optional return> max depth of all pix in pixaa)
   *      Return: depth (return 0 if they're not all the same, or on error)
   */
  l_int32
  pixaaVerifyDepth(PIXAA    *paa,
                   l_int32  *pmaxdepth)
  {
  l_int32  i, npixa, d, maxd, maxdepth, same;
  PIXA    *pixa;
  
      PROCNAME("pixaaVerifyDepth");
  
      if (pmaxdepth) *pmaxdepth = 0;
      if (!paa)
          return ERROR_INT("paa not defined", procName, 0);
  
      npixa = pixaaGetCount(paa, NULL);
      maxdepth = 0;
      same = 1;
      for (i = 0; i < npixa; i++) {
          pixa = pixaaGetPixa(paa, i, L_CLONE);
          if (pixaGetCount(pixa) > 0) {
              d = pixaVerifyDepth(pixa, &maxd);
              maxdepth = L_MAX(maxdepth, maxd);  /* biggest up to this point */
              if (d != maxdepth) same = 0;
          }
          pixaDestroy(&pixa);
      }
      if (pmaxdepth) *pmaxdepth = maxdepth;
      return (same == 1) ? maxdepth : 0;
  }
  
  
  /*!
   *  pixaaIsFull()
   *
   *      Input:  paa
   *              &full (<return> 1 if all pixa in the paa have full pix arrays)
   *      Return: return 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) Does not require boxa associated with each pixa to be full.
   */
  l_int32
  pixaaIsFull(PIXAA    *paa,
              l_int32  *pfull)
  {
  l_int32  i, n, full;
  PIXA    *pixa;
  
      PROCNAME("pixaaIsFull");
  
      if (!pfull)
          return ERROR_INT("&full not defined", procName, 0);
      *pfull = 0;
      if (!paa)
          return ERROR_INT("paa not defined", procName, 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                       *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaaInitFull()
   *
   *      Input:  paa (typically empty)
   *              pixa (to be replicated into the entire pixa ptr array)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaaInitFull(PIXAA  *paa,
                PIXA   *pixa)
  {
  l_int32  i, n;
  PIXA    *pixat;
  
      PROCNAME("pixaaInitFull");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      n = paa->nalloc;
      paa->n = n;
      for (i = 0; i < n; i++) {
          pixat = pixaCopy(pixa, L_COPY);
          pixaaReplacePixa(paa, i, pixat);
      }
  
      return 0;
  }
  
  
  /*!
   *  pixaaReplacePixa()
   *
   *      Input:  paa
   *              index  (to the index-th pixa)
   *              pixa (insert to replace existing one)
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaaReplacePixa(PIXAA   *paa,
                   l_int32  index,
                   PIXA    *pixa)
  {
  
      PROCNAME("pixaaReplacePixa");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
      if (index < 0 || index >= paa->n)
          return ERROR_INT("index not valid", procName, 1);
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      pixaDestroy(&(paa->pixa[index]));
      paa->pixa[index] = pixa;
      return 0;
  }
  
  
  /*!
   *  pixaaClear()
   *
   *      Input:  paa
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) This destroys all pixa in the pixaa, and nulls the ptrs
   *          in the pixa ptr array.
   */
  l_int32
  pixaaClear(PIXAA  *paa)
  {
  l_int32  i, n;
  
      PROCNAME("pixaClear");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
  
      n = pixaaGetCount(paa, NULL);
      for (i = 0; i < n; i++)
          pixaDestroy(&paa->pixa[i]);
      paa->n = 0;
      return 0;
  }
  
  
  /*!
   *  pixaaTruncate()
   *
   *      Input:  paa
   *      Return: 0 if OK, 1 on error
   *
   *  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.
   */
  l_int32
  pixaaTruncate(PIXAA  *paa)
  {
  l_int32  i, n, np;
  PIXA    *pixa;
  
      PROCNAME("pixaaTruncate");
  
      if (!paa)
          return ERROR_INT("paa not defined", procName, 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                        *
   *---------------------------------------------------------------------*/
  #ifdef HAVE_CONFIG_H
  #include "config_auto.h"
  #endif  /* HAVE_CONFIG_H */
  
  /*!
   *  pixaRead()
   *
   *      Input:  filename
   *      Return: pixa, or null on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  PIXA *
  pixaRead(const char  *filename)
  {
  FILE  *fp;
  PIXA  *pixa;
  
      PROCNAME("pixaRead");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return (PIXA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
  #endif  /* !HAVE_LIBPNG */
  
      if (!filename)
          return (PIXA *)ERROR_PTR("filename not defined", procName, NULL);
      if ((fp = fopenReadStream(filename)) == NULL)
          return (PIXA *)ERROR_PTR("stream not opened", procName, NULL);
  
      if ((pixa = pixaReadStream(fp)) == NULL) {
          fclose(fp);
          return (PIXA *)ERROR_PTR("pixa not read", procName, NULL);
      }
  
      fclose(fp);
      return pixa;
  }
  
  
  /*!
   *  pixaReadStream()
   *
   *      Input:  stream
   *      Return: pixa, or null on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  PIXA *
  pixaReadStream(FILE  *fp)
  {
  l_int32  n, i, xres, yres, version;
  l_int32  ignore;
  BOXA    *boxa;
  PIX     *pix;
  PIXA    *pixa;
  
      PROCNAME("pixaReadStream");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return (PIXA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
  #endif  /* !HAVE_LIBPNG */
  
      if (!fp)
          return (PIXA *)ERROR_PTR("stream not defined", procName, NULL);
  
      if (fscanf(fp, "\nPixa Version %d\n", &version) != 1)
          return (PIXA *)ERROR_PTR("not a pixa file", procName, NULL);
      if (version != PIXA_VERSION_NUMBER)
          return (PIXA *)ERROR_PTR("invalid pixa version", procName, NULL);
      if (fscanf(fp, "Number of pix = %d\n", &n) != 1)
          return (PIXA *)ERROR_PTR("not a pixa file", procName, NULL);
  
      if ((boxa = boxaReadStream(fp)) == NULL)
          return (PIXA *)ERROR_PTR("boxa not made", procName, NULL);
      if ((pixa = pixaCreate(n)) == NULL) {
          boxaDestroy(&boxa);
          return (PIXA *)ERROR_PTR("pixa not made", procName, 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", procName, NULL);
          }
          if ((pix = pixReadStreamPng(fp)) == NULL) {
              pixaDestroy(&pixa);
              return (PIXA *)ERROR_PTR("pix not read", procName, NULL);
          }
          pixSetXRes(pix, xres);
          pixSetYRes(pix, yres);
          pixaAddPix(pixa, pix, L_INSERT);
      }
      return pixa;
  }
  
  
  /*!
   *  pixaWrite()
   *
   *      Input:  filename
   *              pixa
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  l_int32
  pixaWrite(const char  *filename,
            PIXA        *pixa)
  {
  FILE  *fp;
  
      PROCNAME("pixaWrite");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return ERROR_INT("no libpng: can't write data", procName, 1);
  #endif  /* !HAVE_LIBPNG */
  
      if (!filename)
          return ERROR_INT("filename not defined", procName, 1);
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 1);
  
      if ((fp = fopenWriteStream(filename, "wb")) == NULL)
          return ERROR_INT("stream not opened", procName, 1);
      if (pixaWriteStream(fp, pixa))
          return ERROR_INT("pixa not written to stream", procName, 1);
      fclose(fp);
      return 0;
  }
  
  
  /*!
   *  pixaWriteStream()
   *
   *      Input:  stream (opened for "wb")
   *              pixa
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  l_int32
  pixaWriteStream(FILE  *fp,
                  PIXA  *pixa)
  {
  l_int32  n, i;
  PIX     *pix;
  
      PROCNAME("pixaWriteStream");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return ERROR_INT("no libpng: can't write data", procName, 1);
  #endif  /* !HAVE_LIBPNG */
  
      if (!fp)
          return ERROR_INT("stream not defined", procName, 1);
      if (!pixa)
          return ERROR_INT("pixa not defined", procName, 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", procName, 1);
          fprintf(fp, " pix[%d]: xres = %d, yres = %d\n",
                  i, pix->xres, pix->yres);
          pixWriteStreamPng(fp, pix, 0.0);
          pixDestroy(&pix);
      }
      return 0;
  }
  
  
  /*---------------------------------------------------------------------*
   *                         Pixaa serialized I/O                        *
   *---------------------------------------------------------------------*/
  /*!
   *  pixaaReadFromFiles()
   *
   *      Input:  dirname (directory)
   *              substr (<optional> substring filter on filenames; can be NULL)
   *              first (0-based)
   *              nfiles (use 0 for everything from @first to the end)
   *      Return: paa, or null on error or if no pixa files are found.
   *
   *  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.
   */
  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;
  
    PROCNAME("pixaaReadFromFiles");
  
    if (!dirname)
        return (PIXAA *)ERROR_PTR("dirname not defined", procName, NULL);
  
    sa = getSortedPathnamesInDirectory(dirname, substr, first, nfiles);
    if (!sa || ((n = sarrayGetCount(sa)) == 0)) {
        sarrayDestroy(&sa);
        return (PIXAA *)ERROR_PTR("no pixa files found", procName, 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", procName, i);
            continue;
        }
        pixaaAddPixa(paa, pixa, L_INSERT);
    }
  
    sarrayDestroy(&sa);
    return paa;
  }
  
  
  /*!
   *  pixaaRead()
   *
   *      Input:  filename
   *      Return: paa, or null on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  PIXAA *
  pixaaRead(const char  *filename)
  {
  FILE   *fp;
  PIXAA  *paa;
  
      PROCNAME("pixaaRead");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return (PIXAA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
  #endif  /* !HAVE_LIBPNG */
  
      if (!filename)
          return (PIXAA *)ERROR_PTR("filename not defined", procName, NULL);
      if ((fp = fopenReadStream(filename)) == NULL)
          return (PIXAA *)ERROR_PTR("stream not opened", procName, NULL);
  
      if ((paa = pixaaReadStream(fp)) == NULL) {
          fclose(fp);
          return (PIXAA *)ERROR_PTR("paa not read", procName, NULL);
      }
  
      fclose(fp);
      return paa;
  }
  
  
  /*!
   *  pixaaReadStream()
   *
   *      Input:  stream
   *      Return: paa, or null on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  PIXAA *
  pixaaReadStream(FILE  *fp)
  {
  l_int32  n, i, version;
  l_int32  ignore;
  BOXA    *boxa;
  PIXA    *pixa;
  PIXAA   *paa;
  
      PROCNAME("pixaaReadStream");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return (PIXAA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
  #endif  /* !HAVE_LIBPNG */
  
      if (!fp)
          return (PIXAA *)ERROR_PTR("stream not defined", procName, NULL);
  
      if (fscanf(fp, "\nPixaa Version %d\n", &version) != 1)
          return (PIXAA *)ERROR_PTR("not a pixaa file", procName, NULL);
      if (version != PIXAA_VERSION_NUMBER)
          return (PIXAA *)ERROR_PTR("invalid pixaa version", procName, NULL);
      if (fscanf(fp, "Number of pixa = %d\n", &n) != 1)
          return (PIXAA *)ERROR_PTR("not a pixaa file", procName, NULL);
  
      if ((paa = pixaaCreate(n)) == NULL)
          return (PIXAA *)ERROR_PTR("paa not made", procName, NULL);
      if ((boxa = boxaReadStream(fp)) == NULL)
          return (PIXAA *)ERROR_PTR("boxa not made", procName, NULL);
      boxaDestroy(&paa->boxa);
      paa->boxa = boxa;
  
      for (i = 0; i < n; i++) {
          if ((fscanf(fp, "\n\n --------------- pixa[%d] ---------------\n",
                      &ignore)) != 1) {
              return (PIXAA *)ERROR_PTR("text reading", procName, NULL);
          }
          if ((pixa = pixaReadStream(fp)) == NULL)
              return (PIXAA *)ERROR_PTR("pixa not read", procName, NULL);
          pixaaAddPixa(paa, pixa, L_INSERT);
      }
  
      return paa;
  }
  
  
  /*!
   *  pixaaWrite()
   *
   *      Input:  filename
   *              paa
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  l_int32
  pixaaWrite(const char  *filename,
             PIXAA       *paa)
  {
  FILE  *fp;
  
      PROCNAME("pixaaWrite");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return ERROR_INT("no libpng: can't read data", procName, 1);
  #endif  /* !HAVE_LIBPNG */
  
      if (!filename)
          return ERROR_INT("filename not defined", procName, 1);
      if (!paa)
          return ERROR_INT("paa not defined", procName, 1);
  
      if ((fp = fopenWriteStream(filename, "wb")) == NULL)
          return ERROR_INT("stream not opened", procName, 1);
      if (pixaaWriteStream(fp, paa))
          return ERROR_INT("paa not written to stream", procName, 1);
      fclose(fp);
  
      return 0;
  }
  
  
  /*!
   *  pixaaWriteStream()
   *
   *      Input:  stream (opened for "wb")
   *              paa
   *      Return: 0 if OK, 1 on error
   *
   *  Notes:
   *      (1) The pix are stored in the file as png.
   *          If the png library is not linked, this will fail.
   */
  l_int32
  pixaaWriteStream(FILE   *fp,
                   PIXAA  *paa)
  {
  l_int32  n, i;
  PIXA    *pixa;
  
      PROCNAME("pixaaWriteStream");
  
  #if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
      return ERROR_INT("no libpng: can't read data", procName, 1);
  #endif  /* !HAVE_LIBPNG */
  
      if (!fp)
          return ERROR_INT("stream not defined", procName, 1);
      if (!paa)
          return ERROR_INT("paa not defined", procName, 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", procName, 1);
          fprintf(fp, "\n\n --------------- pixa[%d] ---------------\n", i);
          pixaWriteStream(fp, pixa);
          pixaDestroy(&pixa);
      }
      return 0;
  }
  