/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -  This software is distributed in the hope that it will be
 -  useful, but with NO WARRANTY OF ANY KIND.
 -  No author or distributor accepts responsibility to anyone for the
 -  consequences of using this software, or for whether it serves any
 -  particular purpose or works at all, unless he or she says so in
 -  writing.  Everyone is granted permission to copy, modify and
 -  redistribute this source code, for commercial or non-commercial
 -  purposes, with the following restrictions: (1) the origin of this
 -  source code must not be misrepresented; (2) modified versions must
 -  be plainly marked as such; and (3) this notice may not be removed
 -  or altered from any source or modified source distribution.
 *====================================================================*/


/*
 *   box.c
 *
 *      Box creation, copy, clone, destruction
 *           BOX      *boxCreate()
 *           BOX      *boxCopy()
 *           BOX      *boxClone()
 *           void      boxDestroy()
 *
 *      Box accessors
 *           l_int32   boxGetRefcount()
 *           l_int32   boxChangeRefcount()
 *
 *      Box geometry
 *           l_int32   boxContains()
 *           l_int32   boxIntersects()
 *           BOXA     *boxaContainedInBox()
 *           BOXA     *boxaIntersectsBox()
 *           BOX      *boxClipToRectangle()
 *
 *      Boxa creation, copy, destruction
 *           BOXA     *boxaCreate()
 *           BOXA     *boxaCopy()
 *           void      boxaDestroy()
 *
 *      Boxa array extension
 *           l_int32   boxaAddBox()
 *           l_int32   boxaExtendArray()
 *
 *      Boxa accessors
 *           l_int32   boxaGetCount()
 *           l_int32   boxaGetBox()
 *
 *      Boxa array modifiers
 *           l_int32   boxaReplaceBox()
 *           l_int32   boxaInsertBox()
 *           l_int32   boxaRemoveBox()
 *
 *      Boxa combination
 *           l_int32   boxaJoin()
 *
 *      Other boxa functions
 *           l_int32   boxaGetExtent()
 *           l_int32   boxaSizeRange()
 *           BOXA     *boxaRemoveLargeComponents()
 *           BOXA     *boxaRemoveSmallComponents()
 *
 *      Boxa/Box transform
 *           BOX       boxTransform()
 *           BOXA      boxaTransform()
 *
 *      Boxa sort
 *           BOXA      boxaSort()
 *
 *      Boxa serialized I/O
 *           BOXA     *boxaRead()
 *           BOXA     *boxaReadStream()
 *           l_int32   boxaWrite()
 *           l_int32   boxaWriteStream()
 *
 *      Box print (for debug)
 *           l_int32   boxPrintStreamInfo()
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "allheaders.h"

static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */


/*---------------------------------------------------------------------*
 *                  Box creation, destruction and copy                 *
 *---------------------------------------------------------------------*/
/*!
 *  boxCreate()
 *
 *      Input:  x, y, width, height
 *      Return: box, or null on error
 *
 *  Note: This clips the box to the +quad.  If no part of the
 *        box is in the +quad, this returns NULL.
 */
BOX *
boxCreate(l_int32  x,
          l_int32  y,
          l_int32  w,
          l_int32  h)
{
BOX  *box;

    PROCNAME("boxCreate");

    if (w <= 0 || h <= 0)
        return (BOX *)ERROR_PTR("w and h not both > 0", procName, NULL);
    if (x < 0) {  /* take part in +quad */
	w = w + x;
	x = 0;
	if (w <= 0)
            return (BOX *)ERROR_PTR("x < 0 and box off +quad", procName, NULL);
    }
    if (y < 0) {  /* take part in +quad */
	h = h + y;
	y = 0;
	if (h <= 0)
            return (BOX *)ERROR_PTR("y < 0 and box off +quad", procName, NULL);
    }

    if ((box = (BOX *) CALLOC(1, sizeof(BOX))) == NULL)
	return (BOX *)ERROR_PTR("box not made", procName, NULL);
    box->x = x;
    box->y = y;
    box->w = w;
    box->h = h;
    box->refcount = 1;

    return box;
}


/*!
 *  boxCopy()
 *
 *      Input:  box
 *      Return: copy of box, or null on error
 */
BOX *
boxCopy(BOX  *box)
{
BOX  *boxc;

    PROCNAME("boxCopy");

    if (!box)
	return (BOX *)ERROR_PTR("box not defined", procName, NULL);

    boxc = boxCreate(box->x, box->y, box->w, box->h);

    return boxc;
}


/*! 
 *  boxClone()
 *
 *      Input:  box
 *      Return: ptr to same box, or null on error
 */
BOX *
boxClone(BOX  *box)
{

    PROCNAME("boxClone");

    if (!box)
	return (BOX *)ERROR_PTR("box not defined", procName, NULL);

    boxChangeRefcount(box, 1);

    return box;
}


/*!
 *  boxDestroy()
 *
 *      Input:  &box (<can be nulled>)
 *      Return: void
 *
 *  Note:
 *      - Decrements the ref count and, if 0, destroys the box.
 *      - Always nulls the input ptr.
 */
void
boxDestroy(BOX  **pbox)
{
BOX  *box;

    PROCNAME("boxDestroy");

    if (pbox == NULL) {
        L_WARNING("ptr address is null!", procName);
	return;
    }

    if ((box = *pbox) == NULL)
	return;

	/* decrement the ref count.  If it is 0, destroy the box. */
    boxChangeRefcount(box, -1);
    if (boxGetRefcount(box) <= 0) {
        FREE((void *)box);
    }

    *pbox = NULL;
    return;
}


/*---------------------------------------------------------------------*
 *                        Box refcount accessors                       *
 *---------------------------------------------------------------------*/
l_int32
boxGetRefcount(BOX  *box)
{
    PROCNAME("boxGetRefcount");

    if (!box)
	return ERROR_INT("box not defined", procName, UNDEF);

    return box->refcount;
}


l_int32
boxChangeRefcount(BOX     *box,
                  l_int32  delta)
{
    PROCNAME("boxChangeRefcount");

    if (!box)
	return ERROR_INT("box not defined", procName, 1);

    box->refcount += delta;
    return 0;
}



/*---------------------------------------------------------------------*
 *                             Box geometry                            *
 *---------------------------------------------------------------------*/
/*!
 *  boxContains()
 *
 *      Input:  box1, box2
 *              &result (<return> 1 if box2 is entirely contained within
 *                       box1, and 0 otherwise)
 *      Return: 0 if OK, 1 on error
 */
l_int32
boxContains(BOX     *box1,
	    BOX     *box2,
            l_int32 *presult)
{
    PROCNAME("boxContains");

    if (!box1 || !box2)
	return ERROR_INT("box1 and box2 not both defined", procName, 1);

    if ((box1->x <= box2->x) &&
        (box1->y <= box2->y) &&
        (box1->x + box1->w >= box2->x + box2->w) &&
        (box1->y + box1->h >= box2->y + box2->h))
        *presult = 1;
    else
        *presult = 0;
    return 0;
}
        	


/*!
 *  boxIntersects()
 *
 *      Input:  box1, box2
 *              &result (<return> 1 if any part of box2 is contained
 *                      in box1, and 0 otherwise)
 *      Return: 0 if OK, 1 on error
 */
l_int32
boxIntersects(BOX      *box1,
	      BOX      *box2,
              l_int32  *presult)
{
l_int32  left1, left2, top1, top2, right1, right2, bot1, bot2;

    PROCNAME("boxIntersects");

    if (!box1 || !box2)
	return ERROR_INT("box1 and box2 not both defined", procName, 1);

    left1 = box1->x;
    left2 = box2->x;
    top1 = box1->y;
    top2 = box2->y;
    right1 = box1->x + box1->w - 1;
    bot1 = box1->y + box1->h - 1;
    right2 = box2->x + box2->w - 1;
    bot2 = box2->y + box2->h - 1;
    if ((bot2 >= top1) && (bot1 >= top2) &&
         (right1 >= left2) && (right2 >= left1))
        *presult = 1;
    else
        *presult = 0;
    return 0;
}
        	

/*!
 *  boxaContainedInBox()
 *
 *      Input:  boxas, box
 *      Return: boxad (boxa with all boxes in boxas that are
 *                     entirely contained in box; or NULL if no boxes
 *                     in boxas are contained within box, or on error)
 */
BOXA *
boxaContainedInBox(BOXA  *boxas,
	           BOX   *box)
{
l_int32  i, n, val;
BOX     *boxt;
BOXA    *boxad;

    PROCNAME("boxaContainedInBox");

    if (!boxas)
	return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    if (!box)
	return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
    if ((n = boxaGetCount(boxas)) == 0)
	return (BOXA *)ERROR_PTR("no boxes in boxas", procName, NULL);

    boxad = boxaCreate(0);
    for (i = 0; i < n; i++) {
        boxt = boxaGetBox(boxas, i, L_CLONE);
        boxContains(box, boxt, &val);
        if (val == 1)
            boxaAddBox(boxad, boxt, L_COPY);
	boxDestroy(&boxt);  /* destroy the clone */
    }

    return boxad;
}


/*!
 *  boxaIntersectsBox()
 *
 *      Input:  boxas, box
 *      Return  boxad (boxa with all boxes in boxas that intersect box;
 *                     or NULL if no boxes intersect the box, or on error)
 */
BOXA *
boxaIntersectsBox(BOXA  *boxas,
	          BOX   *box)
{
l_int32  i, n, val;
BOX     *boxt;
BOXA    *boxad;

    PROCNAME("boxaIntersectsBox");

    if (!boxas)
	return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    if (!box)
	return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
    if ((n = boxaGetCount(boxas)) == 0)
	return (BOXA *)ERROR_PTR("no boxes in boxas", procName, NULL);

    boxad = boxaCreate(0);
    for (i = 0; i < n; i++) {
        boxt = boxaGetBox(boxas, i, L_CLONE);
        boxIntersects(box, boxt, &val);
        if (val == 1)
            boxaAddBox(boxad, boxt, L_COPY);
	boxDestroy(&boxt);  /* destroy the clone */
    }

    return boxad;
}


/*!
 *  boxClipToRectangle()
 *
 *      Input:  box
 *              w, h (rectangle representing image)
 *      Return: part of box within given rectangle, or NULL on error
 *              or if box is entirely outside the rectangle
 *
 *  Note: the rectangle is assumed to go from (0,0) to (w - 1, h - 1)
 */
BOX *
boxClipToRectangle(BOX     *box,
                   l_int32  w,
                   l_int32  h)
{
BOX  *boxd;

    PROCNAME("boxClipToRectangle");

    if (!box)
	return (BOX *)ERROR_PTR("box not defined", procName, NULL);
    if (box->x >= w || box->y >= h ||
	box->x + box->w <= 0 || box->y + box->h <= 0)
	return (BOX *)ERROR_PTR("box outside rectangle", procName, NULL);

    boxd = boxCopy(box);
    if (boxd->x < 0) {
	boxd->w += boxd->x;
        boxd->x = 0;
    }
    if (boxd->y < 0) {
	boxd->h += boxd->y;
        boxd->y = 0;
    }
    if (boxd->x + boxd->w > w)
        boxd->w = w - boxd->x;
    if (boxd->y + boxd->h > h)
        boxd->h = h - boxd->y;
    return boxd;
}



/*---------------------------------------------------------------------*
 *             Boxa creation, destruction, copy, extension             *
 *---------------------------------------------------------------------*/
/*!
 *  boxaCreate()
 *
 *      Input:  n  (initial number of ptrs)
 *      Return: boxa, or null on error
 */
BOXA *
boxaCreate(l_int32  n)
{
BOXA  *boxa;

    PROCNAME("boxaCreate");

    if (n <= 0)
	n = INITIAL_PTR_ARRAYSIZE;

    if ((boxa = (BOXA *)CALLOC(1, sizeof(BOXA))) == NULL)
	return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
    boxa->n = 0;
    boxa->nalloc = n;
    boxa->refcount = 1;
    
    if ((boxa->box = (BOX **)CALLOC(n, sizeof(BOX *))) == NULL)
	return (BOXA *)ERROR_PTR("boxa ptrs not made", procName, NULL);

    return boxa;
}


/*!
 *  boxaCopy()
 *
 *      Input:  boxa
 *              copyflag (L_COPY, L_CLONE, L_COPY_CLONE)
 *      Return: new boxa, or null on error
 *
 *  Note: see pix.h for description of the copyflag.  The copy-clone
 *        makes a new boxa that holds clones of each box.
 */
BOXA *
boxaCopy(BOXA    *boxa,
         l_int32  copyflag)
{
l_int32  i;
BOX     *boxc;
BOXA    *boxac;

    PROCNAME("boxaCopy");

    if (!boxa)
	return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);

    if (copyflag == L_CLONE) {
	boxa->refcount++;
	return boxa;
    }

    if (copyflag != L_COPY && copyflag != L_COPY_CLONE)
	return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);

    if ((boxac = boxaCreate(boxa->nalloc)) == NULL)
	return (BOXA *)ERROR_PTR("boxac not made", procName, NULL);
    for (i = 0; i < boxa->n; i++) {
        if (copyflag == L_COPY)
            boxc = boxaGetBox(boxa, i, L_COPY);
	else   /* copy-clone */
            boxc = boxaGetBox(boxa, i, L_CLONE);
	boxaAddBox(boxac, boxc, L_INSERT);
    }
    return boxac;
}


/*!
 *  boxaDestroy()
 *
 *      Input:  &boxa (<set to null>)
 *      Return: void
 *
 *  Note:
 *      - Decrements the ref count and, if 0, destroys the boxa.
 *      - Always nulls the input ptr.
 */
void
boxaDestroy(BOXA  **pboxa)
{
l_int32  i;
BOXA    *boxa;

    PROCNAME("boxaDestroy");

    if (pboxa == NULL) {
        L_WARNING("ptr address is null!", procName);
	return;
    }

    if ((boxa = *pboxa) == NULL)
	return;

	/* decrement the ref count.  If it is 0, destroy the boxa. */
    boxa->refcount--;
    if (boxa->refcount <= 0) {
        for (i = 0; i < boxa->n; i++)
            boxDestroy(&boxa->box[i]);
        FREE((void *)boxa->box);
        FREE((void *)boxa);
    }

    *pboxa = NULL;
    return;
}


/*!
 *  boxaAddBox()
 *
 *      Input:  boxa
 *              box  (to be added)
 *              copyflag (L_INSERT, L_COPY, L_CLONE)
 *      Return: 0 if OK, 1 on error
 */
l_int32
boxaAddBox(BOXA    *boxa,
           BOX     *box,
           l_int32  copyflag)
{
l_int32  n;
BOX     *boxc;

    PROCNAME("boxaAddBox");

    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);
    if (!box)
	return ERROR_INT("box not defined", procName, 1);

    if (copyflag == L_INSERT)
	boxc = box;
    else if (copyflag == L_COPY)
	boxc = boxCopy(box);
    else if (copyflag == L_CLONE)
	boxc = boxClone(box);
    else
	return ERROR_INT("invalid copyflag", procName, 1);
    if (!boxc)
	return ERROR_INT("boxc not made", procName, 1);

    n = boxaGetCount(boxa);
    if (n >= boxa->nalloc)
	boxaExtendArray(boxa);
    boxa->box[n] = boxc;
    boxa->n++;

    return 0;
}


/*!
 *  boxaExtendArray()
 *
 *      Input:  boxa
 *      Return: 0 if OK; 1 on error
 */
l_int32
boxaExtendArray(BOXA  *boxa)
{

    PROCNAME("boxaExtendArray");

    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);

    if ((boxa->box = (BOX **)reallocNew((void **)&boxa->box,
                               sizeof(l_intptr_t) * boxa->nalloc,
			       2 * sizeof(l_intptr_t) * boxa->nalloc)) == NULL)
	return ERROR_INT("new ptr array not returned", procName, 1);

    boxa->nalloc = 2 * boxa->nalloc;
    return 0;
}



/*---------------------------------------------------------------------*
 *                             Boxa accessors                          *
 *---------------------------------------------------------------------*/
/*!
 *  boxaGetCount()
 *
 *      Input:  boxa
 *      Return: count, or 0 on error
 */
l_int32
boxaGetCount(BOXA  *boxa)
{

    PROCNAME("boxaGetCount");

    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 0);

    return boxa->n;
}


/*!
 *  boxaGetBox()
 *
 *      Input:  boxa
 *              index  (to the index-th box)
 *              accessflag  (L_COPY or L_CLONE)
 *      Return: box, or null on error
 */
BOX *
boxaGetBox(BOXA    *boxa,
           l_int32  index,
	   l_int32  accessflag)
{
    PROCNAME("boxaGetBox");

    if (!boxa)
	return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
    if (index < 0 || index >= boxa->n)
	return (BOX *)ERROR_PTR("index not valid", procName, NULL);

    if (accessflag == L_COPY)
	return boxCopy(boxa->box[index]);
    else if (accessflag == L_CLONE)
	return boxClone(boxa->box[index]);
    else
	return (BOX *)ERROR_PTR("invalid accessflag", procName, NULL);
}


/*---------------------------------------------------------------------*
 *                        Boxa array modifiers                         *
 *---------------------------------------------------------------------*/
/*!
 *  boxaReplaceBox()
 *
 *      Input:  boxa
 *              index  (to the index-th pix)
 *              box (insert to replace existing one)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) In-place replacement of one box.
 *      (2) The previous box at that location is destroyed.
 */
l_int32
boxaReplaceBox(BOXA    *boxa,
               l_int32  index,
               BOX     *box)
{
    PROCNAME("boxaReplaceBox");

    if (!boxa)
        return ERROR_INT("boxa not defined", procName, 1);
    if (index < 0 || index >= boxa->n)
        return ERROR_INT("index not valid", procName, 1);
    if (!box)
        return ERROR_INT("box not defined", procName, 1);

    boxDestroy(&(boxa->box[index]));
    boxa->box[index] = box;
    return 0;
}


/*!
 *  boxaInsertBox()
 *
 *      Input:  boxa
 *              index (of box to be inserted before)
 *              boxs (new box to be inserted)
 *      Return: 0 if OK, 1 on error
 *
 *  Usage Notes:
 *      (1) This does in-place insertion BEFORE the indexed box, so that
 *          the new box is placed at the index position in the array.
 *          To insert at the beginning of the array, set index = 0.
 *      (2) If you want to add a box after the last one in the array,
 *          use boxaAddBox().
 */
l_int32
boxaInsertBox(BOXA    *boxa,
              l_int32  index,
              BOX     *boxs)
{
l_int32  i, nbox;
BOX    **array;

    PROCNAME("boxaInsertBox");

    if (!boxa)
        return ERROR_INT("boxa not defined", procName, 1);
    nbox = boxaGetCount(boxa);
    if (index < 0 || (index >= nbox && nbox > 0))
        return ERROR_INT("index not valid", procName, 1);
    if (!boxs)
        return ERROR_INT("boxs not defined", procName, 1);

        /* insert the new box; use internals; extend array first if req'd */
    if (nbox >= boxa->nalloc)
        boxaExtendArray(boxa);
    array = boxa->box;
    boxa->n++;
    for (i = nbox - 1; i >= index; i--)
        array[i + 1] = array[i];
    array[index] = boxs;

    return 0;
}


/*!
 *  boxaRemoveBox()
 *
 *      Input:  boxa
 *              index (of box to be removed)
 *      Return: 0 if OK, 1 on error
 *
 *  Note: in-place removal of one box
 */
l_int32
boxaRemoveBox(BOXA    *boxa,
              l_int32  index)
{
l_int32  i, nbox;
BOX    **array;

    PROCNAME("boxaRemoveBox");

    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);
    nbox = boxaGetCount(boxa);
    if (index < 0 || index >= nbox)
	return ERROR_INT("index not valid", procName, 1);

        /* use internals for simplicity */
    array = boxa->box;
    boxDestroy(&array[index]);
    for (i = index + 1; i < nbox; i++)
        array[i - 1] = array[i];
    array[nbox - 1] = NULL;
    boxa->n--;

    return 0;
}


/*----------------------------------------------------------------------*
 *                          Boxa Combination                            *
 *----------------------------------------------------------------------*/
/*!
 *  boxaJoin()
 *
 *      Input:  boxad  (dest boxa; add to this one)
 *              boxas  (source boxa; add from this one)
 *              istart  (starting index in nas)
 *              iend  (ending index in nas; use 0 to cat all)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This appends a clone of each indicated box in boxas to boxad
 *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
 *      (3) iend <= 0 means 'read to the end'
 */
l_int32
boxaJoin(BOXA    *boxad,
         BOXA    *boxas,
         l_int32  istart,
         l_int32  iend)
{
l_int32  ns, i;
BOX     *box;

    PROCNAME("boxaJoin");

    if (!boxad)
	return ERROR_INT("boxad not defined", procName, 1);
    if (!boxas)
	return ERROR_INT("boxas not defined", procName, 1);
    ns = boxaGetCount(boxas);
    if (istart < 0)
	istart = 0;
    if (istart >= ns)
	return ERROR_INT("istart out of bounds", procName, 1);
    if (iend <= 0)
	iend = ns - 1;
    if (iend >= ns)
	return ERROR_INT("iend out of bounds", procName, 1);
    if (istart > iend)
	return ERROR_INT("istart > iend; nothing to add", procName, 1);

    for (i = istart; i <= iend; i++) {
	box = boxaGetBox(boxas, i, L_CLONE);
	boxaAddBox(boxad, box, L_INSERT);
    }

    return 0;
}



/*---------------------------------------------------------------------*
 *                        Other Boxa functions                         *
 *---------------------------------------------------------------------*/
/*!
 *  boxaGetExtent()
 *
 *      Input:  boxa
 *              &box (<optional return>, minimum box containing all boxes
 *                    in boxa)
 *              &w  (<return> width)
 *              &h  (<return> height)
 *      Return: 0 if OK, 1 on error
 *
 *  Note: the returned w and h are the minimum size image
 *        that would contain all boxes untranslated.
 */
l_int32
boxaGetExtent(BOXA     *boxa,
              BOX     **pbox,
              l_int32  *pw,
              l_int32  *ph)
{
l_int32  i, n, xmax, ymax, xmin, ymin;
BOX     *box;

    PROCNAME("boxaGetExtent");

    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);
    if (!pw || !ph)
	return ERROR_INT("ptrs not defined", procName, 1);

    *pw = *ph = 0;
    if (pbox)
	*pbox = NULL;
    n = boxaGetCount(boxa);
    if (n == 0)
	return ERROR_INT("no boxes in boxa", procName, 1);

    xmax = ymax = 0;
    xmin = ymin = 100000;
    for (i = 0; i < n; i++) {
	box = boxaGetBox(boxa, i, L_CLONE);
	xmin = L_MIN(xmin, box->x);
	ymin = L_MIN(ymin, box->y);
	xmax = L_MAX(xmax, box->x + box->w);
	ymax = L_MAX(ymax, box->y + box->h);
	boxDestroy(&box);
    }
    *pw = xmax;
    *ph = ymax;
    if (pbox)
      *pbox = boxCreate(xmin, ymin, xmax - xmin, ymax - ymin);

    return 0;
}


/*!
 *  boxaSizeRange()
 *
 *      Input:  boxa
 *              &minw, &minh, &maxw, &maxh (<optional return> range of
 *                                          dimensions of box in the array)
 *      Return: 0 if OK, 1 on error
 */
l_int32  
boxaSizeRange(BOXA     *boxa,
              l_int32  *pminw,
              l_int32  *pminh,
              l_int32  *pmaxw,
              l_int32  *pmaxh)
{
l_int32  minw, minh, maxw, maxh, i, n, w, h;
BOX     *box;

    PROCNAME("boxaSizeRange");

    if (!boxa)
        return ERROR_INT("boxa not defined", procName, 1);
    if (!pminw && !pmaxw && !pminh && !pmaxh)
        return ERROR_INT("no data can be returned", procName, 1);
    
    minw = minh = 1000000;
    maxw = maxh = 0;
    n = boxaGetCount(boxa);
    for (i = 0; i < n; i++) {
        if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL)
	    return ERROR_INT("box not found", procName, 1);
        w = box->w;
        h = box->h;
        if (w < minw)
            minw = w;
        if (h < minh)
            minh = h;
        if (w > maxw)
            maxw = w;
        if (h > maxh)
            maxh = h;
        boxDestroy(&box);
    }

    if (pminw) *pminw = minw;
    if (pminh) *pminh = minh;
    if (pmaxw) *pmaxw = maxw;
    if (pmaxh) *pmaxh = maxh;

    return 0;
}


/*!
 *  boxaRemoveLargeComponents()
 *
 *      Input:  boxa
 *              maxwidth, maxheight (<return> max dimensions allowed)
 *              type (L_REMOVE_IF_EITHER, L_REMOVE_IF_BOTH)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) Returns a clone if no components are removed.
 *      (2) Uses box clones in the new boxa.
 *      (3) If type == L_REMOVE_IF_EITHER, removes a box if
 *          either dimension violates the max size constraint.
 *          If type == L_REMOVE_IF_BOTH, removes a box only if
 *          both dimensions violate the max size constraint.
 */
BOXA *
boxaRemoveLargeComponents(BOXA    *boxas,
                          l_int32  maxwidth,
                          l_int32  maxheight,
			  l_int32  type)
{
l_int32  i, n, w, h, maxw, maxh;
BOX     *box;
BOXA    *boxad;

    PROCNAME("boxaRemoveLargeComponents");

    if (!boxas)
        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    if (type != L_REMOVE_IF_EITHER && type != L_REMOVE_IF_BOTH)
        return (BOXA *)ERROR_PTR("invalid type", procName, NULL);
    
        /* Check if all components satisfy constraint. */
    boxaSizeRange(boxas, NULL, NULL, &maxw, &maxh);
    if (type == L_REMOVE_IF_EITHER && (maxw <= maxwidth && maxh <= maxheight))
        return boxaCopy(boxas, L_CLONE);
    if (type == L_REMOVE_IF_BOTH && (maxw <= maxwidth || maxh <= maxheight))
        return boxaCopy(boxas, L_CLONE);

    n = boxaGetCount(boxas);
    boxad = boxaCreate(n);
    for (i = 0; i < n; i++) {
	if ((box = boxaGetBox(boxas, i, L_CLONE)) == NULL)
	    return (BOXA *)ERROR_PTR("box not found", procName, NULL);
	w = box->w;
	h = box->h;
	if (type == L_REMOVE_IF_EITHER) {
	    if (w > maxwidth || h > maxheight)
		boxDestroy(&box);
            else
	        boxaAddBox(boxad, box, L_INSERT);
	}
	else {   /* L_REMOVE_IF_BOTH */
	    if (w > maxwidth && h > maxheight)
		boxDestroy(&box);
            else
	        boxaAddBox(boxad, box, L_INSERT);
	}
    }
	        
    return boxad;
}


/*!
 *  boxaRemoveSmallComponents()
 *
 *      Input:  boxa
 *              minwidth, minheight (min dimensions allowed)
 *              type (L_REMOVE_IF_EITHER, L_REMOVE_IF_BOTH)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) Returns a clone if no components are removed.
 *      (2) Uses box clones in the new boxa.
 *      (3) If type == L_REMOVE_IF_EITHER, removes a component if
 *          either dimension violates the min size constraint.
 *          If type == L_REMOVE_IF_BOTH, removes a component only if
 *          both dimensions violate the min size constraint.
 */
BOXA *
boxaRemoveSmallComponents(BOXA    *boxas,
                          l_int32  minwidth,
                          l_int32  minheight,
			  l_int32  type)
{
l_int32  i, n, w, h, minw, minh;
BOX     *box;
BOXA    *boxad;

    PROCNAME("boxaRemoveSmallComponents");

    if (!boxas)
        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    if (type != L_REMOVE_IF_EITHER && type != L_REMOVE_IF_BOTH)
        return (BOXA *)ERROR_PTR("invalid type", procName, NULL);
    
        /* Check if all components satisfy constraint. */
    boxaSizeRange(boxas, &minw, &minh, NULL, NULL);
    if (type == L_REMOVE_IF_EITHER && (minw >= minwidth && minh >= minheight))
        return boxaCopy(boxas, L_CLONE);
    if (type == L_REMOVE_IF_BOTH && (minw >= minwidth || minh >= minheight))
        return boxaCopy(boxas, L_CLONE);

    n = boxaGetCount(boxas);
    boxad = boxaCreate(n);
    for (i = 0; i < n; i++) {
	if ((box = boxaGetBox(boxas, i, L_CLONE)) == NULL)
	    return (BOXA *)ERROR_PTR("box not found", procName, NULL);
	w = box->w;
	h = box->h;
	if (type == L_REMOVE_IF_EITHER) {
	    if (w < minwidth || h < minheight)
		boxDestroy(&box);
            else
	        boxaAddBox(boxad, box, L_INSERT);
	}
	else {   /* L_REMOVE_IF_BOTH */
	    if (w < minwidth && h < minheight)
		boxDestroy(&box);
            else
	        boxaAddBox(boxad, box, L_INSERT);
	}
    }
	        
    return boxad;
}



/*---------------------------------------------------------------------*
 *                        Boxa/Box transform                           *
 *---------------------------------------------------------------------*/
/*!
 *  boxaTransform()
 * 
 *      Input:  boxa
 *              shiftx, shifty
 *              scalex, scaley
 *      Return: boxad, or null on error
 *
 *  Note: we shift first, then scale everything
 */
BOXA *
boxaTransform(BOXA      *boxas,
	      l_int32    shiftx,
	      l_int32    shifty,
	      l_float32  scalex,
	      l_float32  scaley)
{
l_int32  i, n;
BOX     *boxs, *boxd;
BOXA    *boxad;

    PROCNAME("boxaTransform");

    if (!boxas)
	return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    n = boxaGetCount(boxas);
    if ((boxad = boxaCreate(n)) == NULL)
	return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
    for (i = 0; i < n; i++) {
        if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL)
	    return (BOXA *)ERROR_PTR("boxs not found", procName, NULL);
	boxd = boxTransform(boxs, shiftx, shifty, scalex, scaley);
	boxDestroy(&boxs);
	boxaAddBox(boxad, boxd, L_INSERT);
    }

    return boxad;
}


/*!
 *  boxTransform()
 * 
 *      Input:  boxs
 *              shiftx, shifty
 *              scalex, scaley
 *      Return: boxd, or null on error
 *
 *  Note: we shift first, then scale everything.
 */
BOX *
boxTransform(BOX       *box,
	     l_int32    shiftx,
	     l_int32    shifty,
	     l_float32  scalex,
	     l_float32  scaley)
{
    PROCNAME("boxTransform");

    if (!box)
	return (BOX *)ERROR_PTR("box not defined", procName, NULL);
    return boxCreate((l_int32)(scalex * (box->x + shiftx) + 0.5),
                     (l_int32)(scaley * (box->y + shifty) + 0.5),
		     (l_int32)(L_MAX(1.0, scalex * box->w + 0.5)),
		     (l_int32)(L_MAX(1.0, scaley * box->h + 0.5)));
}


/*---------------------------------------------------------------------*
 *                              Boxa sort                              *
 *---------------------------------------------------------------------*/
/*!
 *  boxaSort()
 * 
 *      Input:  boxa
 *              sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
 *                        L_SORT_BY_HEIGHT, L_SORT_BY_MIN_DIMENSION,
 *                        L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER,
 *                        L_SORT_BY_AREA)
 *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
 *              &naindex (<optional return> index of sorted order into
 *                        original array)
 *      Return: boxad (sorted version of boxas), or null on error
 */
BOXA *
boxaSort(BOXA    *boxas,
	 l_int32  sorttype,
	 l_int32  sortorder,
         NUMA   **pnaindex)
{
l_int32  i, index, n, size;
BOX     *box;
BOXA    *boxad;
NUMA    *na, *naindex;

    PROCNAME("boxaSort");

    if (!boxas)
	return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && 
        sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT &&
	sorttype != L_SORT_BY_MIN_DIMENSION &&
	sorttype != L_SORT_BY_MAX_DIMENSION &&
	sorttype != L_SORT_BY_PERIMETER && sorttype != L_SORT_BY_AREA)
	return (BOXA *)ERROR_PTR("invalid sort type", procName, NULL);
    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
	return (BOXA *)ERROR_PTR("invalid sort order", procName, NULL);

        /* build up numa of specific data */
    n = boxaGetCount(boxas);
    if ((na = numaCreate(n)) == NULL)
	return (BOXA *)ERROR_PTR("na not made", procName, NULL);
    for (i = 0; i < n; i++) {
        box = boxaGetBox(boxas, i, L_CLONE);
        if (!box)
          return (BOXA *)ERROR_PTR("box not found", procName, NULL);
	switch (sorttype)
	{
	case L_SORT_BY_X:
	    numaAddNumber(na, box->x);
	    break;
	case L_SORT_BY_Y:
	    numaAddNumber(na, box->y);
	    break;
	case L_SORT_BY_WIDTH:
	    numaAddNumber(na, box->w);
	    break;
	case L_SORT_BY_HEIGHT:
	    numaAddNumber(na, box->h);
	    break;
	case L_SORT_BY_MIN_DIMENSION:
	    size = L_MIN(box->w, box->h);
	    numaAddNumber(na, size);
	    break;
	case L_SORT_BY_MAX_DIMENSION:
	    size = L_MAX(box->w, box->h);
	    numaAddNumber(na, size);
	    break;
	case L_SORT_BY_PERIMETER:
	    size = box->w + box->h;
	    numaAddNumber(na, size);
	    break;
	case L_SORT_BY_AREA:
	    size = box->w * box->h;
	    numaAddNumber(na, size);
	    break;
        default:
	    L_WARNING("invalid sort type", procName);
	}
	boxDestroy(&box);
    }

        /* get the sort index for data array */
    if ((naindex = numaSortIndex(na, sortorder)) == NULL)
	return (BOXA *)ERROR_PTR("naindex not made", procName, NULL);

        /* build up sorted boxa using sort index */
    if ((boxad = boxaCreate(n)) == NULL)
	return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
    for (i = 0; i < n; i++) {
        numaGetIValue(naindex, i, &index);
	box = boxaGetBox(boxas, index, L_COPY);
	boxaAddBox(boxad, box, L_INSERT);
    }

    if (pnaindex)
        *pnaindex = naindex;
    else
        numaDestroy(&naindex);
    numaDestroy(&na);
    return boxad;
}


/*---------------------------------------------------------------------*
 *                        Boxa serialized I/O                          *
 *---------------------------------------------------------------------*/
/*!
 *  boxaRead()
 *
 *      Input:  filename
 *      Return: boxa, or null on error
 */
BOXA *
boxaRead(const char  *filename)
{
FILE  *fp;
BOXA  *boxa;

    PROCNAME("boxaRead");

    if (!filename)
	return (BOXA *)ERROR_PTR("filename not defined", procName, NULL);
    if ((fp = fopenReadStream(filename)) == NULL)
	return (BOXA *)ERROR_PTR("stream not opened", procName, NULL);

    if ((boxa = boxaReadStream(fp)) == NULL) {
	fclose(fp);
	return (BOXA *)ERROR_PTR("boxa not read", procName, NULL);
    }

    fclose(fp);
    return boxa;
}


/*!
 *  boxaReadStream()
 *
 *      Input:  stream
 *      Return: boxa, or null on error
 */
BOXA *
boxaReadStream(FILE  *fp)
{
l_int32  n, i, x, y, w, h;
l_int32  ignore;
BOX     *box;
BOXA    *boxa;

    PROCNAME("boxaReadStream");

    if (!fp)
	return (BOXA *)ERROR_PTR("stream not defined", procName, NULL);

    if (fscanf(fp, "\nboxa: number of boxes = %d\n", &n) != 1)
	return (BOXA *)ERROR_PTR("not a boxa file", procName, NULL);
    if ((boxa = boxaCreate(n)) == NULL)
	return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
	
    for (i = 0; i < n; i++) {
	if (fscanf(fp, "  Box[%d]: x = %d, y = %d, w = %d, h = %d\n",
	        &ignore, &x, &y, &w, &h) != 5)
	    return (BOXA *)ERROR_PTR("box descr not valid", procName, NULL);
	if ((box = boxCreate(x, y, w, h)) == NULL)
	    return (BOXA *)ERROR_PTR("box not made", procName, NULL);
	boxaAddBox(boxa, box, L_INSERT);
    }

    return boxa;
}


/*!
 *  boxaWrite()
 *
 *      Input: filename
 *             boxa
 *      Return: 0 if OK, 1 on error
 */
l_int32
boxaWrite(const char  *filename,
	  BOXA        *boxa)
{
FILE  *fp;

    PROCNAME("boxaWrite");

    if (!filename)
	return ERROR_INT("filename not defined", procName, 1);
    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);

    if ((fp = fopen(filename, "wb")) == NULL)
	return ERROR_INT("stream not opened", procName, 1);
    if (boxaWriteStream(fp, boxa))
	return ERROR_INT("boxa not written to stream", procName, 1);
    fclose(fp);

    return 0;
}


/*!
 *  boxaWriteStream()
 *
 *      Input: stream
 *             boxa
 *      Return: 0 if OK, 1 on error
 */
l_int32
boxaWriteStream(FILE  *fp,
	        BOXA  *boxa)
{
l_int32  n, i;
BOX     *box;

    PROCNAME("boxaWriteStream");

    if (!fp)
	return ERROR_INT("stream not defined", procName, 1);
    if (!boxa)
	return ERROR_INT("boxa not defined", procName, 1);

    n = boxaGetCount(boxa);
    fprintf(fp, "\nboxa: number of boxes = %d\n", n);
    for (i = 0; i < n; i++) {
	if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL)
	    return ERROR_INT("box not found", procName, 1);
	fprintf(fp, "  Box[%d]: x = %d, y = %d, w = %d, h = %d\n",
	        i, box->x, box->y, box->w, box->h);
	boxDestroy(&box);
    }
    return 0;
}



/*---------------------------------------------------------------------*
 *                            Debug printing                           *
 *---------------------------------------------------------------------*/
/*!
 *  boxPrintStreamInfo()
 *
 *      Input:  stream
 *              box
 *      Return: 0 if OK, 1 on error
 *
 *  Note: This outputs information about the box.  Use serialization
 *        functions to write to file if you want to read the data back.
 */
l_int32
boxPrintStreamInfo(FILE  *fp,
	           BOX   *box)
{
    PROCNAME("boxPrintStreamInfo");

    if (!fp)
	return ERROR_INT("stream not defined", procName, 1);
    if (!box)
	return ERROR_INT("box not defined", procName, 1);

    fprintf(fp, " Box x (pixels) =           %d\n", box->x);
    fprintf(fp, " Box y (pixels) =           %d\n", box->y);
    fprintf(fp, " Box width (pixels) =       %d\n", box->w);
    fprintf(fp, " Box height (pixels) =      %d\n", box->h);

    return 0;
}


