/*====================================================================*
 -  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.
 *====================================================================*/

/*
 *  warper.c
 *
 *      High-level captcha interface
 *          PIX               *pixSimpleCaptcha()
 *
 *      Random sinusoidal warping
 *          PIX               *pixRandomHarmonicWarp()
 *
 *      Helper functions
 *          static l_float64  *generateRandomNumberArray()
 *          static l_int32     applyWarpTransform()
 *
 *      Version using a LUT for sin
 *          PIX               *pixRandomHarmonicWarpLUT()
 *          static l_int32     applyWarpTransformLUT()
 *          static l_int32     makeSinLUT()
 *          static l_float32   getSinFromLUT()
 *
 *      Stereoscopic warping
 *          PIX               *pixWarpStereoscopic()
 *
 *      Linear and quadratic horizontal stretching
 *          PIX               *pixStretchHorizontal()
 *          PIX               *pixStretchHorizontalSampled()
 *          PIX               *pixStretchHorizontalLI()
 *
 *      Quadratic vertical shear
 *          PIX               *pixQuadraticVShear()
 *          PIX               *pixQuadraticVShearSampled()
 *          PIX               *pixQuadraticVShearLI()
 *
 *      Stereo from a pair of images
 *          PIX               *pixStereoFromPair()
 */

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

static l_float64 *generateRandomNumberArray(l_int32 size);
static l_int32 applyWarpTransform(l_float32 xmag, l_float32 ymag,
                                l_float32 xfreq, l_float32 yfreq,
                                l_float64 *randa, l_int32 nx, l_int32 ny,
                                l_int32 xp, l_int32 yp,
                                l_float32 *px, l_float32 *py);

#define  USE_SIN_TABLE    0



/*----------------------------------------------------------------------*
 *                High-level example captcha interface                  *
 *----------------------------------------------------------------------*/
/*!
 *  pixSimpleCaptcha()
 *
 *      Input:  pixs (8 bpp; no colormap)
 *              border (added white pixels on each side)
 *              nterms (number of x and y harmonic terms)
 *              seed (of random number generator)
 *              color (for colorizing; in 0xrrggbb00 format; use 0 for black)
 *              cmapflag (1 for colormap output; 0 for rgb)
 *      Return: pixd (8 bpp cmap or 32 bpp rgb), or null on error
 *
 *  Notes:
 *      (1) This uses typical default values for generating captchas.
 *          The magnitudes of the harmonic warp are typically to be
 *          smaller when more terms are used, even though the phases
 *          are random.  See, for example, prog/warptest.c.
 */
PIX *
pixSimpleCaptcha(PIX      *pixs,
                 l_int32   border,
                 l_int32   nterms,
                 l_uint32  seed,
                 l_uint32  color,
                 l_int32   cmapflag)
{
l_int32    k;
l_float32  xmag[] = {7.0, 5.0, 4.0, 3.0};
l_float32  ymag[] = {10.0, 8.0, 6.0, 5.0};
l_float32  xfreq[] = {0.12, 0.10, 0.10, 0.11};
l_float32  yfreq[] = {0.15, 0.13, 0.13, 0.11};
PIX       *pixg, *pixgb, *pixw, *pixd;

    PROCNAME("pixSimpleCaptcha");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (nterms < 1 || nterms > 4)
        return (PIX *)ERROR_PTR("nterms must be in {1,2,3,4}", procName, NULL);

    k = nterms - 1;
    pixg = pixConvertTo8(pixs, 0);
    pixgb = pixAddBorder(pixg, border, 255);
    pixw = pixRandomHarmonicWarp(pixgb, xmag[k], ymag[k], xfreq[k], yfreq[k],
                                 nterms, nterms, seed, 255);
    pixd = pixColorizeGray(pixw, color, cmapflag);

    pixDestroy(&pixg);
    pixDestroy(&pixgb);
    pixDestroy(&pixw);
    return pixd;
}


/*----------------------------------------------------------------------*
 *                     Random sinusoidal warping                        *
 *----------------------------------------------------------------------*/
/*!
 *  pixRandomHarmonicWarp()
 *
 *      Input:  pixs (8 bpp; no colormap)
 *              xmag, ymag (maximum magnitude of x and y distortion)
 *              xfreq, yfreq (maximum magnitude of x and y frequency)
 *              nx, ny (number of x and y harmonic terms)
 *              seed (of random number generator)
 *              grayval (color brought in from the outside;
 *                       0 for black, 255 for white)
 *      Return: pixd (8 bpp; no colormap), or null on error
 *
 *  Notes:
 *      (1) To generate the warped image p(x',y'), set up the transforms
 *          that are in getWarpTransform().  For each (x',y') in the
 *          dest, the warp function computes the originating location
 *          (x, y) in the src.  The differences (x - x') and (y - y')
 *          are given as a sum of products of sinusoidal terms.  Each
 *          term is multiplied by a maximum amplitude (in pixels), and the
 *          angle is determined by a frequency and phase, and depends
 *          on the (x', y') value of the dest.  Random numbers with
 *          a variable input seed are used to allow the warping to be
 *          unpredictable.  A linear interpolation is used to find
 *          the value for the source at (x, y); this value is written
 *          into the dest.
 *      (2) This can be used to generate 'captcha's, which are somewhat
 *          randomly distorted images of text.  A typical set of parameters
 *          for a captcha are:
 *                    xmag = 4.0     ymag = 6.0
 *                    xfreq = 0.10   yfreq = 0.13
 *                    nx = 3         ny = 3
 *          Other examples can be found in prog/warptest.c.
 */
PIX *
pixRandomHarmonicWarp(PIX       *pixs,
                      l_float32  xmag,
                      l_float32  ymag,
                      l_float32  xfreq,
                      l_float32  yfreq,
                      l_int32    nx,
                      l_int32    ny,
                      l_uint32   seed,
                      l_int32    grayval)
{
l_int32     w, h, d, i, j, wpls, wpld, val;
l_uint32   *datas, *datad, *lined;
l_float32   x, y;
l_float64  *randa;
PIX        *pixd;

    PROCNAME("pixRandomHarmonicWarp");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 8)
        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);

        /* Compute filter output at each location.  We iterate over
         * the destination pixels.  For each dest pixel, use the
         * warp function to compute the four source pixels that
         * contribute, at the location (x, y).  Each source pixel
         * is divided into 16 x 16 subpixels to get an approximate value. */
    srand(seed);
    randa = generateRandomNumberArray(5 * (nx + ny));
    pixd = pixCreateTemplate(pixs);
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);

    for (i = 0; i < h; i++) {
        lined = datad + i * wpld;
        for (j = 0; j < w; j++) {
            applyWarpTransform(xmag, ymag, xfreq, yfreq, randa, nx, ny,
                               j, i, &x, &y);
            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
            SET_DATA_BYTE(lined, j, val);
        }
    }

    FREE(randa);
    return pixd;
}


/*----------------------------------------------------------------------*
 *                         Static helper functions                      *
 *----------------------------------------------------------------------*/
static l_float64 *
generateRandomNumberArray(l_int32  size)
{
l_int32     i;
l_float64  *randa;

    PROCNAME("generateRandomNumberArray");

    if ((randa = (l_float64 *)CALLOC(size, sizeof(l_float64))) == NULL)
        return (l_float64 *)ERROR_PTR("calloc fail for randa", procName, NULL);

        /* Return random values between 0.5 and 1.0 */
    for (i = 0; i < size; i++)
        randa[i] = 0.5 * (1.0 + (l_float64)rand() / (l_float64)RAND_MAX);
    return randa;
}


/*!
 *  applyWarpTransform()
 *
 *  Notes:
 *      (1) Uses the internal sin function.
 */
static l_int32
applyWarpTransform(l_float32   xmag,
                   l_float32   ymag,
                   l_float32   xfreq,
                   l_float32   yfreq,
                   l_float64  *randa,
                   l_int32     nx,
                   l_int32     ny,
                   l_int32     xp,
                   l_int32     yp,
                   l_float32  *px,
                   l_float32  *py)
{
l_int32    i;
l_float64  twopi, x, y, anglex, angley;

    twopi = 6.283185;
    for (i = 0, x = xp; i < nx; i++) {
        anglex = xfreq * randa[3 * i + 1] * xp + twopi * randa[3 * i + 2];
        angley = yfreq * randa[3 * i + 3] * yp + twopi * randa[3 * i + 4];
        x += xmag * randa[3 * i] * sin(anglex) * sin(angley);
    }
    for (i = nx, y = yp; i < nx + ny; i++) {
        angley = yfreq * randa[3 * i + 1] * yp + twopi * randa[3 * i + 2];
        anglex = xfreq * randa[3 * i + 3] * xp + twopi * randa[3 * i + 4];
        y += ymag * randa[3 * i] * sin(angley) * sin(anglex);
    }

    *px = (l_float32)x;
    *py = (l_float32)y;
    return 0;
}


#if  USE_SIN_TABLE
/*----------------------------------------------------------------------*
 *                       Version using a LUT for sin                    *
 *----------------------------------------------------------------------*/
static l_int32 applyWarpTransformLUT(l_float32 xmag, l_float32 ymag,
                                l_float32 xfreq, l_float32 yfreq,
                                l_float64 *randa, l_int32 nx, l_int32 ny,
                                l_int32 xp, l_int32 yp, l_float32 *lut,
                                l_int32 npts, l_float32 *px, l_float32 *py);
static l_int32 makeSinLUT(l_int32 npts, NUMA **pna);
static l_float32 getSinFromLUT(l_float32 *tab, l_int32 npts,
                               l_float32 radang);

/*!
 *  pixRandomHarmonicWarpLUT()
 *
 *      Input:  pixs (8 bpp; no colormap)
 *              xmag, ymag (maximum magnitude of x and y distortion)
 *              xfreq, yfreq (maximum magnitude of x and y frequency)
 *              nx, ny (number of x and y harmonic terms)
 *              seed (of random number generator)
 *              grayval (color brought in from the outside;
 *                       0 for black, 255 for white)
 *      Return: pixd (8 bpp; no colormap), or null on error
 *
 *  Notes:
 *      (1) See notes and inline comments in pixRandomHarmonicWarp().
 *          This version uses a LUT for the sin function.  It is not
 *          appreciably faster than using the built-in sin function,
 *          and is here for comparison only.
 */
PIX *
pixRandomHarmonicWarpLUT(PIX       *pixs,
                         l_float32  xmag,
                         l_float32  ymag,
                         l_float32  xfreq,
                         l_float32  yfreq,
                         l_int32    nx,
                         l_int32    ny,
                         l_uint32   seed,
                         l_int32    grayval)
{
l_int32     w, h, d, i, j, wpls, wpld, val, npts;
l_uint32   *datas, *datad, *lined;
l_float32   x, y;
l_float32  *lut;
l_float64  *randa;
NUMA       *na;
PIX        *pixd;

    PROCNAME("pixRandomHarmonicWarp");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 8)
        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);

        /* Compute filter output at each location.  We iterate over
         * the destination pixels.  For each dest pixel, use the
         * warp function to compute the four source pixels that
         * contribute, at the location (x, y).  Each source pixel
         * is divided into 16 x 16 subpixels to get an approximate value. */
    srand(seed);
    randa = generateRandomNumberArray(5 * (nx + ny));
    pixd = pixCreateTemplate(pixs);
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);

    npts = 100;
    makeSinLUT(npts, &na);
    lut = numaGetFArray(na, L_NOCOPY);
    for (i = 0; i < h; i++) {
        lined = datad + i * wpld;
        for (j = 0; j < w; j++) {
            applyWarpTransformLUT(xmag, ymag, xfreq, yfreq, randa, nx, ny,
                                  j, i, lut, npts, &x, &y);
            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
            SET_DATA_BYTE(lined, j, val);
        }
    }

    numaDestroy(&na);
    FREE(randa);
    return pixd;
}


/*!
 *  applyWarpTransformLUT()
 *
 *  Notes:
 *      (1) Uses an LUT for computing sin(theta).  There is little speed
 *          advantage to using the LUT.
 */
static l_int32
applyWarpTransformLUT(l_float32   xmag,
                      l_float32   ymag,
                      l_float32   xfreq,
                      l_float32   yfreq,
                      l_float64  *randa,
                      l_int32     nx,
                      l_int32     ny,
                      l_int32     xp,
                      l_int32     yp,
                      l_float32  *lut,
                      l_int32     npts,
                      l_float32  *px,
                      l_float32  *py)
{
l_int32    i;
l_float64  twopi, x, y, anglex, angley, sanglex, sangley;

    twopi = 6.283185;
    for (i = 0, x = xp; i < nx; i++) {
        anglex = xfreq * randa[3 * i + 1] * xp + twopi * randa[3 * i + 2];
        angley = yfreq * randa[3 * i + 3] * yp + twopi * randa[3 * i + 4];
        sanglex = getSinFromLUT(lut, npts, anglex);
        sangley = getSinFromLUT(lut, npts, angley);
        x += xmag * randa[3 * i] * sanglex * sangley;
    }
    for (i = nx, y = yp; i < nx + ny; i++) {
        angley = yfreq * randa[3 * i + 1] * yp + twopi * randa[3 * i + 2];
        anglex = xfreq * randa[3 * i + 3] * xp + twopi * randa[3 * i + 4];
        sanglex = getSinFromLUT(lut, npts, anglex);
        sangley = getSinFromLUT(lut, npts, angley);
        y += ymag * randa[3 * i] * sangley * sanglex;
    }

    *px = (l_float32)x;
    *py = (l_float32)y;
    return 0;
}


static l_int32
makeSinLUT(l_int32  npts,
           NUMA   **pna)
{
l_int32    i, n;
l_float32  delx, fval;
NUMA      *na;

    PROCNAME("makeSinLUT");

    if (!pna)
        return ERROR_INT("&na not defined", procName, 1);
    *pna = NULL;
    if (npts < 2)
        return ERROR_INT("npts < 2", procName, 1);
    n = 2 * npts + 1;
    na = numaCreate(n);
    *pna = na;
    delx = 3.14159265 / (l_float32)npts;
    numaSetXParameters(na, 0.0, delx);
    for (i = 0; i < n / 2; i++)
         numaAddNumber(na, (l_float32)sin((l_float64)i * delx));
    for (i = 0; i < n / 2; i++) {
         numaGetFValue(na, i, &fval);
         numaAddNumber(na, -fval);
    }
    numaAddNumber(na, 0);

    return 0;
}


static l_float32
getSinFromLUT(l_float32  *tab,
              l_int32     npts,
              l_float32   radang)
{
l_int32    index;
l_float32  twopi, invtwopi, findex, diff;

        /* Restrict radang to [0, 2pi] */
    twopi = 6.283185;
    invtwopi = 0.1591549;
    if (radang < 0.0)
        radang += twopi * (1.0 - (l_int32)(-radang * invtwopi));
    else if (radang > 0.0)
        radang -= twopi * (l_int32)(radang * invtwopi);

        /* Interpolate */
    findex = (2.0 * (l_float32)npts) * (radang * invtwopi);
    index = (l_int32)findex;
    if (index == 2 * npts)
        return tab[index];
    diff = findex - index;
    return (1.0 - diff) * tab[index] + diff * tab[index + 1];
}
#endif  /* USE_SIN_TABLE */



/*---------------------------------------------------------------------------*
 *                          Stereoscopic warping                             *
 *---------------------------------------------------------------------------*/
/*!
 *  pixWarpStereoscopic()
 *
 *      Input:  pixs (any depth, colormap ok)
 *              zbend (horizontal separation in pixels of red and cyan
 *                    at the left and right sides, that gives rise to
 *                    quadratic curvature out of the image plane)
 *              zshiftt (uniform pixel translation difference between
 *                      red and cyan, that pushes the top of the image
 *                      plane in (zshiftt < 0) or out (zshiftt > 0))
 *              zshiftb (uniform pixel translation difference between
 *                      red and cyan, that pushes the bottom of the image
 *                      plane in (zshiftb < 0) or out (zshiftb > 0))
 *              ybendt (multiplicative parameter for in-plane vertical
 *                      displacement at the left or right edge at the top:
 *                        y = ybendt * (2x/w - 1)^2 )
 *              ybendb (same as ybendt, except at the left or right edge
 *                      at the bottom)
 *              redleft (1 if the red filter is on the left; 0 otherwise)
 *      Return: pixd (32 bpp), or null on error
 *
 *  Notes:
 *      (1) This function splits out the red channel, mucks around with
 *          it, then recombines with the unmolested cyan channel.
 *      (2) By using a quadratically increasing shift of the red
 *          pixels horizontally and away from the vertical centerline,
 *          the image appears to bend quadratically out of the image
 *          plane, symmetrically with respect to the vertical center
 *          line.  A positive value of @zbend causes the plane to be
 *          curved away from the viewer.  We use linearly interpolated
 *          stretching to avoid the appearance of kinks in the curve.
 *      (3) The parameters @zshiftt and @zshiftb tilt the image plane
 *          about a horizontal line through the center, and at the
 *          same time move that line either in toward the viewer or away.
 *          This is implemented by a combination of horizontal shear
 *          about the center line (for the tilt) and horizontal
 *          translation (to move the entire plane in or out).
 *          A positive value of @zshiftt moves the top of the plane
 *          away from the viewer, and a positive value of @zshiftb
 *          moves the bottom of the plane away.  We use linear interpolated
 *          shear to avoid visible vertical steps in the tilted image.
 *      (4) The image can be bent in the plane and about the vertical
 *          centerline.  The centerline does not shift, and the
 *          parameter @ybend gives the relative shift at left and right
 *          edges, with a downward shift for positive values of @ybend.
 *      (6) When writing out a steroscopic (red/cyan) image in jpeg,
 *          first call l_jpegSetNoChromaSampling() to get sufficient
 *          resolution in the red channel.
 *      (7) Typical values are:
 *             zbend = 20
 *             zshiftt = 15
 *             zshiftb = -15
 *             ybendt = 30
 *             ybendb = 0
 *          If the disparity z-values are too large, it is difficult for
 *          the brain to register the two images.
 *      (8) This function has been cleverly reimplemented by Jeff Breidenbach.
 *          The original implementation used two 32 bpp rgb images,
 *          and merged them at the end.  The result is somewhat faded,
 *          and has a parameter "thresh" that controls the amount of
 *          color in the result.  (The present implementation avoids these
 *          two problems, skipping both the colorization and the alpha
 *          blending at the end, and is about 3x faster)
 *          The basic operations with 32 bpp are as follows:
 *               // Immediate conversion to 32 bpp
 *            Pix *pixt1 = pixConvertTo32(pixs);
 *               // Do vertical shear
 *            Pix *pixr = pixQuadraticVerticalShear(pixt1, L_WARP_TO_RIGHT,
 *                                                  ybendt, ybendb,
 *                                                  L_BRING_IN_WHITE);
 *               // Colorize two versions, toward red and cyan
 *            Pix *pixc = pixCopy(NULL, pixr);
 *            l_int32 thresh = 150;  // if higher, get less original color
 *            pixColorGray(pixr, NULL, L_PAINT_DARK, thresh, 255, 0, 0);
 *            pixColorGray(pixc, NULL, L_PAINT_DARK, thresh, 0, 255, 255);
 *               // Shift the red pixels; e.g., by stretching
 *            Pix *pixrs = pixStretchHorizontal(pixr, L_WARP_TO_RIGHT,
 *                                              L_QUADRATIC_WARP, zbend,
 *                                              L_INTERPOLATED,
 *                                              L_BRING_IN_WHITE);
 *               // Blend the shifted red and unshifted cyan 50:50
 *            Pix *pixg = pixCreate(w, h, 8);
 *            pixSetAllArbitrary(pixg, 128);
 *            pixd = pixBlendWithGrayMask(pixrs, pixc, pixg, 0, 0);
 */
PIX *
pixWarpStereoscopic(PIX     *pixs,
                    l_int32  zbend,
                    l_int32  zshiftt,
                    l_int32  zshiftb,
                    l_int32  ybendt,
                    l_int32  ybendb,
                    l_int32  redleft)
{
l_int32    w, h, zshift;
l_float32  angle;
BOX       *boxleft, *boxright;
PIX       *pixt, *pixt2, *pixt3;
PIX       *pixr, *pixg, *pixb;
PIX       *pixv1, *pixv2, *pixv3, *pixv4;
PIX       *pixr1, *pixr2, *pixr3, *pixr4, *pixrs, *pixrss;
PIX       *pixd;

    PROCNAME("pixWarpStereoscopic");

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

        /* Convert to the output depth, 32 bpp. */
    pixt = pixConvertTo32(pixs);

        /* If requested, do a quad vertical shearing, pushing pixels up
         * or down, depending on their distance from the centerline. */
    pixGetDimensions(pixs, &w, &h, NULL);
    boxleft = boxCreate(0, 0, w / 2, h);
    boxright = boxCreate(w / 2, 0, w - w / 2, h);
    if (ybendt != 0 || ybendb != 0) {
        pixv1 = pixClipRectangle(pixt, boxleft, NULL);
        pixv2 = pixClipRectangle(pixt, boxright, NULL);
        pixv3 = pixQuadraticVShear(pixv1, L_WARP_TO_LEFT, ybendt,
                                          ybendb, L_INTERPOLATED,
                                          L_BRING_IN_WHITE);
        pixv4 = pixQuadraticVShear(pixv2, L_WARP_TO_RIGHT, ybendt,
                                          ybendb, L_INTERPOLATED,
                                          L_BRING_IN_WHITE);
        pixt2 = pixCreate(w, h, 32);
        pixRasterop(pixt2, 0, 0, w / 2, h, PIX_SRC, pixv3, 0, 0);
        pixRasterop(pixt2, w / 2, 0, w - w / 2, h, PIX_SRC, pixv4, 0, 0);
        pixDestroy(&pixv1);
        pixDestroy(&pixv2);
        pixDestroy(&pixv3);
        pixDestroy(&pixv4);
    }
    else
        pixt2 = pixClone(pixt);

        /* Split out the 3 components */
    pixr = pixGetRGBComponent(pixt2, COLOR_RED);
    pixg = pixGetRGBComponent(pixt2, COLOR_GREEN);
    pixb = pixGetRGBComponent(pixt2, COLOR_BLUE);
    pixDestroy(&pixt);
    pixDestroy(&pixt2);

        /* The direction of the stereo disparity below is set
         * for the red filter to be over the left eye.  If the red
         * filter is over the right eye, invert the horizontal shifts. */
    if (redleft) {
        zbend = -zbend;
        zshiftt = -zshiftt;
        zshiftb = -zshiftb;
    }

        /* Shift the red pixels horizontally by an amount that
         * increases quadratically from the centerline. */
    if (zbend == 0)
        pixrs = pixClone(pixr);
    else {
        pixr1 = pixClipRectangle(pixr, boxleft, NULL);
        pixr2 = pixClipRectangle(pixr, boxright, NULL);
        pixr3 = pixStretchHorizontal(pixr1, L_WARP_TO_LEFT, L_QUADRATIC_WARP,
                                     zbend, L_INTERPOLATED, L_BRING_IN_WHITE);
        pixr4 = pixStretchHorizontal(pixr2, L_WARP_TO_RIGHT, L_QUADRATIC_WARP,
                                     zbend, L_INTERPOLATED, L_BRING_IN_WHITE);
        pixrs = pixCreate(w, h, 8);
        pixRasterop(pixrs, 0, 0, w / 2, h, PIX_SRC, pixr3, 0, 0);
        pixRasterop(pixrs, w / 2, 0, w - w / 2, h, PIX_SRC, pixr4, 0, 0);
        pixDestroy(&pixr1);
        pixDestroy(&pixr2);
        pixDestroy(&pixr3);
        pixDestroy(&pixr4);
    }

        /* Perform a combination of horizontal shift and shear of
         * red pixels.  The causes the plane of the image to tilt and
         * also move forward or backward.
         * to tilt the plane of the image and move it forward or backward. */
    if (zshiftt == 0 && zshiftb == 0)
        pixrss = pixClone(pixrs);
    else if (zshiftt == zshiftb)
        pixrss = pixTranslate(NULL, pixrs, zshiftt, 0, L_BRING_IN_WHITE);
    else {
        angle = (l_float32)(zshiftb - zshiftt) / (l_float32)pixGetHeight(pixrs);
        zshift = (zshiftt + zshiftb) / 2;
        pixt3 = pixTranslate(NULL, pixrs, zshift, 0, L_BRING_IN_WHITE);
        pixrss = pixHShearLI(pixt3, h / 2, angle, L_BRING_IN_WHITE);
        pixDestroy(&pixt3);
    }

        /* Combine the unchanged cyan (g,b) image with the shifted red */
    pixd = pixCreateRGBImage(pixrss, pixg, pixb);

    boxDestroy(&boxleft);
    boxDestroy(&boxright);
    pixDestroy(&pixrs);
    pixDestroy(&pixrss);
    pixDestroy(&pixr);
    pixDestroy(&pixg);
    pixDestroy(&pixb);
    return pixd;
}


/*----------------------------------------------------------------------*
 *              Linear and quadratic horizontal stretching              *
 *----------------------------------------------------------------------*/
/*!
 *  pixStretchHorizontal()
 *
 *      Input:  pixs (1, 8 or 32 bpp)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
 *              hmax (horizontal displacement at edge)
 *              operation (L_SAMPLED or L_INTERPOLATED)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched/compressed), or null on error
 *
 *  Notes:
 *      (1) If @hmax > 0, this is an increase in the coordinate value of
 *          pixels in pixd, relative to the same pixel in pixs.
 *      (2) If @dir == L_WARP_TO_LEFT, the pixels on the right edge of
 *          the image are not moved. So, for example, if @hmax > 0
 *          and @dir == L_WARP_TO_LEFT, the pixels in pixd are
 *          contracted toward the right edge of the image, relative
 *          to those in pixs.
 *      (3) If @type == L_LINEAR_WARP, the pixel positions are moved
 *          to the left or right by an amount that varies linearly with
 *          the horizontal location.
 *      (4) If @operation == L_SAMPLED, the dest pixels are taken from
 *          the nearest src pixel.  Otherwise, we use linear interpolation
 *          between pairs of sampled pixels.
 */
PIX *
pixStretchHorizontal(PIX     *pixs,
                     l_int32  dir,
                     l_int32  type,
                     l_int32  hmax,
                     l_int32  operation,
                     l_int32  incolor)
{
l_int32  d;

    PROCNAME("pixStretchHorizontal");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    d = pixGetDepth(pixs);
    if (d != 1 && d != 8 && d != 32)
        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
    if (operation != L_SAMPLED && operation != L_INTERPOLATED)
        return (PIX *)ERROR_PTR("invalid operation", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
    if (d == 1 && operation == L_INTERPOLATED) {
        L_WARNING("Using sampling for 1 bpp", procName);
        operation = L_INTERPOLATED;
    }

    if (operation == L_SAMPLED)
        return pixStretchHorizontalSampled(pixs, dir, type, hmax, incolor);
    else
        return pixStretchHorizontalLI(pixs, dir, type, hmax, incolor);
}


/*!
 *  pixStretchHorizontalSampled()
 *
 *      Input:  pixs (1, 8 or 32 bpp)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
 *              hmax (horizontal displacement at edge)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched/compressed), or null on error
 *
 *  Notes:
 *      (1) See pixStretchHorizontal() for details.
 */
PIX *
pixStretchHorizontalSampled(PIX     *pixs,
                            l_int32  dir,
                            l_int32  type,
                            l_int32  hmax,
                            l_int32  incolor)
{
l_int32    i, j, jd, w, wm, h, d, wpls, wpld, val;
l_uint32  *datas, *datad, *lines, *lined;
PIX       *pixd;

    PROCNAME("pixStretchHorizontalSampled");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 1 && d != 8 && d != 32)
        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);

    pixd = pixCreateTemplate(pixs);
    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
    datas = pixGetData(pixs);
    datad = pixGetData(pixd);
    wpls = pixGetWpl(pixs);
    wpld = pixGetWpl(pixd);
    wm = w - 1;
    for (jd = 0; jd < w; jd++) {
        if (dir == L_WARP_TO_LEFT) {
            if (type == L_LINEAR_WARP)
                j = jd - (hmax * (wm - jd)) / wm;
            else  /* L_QUADRATIC_WARP */
                j = jd - (hmax * (wm - jd) * (wm - jd)) / (wm * wm);
        }
        else if (dir == L_WARP_TO_RIGHT) {
            if (type == L_LINEAR_WARP)
                j = jd - (hmax * jd) / wm;
            else  /* L_QUADRATIC_WARP */
                j = jd - (hmax * jd * jd) / (wm * wm);
        }
        if (j < 0 || j > w - 1) continue;

        switch (d)
        {
        case 1:
            for (i = 0; i < h; i++) {
                lines = datas + i * wpls;
                lined = datad + i * wpld;
                val = GET_DATA_BIT(lines, j);
                if (val)
                    SET_DATA_BIT(lined, jd);
            }
            break;
        case 8:
            for (i = 0; i < h; i++) {
                lines = datas + i * wpls;
                lined = datad + i * wpld;
                val = GET_DATA_BYTE(lines, j);
                SET_DATA_BYTE(lined, jd, val);
            }
            break;
        case 32:
            for (i = 0; i < h; i++) {
                lines = datas + i * wpls;
                lined = datad + i * wpld;
                lined[jd] = lines[j];
            }
            break;
        default:
            L_ERROR_INT("invalid depth: %d", procName, d);
            pixDestroy(&pixd);
            return NULL;
        }
    }

    return pixd;
}


/*!
 *  pixStretchHorizontalLI()
 *
 *      Input:  pixs (1, 8 or 32 bpp)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
 *              hmax (horizontal displacement at edge)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched/compressed), or null on error
 *
 *  Notes:
 *      (1) See pixStretchHorizontal() for details.
 */
PIX *
pixStretchHorizontalLI(PIX     *pixs,
                       l_int32  dir,
                       l_int32  type,
                       l_int32  hmax,
                       l_int32  incolor)
{
l_int32    i, j, jd, jp, jf, w, wm, h, d, wpls, wpld, val, rval, gval, bval;
l_uint32   word0, word1;
l_uint32  *datas, *datad, *lines, *lined;
PIX       *pixd;

    PROCNAME("pixStretchHorizontalLI");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 8 && d != 32)
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);

        /* Standard linear interpolation, subdividing each pixel into 64 */
    pixd = pixCreateTemplate(pixs);
    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
    datas = pixGetData(pixs);
    datad = pixGetData(pixd);
    wpls = pixGetWpl(pixs);
    wpld = pixGetWpl(pixd);
    wm = w - 1;
    for (jd = 0; jd < w; jd++) {
        if (dir == L_WARP_TO_LEFT) {
            if (type == L_LINEAR_WARP)
                j = 64 * jd - 64 * (hmax * (wm - jd)) / wm;
            else  /* L_QUADRATIC_WARP */
                j = 64 * jd - 64 * (hmax * (wm - jd) * (wm - jd)) / (wm * wm);
        }
        else if (dir == L_WARP_TO_RIGHT) {
            if (type == L_LINEAR_WARP)
                j = 64 * jd - 64 * (hmax * jd) / wm;
            else  /* L_QUADRATIC_WARP */
                j = 64 * jd - 64 * (hmax * jd * jd) / (wm * wm);
        }
        jp = j / 64;
        jf = j & 0x3f;
        if (jp < 0 || jp > wm) continue;

        switch (d)
        {
        case 8:
            if (jp < wm) {
                for (i = 0; i < h; i++) {
                    lines = datas + i * wpls;
                    lined = datad + i * wpld;
                    val = ((63 - jf) * GET_DATA_BYTE(lines, jp) +
                           jf * GET_DATA_BYTE(lines, jp + 1) + 31) / 63;
                    SET_DATA_BYTE(lined, jd, val);
                }
            }
            else {  /* jp == wm */
                for (i = 0; i < h; i++) {
                    lines = datas + i * wpls;
                    lined = datad + i * wpld;
                    val = GET_DATA_BYTE(lines, jp);
                    SET_DATA_BYTE(lined, jd, val);
                }
            }
            break;
        case 32:
            if (jp < wm) {
                for (i = 0; i < h; i++) {
                    lines = datas + i * wpls;
                    lined = datad + i * wpld;
                    word0 = *(lines + jp);
                    word1 = *(lines + jp + 1);
                    rval = ((63 - jf) * ((word0 >> L_RED_SHIFT) & 0xff) +
                           jf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
                    gval = ((63 - jf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
                           jf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
                    bval = ((63 - jf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
                           jf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
                    composeRGBPixel(rval, gval, bval, lined + jd);
                }
            }
            else {  /* jp == wm */
                for (i = 0; i < h; i++) {
                    lines = datas + i * wpls;
                    lined = datad + i * wpld;
                    lined[jd] = lines[jp];
                }
            }
            break;
        default:
            L_ERROR_INT("invalid depth: %d", procName, d);
            pixDestroy(&pixd);
            return NULL;
        }
    }

    return pixd;
}


/*----------------------------------------------------------------------*
 *                       Quadratic vertical shear                       *
 *----------------------------------------------------------------------*/
/*!
 *  pixQuadraticVShear()
 *
 *      Input:  pixs (1, 8 or 32 bpp)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              vmaxt (max vertical displacement at edge and at top)
 *              vmaxb (max vertical displacement at edge and at bottom)
 *              operation (L_SAMPLED or L_INTERPOLATED)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched), or null on error
 *
 *  Notes:
 *      (1) This gives a quadratic bending, upward or downward, as you
 *          move to the left or right.
 *      (2) If @dir == L_WARP_TO_LEFT, the right edge is unchanged, and
 *          the left edge pixels are moved maximally up or down.
 *      (3) Parameters @vmaxt and @vmaxb control the maximum amount of
 *          vertical pixel shear at the top and bottom, respectively.
 *          If @vmaxt > 0, the vertical displacement of pixels at the
 *          top is downward.  Likewise, if @vmaxb > 0, the vertical
 *          displacement of pixels at the bottom is downward.
 *      (4) If @operation == L_SAMPLED, the dest pixels are taken from
 *          the nearest src pixel.  Otherwise, we use linear interpolation
 *          between pairs of sampled pixels.
 *      (5) This is for quadratic shear.  For uniform (linear) shear,
 *          use the standard shear operators.
 */
PIX *
pixQuadraticVShear(PIX     *pixs,
                   l_int32  dir,
                   l_int32  vmaxt,
                   l_int32  vmaxb,
                   l_int32  operation,
                   l_int32  incolor)
{
l_int32    w, h, d;

    PROCNAME("pixQuadraticVShear");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 1 && d != 8 && d != 32)
        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (operation != L_SAMPLED && operation != L_INTERPOLATED)
        return (PIX *)ERROR_PTR("invalid operation", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);

    if (vmaxt == 0 && vmaxb == 0)
        return pixCopy(NULL, pixs);

    if (operation == L_INTERPOLATED && d == 1) {
        L_WARNING("no interpolation for 1 bpp; using sampling", procName);
        operation = L_SAMPLED;
    }

    if (operation == L_SAMPLED)
        return pixQuadraticVShearSampled(pixs, dir, vmaxt, vmaxb, incolor);
    else  /* operation == L_INTERPOLATED */
        return pixQuadraticVShearLI(pixs, dir, vmaxt, vmaxb, incolor);
}


/*!
 *  pixQuadraticVShearSampled()
 *
 *      Input:  pixs (1, 8 or 32 bpp)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              vmaxt (max vertical displacement at edge and at top)
 *              vmaxb (max vertical displacement at edge and at bottom)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched), or null on error
 *
 *  Notes:
 *      (1) See pixQuadraticVShear() for details.
 */
PIX *
pixQuadraticVShearSampled(PIX     *pixs,
                          l_int32  dir,
                          l_int32  vmaxt,
                          l_int32  vmaxb,
                          l_int32  incolor)
{
l_int32    i, j, id, w, h, d, wm, hm, wpls, wpld, val;
l_uint32  *datas, *datad, *lines, *lined;
l_float32  delrowt, delrowb, denom1, denom2, dely;
PIX       *pixd;

    PROCNAME("pixQuadraticVShearSampled");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 1 && d != 8 && d != 32)
        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);

    if (vmaxt == 0 && vmaxb == 0)
        return pixCopy(NULL, pixs);

    pixd = pixCreateTemplate(pixs);
    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
    datas = pixGetData(pixs);
    datad = pixGetData(pixd);
    wpls = pixGetWpl(pixs);
    wpld = pixGetWpl(pixd);
    wm = w - 1;
    hm = h - 1;
    denom1 = 1. / (l_float32)h;
    denom2 = 1. / (l_float32)(wm * wm);
    for (j = 0; j < w; j++) {
        if (dir == L_WARP_TO_LEFT) {
            delrowt = (l_float32)(vmaxt * (wm - j) * (wm - j)) * denom2;
            delrowb = (l_float32)(vmaxb * (wm - j) * (wm - j)) * denom2;
        }
        else if (dir == L_WARP_TO_RIGHT) {
            delrowt = (l_float32)(vmaxt * j * j) * denom2;
            delrowb = (l_float32)(vmaxb * j * j) * denom2;
        }
        switch (d)
        {
        case 1:
            for (id = 0; id < h; id++) {
                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
                i = id - (l_int32)(dely + 0.5);
                if (i < 0 || i > hm) continue;
                lines = datas + i * wpls;
                lined = datad + id * wpld;
                val = GET_DATA_BIT(lines, j);
                if (val)
                    SET_DATA_BIT(lined, j);
            }
            break;
        case 8:
            for (id = 0; id < h; id++) {
                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
                i = id - (l_int32)(dely + 0.5);
                if (i < 0 || i > hm) continue;
                lines = datas + i * wpls;
                lined = datad + id * wpld;
                val = GET_DATA_BYTE(lines, j);
                SET_DATA_BYTE(lined, j, val);
            }
            break;
        case 32:
            for (id = 0; id < h; id++) {
                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
                i = id - (l_int32)(dely + 0.5);
                if (i < 0 || i > hm) continue;
                lines = datas + i * wpls;
                lined = datad + id * wpld;
                lined[j] = lines[j];
            }
            break;
        default:
            L_ERROR_INT("invalid depth: %d", procName, d);
            pixDestroy(&pixd);
            return NULL;
        }
    }

    return pixd;
}


/*!
 *  pixQuadraticVShearLI()
 *
 *      Input:  pixs (8 or 32 bpp, or colormapped)
 *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
 *              vmaxt (max vertical displacement at edge and at top)
 *              vmaxb (max vertical displacement at edge and at bottom)
 *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
 *      Return: pixd (stretched), or null on error
 *
 *  Notes:
 *      (1) See pixQuadraticVShear() for details.
 */
PIX *
pixQuadraticVShearLI(PIX     *pixs,
                     l_int32  dir,
                     l_int32  vmaxt,
                     l_int32  vmaxb,
                     l_int32  incolor)
{
l_int32    i, j, id, yp, yf, w, h, d, wm, hm, wpls, wpld;
l_int32    val, rval, gval, bval;
l_uint32   word0, word1;
l_uint32  *datas, *datad, *lines, *lined;
l_float32  delrowt, delrowb, denom1, denom2, dely;
PIX       *pix, *pixd;
PIXCMAP   *cmap;

    PROCNAME("pixQuadraticVShearLI");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    pixGetDimensions(pixs, &w, &h, &d);
    if (d == 1)
        return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL);
    cmap = pixGetColormap(pixs);
    if (d != 8 && d != 32 && !cmap)
        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);

    if (vmaxt == 0 && vmaxb == 0)
        return pixCopy(NULL, pixs);

        /* Remove any existing colormap */
    if (cmap)
        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
    else
        pix = pixClone(pixs);
    d = pixGetDepth(pix);
    if (d != 8 && d != 32) {
        pixDestroy(&pix);
        return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
    }

        /* Standard linear interp: subdivide each pixel into 64 parts */
    pixd = pixCreateTemplate(pix);
    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
    datas = pixGetData(pix);
    datad = pixGetData(pixd);
    wpls = pixGetWpl(pix);
    wpld = pixGetWpl(pixd);
    wm = w - 1;
    hm = h - 1;
    denom1 = 1.0 / (l_float32)h;
    denom2 = 1.0 / (l_float32)(wm * wm);
    for (j = 0; j < w; j++) {
        if (dir == L_WARP_TO_LEFT) {
            delrowt = (l_float32)(vmaxt * (wm - j) * (wm - j)) * denom2;
            delrowb = (l_float32)(vmaxb * (wm - j) * (wm - j)) * denom2;
        }
        else if (dir == L_WARP_TO_RIGHT) {
            delrowt = (l_float32)(vmaxt * j * j) * denom2;
            delrowb = (l_float32)(vmaxb * j * j) * denom2;
        }
        switch (d)
        {
        case 8:
            for (id = 0; id < h; id++) {
                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
                i = 64 * id - (l_int32)(64.0 * dely);
                yp = i / 64;
                yf = i & 63;
                if (yp < 0 || yp > hm) continue;
                lines = datas + yp * wpls;
                lined = datad + id * wpld;
                if (yp < hm)
                    val = ((63 - yf) * GET_DATA_BYTE(lines, j) +
                           yf * GET_DATA_BYTE(lines + wpls, j) + 31) / 63;
                else  /* yp == hm */
                    val = GET_DATA_BYTE(lines, j);
                SET_DATA_BYTE(lined, j, val);
            }
            break;
        case 32:
            for (id = 0; id < h; id++) {
                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
                i = 64 * id - (l_int32)(64.0 * dely);
                yp = i / 64;
                yf = i & 63;
                if (yp < 0 || yp > hm) continue;
                lines = datas + yp * wpls;
                lined = datad + id * wpld;
                if (yp < hm) {
                    word0 = *(lines + j);
                    word1 = *(lines + wpls + j);
                    rval = ((63 - yf) * ((word0 >> L_RED_SHIFT) & 0xff) +
                           yf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
                    gval = ((63 - yf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
                           yf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
                    bval = ((63 - yf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
                           yf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
                    composeRGBPixel(rval, gval, bval, lined + j);
                }
                else {  /* yp == hm */
                    lined[j] = lines[j];
                }
            }
            break;
        default:
            L_ERROR_INT("invalid depth: %d", procName, d);
            pixDestroy(&pix);
            pixDestroy(&pixd);
            return NULL;
        }
    }

    pixDestroy(&pix);
    return pixd;
}


/*----------------------------------------------------------------------*
 *                     Stereo from a pair of images                     *
 *----------------------------------------------------------------------*/
/*!
 *  pixStereoFromPair()
 *
 *      Input:  pix1 (32 bpp rgb)
 *              pix2 (32 bpp rgb)
 *              rwt, gwt, bwt (weighting factors used for each component in
                               pix1 to determine the output red channel)
 *      Return: pixd (stereo enhanced), or null on error
 *
 *  Notes:
 *      (1) pix1 and pix2 are a pair of stereo images, ideally taken
 *          concurrently in the same plane, with some lateral translation.
 *      (2) The output red channel is determined from @pix1.
 *          The output green and blue channels are taken from the green
 *          and blue channels, respectively, of @pix2.
 *      (3) The weights determine how much of each component in @pix1
 *          goes into the output red channel.  The sum of weights
 *          must be 1.0.  If it's not, we scale the weights to
 *          satisfy this criterion.
 *      (4) The most general pixel mapping allowed here is:
 *            rval = rwt * r1 + gwt * g1 + bwt * b1  (from pix1)
 *            gval = g2   (from pix2)
 *            bval = b2   (from pix2)
 *      (5) The simplest method is to use rwt = 1.0, gwt = 0.0, bwt = 0.0,
 *          but this causes unpleasant visual artifacts with red in the image.
 *          Use of green and blue from @pix1 in the red channel,
 *          instead of red, tends to fix that problem.
 */
PIX *
pixStereoFromPair(PIX       *pix1,
                  PIX       *pix2,
                  l_float32  rwt,
                  l_float32  gwt,
                  l_float32  bwt)
{
l_int32    i, j, w, h, wpl1, wpl2, rval, gval, bval;
l_uint32   word1, word2;
l_uint32  *data1, *data2, *datad, *line1, *line2, *lined;
l_float32  sum;
PIX       *pixd;

    PROCNAME("pixStereoFromPair");

    if (!pix1 || !pix2)
        return (PIX *)ERROR_PTR("pix1, pix2 not both defined", procName, NULL);
    if (pixGetDepth(pix1) != 32 || pixGetDepth(pix2) != 32)
        return (PIX *)ERROR_PTR("pix1, pix2 not both 32 bpp", procName, NULL);

        /* Make sure the sum of weights is 1.0; otherwise, you can get
         * overflow in the gray value. */
    if (rwt == 0.0 && gwt == 0.0 && bwt == 0.0) {
        rwt = L_RED_WEIGHT;
        gwt = L_GREEN_WEIGHT;
        bwt = L_BLUE_WEIGHT;
    }
    sum = rwt + gwt + bwt;
    if (L_ABS(sum - 1.0) > 0.0001) {  /* maintain ratios with sum == 1.0 */
        L_WARNING("weights don't sum to 1; maintaining ratios", procName);
        rwt = rwt / sum;
        gwt = gwt / sum;
        bwt = bwt / sum;
    }

    pixGetDimensions(pix1, &w, &h, NULL);
    pixd = pixCreateTemplate(pix1);
    data1 = pixGetData(pix1);
    data2 = pixGetData(pix2);
    datad = pixGetData(pixd);
    wpl1 = pixGetWpl(pix1);
    wpl2 = pixGetWpl(pix2);
    for (i = 0; i < h; i++) {
        line1 = data1 + i * wpl1;
        line2 = data2 + i * wpl2;
        lined = datad + i * wpl1;  /* wpl1 works for pixd */
        for (j = 0; j < w; j++) {
            word1 = *(line1 + j);
            word2 = *(line2 + j);
            rval = (l_int32)(rwt * ((word1 >> L_RED_SHIFT) & 0xff) +
                             gwt * ((word1 >> L_GREEN_SHIFT) & 0xff) +
                             bwt * ((word1 >> L_BLUE_SHIFT) & 0xff) + 0.5);
            gval = (word2 >> L_GREEN_SHIFT) & 0xff;
            bval = (word2 >> L_BLUE_SHIFT) & 0xff;
            composeRGBPixel(rval, gval, bval, lined + j);
        }
    }

    return pixd;
}

