Example #1
0
    /* Reconstruction with compaction */
static PIXA *
ReconstructPixa2(L_PTRA  *papix,
                 L_PTRA  *pabox)
{
l_int32  i, imax, nactual;
BOX     *box;
PIX     *pix;
PIXA    *pixat;

    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "Before removal:    imax = %4d, actual = %4d\n",
            imax, nactual);

        /* Remove half */
    pixat = pixaCreate(imax + 1);
    for (i = 0; i <= imax; i++) {
        if (i % 2 == 0) {
            pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
            box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
            if (pix) pixaAddPix(pixat, pix, L_INSERT);
            if (box) pixaAddBox(pixat, box, L_INSERT);
        }
    }

        /* Compact */
    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "Before compaction: imax = %4d, actual = %4d\n",
            imax, nactual);
    ptraCompactArray(papix);
    ptraCompactArray(pabox);
    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "After compaction:  imax = %4d, actual = %4d\n",
            imax, nactual);

        /* Remove the rest (and test compaction with removal) */
    while (1) {
        ptraGetActualCount(papix, &nactual);
        if (nactual == 0) break;

        pix = (PIX *)ptraRemove(papix, 0, L_COMPACTION);
        box = (BOX *)ptraRemove(pabox, 0, L_COMPACTION);
        pixaAddPix(pixat, pix, L_INSERT);
        pixaAddBox(pixat, box, L_INSERT);
    }

    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "After removal:     imax = %4d, actual = %4d\n\n",
            imax, nactual);

    return pixat;
}
/*!
 *  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;
}
Example #3
0
/*!
 *  pixaClipToPix()
 *
 *      Input:  pixas
 *              pixs
 *      Return: pixad, or null on error
 *
 *  Notes:
 *      (1) This is intended for use in situations where pixas
 *          was originally generated from the input pixs.
 *      (2) Returns a pixad where each pix in pixas is ANDed
 *          with its associated region of the input pixs.  This
 *          region is specified by the the box that is associated
 *          with the pix.
 *      (3) In a typical application of this function, pixas has
 *          a set of region masks, so this generates a pixa of
 *          the parts of pixs that correspond to each region
 *          mask component, along with the bounding box for
 *          the region.
 */
PIXA *
pixaClipToPix(PIXA  *pixas,
              PIX   *pixs)
{
l_int32  i, n;
BOX     *box;
PIX     *pix, *pixc;
PIXA    *pixad;

    PROCNAME("pixaClipToPix");

    if (!pixas)
        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
    if (!pixs)
        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);

    n = pixaGetCount(pixas);
    if ((pixad = pixaCreate(n)) == NULL)
        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);

    for (i = 0; i < n; i++) {
        pix = pixaGetPix(pixas, i, L_CLONE);
        box = pixaGetBox(pixas, i, L_COPY);
        pixc = pixClipRectangle(pixs, box, NULL);
        pixAnd(pixc, pixc, pix);
        pixaAddPix(pixad, pixc, L_INSERT);
        pixaAddBox(pixad, box, L_INSERT);
        pixDestroy(&pix);
    }

    return pixad;
}
Example #4
0
/*!
 *  pixaSortByIndex()
 * 
 *      Input:  pixas
 *              naindex (na that maps from the new pixa to the input pixa)
 *              copyflag (L_COPY, L_CLONE)
 *      Return: pixad (sorted), or null on error
 */
PIXA *
pixaSortByIndex(PIXA    *pixas,
                NUMA    *naindex,
                l_int32  copyflag)
{
l_int32  i, n, index;
BOX     *box;
PIX     *pix;
PIXA    *pixad;

    PROCNAME("pixaSortByIndex");

    if (!pixas)
        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
    if (!naindex)
        return (PIXA *)ERROR_PTR("naindex not defined", procName, NULL);
    if (copyflag != L_CLONE && copyflag != L_COPY)
        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);

    n = pixaGetCount(pixas);
    pixad = pixaCreate(n);
    for (i = 0; i < n; i++) {
        numaGetIValue(naindex, i, &index);
        pix = pixaGetPix(pixas, index, copyflag);
        box = pixaGetBox(pixas, index, copyflag);
        pixaAddPix(pixad, pix, L_INSERT);
        pixaAddBox(pixad, box, L_INSERT);
    }

    return pixad;
}
Example #5
0
    /* Reconstruction without compaction */
static PIXA *
ReconstructPixa1(L_PTRA  *papix,
                 L_PTRA  *pabox)
{
l_int32  i, imax, nactual;
BOX     *box;
PIX     *pix;
PIXA    *pixat;

    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "Before removal:  imax = %4d, actual = %4d\n",
            imax, nactual);

    pixat = pixaCreate(imax + 1);
    for (i = 0; i <= imax; i++) {
        pix = (PIX *)ptraRemove(papix, i, L_NO_COMPACTION);
        box = (BOX *)ptraRemove(pabox, i, L_NO_COMPACTION);
        if (pix) pixaAddPix(pixat, pix, L_INSERT);
        if (box) pixaAddBox(pixat, box, L_INSERT);
    }

    ptraGetMaxIndex(papix, &imax);
    ptraGetActualCount(papix, &nactual);
    fprintf(stderr, "After removal:   imax = %4d, actual = %4d\n\n",
            imax, nactual);

    return pixat;
}
Example #6
0
void Java_com_example_ocr_Pixa_nativeAddBox(JNIEnv *env, jclass clazz,
                                                             jint nativePixa, jint nativeBox,
                                                             jint mode) {
  PIXA *pixa = (PIXA *) nativePixa;
  BOX *box = (BOX *) nativeBox;

  pixaAddBox(pixa, box, mode);
}
Example #7
0
void Java_com_googlecode_leptonica_android_Pixa_nativeAddBox(JNIEnv *env, jclass clazz,
        jlong nativePixa, jlong nativeBox,
        jint mode) {
    PIXA *pixa = (PIXA *) nativePixa;
    BOX *box = (BOX *) nativeBox;

    pixaAddBox(pixa, box, mode);
}
Example #8
0
void Java_com_googlecode_leptonica_android_Pixa_nativeAdd(JNIEnv *env, jclass clazz, jint nativePixa,
                                                     jint nativePix, jint nativeBox, jint mode) {
  LOGV(__FUNCTION__);

  PIXA *pixa = (PIXA *) nativePixa;
  PIX *pix = (PIX *) nativePix;
  BOX *box = (BOX *) nativeBox;

  pixaAddPix(pixa, pix, mode);
  pixaAddBox(pixa, box, mode);
}
Example #9
0
/*!
 *  pixaSort2dByIndex()
 * 
 *      Input:  pixas
 *              naa (numaa that maps from the new pixaa to the input pixas)
 *              copyflag (L_CLONE or L_COPY)
 *      Return: pixaa (sorted), or null on error
 */
PIXAA *
pixaSort2dByIndex(PIXA    *pixas,
                  NUMAA   *naa,
                  l_int32  copyflag)
{
l_int32  pixtot, ntot, i, j, n, nn, index;
BOX     *box;
NUMA    *na;
PIX     *pix;
PIXA    *pixa;
PIXAA   *pixaa;

    PROCNAME("pixaSort2dByIndex");

    if (!pixas)
        return (PIXAA *)ERROR_PTR("pixas not defined", procName, NULL);
    if (!naa)
        return (PIXAA *)ERROR_PTR("naindex not defined", procName, NULL);

        /* Check counts */
    ntot = numaaGetNumberCount(naa);
    pixtot = pixaGetCount(pixas);
    if (ntot != pixtot)
        return (PIXAA *)ERROR_PTR("element count mismatch", procName, NULL);

    n = numaaGetCount(naa);
    pixaa = pixaaCreate(n);
    for (i = 0; i < n; i++) {
        na = numaaGetNuma(naa, i, L_CLONE);
        nn = numaGetCount(na);
        pixa = pixaCreate(nn);
        for (j = 0; j < nn; j++) {
            numaGetIValue(na, j, &index);
            pix = pixaGetPix(pixas, index, copyflag);
            box = pixaGetBox(pixas, index, copyflag);
            pixaAddPix(pixa, pix, L_INSERT);
            pixaAddBox(pixa, box, L_INSERT);
        }
        pixaaAddPixa(pixaa, pixa, L_INSERT);
        numaDestroy(&na);
    }

    return pixaa;
}
Example #10
0
/*!
 *  pixaaFlattenToPixa()
 *
 *      Input:  pixaa
 *              &naindex  (<optional return> the pixa index in the pixaa)
 *              copyflag  (L_COPY or L_CLONE)
 *      Return: pixa, or null on error
 *
 *  Notes:
 *      (1) This 'flattens' the pixaa to a pixa, taking the pix in
 *          order in the first pixa, then the second, etc.
 *      (2) If &naindex is defined, we generate a Numa that gives, for
 *          each pix in the pixaa, the index of the pixa to which it belongs.
 */
PIXA *
pixaaFlattenToPixa(PIXAA   *pixaa,
                   NUMA   **pnaindex,
                   l_int32  copyflag)
{
l_int32  i, j, m, n;
BOX     *box;
NUMA    *naindex;
PIX     *pix;
PIXA    *pixa, *pixat;

    PROCNAME("pixaaFlattenToPixa");

    if (pnaindex) *pnaindex = NULL;
    if (!pixaa)
        return (PIXA *)ERROR_PTR("pixaa not defined", procName, NULL);
    if (copyflag != L_COPY && copyflag != L_CLONE)
        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);

    if (pnaindex) {
        naindex = numaCreate(0);
        *pnaindex = naindex;
    }

    n = pixaaGetCount(pixaa);
    pixa = pixaCreate(n);
    for (i = 0; i < n; i++) {
        pixat = pixaaGetPixa(pixaa, i, L_CLONE);
        m = pixaGetCount(pixat);
        for (j = 0; j < m; j++) {
            pix = pixaGetPix(pixat, j, copyflag);
            box = pixaGetBox(pixat, j, copyflag);
            pixaAddPix(pixa, pix, L_INSERT);
            pixaAddBox(pixa, box, L_INSERT);
            if (pnaindex)
                numaAddNumber(naindex, i);  /* save 'row' number */
        }
        pixaDestroy(&pixat);
    }

    return pixa;
}
Example #11
0
/*!
 *  pixaSelectWithIndicator()
 *
 *      Input:  pixas
 *              na (indicator numa)
 *              &changed (<optional return> 1 if changed; 0 if clone returned)
 *      Return: pixad, or null on error
 *
 *  Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) The indicator numa has values 0 (ignore) and 1 (accept).
 */
PIXA *
pixaSelectWithIndicator(PIXA     *pixas,
                        NUMA     *na,
                        l_int32  *pchanged)
{
l_int32  i, n, ival, nsave;
BOX     *box;
PIX     *pixt;
PIXA    *pixad;

    PROCNAME("pixaSelectWithIndicator");

    if (!pixas)
        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
    if (!na)
        return (PIXA *)ERROR_PTR("na not defined", procName, NULL);

    nsave = 0;
    n = numaGetCount(na);
    for (i = 0; i < n; i++) {
        numaGetIValue(na, i, &ival);
        if (ival == 1) nsave++;
    }

    if (nsave == n) {
        if (pchanged) *pchanged = FALSE;
        return pixaCopy(pixas, L_CLONE);
    }
    if (pchanged) *pchanged = TRUE;
    pixad = pixaCreate(nsave);
    for (i = 0; i < n; i++) {
        numaGetIValue(na, i, &ival);
        if (ival == 0) continue;
        pixt = pixaGetPix(pixas, i, L_CLONE);
        box = pixaGetBox(pixas, i, L_CLONE);
        pixaAddPix(pixad, pixt, L_INSERT);
        pixaAddBox(pixad, box, L_INSERT);
    }

    return pixad;
}
Example #12
0
/*!
 *  wshedSaveBasin()
 *
 *      Input:  wshed
 *              index (index of basin to be located)
 *              level (filling level reached at the time this function
 *                     is called)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This identifies a single watershed.  It does not change
 *          the LUT, which must be done subsequently.
 *      (2) The fill level of a basin is taken to be @level - 1.
 */
static void
wshedSaveBasin(L_WSHED *wshed,
               l_int32 index,
               l_int32 level) {
    BOX *box;
    PIX *pix;

    PROCNAME("wshedSaveBasin");

    if (!wshed) {
        L_ERROR("wshed not defined\n", procName);
        return;
    }

    if (identifyWatershedBasin(wshed, index, level, &box, &pix) == 0) {
        pixaAddPix(wshed->pixad, pix, L_INSERT);
        pixaAddBox(wshed->pixad, box, L_INSERT);
        numaAddNumber(wshed->nalevels, level - 1);
    }
    return;
}
/*!
 *  pixaCopy()
 *
 *      Input:  pixas
 *              copyflag:
 *                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
 *
 *  Note: see pix.h for description of the copy types.
 */
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;
}
Example #14
0
/*!
 *  pixSaveTiledOutline()
 *
 *      Input:  pixs (1, 2, 4, 8, 32 bpp)
 *              pixa (the pix are accumulated here)
 *              scalefactor (0.0 to disable; otherwise this is a scale factor)
 *              newrow (0 if placed on the same row as previous; 1 otherwise)
 *              space (horizontal and vertical spacing, in pixels)
 *              linewidth (width of added outline for image; 0 for no outline)
 *              dp (depth of pixa; 8 or 32 bpp; only used on first call)
 *      Return: 0 if OK, 1 on error.
 *
 *  Notes:
 *      (1) Before calling this function for the first time, use
 *          pixaCreate() to make the @pixa that will accumulate the pix.
 *          This is passed in each time pixSaveTiled() is called.
 *      (2) @scalefactor scales the input image.  After scaling and
 *          possible depth conversion, the image is saved in the input
 *          pixa, along with a box that specifies the location to
 *          place it when tiled later.  Disable saving the pix by
 *          setting @scalefactor == 0.0.
 *      (3) @newrow and @space specify the location of the new pix
 *          with respect to the last one(s) that were entered.
 *      (4) @dp specifies the depth at which all pix are saved.  It can
 *          be only 8 or 32 bpp.  Any colormap is removed.  This is only
 *          used at the first invocation.
 *      (5) This function uses two variables from call to call.
 *          If they were static, the function would not be .so or thread
 *          safe, and furthermore, there would be interference with two or
 *          more pixa accumulating images at a time.  Consequently,
 *          we use the first pix in the pixa to store and obtain both
 *          the depth and the current position of the bottom (one pixel
 *          below the lowest image raster line when laid out using
 *          the boxa).  The bottom variable is stored in the input format
 *          field, which is the only field available for storing an int.
 */
l_int32
pixSaveTiledOutline(PIX       *pixs,
                    PIXA      *pixa,
                    l_float32  scalefactor,
                    l_int32    newrow,
                    l_int32    space,
                    l_int32    linewidth,
                    l_int32    dp)
{
l_int32  n, top, left, bx, by, bw, w, h, depth, bottom;
BOX     *box;
PIX     *pix1, *pix2, *pix3, *pix4;

    PROCNAME("pixSaveTiledOutline");

    if (scalefactor == 0.0) return 0;

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

    n = pixaGetCount(pixa);
    if (n == 0) {
        bottom = 0;
        if (dp != 8 && dp != 32) {
            L_WARNING("dp not 8 or 32 bpp; using 32\n", procName);
            depth = 32;
        } else {
            depth = dp;
        }
    } else {  /* extract the depth and bottom params from the first pix */
        pix1 = pixaGetPix(pixa, 0, L_CLONE);
        depth = pixGetDepth(pix1);
        bottom = pixGetInputFormat(pix1);  /* not typical usage! */
        pixDestroy(&pix1);
    }

        /* Remove colormap if it exists; otherwise a copy.  This
         * guarantees that pix4 is not a clone of pixs. */
    pix1 = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY);

        /* Scale and convert to output depth */
    if (scalefactor == 1.0) {
        pix2 = pixClone(pix1);
    } else if (scalefactor > 1.0) {
        pix2 = pixScale(pix1, scalefactor, scalefactor);
    } else if (scalefactor < 1.0) {
        if (pixGetDepth(pix1) == 1)
            pix2 = pixScaleToGray(pix1, scalefactor);
        else
            pix2 = pixScale(pix1, scalefactor, scalefactor);
    }
    pixDestroy(&pix1);
    if (depth == 8)
        pix3 = pixConvertTo8(pix2, 0);
    else
        pix3 = pixConvertTo32(pix2);
    pixDestroy(&pix2);

        /* Add black outline */
    if (linewidth > 0)
        pix4 = pixAddBorder(pix3, linewidth, 0);
    else
        pix4 = pixClone(pix3);
    pixDestroy(&pix3);

        /* Find position of current pix (UL corner plus size) */
    if (n == 0) {
        top = 0;
        left = 0;
    } else if (newrow == 1) {
        top = bottom + space;
        left = 0;
    } else if (n > 0) {
        pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL);
        top = by;
        left = bx + bw + space;
    }

    pixGetDimensions(pix4, &w, &h, NULL);
    bottom = L_MAX(bottom, top + h);
    box = boxCreate(left, top, w, h);
    pixaAddPix(pixa, pix4, L_INSERT);
    pixaAddBox(pixa, box, L_INSERT);

        /* Save the new bottom value */
    pix1 = pixaGetPix(pixa, 0, L_CLONE);
    pixSetInputFormat(pix1, bottom);  /* not typical usage! */
    pixDestroy(&pix1);
    return 0;
}
Example #15
0
/*!
 *  pixSaveTiledOutline()
 *
 *      Input:  pixs (1, 2, 4, 8, 32 bpp)
 *              pixa (the pix are accumulated here)
 *              reduction (0 to disable; otherwise this is a reduction factor)
 *              newrow (0 if placed on the same row as previous; 1 otherwise)
 *              space (horizontal and vertical spacing, in pixels)
 *              linewidth (width of added outline for image; 0 for no outline)
 *              dp (depth of pixa; 8 or 32 bpp; only used on first call)
 *      Return: 0 if OK, 1 on error.
 *
 *  Notes:
 *      (1) Before calling this function for the first time, use
 *          pixaCreate() to make the @pixa that will accumulate the pix.
 *          This is passed in each time pixSaveTiled() is called.
 *      (2) @reduction is the integer reduction factor for the input
 *          image.  After reduction and possible depth conversion,
 *          the image is saved in the input pixa, along with a box
 *          that specifies the location to place it when tiled later.
 *          Disable saving the pix by setting reduction == 0.
 *      (3) @newrow and @space specify the location of the new pix
 *          with respect to the last one(s) that were entered.
 *      (4) @dp specifies the depth at which all pix are saved.  It can
 *          be only 8 or 32 bpp.  Any colormap is removed.  This is only
 *          used at the first invocation.
 *      (5) This function uses two variables from call to call.
 *          If they were static, the function would not be .so or thread
 *          safe, and furthermore, there would be interference with two or
 *          more pixa accumulating images at a time.  Consequently,
 *          we use the first pix in the pixa to store and obtain both
 *          the depth and the current position of the bottom (one pixel
 *          below the lowest image raster line when laid out using
 *          the boxa).  The bottom variable is stored in the input format
 *          field, which is the only field available for storing an int.
 */
l_int32
pixSaveTiledOutline(PIX     *pixs,
                    PIXA    *pixa,
                    l_int32  reduction,
                    l_int32  newrow,
                    l_int32  space,
                    l_int32  linewidth,
                    l_int32  dp)
{
l_int32         n, top, left, bx, by, bw, w, h, depth, bottom;
l_float32       scale;
BOX            *box;
PIX            *pix, *pixt1, *pixt2, *pixt3;

    PROCNAME("pixSaveTiledOutline");

    if (reduction == 0) return 0;

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

    n = pixaGetCount(pixa);
    if (n == 0) {
        bottom = 0;
        if (dp != 8 && dp != 32) {
            L_WARNING("dp not 8 or 32 bpp; using 32", procName);
            depth = 32;
        } else
            depth = dp;
    }
    else {  /* extract the depth and bottom params from the first pix */
        pix = pixaGetPix(pixa, 0, L_CLONE);
        depth = pixGetDepth(pix);
        bottom = pixGetInputFormat(pix);  /* not typical usage! */
        pixDestroy(&pix);
    }

        /* Scale and convert to output depth */
    if (reduction == 1)
        pixt1 = pixClone(pixs);
    else {
        scale = 1. / (l_float32)reduction;
        if (pixGetDepth(pixs) == 1)
            pixt1 = pixScaleToGray(pixs, scale);
        else
            pixt1 = pixScale(pixs, scale, scale);
    }
    if (depth == 8)
        pixt2 = pixConvertTo8(pixt1, 0);
    else
        pixt2 = pixConvertTo32(pixt1);
    pixDestroy(&pixt1);

        /* Add black outline */
    if (linewidth > 0)
        pixt3 = pixAddBorder(pixt2, linewidth, 0);
    else
        pixt3 = pixClone(pixt2);
    pixDestroy(&pixt2);

        /* Find position of current pix (UL corner plus size) */
    if (n == 0) {
        top = 0;
        left = 0;
    }
    else if (newrow == 1) {
        top = bottom + space;
        left = 0;
    }
    else if (n > 0) {
        pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL);
        top = by;
        left = bx + bw + space;
    }

    pixGetDimensions(pixt3, &w, &h, NULL);
    bottom = L_MAX(bottom, top + h);
    box = boxCreate(left, top, w, h);
    pixaAddPix(pixa, pixt3, L_INSERT);
    pixaAddBox(pixa, box, L_INSERT);

        /* Save the new bottom value */
    pix = pixaGetPix(pixa, 0, L_CLONE);
    pixSetInputFormat(pix, bottom);  /* not typical usage! */
    pixDestroy(&pix);

    return 0;
}
// do a desperate attempt at cracking lines
Pixa *CubeLineSegmenter::CrackLine(Pix *cracked_line_pix,
                                   Box *cracked_line_box, int line_cnt) {
  // create lines pixa array
  Pixa **lines_pixa = new Pixa*[line_cnt];
  if (lines_pixa == NULL) {
    return NULL;
  }

  memset(lines_pixa, 0, line_cnt * sizeof(*lines_pixa));

  // compute line conn comps
  Pixa *line_con_comps_pix;
  Boxa *line_con_comps = ComputeLineConComps(cracked_line_pix,
    cracked_line_box, &line_con_comps_pix);

  if (line_con_comps == NULL) {
    delete []lines_pixa;
    return NULL;
  }

  // assign each conn comp to the a line based on its centroid
  for (int con = 0; con < line_con_comps->n; con++) {
    Box *con_box = line_con_comps->box[con];
    Pix *con_pix = line_con_comps_pix->pix[con];
    int mid_y = (con_box->y - cracked_line_box->y) + (con_box->h / 2),
      line_idx = MIN(line_cnt - 1,
                     (mid_y * line_cnt / cracked_line_box->h));

    // create the line if it has not been created?
    if (lines_pixa[line_idx] == NULL) {
      lines_pixa[line_idx] = pixaCreate(line_con_comps->n);
      if (lines_pixa[line_idx] == NULL) {
        delete []lines_pixa;
        boxaDestroy(&line_con_comps);
        pixaDestroy(&line_con_comps_pix);
        return NULL;
      }
    }

    // add the concomp to the line
    if (pixaAddPix(lines_pixa[line_idx], con_pix, L_CLONE) != 0 ||
        pixaAddBox(lines_pixa[line_idx], con_box, L_CLONE)) {
      delete []lines_pixa;
      boxaDestroy(&line_con_comps);
      pixaDestroy(&line_con_comps_pix);
    }
  }

  // create the lines pixa
  Pixa *lines = pixaCreate(line_cnt);
  bool success = true;

  // create and check the validity of the lines
  for (int line = 0; line < line_cnt; line++) {
    Pixa *line_pixa = lines_pixa[line];

    // skip invalid lines
    if (line_pixa == NULL) {
      continue;
    }

    // merge the pix, check the validity of the line
    // and add it to the lines pixa
    Box *line_box;
    Pix *line_pix = Pixa2Pix(line_pixa, &line_box);
    if (line_pix == NULL ||
        line_box == NULL ||
        ValidLine(line_pix, line_box) == false ||
        pixaAddPix(lines, line_pix, L_INSERT) != 0 ||
        pixaAddBox(lines, line_box, L_INSERT) != 0) {
      if (line_pix != NULL) {
        pixDestroy(&line_pix);
      }

      if (line_box != NULL) {
        boxDestroy(&line_box);
      }

      success = false;

      break;
    }
  }

  // cleanup
  for (int line = 0; line < line_cnt; line++) {
    if (lines_pixa[line] != NULL) {
      pixaDestroy(&lines_pixa[line]);
    }
  }

  delete []lines_pixa;
  boxaDestroy(&line_con_comps);
  pixaDestroy(&line_con_comps_pix);

  if (success == false) {
    pixaDestroy(&lines);
    lines = NULL;
  }

  return lines;
}