text: /*====================================================================*
   -  Copyright (C) 2001 Leptonica.  All rights reserved.
   -
   -  Redistribution and use in source and binary forms, with or without
   -  modification, are permitted provided that the following conditions
   -  are met:
   -  1. Redistributions of source code must retain the above copyright
   -     notice, this list of conditions and the following disclaimer.
   -  2. Redistributions in binary form must reproduce the above
   -     copyright notice, this list of conditions and the following
   -     disclaimer in the documentation and/or other materials
   -     provided with the distribution.
   -
   -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
   -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *====================================================================*/
  
  
  /*!
   * \file shear.c
   * <pre>
   *
   *    About arbitrary lines
   *           PIX      *pixHShear()
   *           PIX      *pixVShear()
   *
   *    About special 'points': UL corner and center
   *           PIX      *pixHShearCorner()
   *           PIX      *pixVShearCorner()
   *           PIX      *pixHShearCenter()
   *           PIX      *pixVShearCenter()
   *
   *    In place about arbitrary lines
   *           l_int32   pixHShearIP()
   *           l_int32   pixVShearIP()
   *
   *    Linear interpolated shear about arbitrary lines
   *           PIX      *pixHShearLI()
   *           PIX      *pixVShearLI()
   *
   *    Static helper
   *      static l_float32  normalizeAngleForShear()
   * </pre>
   */
  
  #include <string.h>
  #include <math.h>
  #include "allheaders.h"
  
      /* Shear angle must not get too close to -pi/2 or pi/2 */
  static const l_float32   MIN_DIFF_FROM_HALF_PI = 0.04;
  
  static l_float32 normalizeAngleForShear(l_float32 radang, l_float32 mindif);
  
  
  #ifndef  NO_CONSOLE_IO
  #define  DEBUG     0
  #endif  /* ~NO_CONSOLE_IO */
  
  
  /*-------------------------------------------------------------*
   *                    About arbitrary lines                    *
   *-------------------------------------------------------------*/
  /*!
   * \brief   pixHShear()
   *
   * \param[in]    pixd [optional], this can be null, equal to pixs,
   *                    or different from pixs
   * \param[in]    pixs no restrictions on depth
   * \param[in]    yloc location of horizontal line, measured from origin
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, always
   *
   * <pre>
   * Notes:
   *      (1) There are 3 cases:
   *            (a) pixd == null (make a new pixd)
   *            (b) pixd == pixs (in-place)
   *            (c) pixd != pixs
   *      (2) For these three cases, use these patterns, respectively:
   *              pixd = pixHShear(NULL, pixs, ...);
   *              pixHShear(pixs, pixs, ...);
   *              pixHShear(pixd, pixs, ...);
   *      (3) This shear leaves the horizontal line of pixels at y = yloc
   *          invariant.  For a positive shear angle, pixels above this
   *          line are shoved to the right, and pixels below this line
   *          move to the left.
   *      (4) With positive shear angle, this can be used, along with
   *          pixVShear(), to perform a cw rotation, either with 2 shears
   *          (for small angles) or in the general case with 3 shears.
   *      (5) Changing the value of yloc is equivalent to translating
   *          the result horizontally.
   *      (6) This brings in 'incolor' pixels from outside the image.
   *      (7) For in-place operation, pixs cannot be colormapped,
   *          because the in-place operation only blits in 0 or 1 bits,
   *          not an arbitrary colormap index.
   *      (8) The angle is brought into the range [-pi, -pi].  It is
   *          not permitted to be within MIN_DIFF_FROM_HALF_PI radians
   *          from either -pi/2 or pi/2.
   * </pre>
   */
  PIX *
  pixHShear(PIX       *pixd,
            PIX       *pixs,
            l_int32    yloc,
            l_float32  radang,
            l_int32    incolor)
  {
  l_int32    sign, w, h;
  l_int32    y, yincr, inityincr, hshift;
  l_float32  tanangle, invangle;
  
      PROCNAME("pixHShear");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return (PIX *)ERROR_PTR("invalid incolor value", procName, pixd);
  
      if (pixd == pixs) {  /* in place */
          if (pixGetColormap(pixs))
              return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
          pixHShearIP(pixd, yloc, radang, incolor);
          return pixd;
      }
  
          /* Make sure pixd exists and is same size as pixs */
      if (!pixd) {
          if ((pixd = pixCreateTemplate(pixs)) == NULL)
              return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
      } else {  /* pixd != pixs */
          pixResizeImageData(pixd, pixs);
      }
  
          /* Normalize angle.  If no rotation, return a copy */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0)
          return pixCopy(pixd, pixs);
  
          /* Initialize to value of incoming pixels */
      pixSetBlackOrWhite(pixd, incolor);
  
      pixGetDimensions(pixs, &w, &h, NULL);
      sign = L_SIGN(radang);
      tanangle = tan(radang);
      invangle = L_ABS(1. / tanangle);
      inityincr = (l_int32)(invangle / 2.);
      yincr = (l_int32)invangle;
      pixRasterop(pixd, 0, yloc - inityincr, w, 2 * inityincr, PIX_SRC,
                  pixs, 0, yloc - inityincr);
  
      for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
          yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
          if (h - y < yincr)  /* reduce for last one if req'd */
              yincr = h - y;
          pixRasterop(pixd, -sign*hshift, y, w, yincr, PIX_SRC, pixs, 0, y);
  #if DEBUG
          fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n", y, hshift, yincr);
  #endif /* DEBUG */
          y += yincr;
      }
  
      for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
          yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
          if (y < yincr)  /* reduce for last one if req'd */
              yincr = y;
          pixRasterop(pixd, -sign*hshift, y - yincr, w, yincr, PIX_SRC,
              pixs, 0, y - yincr);
  #if DEBUG
          fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n",
                  y - yincr, hshift, yincr);
  #endif /* DEBUG */
          y -= yincr;
      }
  
      return pixd;
  }
  
  
  /*!
   * \brief   pixVShear()
   *
   * \param[in]    pixd [optional], this can be null, equal to pixs,
   *                    or different from pixs
   * \param[in]    pixs no restrictions on depth
   * \param[in]    xloc location of vertical line, measured from origin
   * \param[in]    radang  angle in radians; not too close to +-(pi / 2)
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) There are 3 cases:
   *            (a) pixd == null (make a new pixd)
   *            (b) pixd == pixs (in-place)
   *            (c) pixd != pixs
   *      (2) For these three cases, use these patterns, respectively:
   *              pixd = pixVShear(NULL, pixs, ...);
   *              pixVShear(pixs, pixs, ...);
   *              pixVShear(pixd, pixs, ...);
   *      (3) This shear leaves the vertical line of pixels at x = xloc
   *          invariant.  For a positive shear angle, pixels to the right
   *          of this line are shoved downward, and pixels to the left
   *          of the line move upward.
   *      (4) With positive shear angle, this can be used, along with
   *          pixHShear(), to perform a cw rotation, either with 2 shears
   *          (for small angles) or in the general case with 3 shears.
   *      (5) Changing the value of xloc is equivalent to translating
   *          the result vertically.
   *      (6) This brings in 'incolor' pixels from outside the image.
   *      (7) For in-place operation, pixs cannot be colormapped,
   *          because the in-place operation only blits in 0 or 1 bits,
   *          not an arbitrary colormap index.
   *      (8) The angle is brought into the range [-pi, -pi].  It is
   *          not permitted to be within MIN_DIFF_FROM_HALF_PI radians
   *          from either -pi/2 or pi/2.
   * </pre>
   */
  PIX *
  pixVShear(PIX       *pixd,
            PIX       *pixs,
            l_int32    xloc,
            l_float32  radang,
            l_int32    incolor)
  {
  l_int32    sign, w, h;
  l_int32    x, xincr, initxincr, vshift;
  l_float32  tanangle, invangle;
  
      PROCNAME("pixVShear");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
  
      if (pixd == pixs) {  /* in place */
          if (pixGetColormap(pixs))
              return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
          pixVShearIP(pixd, xloc, radang, incolor);
          return pixd;
      }
  
          /* Make sure pixd exists and is same size as pixs */
      if (!pixd) {
          if ((pixd = pixCreateTemplate(pixs)) == NULL)
              return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
      } else {  /* pixd != pixs */
          pixResizeImageData(pixd, pixs);
      }
  
          /* Normalize angle.  If no rotation, return a copy */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0)
          return pixCopy(pixd, pixs);
  
          /* Initialize to value of incoming pixels */
      pixSetBlackOrWhite(pixd, incolor);
  
      pixGetDimensions(pixs, &w, &h, NULL);
      sign = L_SIGN(radang);
      tanangle = tan(radang);
      invangle = L_ABS(1. / tanangle);
      initxincr = (l_int32)(invangle / 2.);
      xincr = (l_int32)invangle;
      pixRasterop(pixd, xloc - initxincr, 0, 2 * initxincr, h, PIX_SRC,
                  pixs, xloc - initxincr, 0);
  
      for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
          xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
          if (w - x < xincr)  /* reduce for last one if req'd */
              xincr = w - x;
          pixRasterop(pixd, x, sign*vshift, xincr, h, PIX_SRC, pixs, x, 0);
  #if DEBUG
          fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n", x, vshift, xincr);
  #endif /* DEBUG */
          x += xincr;
      }
  
      for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
          xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
          if (x < xincr)  /* reduce for last one if req'd */
              xincr = x;
          pixRasterop(pixd, x - xincr, sign*vshift, xincr, h, PIX_SRC,
              pixs, x - xincr, 0);
  #if DEBUG
          fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n",
                  x - xincr, vshift, xincr);
  #endif /* DEBUG */
          x -= xincr;
      }
  
      return pixd;
  }
  
  
  
  /*-------------------------------------------------------------*
   *             Shears about UL corner and center               *
   *-------------------------------------------------------------*/
  /*!
   * \brief   pixHShearCorner()
   *
   * \param[in]    pixd [optional], if not null, must be equal to pixs
   * \param[in]    pixs
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, or NULL on error.
   *
   * <pre>
   * Notes:
   *      (1) See pixHShear() for usage.
   *      (2) This does a horizontal shear about the UL corner, with (+) shear
   *          pushing increasingly leftward (-x) with increasing y.
   * </pre>
   */
  PIX *
  pixHShearCorner(PIX       *pixd,
                  PIX       *pixs,
                  l_float32  radang,
                  l_int32    incolor)
  {
      PROCNAME("pixHShearCorner");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
  
      return pixHShear(pixd, pixs, 0, radang, incolor);
  }
  
  
  /*!
   * \brief   pixVShearCorner()
   *
   * \param[in]    pixd [optional], if not null, must be equal to pixs
   * \param[in]    pixs
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, or NULL on error.
   *
   * <pre>
   * Notes:
   *      (1) See pixVShear() for usage.
   *      (2) This does a vertical shear about the UL corner, with (+) shear
   *          pushing increasingly downward (+y) with increasing x.
   * </pre>
   */
  PIX *
  pixVShearCorner(PIX       *pixd,
                  PIX       *pixs,
                  l_float32  radang,
                  l_int32    incolor)
  {
      PROCNAME("pixVShearCorner");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
  
      return pixVShear(pixd, pixs, 0, radang, incolor);
  }
  
  
  /*!
   * \brief   pixHShearCenter()
   *
   * \param[in]    pixd [optional], if not null, must be equal to pixs
   * \param[in]    pixs
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, or NULL on error.
   *
   * <pre>
   * Notes:
   *      (1) See pixHShear() for usage.
   *      (2) This does a horizontal shear about the center, with (+) shear
   *          pushing increasingly leftward (-x) with increasing y.
   * </pre>
   */
  PIX *
  pixHShearCenter(PIX       *pixd,
                  PIX       *pixs,
                  l_float32  radang,
                  l_int32    incolor)
  {
      PROCNAME("pixHShearCenter");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
  
      return pixHShear(pixd, pixs, pixGetHeight(pixs) / 2, radang, incolor);
  }
  
  
  /*!
   * \brief   pixVShearCenter()
   *
   * \param[in]    pixd [optional], if not null, must be equal to pixs
   * \param[in]    pixs
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd, or NULL on error.
   *
   * <pre>
   * Notes:
   *      (1) See pixVShear() for usage.
   *      (2) This does a vertical shear about the center, with (+) shear
   *          pushing increasingly downward (+y) with increasing x.
   * </pre>
   */
  PIX *
  pixVShearCenter(PIX       *pixd,
                  PIX       *pixs,
                  l_float32  radang,
                  l_int32    incolor)
  {
      PROCNAME("pixVShearCenter");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
  
      return pixVShear(pixd, pixs, pixGetWidth(pixs) / 2, radang, incolor);
  }
  
  
  
  /*--------------------------------------------------------------------------*
   *                       In place about arbitrary lines                     *
   *--------------------------------------------------------------------------*/
  /*!
   * \brief   pixHShearIP()
   *
   * \param[in]    pixs
   * \param[in]    yloc location of horizontal line, measured from origin
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  0 if OK; 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This is an in-place version of pixHShear(); see comments there.
   *      (2) This brings in 'incolor' pixels from outside the image.
   *      (3) pixs cannot be colormapped, because the in-place operation
   *          only blits in 0 or 1 bits, not an arbitrary colormap index.
   *      (4) Does a horizontal full-band shear about the line with (+) shear
   *          pushing increasingly leftward (-x) with increasing y.
   * </pre>
   */
  l_int32
  pixHShearIP(PIX       *pixs,
              l_int32    yloc,
              l_float32  radang,
              l_int32    incolor)
  {
  l_int32    sign, w, h;
  l_int32    y, yincr, inityincr, hshift;
  l_float32  tanangle, invangle;
  
      PROCNAME("pixHShearIP");
  
      if (!pixs)
          return ERROR_INT("pixs not defined", procName, 1);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return ERROR_INT("invalid incolor value", procName, 1);
      if (pixGetColormap(pixs))
          return ERROR_INT("pixs is colormapped", procName, 1);
  
          /* Normalize angle */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0)
          return 0;
  
      sign = L_SIGN(radang);
      pixGetDimensions(pixs, &w, &h, NULL);
      tanangle = tan(radang);
      invangle = L_ABS(1. / tanangle);
      inityincr = (l_int32)(invangle / 2.);
      yincr = (l_int32)invangle;
  
      if (inityincr > 0)
          pixRasteropHip(pixs, yloc - inityincr, 2 * inityincr, 0, incolor);
  
      for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
          yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
          if (yincr == 0) continue;
          if (h - y < yincr)  /* reduce for last one if req'd */
              yincr = h - y;
          pixRasteropHip(pixs, y, yincr, -sign*hshift, incolor);
          y += yincr;
      }
  
      for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
          yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
          if (yincr == 0) continue;
          if (y < yincr)  /* reduce for last one if req'd */
              yincr = y;
          pixRasteropHip(pixs, y - yincr, yincr, -sign*hshift, incolor);
          y -= yincr;
      }
  
      return 0;
  }
  
  
  /*!
   * \brief   pixVShearIP()
   *
   * \param[in]    pixs all depths; not colormapped
   * \param[in]    xloc  location of vertical line, measured from origin
   * \param[in]    radang  angle in radians
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  0 if OK; 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This is an in-place version of pixVShear(); see comments there.
   *      (2) This brings in 'incolor' pixels from outside the image.
   *      (3) pixs cannot be colormapped, because the in-place operation
   *          only blits in 0 or 1 bits, not an arbitrary colormap index.
   *      (4) Does a vertical full-band shear about the line with (+) shear
   *          pushing increasingly downward (+y) with increasing x.
   * </pre>
   */
  l_int32
  pixVShearIP(PIX       *pixs,
              l_int32    xloc,
              l_float32  radang,
              l_int32    incolor)
  {
  l_int32    sign, w, h;
  l_int32    x, xincr, initxincr, vshift;
  l_float32  tanangle, invangle;
  
      PROCNAME("pixVShearIP");
  
      if (!pixs)
          return ERROR_INT("pixs not defined", procName, 1);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return ERROR_INT("invalid incolor value", procName, 1);
      if (pixGetColormap(pixs))
          return ERROR_INT("pixs is colormapped", procName, 1);
  
          /* Normalize angle */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0)
          return 0;
  
      sign = L_SIGN(radang);
      pixGetDimensions(pixs, &w, &h, NULL);
      tanangle = tan(radang);
      invangle = L_ABS(1. / tanangle);
      initxincr = (l_int32)(invangle / 2.);
      xincr = (l_int32)invangle;
  
      if (initxincr > 0)
          pixRasteropVip(pixs, xloc - initxincr, 2 * initxincr, 0, incolor);
  
      for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
          xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
          if (xincr == 0) continue;
          if (w - x < xincr)  /* reduce for last one if req'd */
              xincr = w - x;
          pixRasteropVip(pixs, x, xincr, sign*vshift, incolor);
          x += xincr;
      }
  
      for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
          xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
          if (xincr == 0) continue;
          if (x < xincr)  /* reduce for last one if req'd */
              xincr = x;
          pixRasteropVip(pixs, x - xincr, xincr, sign*vshift, incolor);
          x -= xincr;
      }
  
      return 0;
  }
  
  
  /*-------------------------------------------------------------------------*
   *              Linear interpolated shear about arbitrary lines            *
   *-------------------------------------------------------------------------*/
  /*!
   * \brief   pixHShearLI()
   *
   * \param[in]    pixs 8 bpp or 32 bpp, or colormapped
   * \param[in]    yloc location of horizontal line, measured from origin
   * \param[in]    radang  angle in radians, in range (-pi/2 ... pi/2)
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd sheared, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) This does horizontal shear with linear interpolation for
   *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
   *          It is relatively slow compared to the sampled version
   *          implemented by rasterop, but the result is much smoother.
   *      (2) This shear leaves the horizontal line of pixels at y = yloc
   *          invariant.  For a positive shear angle, pixels above this
   *          line are shoved to the right, and pixels below this line
   *          move to the left.
   *      (3) Any colormap is removed.
   *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
   *          where del == MIN_DIFF_FROM_HALF_PI.
   * </pre>
   */
  PIX *
  pixHShearLI(PIX       *pixs,
              l_int32    yloc,
              l_float32  radang,
              l_int32    incolor)
  {
  l_int32    i, jd, x, xp, xf, w, h, d, wm, wpls, wpld, val, rval, gval, bval;
  l_uint32   word0, word1;
  l_uint32  *datas, *datad, *lines, *lined;
  l_float32  tanangle, xshift;
  PIX       *pix, *pixd;
  
      PROCNAME("pixHShearLI");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
      pixGetDimensions(pixs, &w, &h, &d);
      if (d != 8 && d != 32 && !pixGetColormap(pixs))
          return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
      if (yloc < 0 || yloc >= h)
          return (PIX *)ERROR_PTR("yloc not in [0 ... h-1]", procName, NULL);
  
      if (pixGetColormap(pixs))
          pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
      else
          pix = pixClone(pixs);
  
          /* Normalize angle.  If no rotation, return a copy */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0) {
          pixDestroy(&pix);
          return pixCopy(NULL, pixs);
      }
  
          /* Initialize to value of incoming pixels */
      pixd = pixCreateTemplate(pix);
      pixSetBlackOrWhite(pixd, incolor);
  
          /* Standard linear interp: subdivide each pixel into 64 parts */
      d = pixGetDepth(pixd);  /* 8 or 32 */
      datas = pixGetData(pix);
      datad = pixGetData(pixd);
      wpls = pixGetWpl(pix);
      wpld = pixGetWpl(pixd);
      tanangle = tan(radang);
      for (i = 0; i < h; i++) {
          lines = datas + i * wpls;
          lined = datad + i * wpld;
          xshift = (yloc - i) * tanangle;
          for (jd = 0; jd < w; jd++) {
              x = (l_int32)(64.0 * (-xshift + jd) + 0.5);
              xp = x / 64;
              xf = x & 63;
              wm = w - 1;
              if (xp < 0 || xp > wm) continue;
              if (d == 8) {
                  if (xp < wm) {
                      val = ((63 - xf) * GET_DATA_BYTE(lines, xp) +
                             xf * GET_DATA_BYTE(lines, xp + 1) + 31) / 63;
                  } else {  /* xp == wm */
                      val = GET_DATA_BYTE(lines, xp);
                  }
                  SET_DATA_BYTE(lined, jd, val);
              } else {  /* d == 32 */
                  if (xp < wm) {
                      word0 = *(lines + xp);
                      word1 = *(lines + xp + 1);
                      rval = ((63 - xf) * ((word0 >> L_RED_SHIFT) & 0xff) +
                             xf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
                      gval = ((63 - xf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
                             xf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
                      bval = ((63 - xf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
                             xf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
                      composeRGBPixel(rval, gval, bval, lined + jd);
                  } else {  /* xp == wm */
                      lined[jd] = lines[xp];
                  }
              }
          }
      }
  
      pixDestroy(&pix);
      return pixd;
  }
  
  
  /*!
   * \brief   pixVShearLI()
   *
   * \param[in]    pixs 8 bpp or 32 bpp, or colormapped
   * \param[in]    xloc  location of vertical line, measured from origin
   * \param[in]    radang  angle in radians, in range (-pi/2 ... pi/2)
   * \param[in]    incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK;
   * \return  pixd sheared, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) This does vertical shear with linear interpolation for
   *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
   *          It is relatively slow compared to the sampled version
   *          implemented by rasterop, but the result is much smoother.
   *      (2) This shear leaves the vertical line of pixels at x = xloc
   *          invariant.  For a positive shear angle, pixels to the right
   *          of this line are shoved downward, and pixels to the left
   *          of the line move upward.
   *      (3) Any colormap is removed.
   *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
   *          where del == MIN_DIFF_FROM_HALF_PI.
   * </pre>
   */
  PIX *
  pixVShearLI(PIX       *pixs,
              l_int32    xloc,
              l_float32  radang,
              l_int32    incolor)
  {
  l_int32    id, y, yp, yf, j, w, h, d, hm, wpls, wpld, val, rval, gval, bval;
  l_uint32   word0, word1;
  l_uint32  *datas, *datad, *lines, *lined;
  l_float32  tanangle, yshift;
  PIX       *pix, *pixd;
  
      PROCNAME("pixVShearLI");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
      pixGetDimensions(pixs, &w, &h, &d);
      if (d != 8 && d != 32 && !pixGetColormap(pixs))
          return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
      if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
          return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
      if (xloc < 0 || xloc >= w)
          return (PIX *)ERROR_PTR("xloc not in [0 ... w-1]", procName, NULL);
  
      if (pixGetColormap(pixs))
          pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
      else
          pix = pixClone(pixs);
  
          /* Normalize angle.  If no rotation, return a copy */
      radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
      if (radang == 0.0 || tan(radang) == 0.0) {
          pixDestroy(&pix);
          return pixCopy(NULL, pixs);
      }
  
          /* Initialize to value of incoming pixels */
      pixd = pixCreateTemplate(pix);
      pixSetBlackOrWhite(pixd, incolor);
  
          /* Standard linear interp: subdivide each pixel into 64 parts */
      d = pixGetDepth(pixd);  /* 8 or 32 */
      datas = pixGetData(pix);
      datad = pixGetData(pixd);
      wpls = pixGetWpl(pix);
      wpld = pixGetWpl(pixd);
      tanangle = tan(radang);
      for (j = 0; j < w; j++) {
          yshift = (j - xloc) * tanangle;
          for (id = 0; id < h; id++) {
              y = (l_int32)(64.0 * (-yshift + id) + 0.5);
              yp = y / 64;
              yf = y & 63;
              hm = h - 1;
              if (yp < 0 || yp > hm) continue;
              lines = datas + yp * wpls;
              lined = datad + id * wpld;
              if (d == 8) {
                  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);
              } else {  /* d == 32 */
                  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];
                  }
              }
          }
      }
  
      pixDestroy(&pix);
      return pixd;
  }
  
  
  /*-------------------------------------------------------------------------*
   *                           Angle normalization                           *
   *-------------------------------------------------------------------------*/
  static l_float32
  normalizeAngleForShear(l_float32  radang,
                         l_float32  mindif)
  {
  l_float32  pi2;
  
      PROCNAME("normalizeAngleForShear");
  
         /* Bring angle into range [-pi/2, pi/2] */
      pi2 = 3.14159265 / 2.0;
      if (radang < -pi2 || radang > pi2)
          radang = radang - (l_int32)(radang / pi2) * pi2;
  
         /* If angle is too close to pi/2 or -pi/2, move it */
      if (radang > pi2 - mindif) {
          L_WARNING("angle close to pi/2; shifting away\n", procName);
          radang = pi2 - mindif;
      } else if (radang < -pi2 + mindif) {
          L_WARNING("angle close to -pi/2; shifting away\n", procName);
          radang = -pi2 + mindif;
      }
  
      return radang;
  }
  