/*
 *   cupsImage zoom routines for CUPS.
 *
 *   Copyright 2007-2011 by Apple Inc.
 *   Copyright 1993-2006 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "COPYING"
 *   which should have been included with this file.
 *
 * Contents:
 *
 *   _cupsImageZoomDelete() - Free a zoom record...
 *   _cupsImageZoomFill()   - Fill a zoom record...
 *   _cupsImageZoomNew()    - Allocate a pixel zoom record...
 *   zoom_bilinear()        - Fill a zoom record with image data utilizing
 *                            bilinear interpolation.
 *   zoom_nearest()         - Fill a zoom record quickly using nearest-neighbor
 *                            sampling.
 */

/*
 * Include necessary headers...
 */

#include "image-private.h"


/*
 * Local functions...
 */

static void	zoom_bilinear(cups_izoom_t *z, int iy);
static void	zoom_nearest(cups_izoom_t *z, int iy);


/*
 * '_cupsImageZoomDelete()' - Free a zoom record...
 */

void
_cupsImageZoomDelete(cups_izoom_t *z)	/* I - Zoom record to free */
{
  free(z->rows[0]);
  free(z->rows[1]);
  free(z->in);
  free(z);
}


/*
 * '_cupsImageZoomFill()' - Fill a zoom record with image data utilizing bilinear
 *                         interpolation.
 */

void
_cupsImageZoomFill(cups_izoom_t *z,	/* I - Zoom record to fill */
                   int     iy)		/* I - Zoom image row */
{
  switch (z->type)
  {
    case CUPS_IZOOM_FAST :
        zoom_nearest(z, iy);
	break;

    default :
        zoom_bilinear(z, iy);
	break;
  }
}


/*
 * '_cupsImageZoomNew()' - Allocate a pixel zoom record...
 */

cups_izoom_t *
_cupsImageZoomNew(
    cups_image_t  *img,			/* I - cupsImage to zoom */
    int           xc0,			/* I - Upper-lefthand corner */
    int           yc0,			/* I - ... */
    int           xc1,			/* I - Lower-righthand corner */
    int           yc1,			/* I - ... */
    int           xsize,		/* I - Final width of image */
    int           ysize,		/* I - Final height of image */
    int           rotated,		/* I - Non-zero if image is rotated 90 degs */
    cups_iztype_t type)			/* I - Zoom type */
{
  cups_izoom_t	*z;			/* New zoom record */
  int		flip;			/* Flip on X axis? */


  if (xsize > CUPS_IMAGE_MAX_WIDTH ||
      ysize > CUPS_IMAGE_MAX_HEIGHT ||
      (xc1 - xc0) > CUPS_IMAGE_MAX_WIDTH ||
      (yc1 - yc0) > CUPS_IMAGE_MAX_HEIGHT)
    return (NULL);		/* Protect against integer overflow */

  if ((z = (cups_izoom_t *)calloc(1, sizeof(cups_izoom_t))) == NULL)
    return (NULL);

  z->img     = img;
  z->row     = 0;
  z->depth   = cupsImageGetDepth(img);
  z->rotated = rotated;
  z->type    = type;

  if (xsize < 0)
  {
    flip  = 1;
    xsize = -xsize;
  }
  else
  {
    flip  = 0;
  }

  if (rotated)
  {
    z->xorig   = xc1;
    z->yorig   = yc0;
    z->width   = yc1 - yc0 + 1;
    z->height  = xc1 - xc0 + 1;
    z->xsize   = xsize;
    z->ysize   = ysize;
    z->xmod    = z->width % z->xsize;
    z->xstep   = z->width / z->xsize;
    z->xincr   = 1;
    z->ymod    = z->height % z->ysize;
    z->ystep   = z->height / z->ysize;
    z->yincr   = 1;
    z->instep  = z->xstep * z->depth;
    z->inincr  = /* z->xincr * */ z->depth; /* z->xincr is always 1 */

    if (z->width < img->ysize)
      z->xmax = z->width;
    else
      z->xmax = z->width - 1;

    if (z->height < img->xsize)
      z->ymax = z->height;
    else
      z->ymax = z->height - 1;
  }
  else
  {
    z->xorig   = xc0;
    z->yorig   = yc0;
    z->width   = xc1 - xc0 + 1;
    z->height  = yc1 - yc0 + 1;
    z->xsize   = xsize;
    z->ysize   = ysize;
    z->xmod    = z->width % z->xsize;
    z->xstep   = z->width / z->xsize;
    z->xincr   = 1;
    z->ymod    = z->height % z->ysize;
    z->ystep   = z->height / z->ysize;
    z->yincr   = 1;
    z->instep  = z->xstep * z->depth;
    z->inincr  = /* z->xincr * */ z->depth; /* z->xincr is always 1 */

    if (z->width < img->xsize)
      z->xmax = z->width;
    else
      z->xmax = z->width - 1;

    if (z->height < img->ysize)
      z->ymax = z->height;
    else
      z->ymax = z->height - 1;
  }

  if (flip)
  {
    z->instep = -z->instep;
    z->inincr = -z->inincr;
  }

  if ((z->rows[0] = (cups_ib_t *)malloc(z->xsize * z->depth)) == NULL)
  {
    free(z);
    return (NULL);
  }

  if ((z->rows[1] = (cups_ib_t *)malloc(z->xsize * z->depth)) == NULL)
  {
    free(z->rows[0]);
    free(z);
    return (NULL);
  }

  if ((z->in = (cups_ib_t *)malloc(z->width * z->depth)) == NULL)
  {
    free(z->rows[0]);
    free(z->rows[1]);
    free(z);
    return (NULL);
  }

  return (z);
}


/*
 * 'zoom_bilinear()' - Fill a zoom record with image data utilizing bilinear
 *                     interpolation.
 */

static void
zoom_bilinear(cups_izoom_t *z,		/* I - Zoom record to fill */
              int          iy)		/* I - Zoom image row */
{
  cups_ib_t	*r,			/* Row pointer */
		*inptr;			/* Pixel pointer */
  int		xerr0,			/* X error counter */
		xerr1;			/* ... */
  int		ix,
		x,
		count,
		z_depth,
		z_xstep,
		z_xincr,
		z_instep,
		z_inincr,
		z_xmax,
		z_xmod,
		z_xsize;


  if (iy > z->ymax)
    iy = z->ymax;

  z->row ^= 1;

  z_depth  = z->depth;
  z_xsize  = z->xsize;
  z_xmax   = z->xmax;
  z_xmod   = z->xmod;
  z_xstep  = z->xstep;
  z_xincr  = z->xincr;
  z_instep = z->instep;
  z_inincr = z->inincr;

  if (z->rotated)
    cupsImageGetCol(z->img, z->xorig - iy, z->yorig, z->width, z->in);
  else
    cupsImageGetRow(z->img, z->xorig, z->yorig + iy, z->width, z->in);

  if (z_inincr < 0)
    inptr = z->in + (z->width - 1) * z_depth;
  else
    inptr = z->in;

  for (x = z_xsize, xerr0 = z_xsize, xerr1 = 0, ix = 0, r = z->rows[z->row];
       x > 0;
       x --)
  {
    if (ix < z_xmax)
    {
      for (count = 0; count < z_depth; count ++)
        *r++ = (inptr[count] * xerr0 + inptr[z_depth + count] * xerr1) / z_xsize;
    }
    else
    {
      for (count = 0; count < z_depth; count ++)
        *r++ = inptr[count];
    }

    ix    += z_xstep;
    inptr += z_instep;
    xerr0 -= z_xmod;
    xerr1 += z_xmod;

    if (xerr0 <= 0)
    {
      xerr0 += z_xsize;
      xerr1 -= z_xsize;
      ix    += z_xincr;
      inptr += z_inincr;
    }
  }
}


/*
 * 'zoom_nearest()' - Fill a zoom record quickly using nearest-neighbor
 *                    sampling.
 */

static void
zoom_nearest(cups_izoom_t *z,		/* I - Zoom record to fill */
             int          iy)		/* I - Zoom image row */
{
  cups_ib_t	*r,			/* Row pointer */
		*inptr;			/* Pixel pointer */
  int		xerr0;			/* X error counter */
  int		ix,
		x,
		count,
		z_depth,
		z_xstep,
		z_xincr,
		z_instep,
		z_inincr,
		z_xmod,
		z_xsize;


  if (iy > z->ymax)
    iy = z->ymax;

  z->row ^= 1;

  z_depth  = z->depth;
  z_xsize  = z->xsize;
  z_xmod   = z->xmod;
  z_xstep  = z->xstep;
  z_xincr  = z->xincr;
  z_instep = z->instep;
  z_inincr = z->inincr;

  if (z->rotated)
    cupsImageGetCol(z->img, z->xorig - iy, z->yorig, z->width, z->in);
  else
    cupsImageGetRow(z->img, z->xorig, z->yorig + iy, z->width, z->in);

  if (z_inincr < 0)
    inptr = z->in + (z->width - 1) * z_depth;
  else
    inptr = z->in;

  for (x = z_xsize, xerr0 = z_xsize, ix = 0, r = z->rows[z->row];
       x > 0;
       x --)
  {
    for (count = 0; count < z_depth; count ++)
      *r++ = inptr[count];

    ix    += z_xstep;
    inptr += z_instep;
    xerr0 -= z_xmod;

    if (xerr0 <= 0)
    {
      xerr0 += z_xsize;
      ix    += z_xincr;
      inptr += z_inincr;
    }
  }
}

