/*
% Copyright (C) 2003-2025 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                 RRRR   EEEEE  SSSSS  IIIII  ZZZZZ  EEEEE                    %
%                 R   R  E      SS       I       ZZ  E                        %
%                 RRRR   EEE     SSS     I     ZZZ   EEE                      %
%                 R R    E         SS    I    ZZ     E                        %
%                 R  R   EEEEE  SSSSS  IIIII  ZZZZZ  EEEEE                    %
%                                                                             %
%                    GraphicsMagick Image Resize Methods                      %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/enum_strings.h"
#include "magick/log.h"
#include "magick/monitor.h"
#include "magick/omp_data_view.h"
#include "magick/pixel_cache.h"
#include "magick/resize.h"
#include "magick/utility.h"

/*
  Typedef declarations.
*/
typedef struct _ContributionInfo
{
  double
    weight;

  long
    pixel;
} ContributionInfo;

typedef struct _FilterInfo
{
  double
    (*function)(const double,const double),
    support;
} FilterInfo;
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   B e s s e l O r d e r O n e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  BesselOrderOne() computes the Bessel function of x of the first kind of
%  order 0:
%
%    Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
%
%       j1(x) = x*j1(x);
%
%    For x in (8,inf)
%
%       j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
%
%    where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
%
%       cos(x1) =  cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
%               =  1/sqrt(2) * (sin(x) - cos(x))
%       sin(x1) =  sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
%               = -1/sqrt(2) * (sin(x) + cos(x))
%
%  The format of the BesselOrderOne method is:
%
%      double BesselOrderOne(double x)
%
%  A description of each parameter follows:
%
%    o value: Method BesselOrderOne returns the Bessel function of x of the
%      first kind of orders 1.
%
%    o x: double value.
%
%
*/

static double J1(double x)
{
  double
    p,
    q;

  register long
    i;

  static const double
    Pone[] =
    {
       0.581199354001606143928050809e+21,
      -0.6672106568924916298020941484e+20,
       0.2316433580634002297931815435e+19,
      -0.3588817569910106050743641413e+17,
       0.2908795263834775409737601689e+15,
      -0.1322983480332126453125473247e+13,
       0.3413234182301700539091292655e+10,
      -0.4695753530642995859767162166e+7,
       0.270112271089232341485679099e+4
    },
    Qone[] =
    {
      0.11623987080032122878585294e+22,
      0.1185770712190320999837113348e+20,
      0.6092061398917521746105196863e+17,
      0.2081661221307607351240184229e+15,
      0.5243710262167649715406728642e+12,
      0.1013863514358673989967045588e+10,
      0.1501793594998585505921097578e+7,
      0.1606931573481487801970916749e+4,
      0.1e+1
    };

  p=Pone[8];
  q=Qone[8];
  for (i=7; i >= 0; i--)
  {
    p=p*x*x+Pone[i];
    q=q*x*x+Qone[i];
  }
  return(p/q);
}

static double P1(double x)
{
  double
    p,
    q;

  register long
    i;

  static const double
    Pone[] =
    {
      0.352246649133679798341724373e+5,
      0.62758845247161281269005675e+5,
      0.313539631109159574238669888e+5,
      0.49854832060594338434500455e+4,
      0.2111529182853962382105718e+3,
      0.12571716929145341558495e+1
    },
    Qone[] =
    {
      0.352246649133679798068390431e+5,
      0.626943469593560511888833731e+5,
      0.312404063819041039923015703e+5,
      0.4930396490181088979386097e+4,
      0.2030775189134759322293574e+3,
      0.1e+1
    };

  p=Pone[5];
  q=Qone[5];
  for (i=4; i >= 0; i--)
  {
    p=p*(8.0/x)*(8.0/x)+Pone[i];
    q=q*(8.0/x)*(8.0/x)+Qone[i];
  }
  return(p/q);
}

static double Q1(double x)
{
  double
    p,
    q;

  register long
    i;

  static const double
    Pone[] =
    {
      0.3511751914303552822533318e+3,
      0.7210391804904475039280863e+3,
      0.4259873011654442389886993e+3,
      0.831898957673850827325226e+2,
      0.45681716295512267064405e+1,
      0.3532840052740123642735e-1
    },
    Qone[] =
    {
      0.74917374171809127714519505e+4,
      0.154141773392650970499848051e+5,
      0.91522317015169922705904727e+4,
      0.18111867005523513506724158e+4,
      0.1038187585462133728776636e+3,
      0.1e+1
    };

  p=Pone[5];
  q=Qone[5];
  for (i=4; i >= 0; i--)
  {
    p=p*(8.0/x)*(8.0/x)+Pone[i];
    q=q*(8.0/x)*(8.0/x)+Qone[i];
  }
  return(p/q);
}

static double BesselOrderOne(double x)
{
  double
    p,
    q;

  if (x == 0.0)
    return(0.0);
  p=x;
  if (x < 0.0)
    x=(-x);
  if (x < 8.0)
    return(p*J1(x));
  q=sqrt(2.0/(MagickPI*x))*(P1(x)*(1.0/sqrt(2.0)*(sin(x)-cos(x)))-8.0/x*Q1(x)*
    (-1.0/sqrt(2.0)*(sin(x)+cos(x))));
  if (p < 0.0)
    q=(-q);
  return(q);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   M a g n i f y I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  MagnifyImage() is a convenience method that scales an image proportionally
%  to twice its size.
%
%  The format of the MagnifyImage method is:
%
%      Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *MagnifyImage(const Image * restrict image,ExceptionInfo *exception)
{
#define MagnifyImageText "[%s] Magnify...  "

  Image
    * restrict magnify_image;

  long
    rows,
    y;

  PixelPacket
    * restrict scanline;

  register long
    x;

  register PixelPacket
    * restrict p,
    * restrict q,
    *r;

  /*
    Initialize magnify image attributes.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  magnify_image=CloneImage(image,2*image->columns,2*image->rows,True,exception);
  if (magnify_image == (Image *) NULL)
    return((Image *) NULL);

  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Magnifying image of size %lux%lu to %lux%lu",
                        image->columns,image->rows,magnify_image->columns,magnify_image->rows);

  magnify_image->storage_class=DirectClass;
  /*
    Allocate image buffer and scanline buffer for 4 rows of the image.
  */
  scanline=MagickAllocateArray(PixelPacket *,
                               magnify_image->columns,sizeof(PixelPacket));
  if (scanline == (PixelPacket *) NULL)
    {
      DestroyImage(magnify_image);
      ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
                           UnableToMagnifyImage);
    }
  /*
    Initialize magnify image pixels.
  */
  for (y=0; y < (long) image->rows; y++)
    {
      const PixelPacket
        *pixels;

      pixels=AcquireImagePixels(image,0,y,image->columns,1,exception);
      q=SetImagePixels(magnify_image,0,y,magnify_image->columns,1);
      if ((pixels == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
        break;
      (void) memcpy(q,pixels,image->columns*sizeof(PixelPacket));
      if (!SyncImagePixels(magnify_image))
        break;
    }
  /*
    Magnify each row.
  */
  for (y=0; y < (long) image->rows; y++)
    {
      p=GetImagePixels(magnify_image,0,(long) (image->rows-y-1),
                       magnify_image->columns,1);
      if (p == (PixelPacket *) NULL)
        break;
      (void) memcpy(scanline,p,magnify_image->columns*sizeof(PixelPacket));
      q=GetImagePixels(magnify_image,0,(long) (2*(image->rows-y-1)),
                       magnify_image->columns,1);
      if (q == (PixelPacket *) NULL)
        break;
      p=scanline+image->columns-1;
      q+=2*((size_t) image->columns-1);
      *q=(*p);
      *(q+1)=(*(p));
      for (x=1; x < (long) image->columns; x++)
        {
          p--;
          q-=2;
          *q=(*p);
          (q+1)->red=(Quantum) (((double) p->red+(double) (p+1)->red)/2.0+0.5);
          (q+1)->green=(Quantum) (((double) p->green+(double) (p+1)->green)/2.0+0.5);
          (q+1)->blue=(Quantum) (((double) p->blue+(double) (p+1)->blue)/2.0+0.5);
          (q+1)->opacity=(Quantum)
            (((double) p->opacity+(double) (p+1)->opacity)/2.0+0.5);
        }
      if (!SyncImagePixels(magnify_image))
        break;
    }

  for (y=0; y < (long) image->rows; y++)
    {
      rows=(long) Min(image->rows-y,3);
      p=GetImagePixels(magnify_image,0,2*y,magnify_image->columns,rows);
      if (p == (PixelPacket *) NULL)
        break;
      q=p;
      if (rows > 1)
        q=p+magnify_image->columns;
      r=p;
      if (rows > 2)
        r=q+magnify_image->columns;
      for (x=0; x < (long) (image->columns-1); x++)
        {
          q->red=(Quantum) (((double) p->red+(double) r->red)/2.0+0.5);
          q->green=(Quantum) (((double) p->green+(double) r->green)/2.0+0.5);
          q->blue=(Quantum) (((double) p->blue+(double) r->blue)/2.0+0.5);
          q->opacity=(Quantum) (((double) p->opacity+(double) r->opacity)/2.0+0.5);
          (q+1)->red=(Quantum) (((double) p->red+(double) (p+2)->red+
                                 (double) r->red+(double) (r+2)->red)/4.0+0.5);
          (q+1)->green=(Quantum) (((double) p->green+(double) (p+2)->green+
                                   (double) r->green+(double) (r+2)->green)/4.0+0.5);
          (q+1)->blue=(Quantum) (((double) p->blue+(double) (p+2)->blue+
                                  (double) r->blue+(double) (r+2)->blue)/4.0+0.5);
          (q+1)->opacity=(Quantum) (((double) p->opacity+(double) (p+2)->opacity+
                                     (double) r->opacity+(double) (r+2)->opacity)/4.0+0.5);
          q+=2;
          p+=2;
          r+=2;
        }
      q->red=(Quantum) (((double) p->red+(double) r->red)/2.0+0.5);
      q->green=(Quantum) (((double) p->green+(double) r->green)/2.0+0.5);
      q->blue=(Quantum) (((double) p->blue+(double) r->blue)/2.0+0.5);
      q->opacity=(Quantum) (((double) p->opacity+(double) r->opacity)/2.0+0.5);
      p++;
      q++;
      r++;
      q->red=(Quantum) (((double) p->red+(double) r->red)/2.0+0.5);
      q->green=(Quantum) (((double) p->green+(double) r->green)/2.0+0.5);
      q->blue=(Quantum) (((double) p->blue+(double) r->blue)/2.0+0.5);
      q->opacity=(Quantum) (((double) p->opacity+(double) r->opacity)/2.0+0.5);
      if (!SyncImagePixels(magnify_image))
        break;
      if (QuantumTick(y,image->rows))
        if (!MagickMonitorFormatted(y,image->rows,exception,
                                    MagnifyImageText,image->filename))
          break;
    }
  p=GetImagePixels(magnify_image,0,(long) (2*image->rows-2),
                   magnify_image->columns,1);
  if (p != (PixelPacket *) NULL)
    (void) memcpy(scanline,p,magnify_image->columns*sizeof(PixelPacket));
  q=GetImagePixels(magnify_image,0,(long) (2*image->rows-1),
                   magnify_image->columns,1);
  if (q != (PixelPacket *) NULL)
    (void) memcpy(q,scanline,magnify_image->columns*sizeof(PixelPacket));
  (void) SyncImagePixels(magnify_image);
  MagickFreeMemory(scanline);
  magnify_image->is_grayscale=image->is_grayscale;
  return(magnify_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   M i n i f y I m a g e                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  MinifyImage() is a convenience method that scales an image proportionally
%  to half its size.
%
%  The format of the MinifyImage method is:
%
%      Image *MinifyImage(const Image *image,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *MinifyImage(const Image * restrict image,ExceptionInfo *exception)
{
#define Minify(weight)                          \
  total.red+=(weight)*(r->red);                 \
  total.green+=(weight)*(r->green);             \
  total.blue+=(weight)*(r->blue);               \
  total.opacity+=(weight)*(r->opacity);         \
  r++;
#define MinifyImageText "[%s] Minify..."

  Image
    *restrict minify_image;

  long
    y;

  /*
    Initialize minified image.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);

  minify_image=CloneImage(image,Max(image->columns/2,1),Max(image->rows/2,1),
                          True,exception);
  if (minify_image == (Image *) NULL)
    return((Image *) NULL);

  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Minifying image of size %lux%lu to %lux%lu",
                        image->columns,image->rows,
                        minify_image->columns,minify_image->rows);

  minify_image->storage_class=DirectClass;
  /*
    Reduce each row.
  */
  {
    unsigned long
      row_count=0;

    MagickBool
      monitor_active;

    DoublePixelPacket
      zero;

    MagickPassFail
      status=MagickPass;

    (void) memset(&zero,0,sizeof(DoublePixelPacket));

    monitor_active=MagickMonitorActive();

#if defined(HAVE_OPENMP)
#  if defined(TUNE_OPENMP)
#    pragma omp parallel for schedule(runtime) shared(row_count, status)
#  else
#    if defined(USE_STATIC_SCHEDULING_ONLY)
#      pragma omp parallel for schedule(static) shared(row_count, status)
#    else
#      pragma omp parallel for schedule(guided) shared(row_count, status)
#    endif
#  endif
#endif
    for (y=0; y < (long) minify_image->rows; y++)
      {
        DoublePixelPacket
          total;

        register const PixelPacket
          *p,
          *r;

        register long
          x;

        register PixelPacket
          *q;

        MagickBool
          thread_status;

        thread_status=status;
        if (thread_status == MagickFail)
          continue;

        p=AcquireImagePixels(image,-2,2*(y-1),image->columns+4,4,exception);
        q=SetImagePixelsEx(minify_image,0,y,minify_image->columns,1,exception);
        if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
          thread_status=MagickFail;
        if (thread_status != MagickFail)
          {
            for (x=0; x < (long) minify_image->columns; x++)
              {
                /*
                  Compute weighted average of target pixel color components.
                */
                total=zero;
                r=p;
                Minify(3.0); Minify(7.0);  Minify(7.0);  Minify(3.0);
                r=p+((size_t) image->columns+4);
                Minify(7.0); Minify(15.0); Minify(15.0); Minify(7.0);
                r=p+2*((size_t) image->columns+4);
                Minify(7.0); Minify(15.0); Minify(15.0); Minify(7.0);
                r=p+3*((size_t) image->columns+4);
                Minify(3.0); Minify(7.0);  Minify(7.0);  Minify(3.0);
                q->red=(Quantum) (total.red/128.0+0.5);
                q->green=(Quantum) (total.green/128.0+0.5);
                q->blue=(Quantum) (total.blue/128.0+0.5);
                q->opacity=(Quantum) (total.opacity/128.0+0.5);
                p+=2;
                q++;
              }
            if (!SyncImagePixelsEx(minify_image,exception))
              thread_status=MagickFail;
          }

        if (monitor_active)
          {
            unsigned long
              thread_row_count;

#if defined(HAVE_OPENMP)
#  pragma omp atomic
#endif
            row_count++;
#if defined(HAVE_OPENMP)
#  pragma omp flush (row_count)
#endif
            thread_row_count=row_count;
            if (QuantumTick(thread_row_count,image->rows))
              if (!MagickMonitorFormatted(thread_row_count,image->rows,exception,
                                          MinifyImageText,image->filename))
                thread_status=MagickFail;
          }

        if (thread_status == MagickFail)
          {
            status=MagickFail;
#if defined(HAVE_OPENMP)
#  pragma omp flush (status)
#endif
          }
      }
  }
  minify_image->is_grayscale=image->is_grayscale;
  return(minify_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e s i z e I m a g e                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ResizeImage() scales an image to the desired dimensions with one of these
%  filters:
%
%    Bessel   Blackman   Box
%    Catrom   Cubic      Gaussian
%    Hanning  Hermite    Lanczos
%    Mitchell Point      Quandratic
%    Sinc     Triangle
%
%  Most of the filters are FIR (finite impulse response), however, Bessel,
%  Gaussian, and Sinc are IIR (infinite impulse response).  Bessel and Sinc
%  are windowed (brought down to zero) with the Blackman filter.
%
%  ResizeImage() was inspired by Paul Heckbert's zoom program.
%
%  The format of the ResizeImage method is:
%
%      Image *ResizeImage(Image *image,const unsigned long columns,
%        const unsigned long rows,const FilterTypes filter,const double blur,
%        ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o columns: The number of columns in the scaled image.
%
%    o rows: The number of rows in the scaled image.
%
%    o filter: Image filter to use.
%
%    o blur: The blur factor where > 1 is blurry, < 1 is sharp.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/

static double Bessel(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x == 0.0)
    return(MagickPI/4.0);
  return(BesselOrderOne(MagickPI*x)/(2.0*x));
}

static double Sinc(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x == 0.0)
    return(1.0);
  return(sin(MagickPI*x)/(MagickPI*x));
}

static double Blackman(const double x,const double support)
{
  ARG_NOT_USED(support);
  return(0.42+0.5*cos(MagickPI*x)+0.08*cos(2*MagickPI*x));
}

static double BlackmanBessel(const double x,const double support)
{
  return(Blackman(x/support,support)*Bessel(x,support));
}

static double BlackmanSinc(const double x,const double support)
{
  return(Blackman(x/support,support)*Sinc(x,support));
}

static double Box(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -0.5)
    return(0.0);
  if (x < 0.5)
    return(1.0);
  return(0.0);
}

static double Catrom(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -2.0)
    return(0.0);
  if (x < -1.0)
    return(0.5*(4.0+x*(8.0+x*(5.0+x))));
  if (x < 0.0)
    return(0.5*(2.0+x*x*(-5.0-3.0*x)));
  if (x < 1.0)
    return(0.5*(2.0+x*x*(-5.0+3.0*x)));
  if (x < 2.0)
    return(0.5*(4.0+x*(-8.0+x*(5.0-x))));
  return(0.0);
}

static double Cubic(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -2.0)
    return(0.0);
  if (x < -1.0)
    return((2.0+x)*(2.0+x)*(2.0+x)/6.0);
  if (x < 0.0)
    return((4.0+x*x*(-6.0-3.0*x))/6.0);
  if (x < 1.0)
    return((4.0+x*x*(-6.0+3.0*x))/6.0);
  if (x < 2.0)
    return((2.0-x)*(2.0-x)*(2.0-x)/6.0);
  return(0.0);
}

static double Gaussian(const double x,const double support)
{
  ARG_NOT_USED(support);
  return(exp(-2.0*x*x)*sqrt(2.0/MagickPI));
}

static double Hanning(const double x,const double support)
{
  ARG_NOT_USED(support);
  return(0.5+0.5*cos(MagickPI*x));
}

static double Hamming(const double x,const double support)
{
  ARG_NOT_USED(support);
  return(0.54+0.46*cos(MagickPI*x));
}

static double Hermite(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -1.0)
    return(0.0);
  if (x < 0.0)
    return((2.0*(-x)-3.0)*(-x)*(-x)+1.0);
  if (x < 1.0)
    return((2.0*x-3.0)*x*x+1.0);
  return(0.0);
}

static double Lanczos(const double x,const double support)
{
  if (x < -3.0)
    return(0.0);
  if (x < 0.0)
    return(Sinc(-x,support)*Sinc(-x/3.0,support));
  if (x < 3.0)
    return(Sinc(x,support)*Sinc(x/3.0,support));
  return(0.0);
}

static double Mitchell(const double x,const double support)
{
#define B   (1.0/3.0)
#define C   (1.0/3.0)
#define P0  ((  6.0- 2.0*B       )/6.0)
#define P2  ((-18.0+12.0*B+ 6.0*C)/6.0)
#define P3  (( 12.0- 9.0*B- 6.0*C)/6.0)
#define Q0  ((       8.0*B+24.0*C)/6.0)
#define Q1  ((     -12.0*B-48.0*C)/6.0)
#define Q2  ((       6.0*B+30.0*C)/6.0)
#define Q3  ((     - 1.0*B- 6.0*C)/6.0)

  ARG_NOT_USED(support);
  if (x < -2.0)
    return(0.0);
  if (x < -1.0)
    return(Q0-x*(Q1-x*(Q2-x*Q3)));
  if (x < 0.0)
    return(P0+x*x*(P2-x*P3));
  if (x < 1.0)
    return(P0+x*x*(P2+x*P3));
  if (x < 2.0)
    return(Q0+x*(Q1+x*(Q2+x*Q3)));
  return(0.0);
}

static double Quadratic(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -1.5)
    return(0.0);
  if (x < -0.5)
    return(0.5*(x+1.5)*(x+1.5));
  if (x < 0.5)
    return(0.75-x*x);
  if (x < 1.5)
    return(0.5*(x-1.5)*(x-1.5));
  return(0.0);
}

static double Triangle(const double x,const double support)
{
  ARG_NOT_USED(support);
  if (x < -1.0)
    return(0.0);
  if (x < 0.0)
    return(1.0+x);
  if (x < 1.0)
    return(1.0-x);
  return(0.0);
}

static MagickPassFail
HorizontalFilter(const Image * restrict source,Image * restrict destination,
                 const double x_factor,const FilterInfo * restrict filter_info,
                 const double blur,ThreadViewDataSet *view_data_set,
                 const size_t span,unsigned long * restrict quantum_p,
                 ExceptionInfo *exception)
{
#define ResizeImageText "[%s] Resize..."

  double
    scale,
    support;

  DoublePixelPacket
    zero;

  long
    x;

  unsigned long
    quantum;

  MagickBool
    monitor_active;

  const MagickBool
    matte = ((destination->matte) || (destination->colorspace == CMYKColorspace));

  MagickPassFail
    status=MagickPass;

  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "Horizontal Filter: %lux%lu => %lux%lu "
                          "(x_factor %g, blur %g, span %" MAGICK_SIZE_T_F "u) ...",
                          source->columns, source->rows,
                          destination->columns, destination->rows,
                          x_factor, blur, (MAGICK_SIZE_T) span);

  quantum = *quantum_p;

  scale=blur*Max(1.0/x_factor,1.0);
  support=scale*filter_info->support;
  destination->storage_class=source->storage_class;
  if (support > 0.5)
    destination->storage_class=DirectClass;
  else
    {
      /*
        Reduce to point sampling.
      */
      support=0.5+MagickEpsilon;
      scale=1.0;
    }
  scale=1.0/scale;
  (void) memset(&zero,0,sizeof(DoublePixelPacket));

  monitor_active=MagickMonitorActive();

#if defined(HAVE_OPENMP)
#  if defined(TUNE_OPENMP)
#    pragma omp parallel for schedule(runtime) shared(status, quantum)
#  else
#    if defined(USE_STATIC_SCHEDULING_ONLY)
#      pragma omp parallel for schedule(static) shared(status, quantum)
#    else
#      pragma omp parallel for schedule(guided) shared(status, quantum)
#    endif
#  endif
#endif
  for (x=0; x < (long) destination->columns; x++)
    {
      double
        center,
        density;

      ContributionInfo
        * restrict contribution;

      register const PixelPacket
        * restrict p;

      register PixelPacket
        * restrict q = (PixelPacket *) NULL;

      const IndexPacket
        * restrict source_indexes;

      IndexPacket
        * restrict indexes;

      long
        n,
        start,
        stop,
        y;

      MagickBool
        thread_status;

      thread_status=status;
      if (thread_status == MagickFail)
        continue;

      contribution=AccessThreadViewData(view_data_set);
      /* fprintf(stderr,"Thread %d contribution %p\n",omp_get_thread_num(),contribution); */
      center=(double) (x+0.5)/x_factor;
      start=(long) Max(center-support+0.5,0);
      stop=(long) Min(center+support+0.5,source->columns);
      density=0.0;
      for (n=0; n < (stop-start); n++)
        {
          contribution[n].pixel=start+n;
          contribution[n].weight=
            filter_info->function(scale*((double) start+n-center+0.5),filter_info->support);
          density+=contribution[n].weight;
        }
      if ((density != 0.0) && (density != 1.0))
        {
          /*
            Normalize.
          */
          long
            i;

          density=1.0/density;
          for (i=0; i < n; i++)
            contribution[i].weight*=density;
        }

      p=AcquireImagePixels(source,contribution[0].pixel,0,
                           contribution[n-1].pixel-contribution[0].pixel+1,
                           source->rows,exception);
      if (p == (const PixelPacket *) NULL)
        thread_status=MagickFail;

      if (thread_status != MagickFail)
        q=SetImagePixelsEx(destination,x,0,1,destination->rows,exception);
      if (q == (PixelPacket *) NULL)
        thread_status=MagickFail;

      if (thread_status != MagickFail)
        {
          source_indexes=AccessImmutableIndexes(source);
          indexes=AccessMutableIndexes(destination);
          if (matte)
            {
              for (y=0; y < (long) destination->rows; y++)
                {
                  double
                    transparency_coeff,
                    normalize,
                    weight;

                  DoublePixelPacket
                    pixel;

                  long
                    j;

                  register long
                    i;

                  pixel=zero;
                  normalize=0.0;
                  for (i=0; i < n; i++)
                    {
                      j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
                        (contribution[i].pixel-contribution[0].pixel);
                      weight=contribution[i].weight;
                      transparency_coeff = weight * (1 - ((double) p[j].opacity/TransparentOpacity));
                      pixel.red+=transparency_coeff*p[j].red;
                      pixel.green+=transparency_coeff*p[j].green;
                      pixel.blue+=transparency_coeff*p[j].blue;
                      pixel.opacity+=weight*p[j].opacity;
                      normalize += transparency_coeff;
                    }
                  normalize = 1.0 / (AbsoluteValue(normalize) <= MagickEpsilon ? 1.0 : normalize);
                  pixel.red *= normalize;
                  pixel.green *= normalize;
                  pixel.blue *= normalize;
                  q[y].red=RoundDoubleToQuantum(pixel.red);
                  q[y].green=RoundDoubleToQuantum(pixel.green);
                  q[y].blue=RoundDoubleToQuantum(pixel.blue);
                  q[y].opacity=RoundDoubleToQuantum(pixel.opacity);

                  if ((indexes != (IndexPacket *) NULL) &&
                      (source_indexes != (IndexPacket *) NULL))
                    {
                      i=Min(Max((long) (center+0.5),start),stop-1);
                      j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
                        (contribution[i-start].pixel-contribution[0].pixel);
                      indexes[y]=source_indexes[j];
                    }
                }
            }
          else
            {
              for (y=0; y < (long) destination->rows; y++)
                {
                  double
                    weight;

                  DoublePixelPacket
                    pixel=zero;

                  long
                    j;

                  register long
                    i;

                  for (i=0; i < n; i++)
                    {
                      j=(long) (y*(contribution[n-1].pixel-contribution[0].pixel+1)+
                                (contribution[i].pixel-contribution[0].pixel));
                      weight=contribution[i].weight;
                      pixel.red+=weight*p[j].red;
                      pixel.green+=weight*p[j].green;
                      pixel.blue+=weight*p[j].blue;
                    }
                  q[y].red=RoundDoubleToQuantum(pixel.red);
                  q[y].green=RoundDoubleToQuantum(pixel.green);
                  q[y].blue=RoundDoubleToQuantum(pixel.blue);
                  q[y].opacity=OpaqueOpacity;

                  if ((indexes != (IndexPacket *) NULL) &&
                      (source_indexes != (IndexPacket *) NULL))
                    {
                      i=Min(Max((long) (center+0.5),start),stop-1);
                      j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
                        (contribution[i-start].pixel-contribution[0].pixel);
                      indexes[y]=source_indexes[j];
                    }
                }
            }
          if (!SyncImagePixelsEx(destination,exception))
            thread_status=MagickFail;
        }

      if (monitor_active)
        {
          unsigned long
            thread_quantum;

#if defined(HAVE_OPENMP)
#  pragma omp flush (quantum)
#endif
          thread_quantum=quantum;
          if (QuantumTick(thread_quantum,span))
            if (!MagickMonitorFormatted(thread_quantum,span,exception,
                                        ResizeImageText,source->filename))
              thread_status=MagickFail;

#if defined(HAVE_OPENMP)
#  pragma omp atomic
#endif
          quantum++;
        }

      if (thread_status == MagickFail)
        {
          status=MagickFail;
#if defined(HAVE_OPENMP)
#  pragma omp flush (status)
#endif
        }
    }

  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "%s exit Horizontal Filter",
                          (status == MagickFail ? "Error" : "Normal"));

  *quantum_p = quantum;

  return (status);
}

static MagickPassFail
VerticalFilter(const Image * restrict source,Image * restrict destination,
               const double y_factor,const FilterInfo * restrict filter_info,
               const double blur,ThreadViewDataSet *view_data_set,
               const size_t span,unsigned long * restrict quantum_p,
               ExceptionInfo *exception)
{
  double
    scale,
    support;

  DoublePixelPacket
    zero;

  long
    y;

  unsigned long
    quantum;

  MagickBool
    monitor_active;

  const MagickBool
    matte = ((destination->matte) || (destination->colorspace == CMYKColorspace));

  MagickPassFail
    status=MagickPass;

  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "Vertical Filter: %lux%lu => %lux%lu "
                          "(y_factor %g, blur %g, span %" MAGICK_SIZE_T_F "u) ...",
                          source->columns, source->rows,
                          destination->columns, destination->rows,
                          y_factor, blur, (MAGICK_SIZE_T) span);

  quantum = *quantum_p;

  /*
    Apply filter to resize vertically from source to destination.
  */
  scale=blur*Max(1.0/y_factor,1.0);
  support=scale*filter_info->support;
  destination->storage_class=source->storage_class;
  if (support > 0.5)
    destination->storage_class=DirectClass;
  else
    {
      /*
        Reduce to point sampling.
      */
      support=0.5+MagickEpsilon;
      scale=1.0;
    }
  scale=1.0/scale;
  (void) memset(&zero,0,sizeof(DoublePixelPacket));

  monitor_active=MagickMonitorActive();

#if defined(HAVE_OPENMP)
#  if defined(TUNE_OPENMP)
#    pragma omp parallel for schedule(runtime) shared(status, quantum)
#  else
#    if defined(USE_STATIC_SCHEDULING_ONLY)
#      pragma omp parallel for schedule(static) shared(status, quantum)
#    else
#      pragma omp parallel for schedule(guided) shared(status, quantum)
#    endif
#  endif
#endif
  for (y=0; y < (long) destination->rows; y++)
    {
      double
        center,
        density;

      ContributionInfo
        * restrict contribution;

      register const PixelPacket
        * restrict p;

      register PixelPacket
        * restrict q = (PixelPacket *) NULL;

      const IndexPacket
        * restrict source_indexes;

      IndexPacket
        * restrict indexes;

      long
        n,
        start,
        stop,
        x;

      MagickBool
        thread_status;

      thread_status=status;
      if (thread_status == MagickFail)
        continue;

      contribution=AccessThreadViewData(view_data_set);
      center=(double) (y+0.5)/y_factor;
      start=(long) Max(center-support+0.5,0);
      stop=(long) Min(center+support+0.5,source->rows);
      density=0.0;
      for (n=0; n < (stop-start); n++)
        {
          contribution[n].pixel=start+n;
          contribution[n].weight=
            filter_info->function(scale*((double) start+n-center+0.5),filter_info->support);
          density+=contribution[n].weight;
        }
      if ((density != 0.0) && (density != 1.0))
        {
          /*
            Normalize.
          */
          long
            i;

          density=1.0/density;
          for (i=0; i < n; i++)
            contribution[i].weight*=density;
        }

      p=AcquireImagePixels(source,0,contribution[0].pixel,source->columns,
                           contribution[n-1].pixel-contribution[0].pixel+1,
                           exception);
      if (p == (const PixelPacket *) NULL)
        thread_status=MagickFail;

      if (thread_status != MagickFail)
        q=SetImagePixelsEx(destination,0,y,destination->columns,1,exception);
      if (q == (PixelPacket *) NULL)
        thread_status=MagickFail;

      if (thread_status != MagickFail)
        {
          source_indexes=AccessImmutableIndexes(source);
          indexes=AccessMutableIndexes(destination);
          if (matte)
            {
              for (x=0; x < (long) destination->columns; x++)
                {
                  double
                    transparency_coeff,
                    normalize,
                    weight;

                  DoublePixelPacket
                    pixel=zero;

                  long
                    j;

                  register long
                    i;

                  normalize=0.0;
                  for (i=0; i < n; i++)
                    {
                      j=(long) ((contribution[i].pixel-contribution[0].pixel)*
                                source->columns+x);
                      weight=contribution[i].weight;
                      transparency_coeff = weight * (1 - ((double) p[j].opacity/TransparentOpacity));
                      pixel.red+=transparency_coeff*p[j].red;
                      pixel.green+=transparency_coeff*p[j].green;
                      pixel.blue+=transparency_coeff*p[j].blue;
                      pixel.opacity+=weight*p[j].opacity;
                      normalize += transparency_coeff;
                    }

                  normalize = 1.0 / (AbsoluteValue(normalize) <= MagickEpsilon ? 1.0 : normalize);
                  pixel.red *= normalize;
                  pixel.green *= normalize;
                  pixel.blue *= normalize;
                  q[x].red=RoundDoubleToQuantum(pixel.red);
                  q[x].green=RoundDoubleToQuantum(pixel.green);
                  q[x].blue=RoundDoubleToQuantum(pixel.blue);
                  q[x].opacity=RoundDoubleToQuantum(pixel.opacity);

                  if ((indexes != (IndexPacket *) NULL) &&
                      (source_indexes != (IndexPacket *) NULL))
                    {
                      i=Min(Max((long) (center+0.5),start),stop-1);
                      j=(long) ((contribution[i-start].pixel-contribution[0].pixel)*
                                source->columns+x);
                      indexes[x]=source_indexes[j];
                    }
                }
            }
          else
            {
              for (x=0; x < (long) destination->columns; x++)
                {
                  double
                    weight;

                  DoublePixelPacket
                    pixel;

                  long
                    j;

                  register long
                    i;

                  pixel=zero;
                  for (i=0; i < n; i++)
                    {
                      j=(long) ((contribution[i].pixel-contribution[0].pixel)*
                                source->columns+x);
                      weight=contribution[i].weight;
                      pixel.red+=weight*p[j].red;
                      pixel.green+=weight*p[j].green;
                      pixel.blue+=weight*p[j].blue;
                    }
                  q[x].red=RoundDoubleToQuantum(pixel.red);
                  q[x].green=RoundDoubleToQuantum(pixel.green);
                  q[x].blue=RoundDoubleToQuantum(pixel.blue);
                  q[x].opacity=OpaqueOpacity;

                  if ((indexes != (IndexPacket *) NULL) &&
                      (source_indexes != (IndexPacket *) NULL))
                    {
                      i=Min(Max((long) (center+0.5),start),stop-1);
                      j=(long) ((contribution[i-start].pixel-contribution[0].pixel)*
                                source->columns+x);
                      indexes[x]=source_indexes[j];
                    }
                }
            }
          if (!SyncImagePixelsEx(destination,exception))
            thread_status=MagickFail;
        }

      if (monitor_active)
        {
          unsigned long
            thread_quantum;


#if defined(HAVE_OPENMP)
#  pragma omp flush (quantum)
#endif
          thread_quantum=quantum;
          if (QuantumTick(thread_quantum,span))
            if (!MagickMonitorFormatted(thread_quantum,span,exception,
                                        ResizeImageText,source->filename))
              thread_status=MagickFail;

#if defined(HAVE_OPENMP)
#  pragma omp atomic
#endif
          quantum++;
        }

      if (thread_status == MagickFail)
        {
          status=MagickFail;
#if defined(HAVE_OPENMP)
#  pragma omp flush (status)
#endif
        }
    }

  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "%s exit Vertical Filter",
                          (status == MagickFail ? "Error" : "Normal"));

  *quantum_p = quantum;

  return (status);
}

MagickExport Image *ResizeImage(const Image *image,const unsigned long columns,
                                const unsigned long rows,const FilterTypes filter,
                                const double blur,
                                ExceptionInfo *exception)
{
  ThreadViewDataSet
    *view_data_set;

  double
    support,
    x_factor,
    x_support,
    y_factor,
    y_support;

  Image
    *source_image,
    *resize_image;

  register long
    i;

  static const FilterInfo
    filters[SincFilter+1] =
    {
      { Box, 0.0 },
      { Box, 0.0 },
      { Box, 0.5 },
      { Triangle, 1.0 },
      { Hermite, 1.0 },
      { Hanning, 1.0 },
      { Hamming, 1.0 },
      { Blackman, 1.0 },
      { Gaussian, 1.25 },
      { Quadratic, 1.5 },
      { Cubic, 2.0 },
      { Catrom, 2.0 },
      { Mitchell, 2.0 },
      { Lanczos, 3.0 },
      { BlackmanBessel, 3.2383 },
      { BlackmanSinc, 4.0 }
    };

  size_t
    span;

  MagickPassFail
    status;

  unsigned long
    quantum;

  MagickBool
    order;

  /*
    Initialize resize image attributes.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  assert(((int) filter >= 0) && ((int) filter <= SincFilter));

  if ((image->columns == 0UL) || (image->rows == 0UL) ||
      (columns == 0UL) || (rows == 0UL))
    ThrowImageException(ImageError,UnableToResizeImage,
                        MagickMsg(OptionError,NonzeroWidthAndHeightRequired));

  if ((columns == image->columns) && (rows == image->rows) && (blur == 1.0))
    return (CloneImage(image,0,0,True,exception));

  resize_image=CloneImage(image,columns,rows,True,exception);
  if (resize_image == (Image *) NULL)
    return ((Image *) NULL);

  order=(((double) columns*((size_t) image->rows+rows)) >
         ((double) rows*((size_t) image->columns+columns)));
  if (order)
    source_image=CloneImage(resize_image,columns,image->rows,True,exception);
  else
    source_image=CloneImage(resize_image,image->columns,rows,True,exception);
  if (source_image == (Image *) NULL)
    {
      DestroyImage(resize_image);
      return ((Image *) NULL);
    }

  /*
    Allocate filter contribution info.
  */
  x_factor=(double) resize_image->columns/image->columns;
  y_factor=(double) resize_image->rows/image->rows;
  i=(long) DefaultResizeFilter;
  if (filter != UndefinedFilter)
    i=(long) filter;
  else
    if ((image->storage_class == PseudoClass) || image->matte ||
        ((x_factor*y_factor) > 1.0))
      i=(long) MitchellFilter;

  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "Resizing image of size %lux%lu to %lux%lu using %s filter",
                          image->columns,image->rows,columns,rows,
                          ResizeFilterToString((FilterTypes)i));

  x_support=blur*Max(1.0/x_factor,1.0)*filters[i].support;
  y_support=blur*Max(1.0/y_factor,1.0)*filters[i].support;
  support=Max(x_support,y_support);
  if (support < filters[i].support)
    support=filters[i].support;
  /*
    Allocate view data set.
  */
  view_data_set=AllocateThreadViewDataArray(image,exception,
                                            (size_t) (2.0*Max(support,0.5)+3),
                                            sizeof(ContributionInfo));
  if (view_data_set == (ThreadViewDataSet *) NULL)
    {
      DestroyImage(resize_image);
      DestroyImage(source_image);
      ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
                           UnableToResizeImage);
    }
  /*
    Resize image.
  */
  status=MagickPass;
  quantum=0;
  if (IsEventLogged(TransformEvent))
    (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                          "Resize filter order: %s",
                          order ? "Horizontal/Vertical" : "Vertical/Horizontal");
  if (order)
    {
      span=(size_t) source_image->columns+resize_image->rows;
      status=HorizontalFilter(image,source_image,x_factor,&filters[i],blur,
                              view_data_set,span,&quantum,exception);
      if (status != MagickFail)
        status=VerticalFilter(source_image,resize_image,y_factor,&filters[i],
                              blur,view_data_set,span,&quantum,exception);
    }
  else
    {
      span=(size_t) resize_image->columns+source_image->rows;
      status=VerticalFilter(image,source_image,y_factor,&filters[i],blur,
                            view_data_set,span,&quantum,exception);
      if (status != MagickFail)
        status=HorizontalFilter(source_image,resize_image,x_factor,&filters[i],
                                blur,view_data_set,span,&quantum,exception);
    }
  /*
    Free allocated memory.
  */
  DestroyThreadViewDataSet(view_data_set);
  DestroyImage(source_image);
  if (status == MagickFail)
    {
      DestroyImage(resize_image);
      return((Image *) NULL);
    }
  resize_image->is_grayscale=image->is_grayscale;
  return(resize_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   S a m p l e I m a g e                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  SampleImage() scales an image to the desired dimensions with pixel
%  sampling.  Unlike other scaling methods, this method does not introduce
%  any additional color into the scaled image. SampleImage() is extremely
%  fast and may be used where speed is most important.
%
%  The format of the SampleImage method is:
%
%      Image *SampleImage(const Image *image,const unsigned long columns,
%        const unsigned long rows,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o columns: The number of columns in the sampled image.
%
%    o rows: The number of rows in the sampled image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *
SampleImage(const Image *image,const unsigned long columns,
            const unsigned long rows,ExceptionInfo *exception)
{
  double
    *x_offset,
    *y_offset;

  Image
    *sample_image;

  long
    j,
    y;

  PixelPacket
    *pixels;

  /*
    Initialize sampled image attributes.
  */
  assert(image != (const Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  if ((columns == 0) || (rows == 0))
    ThrowImageException(ImageError,UnableToResizeImage,
                        MagickMsg(CorruptImageError,
                                  NegativeOrZeroImageSize));
  if ((columns == image->columns) && (rows == image->rows))
    return(CloneImage(image,0,0,True,exception));
  sample_image=CloneImage(image,columns,rows,True,exception);
  if (sample_image == (Image *) NULL)
    return((Image *) NULL);

  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Sampling image of size %lux%lu to %lux%lu",
                        image->columns,image->rows,sample_image->columns,
                        sample_image->rows);
  /*
    Allocate scan line buffer and column offset buffers.
  */
  pixels=MagickAllocateArray(PixelPacket *,image->columns,sizeof(PixelPacket));
  x_offset=MagickAllocateArray(double *,sample_image->columns,sizeof(double));
  y_offset=MagickAllocateArray(double *,sample_image->rows,sizeof(double));
  if ((pixels == (PixelPacket *) NULL) ||
      (x_offset == (double *) NULL) ||
      (y_offset == (double *) NULL))
    {
      MagickFreeMemory(y_offset);
      MagickFreeMemory(x_offset);
      MagickFreeMemory(pixels);
      DestroyImage(sample_image);
      ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
                           UnableToSampleImage);
    }
  /*
    Initialize pixel offsets.
  */
  {
    long
      x;

    for (x=0; x < (long) sample_image->columns; x++)
      x_offset[x]=(double) x*image->columns/(double) sample_image->columns;
    for (y=0; y < (long) sample_image->rows; y++)
      y_offset[y]=(double) y*image->rows/(double) sample_image->rows;
  }
  /*
    Sample each row.
    This algorithm will not benefit from OpenMP.
  */
  j=(-1);
  for (y=0; y < (long) sample_image->rows; y++)
    {
      register const PixelPacket
        *p;

      register PixelPacket
        *q;

      register const IndexPacket
        *indexes;

      register IndexPacket
        *sample_indexes;

      register long
        x;

      q=SetImagePixels(sample_image,0,y,sample_image->columns,1);
      if (q == (PixelPacket *) NULL)
        break;
      if (j != (long) y_offset[y])
        {
          /*
            Read a scan line.
          */
          j=(long) y_offset[y];
          p=AcquireImagePixels(image,0,j,image->columns,1,exception);
          if (p == (const PixelPacket *) NULL)
            break;
          (void) memcpy(pixels,p,image->columns*sizeof(PixelPacket));
        }
      /*
        Sample each column.
      */
      for (x=0; x < (long) sample_image->columns; x++)
        *q++=pixels[(long) x_offset[x]];
      indexes=AccessImmutableIndexes(image);
      sample_indexes=AccessMutableIndexes(sample_image);
      if ((indexes != (IndexPacket *) NULL) &&
          (sample_indexes != (IndexPacket *) NULL))
        for (x=0; x < (long) sample_image->columns; x++)
          sample_indexes[x]=indexes[(long) x_offset[x]];
      if (!SyncImagePixels(sample_image))
        break;
      if (QuantumTick(y,sample_image->rows))
        if (!MagickMonitorFormatted(y,sample_image->rows,exception,
                                    "[%s] Sample (%lux%lu --> %lux%lu) image...",
                                    image->filename,image->columns,image->rows,
                                    sample_image->columns, sample_image->rows))
          break;
    }
  MagickFreeMemory(y_offset);
  MagickFreeMemory(x_offset);
  MagickFreeMemory(pixels);
  /*
    Sampling does not change the image properties.
  */
  sample_image->is_monochrome=image->is_monochrome;
  sample_image->is_grayscale=image->is_grayscale;
  return(sample_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   S c a l e I m a g e                                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ScaleImage() changes the size of an image to the specified dimensions.
%  This method is reasonably fast but it is not currently multi-threaded
%  and does not support image filters. The quality of the resized image
%  is sufficient for most purposes.
%
%  The format of the ScaleImage method is:
%
%      Image *ScaleImage(const Image *image,const unsigned long columns,
%        const unsigned long rows,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o columns: The number of columns in the scaled image.
%
%    o rows: The number of rows in the scaled image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *ScaleImage(const Image *image,const unsigned long columns,
                               const unsigned long rows,ExceptionInfo *exception)
{
#define ScaleImageText "[%s] Scale..."

  double
    x_scale,
    x_span,
    y_scale,
    y_span,
    factor,
    x_volume,
    *y_volumes = (double *) NULL;

  DoublePixelPacket
    pixel,
    *scale_scanline = (DoublePixelPacket *) NULL,
    *scanline = (DoublePixelPacket *) NULL,
    *x_vector = (DoublePixelPacket *) NULL,
    *y_vector = (DoublePixelPacket *) NULL,
    zero;

  Image
    *scale_image;

  long
    number_rows,
    y;

  register const PixelPacket
    *p;

  register long
    i,
    x;

  register PixelPacket
    *q;

  register DoublePixelPacket
    *s,
    *t;

  unsigned int
    next_column,
    next_row;

  /*
    Initialize scaled image attributes.
  */
  assert(image != (const Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  if ((columns == 0) || (rows == 0) ||
      (image->columns == 0) || (image->rows == 0))
    {
      ThrowImageException(ImageError,UnableToResizeImage,
                          MagickMsg(OptionError,NonzeroWidthAndHeightRequired));
      return((Image *) NULL);
    }
  if ((columns == image->columns) && (rows == image->rows))
    scale_image=CloneImage(image,0,0,True,exception);
  else
    scale_image=CloneImage(image,columns,rows,True,exception);

  if (scale_image == (Image *) NULL)
    return((Image *) NULL);

  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
                        "Scaling image of size %lux%lu to %lux%lu",
                        image->columns,image->rows,scale_image->columns,
                        scale_image->rows);

  if ((columns == image->columns) && (rows == image->rows))
    return scale_image;

  scale_image->storage_class=DirectClass;
  /*
    Allocate memory.
  */
  x_vector=MagickAllocateClearedArray(DoublePixelPacket *,
                                      image->columns,sizeof(DoublePixelPacket));
  scanline=x_vector;
  if (image->rows != scale_image->rows)
    scanline=MagickAllocateArray(DoublePixelPacket *,
                                 image->columns,sizeof(DoublePixelPacket));
  scale_scanline=MagickAllocateArray(DoublePixelPacket *,
                                     scale_image->columns,sizeof(DoublePixelPacket));
  y_vector=MagickAllocateClearedArray(DoublePixelPacket *,
                                      image->columns,sizeof(DoublePixelPacket));
  y_volumes=MagickAllocateClearedArray(double *, image->columns,sizeof(double));
  if ((scanline == (DoublePixelPacket *) NULL) ||
      (scale_scanline == (DoublePixelPacket *) NULL) ||
      (x_vector == (DoublePixelPacket *) NULL) ||
      (y_vector == (DoublePixelPacket *) NULL) ||
      (y_volumes == (double *) NULL))
    {
      if (scanline == x_vector)
        scanline=(DoublePixelPacket *) NULL;
      MagickFreeMemory(scanline);
      MagickFreeMemory(scale_scanline);
      MagickFreeMemory(x_vector);
      MagickFreeMemory(y_vector);
      MagickFreeMemory(y_volumes);
      DestroyImage(scale_image);
      ThrowImageException3(ResourceLimitError,MemoryAllocationFailed,
                           UnableToScaleImage);
    }
  /*
    Scale image.
  */
  number_rows=0;
  next_row=True;
  y_span=1.0;
  y_scale=(double) scale_image->rows/image->rows;
  (void) memset(&zero,0,(size_t) sizeof(DoublePixelPacket));
  i=0;
  for (y=0; y < (long) scale_image->rows; y++)
    {
      q=SetImagePixels(scale_image,0,y,scale_image->columns,1);
      if (q == (PixelPacket *) NULL)
        break;
      if (scale_image->rows == image->rows)
        {
          /*
            Read a new scanline.
          */
          p=AcquireImagePixels(image,0,i++,image->columns,1,exception);
          if (p == (const PixelPacket *) NULL)
            break;
          for (x=0; x < (long) image->columns; x++)
            {
              if (p->opacity == TransparentOpacity)
                {
                  x_vector[x].red=0.0;
                  x_vector[x].green=0.0;
                  x_vector[x].blue=0.0;
                }
              else
                {
                  x_vector[x].red=p->red;
                  x_vector[x].green=p->green;
                  x_vector[x].blue=p->blue;
                }
              x_vector[x].opacity=p->opacity;
              p++;
            }
        }
      else
        {
          /*
            Scale Y direction.
          */
          while (y_scale < y_span)
            {
              if (next_row && (number_rows < (long) image->rows))
                {
                  /*
                    Read a new scanline.
                  */
                  p=AcquireImagePixels(image,0,i++,image->columns,1,exception);
                  if (p == (const PixelPacket *) NULL)
                    break;
                  for (x=0; x < (long) image->columns; x++)
                    {
                      if (p->opacity == TransparentOpacity)
                        {
                          x_vector[x].red=0;
                          x_vector[x].green=0;
                          x_vector[x].blue=0;
                        }
                      else
                        {
                          x_vector[x].red=p->red;
                          x_vector[x].green=p->green;
                          x_vector[x].blue=p->blue;
                        }
                      x_vector[x].opacity=p->opacity;
                      p++;
                    }
                  number_rows++;
                }
              for (x=0; x < (long) image->columns; x++)
                {
                  if (x_vector[x].opacity < (double) TransparentOpacity)
                    y_volumes[x] += y_scale;
                  y_vector[x].red+=y_scale*x_vector[x].red;
                  y_vector[x].green+=y_scale*x_vector[x].green;
                  y_vector[x].blue+=y_scale*x_vector[x].blue;
                  y_vector[x].opacity+=y_scale*x_vector[x].opacity;
                }
              y_span-=y_scale;
              y_scale=(double) scale_image->rows/image->rows;
              next_row=True;
            }
          if (next_row && (number_rows < (long) image->rows))
            {
              /*
                Read a new scanline.
              */
              p=AcquireImagePixels(image,0,i++,image->columns,1,exception);
              if (p == (const PixelPacket *) NULL)
                break;
              for (x=0; x < (long) image->columns; x++)
                {
                  if (p->opacity == TransparentOpacity)
                    {
                      x_vector[x].red=0;
                      x_vector[x].green=0;
                      x_vector[x].blue=0;
                    }
                  else
                    {
                      x_vector[x].red=p->red;
                      x_vector[x].green=p->green;
                      x_vector[x].blue=p->blue;
                    }
                  x_vector[x].opacity=p->opacity;
                  p++;
                }
              number_rows++;
              next_row=False;
            }
          s=scanline;
          for (x=0; x < (long) image->columns; x++)
            {
              if (x_vector[x].opacity < (double) TransparentOpacity)
                y_volumes[x] += y_span;
              pixel.red=y_vector[x].red+y_span*x_vector[x].red;
              pixel.green=y_vector[x].green+y_span*x_vector[x].green;
              pixel.blue=y_vector[x].blue+y_span*x_vector[x].blue;
              pixel.opacity=y_vector[x].opacity+y_span*x_vector[x].opacity;
              /*
                Scale color values if blended pixel contains contributions from
                both fully transparent and non-fully transparent pixels
               */
              if (y_volumes[x] > 0.0 && y_volumes[x] < 1.0)
                {
                  factor = 1 / y_volumes[x];
                  pixel.red *= factor;
                  pixel.green *= factor;
                  pixel.blue *= factor;
                }
              s->red=pixel.red > MaxRGBDouble ? MaxRGBDouble : pixel.red;
              s->green=pixel.green > MaxRGBDouble ? MaxRGBDouble : pixel.green;
              s->blue=pixel.blue > MaxRGBDouble ? MaxRGBDouble : pixel.blue;
              s->opacity=pixel.opacity > MaxRGBDouble ? MaxRGBDouble : pixel.opacity;
              s++;
              y_vector[x].red=0;
              y_vector[x].green=0;
              y_vector[x].blue=0;
              y_vector[x].opacity=0;
              y_volumes[x] = 0;
            }
          y_scale-=y_span;
          if (y_scale <= 0)
            {
              y_scale=(double) scale_image->rows/image->rows;
              next_row=True;
            }
          y_span=1.0;
        }
      if (scale_image->columns == image->columns)
        {
          /*
            Transfer scanline to scaled image.
          */
          s=scanline;
          for (x=0; x < (long) scale_image->columns; x++)
            {
              q->red=(Quantum) (s->red+0.5);
              q->green=(Quantum) (s->green+0.5);
              q->blue=(Quantum) (s->blue+0.5);
              q->opacity=(Quantum) (s->opacity+0.5);
              q++;
              s++;
            }
        }
      else
        {
          /*
            Scale X direction.
          */
          pixel=zero;
          next_column=False;
          x_span=1.0;
          s=scanline;
          t=scale_scanline;
          x_volume = 0.0;
          for (x=0; x < (long) image->columns; x++)
            {
              x_scale=(double) scale_image->columns/image->columns;
              while (x_scale >= x_span)
                {
                  if (next_column)
                    {
                      /*
                        Scale color values if blended pixel contains
                        contributions from both fully transparent and
                        non-fully transparent pixels
                       */
                      if (x_volume > 0.0 && x_volume < 1.0)
                        {
                          factor = 1 / x_volume;
                          t->red *= factor;
                          t->green *= factor;
                          t->blue *= factor;
                        }
                      x_volume = 0.0;
                      pixel=zero;
                      t++;
                    }
                  if (s->opacity < (double) TransparentOpacity)
                    x_volume += x_span;
                  pixel.red+=x_span*s->red;
                  pixel.green+=x_span*s->green;
                  pixel.blue+=x_span*s->blue;
                  pixel.opacity+=x_span*s->opacity;
                  t->red=pixel.red > MaxRGBDouble ? MaxRGBDouble : pixel.red;
                  t->green=pixel.green > MaxRGBDouble ? MaxRGBDouble : pixel.green;
                  t->blue=pixel.blue > MaxRGBDouble ? MaxRGBDouble : pixel.blue;
                  t->opacity=pixel.opacity > MaxRGBDouble ? MaxRGBDouble : pixel.opacity;
                  x_scale-=x_span;
                  x_span=1.0;
                  next_column=True;
                }
              if (x_scale > 0.0)
                {
                  if (next_column)
                    {
                      /*
                        Scale color values if blended pixel contains
                        contributions from both fully transparent and
                        non-fully transparent pixels
                       */
                      if (x_volume > 0.0 && x_volume < 1.0)
                        {
                          factor = 1 / x_volume;
                          t->red *= factor;
                          t->green *= factor;
                          t->blue *= factor;
                        }
                      x_volume = 0.0;
                      pixel=zero;
                      next_column=False;
                      t++;
                    }
                  if (s->opacity < (double) TransparentOpacity)
                    x_volume += x_scale;
                  pixel.red+=x_scale*s->red;
                  pixel.green+=x_scale*s->green;
                  pixel.blue+=x_scale*s->blue;
                  pixel.opacity+=x_scale*s->opacity;
                  x_span-=x_scale;
                }
              s++;
            }
          if (x_span > 0.0)
            {
              s--;
              if (s->opacity < (double) TransparentOpacity)
                x_volume += x_span;
              pixel.red+=x_span*s->red;
              pixel.green+=x_span*s->green;
              pixel.blue+=x_span*s->blue;
              pixel.opacity+=x_span*s->opacity;
            }
          if (!next_column && ((t-scale_scanline) < (long) scale_image->columns))
            {
              t->red=pixel.red > MaxRGBDouble ? MaxRGBDouble : pixel.red;
              t->green=pixel.green > MaxRGBDouble ? MaxRGBDouble : pixel.green;
              t->blue=pixel.blue > MaxRGBDouble ? MaxRGBDouble : pixel.blue;
              t->opacity=pixel.opacity > MaxRGBDouble ? MaxRGBDouble : pixel.opacity;
            }
          /*
            Transfer scanline to scaled image.
          */
          t=scale_scanline;
          for (x=0; x < (long) scale_image->columns; x++)
            {
              q->red=(Quantum) (t->red+0.5);
              q->green=(Quantum) (t->green+0.5);
              q->blue=(Quantum) (t->blue+0.5);
              q->opacity=(Quantum) (t->opacity+0.5);
              q++;
              t++;
            }
        }
      if (!SyncImagePixels(scale_image))
        break;
      if (QuantumTick(y,scale_image->rows))
        if (!MagickMonitorFormatted(y,scale_image->rows,exception,
                                    ScaleImageText,image->filename))
          break;
    }
  /*
    Free allocated memory.
  */
  if (scanline == x_vector)
    scanline=(DoublePixelPacket *) NULL;
  MagickFreeMemory(scanline);
  MagickFreeMemory(scale_scanline);
  MagickFreeMemory(x_vector);
  MagickFreeMemory(y_vector);
  MagickFreeMemory(y_volumes);
  scale_image->is_grayscale=image->is_grayscale;
  return(scale_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   T h u m b n a i l I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ThumbnailImage() changes the size of an image to the given dimensions.
%  This method was designed as a low cost thumbnail generator.
%  ThumbnailImage() is typically very fast but an attempt is made to improve
%  quality by first using a simple sampling algorithm for part of the
%  reduction, and then a filtering algorithm to produce the final image.
%
%  The format of the ThumbnailImage method is:
%
%      Image *ThumbnailImage(const Image *image,const unsigned long columns,
%        const unsigned long rows,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image: The image.
%
%    o columns: The number of columns in the scaled image.
%
%    o rows: The number of rows in the scaled image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *ThumbnailImage(const Image *image,
  const unsigned long columns,const unsigned long rows,ExceptionInfo *exception)
{
  double
    x_factor,
    y_factor;

  Image
    *sample_image,
    *thumbnail_image;

  FilterTypes
    resize_filter;

  if ((columns == 0) || (rows == 0) ||
      (image->columns == 0) || (image->rows == 0))
    ThrowImageException(ImageError,UnableToResizeImage,
                        MagickMsg(OptionError,NonzeroWidthAndHeightRequired));

  /*
    Thumbnailing defaults to a fast box filter, but allow user to
    override the filter used.
  */
  if (UndefinedFilter != image->filter)
    resize_filter=image->filter;
  else
    resize_filter=DefaultThumbnailFilter;

  x_factor=(double) columns/image->columns;
  y_factor=(double) rows/image->rows;
  if ((x_factor*y_factor) > 0.1)
    return(ResizeImage(image,columns,rows,resize_filter,image->blur,exception));
  sample_image=SampleImage(image,5*columns,5*rows,exception);
  if (sample_image == (Image *) NULL)
    return((Image *) NULL);
  thumbnail_image=ResizeImage(sample_image,columns,rows,resize_filter,
    sample_image->blur,exception);
  DestroyImage(sample_image);
  return(thumbnail_image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
+   Z o o m I m a g e                                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ZoomImage() creates a new image that is a scaled size of an existing one.
%  It allocates the memory necessary for the new Image structure and returns a
%  pointer to the new image.  The Point filter gives fast pixel replication,
%  Triangle is equivalent to bi-linear interpolation, and Mitchel giver slower,
%  very high-quality results.  See Graphic Gems III for details on this
%  algorithm.
%
%  The filter member of the Image structure specifies which image filter to
%  use. Blur specifies the blur factor where > 1 is blurry, < 1 is sharp.
%
%  The format of the ZoomImage method is:
%
%      Image *ZoomImage(const Image *image,const unsigned long columns,
%        const unsigned long rows,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o zoom_image: Method ZoomImage returns a pointer to the image after
%      scaling.  A null image is returned if there is a memory shortage.
%
%    o image: The image.
%
%    o columns: An integer that specifies the number of columns in the zoom
%      image.
%
%    o rows: An integer that specifies the number of rows in the scaled
%      image.
%
%    o exception: Return any errors or warnings in this structure.
%
%
*/
MagickExport Image *ZoomImage(const Image *image,const unsigned long columns,
  const unsigned long rows,ExceptionInfo *exception)
{
  Image
    *zoom_image;

  assert(image != (const Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);

  zoom_image=ResizeImage(image,columns,rows,image->filter,image->blur,
                         exception);
  return(zoom_image);
}
