/*
This file contains code from the GraphicsMagick software distributed by the
GraphicsMagick Group.

image_downsize_gm is based on GraphicsMagick's ResizeImage function.

The licenses which components of this software fall under are as follows.

1)
  In November 2002, the GraphicsMagick Group created GraphicsMagick
  from ImageMagick Studio's ImageMagick and applied the "MIT" style
  license:

  Copyright (C) 2002 - 2010 GraphicsMagick Group

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation files
  (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software,
  and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.

2)
  In October 1999, ImageMagick Studio assumed the responsibility for
  the development of ImageMagick (forking from the distribution by
  E. I. du Pont de Nemours and Company) and applied a new license:

  Copyright (C) 2002 ImageMagick Studio, a non-profit organization dedicated
  to making software imaging solutions freely available.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files ("ImageMagick"),
  to deal in ImageMagick without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of ImageMagick, and to permit persons to whom the
  ImageMagick is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of ImageMagick.

  The software is provided "as is", without warranty of any kind, express or
  implied, including but not limited to the warranties of merchantability,
  fitness for a particular purpose and noninfringement.  In no event shall
  ImageMagick Studio be liable for any claim, damages or other liability,
  whether in an action of contract, tort or otherwise, arising from, out of
  or in connection with ImageMagick or the use or other dealings in
  ImageMagick.

  Except as contained in this notice, the name of the ImageMagick Studio
  shall not be used in advertising or otherwise to promote the sale, use or
  other dealings in ImageMagick without prior written authorization from the
  ImageMagick Studio.

3)
  From 1991 to October 1999 (through ImageMagick 4.2.9), ImageMagick
  was developed and distributed by E. I. du Pont de Nemours and
  Company:

  Copyright 1999 E. I. du Pont de Nemours and Company

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files
  ("ImageMagick"), to deal in ImageMagick without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of ImageMagick, and to
  permit persons to whom the ImageMagick is furnished to do so, subject
  to the following conditions:

  The above copyright notice and this permission notice shall be included
  in all copies or substantial portions of ImageMagick.

  The software is provided "as is", without warranty of any kind, express
  or implied, including but not limited to the warranties of
  merchantability, fitness for a particular purpose and noninfringement.
  In no event shall E. I. du Pont de Nemours and Company be liable for
  any claim, damages or other liability, whether in an action of
  contract, tort or otherwise, arising from, out of or in connection with
  ImageMagick or the use or other dealings in ImageMagick.

  Except as contained in this notice, the name of the E. I. du Pont de
  Nemours and Company shall not be used in advertising or otherwise to
  promote the sale, use or other dealings in ImageMagick without prior
  written authorization from the E. I. du Pont de Nemours and Company.

---------------------------------------------------------------------------

| Copyright (C) 2002 - 2010 GraphicsMagick Group
*/

#include "magick.h"

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/(PI*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);
}

static float Bessel(const float x,const float ARGUNUSED(support))
{
  if (x == 0.0)
    return(PI/4.0);
  return(BesselOrderOne(PI*x)/(2.0*x));
}

static float Sinc(const float x,const float ARGUNUSED(support))
{
  if (x == 0.0)
    return(1.0);
  return(sin(PI*x)/(PI*x));
}

static float Blackman(const float x,const float ARGUNUSED(support))
{
  return(0.42+0.5*cos(PI*x)+0.08*cos(2*PI*x));
}

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

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

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

static float Catrom(const float x,const float ARGUNUSED(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 float Cubic(const float x,const float ARGUNUSED(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 float Gaussian(const float x,const float ARGUNUSED(support))
{
  return(exp(-2.0*x*x)*sqrt(2.0/PI));
}

static float Hanning(const float x,const float ARGUNUSED(support))
{
  return(0.5+0.5*cos(PI*x));
}

static float Hamming(const float x,const float ARGUNUSED(support))
{
  return(0.54+0.46*cos(PI*x));
}

static float Hermite(const float x,const float ARGUNUSED(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 float Lanczos(const float x,const float 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 float Mitchell(const float x,const float ARGUNUSED(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)

  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 float Quadratic(const float x,const float ARGUNUSED(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 float Triangle(const float x,const float ARGUNUSED(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 void
image_downsize_gm_horizontal_filter(image *im, ImageInfo *source, ImageInfo *destination,
  const float x_factor, const FilterInfo *filter_info, ContributionInfo *contribution, int rotate)
{
  float scale, support;
  int x;
  int dstX = 0;
  int dstW = destination->columns;

  if (im->width_padding) {
    dstX = im->width_padding;
    dstW = im->width_inner;
  }

  scale = BLUR * MAX(1.0 / x_factor, 1.0);
  support = scale * filter_info->support;
  if (support <= 0.5) {
    // Reduce to point sampling
    support = 0.5 + EPSILON;
    scale = 1.0;
  }
  scale = 1.0 / scale;

  for (x = dstX; (x < dstX + dstW); x++) {
    float center, density;
    int n, start, stop, y;

    center  = (float)(x - dstX + 0.5) / x_factor;
    start   = (int)MAX(center - support + 0.5, 0);
    stop    = (int)MIN(center + support + 0.5, source->columns);
    density = 0.0;

    //DEBUG_TRACE("x %d: center %.2f, start %d, stop %d\n", x, center, start, stop);

    for (n = 0; n < (stop - start); n++) {
      contribution[n].pixel = start + n;
      contribution[n].weight = filter_info->function(scale * (start + n - center + 0.5), filter_info->support);
      density += contribution[n].weight;
      //DEBUG_TRACE("  contribution[%d].pixel %d, weight %.2f, density %.2f\n", n, contribution[n].pixel, contribution[n].weight, density);
    }

    if ((density != 0.0) && (density != 1.0)) {
      // Normalize
      int i;

      density = 1.0 / density;
      for (i = 0; i < n; i++) {
        contribution[i].weight *= density;
        //DEBUG_TRACE("  normalize contribution[%d].weight to %.2f\n", i, contribution[i].weight);
      }
    }

    for (y = 0; y < destination->rows; y++) {
      float weight;
      float red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
      pix p;
      int j;
      register int i;

      //DEBUG_TRACE("y %d:\n", y);

      if (im->has_alpha) {
        float normalize;

        normalize = 0.0;
        for (i = 0; i < n; i++) {
          j = (int)((y * source->columns) + contribution[i].pixel);
          weight = contribution[i].weight;
          p = source->buf[j];

          // XXX The original GM code weighted based on transparency for some reason,
          // but this produces bad results, so we use only the weight
          //transparency_coeff = weight * ((float)COL_ALPHA(p) / 255);

          /*
          DEBUG_TRACE("    merging with pix (%d, %d) @ %d (%d %d %d %d) weight %.2f\n",
            x, contribution[i].pixel, j,
            COL_RED(p), COL_GREEN(p), COL_BLUE(p), COL_ALPHA(p),
            weight);
          */

          red   += weight * COL_RED(p);
          green += weight * COL_GREEN(p);
          blue  += weight * COL_BLUE(p);
          alpha += weight * COL_ALPHA(p);
          normalize += weight;
        }

        normalize = 1.0 / (ABS(normalize) <= EPSILON ? 1.0 : normalize);
        red   *= normalize;
        green *= normalize;
        blue  *= normalize;
      }
      else {
        for (i = 0; i < n; i++) {
          j = (int)((y * source->columns) + contribution[i].pixel);
          weight = contribution[i].weight;
          p = source->buf[j];

          /*
          DEBUG_TRACE("    merging with pix (%d, %d) @ %d (%d %d %d) weight %.2f\n",
            contribution[i].pixel, y, j,
            COL_RED(p), COL_GREEN(p), COL_BLUE(p),
            weight);
          */

          red   += weight * COL_RED(p);
          green += weight * COL_GREEN(p);
          blue  += weight * COL_BLUE(p);
        }

        alpha = 255.0;
      }

      /*
      DEBUG_TRACE("  -> (%d, %d) @ %d (%d %d %d %d)\n",
        x, y, (y * destination->columns) + x,
        ROUND_FLOAT_TO_INT(red),
        ROUND_FLOAT_TO_INT(green),
        ROUND_FLOAT_TO_INT(blue),
        ROUND_FLOAT_TO_INT(alpha));
      */

      if (rotate && im->orientation != ORIENTATION_NORMAL) {
        int ox, oy; // new destination pixel coordinates after rotating

        image_get_rotated_coords(im, x, y, &ox, &oy);

        if (im->orientation >= 5) {
          // 90 and 270 rotations, width/height are swapped
          destination->buf[(oy * destination->rows) + ox] = COL_FULL(
            ROUND_FLOAT_TO_INT(red),
            ROUND_FLOAT_TO_INT(green),
            ROUND_FLOAT_TO_INT(blue),
            ROUND_FLOAT_TO_INT(alpha)
          );
        }
        else {
          destination->buf[(oy * destination->columns) + ox] = COL_FULL(
            ROUND_FLOAT_TO_INT(red),
            ROUND_FLOAT_TO_INT(green),
            ROUND_FLOAT_TO_INT(blue),
            ROUND_FLOAT_TO_INT(alpha)
          );
        }
      }
      else {
        destination->buf[(y * destination->columns) + x] = COL_FULL(
          ROUND_FLOAT_TO_INT(red),
          ROUND_FLOAT_TO_INT(green),
          ROUND_FLOAT_TO_INT(blue),
          ROUND_FLOAT_TO_INT(alpha)
        );
      }
    }
  }
}

static void
image_downsize_gm_vertical_filter(image *im, ImageInfo *source, ImageInfo *destination,
  const float y_factor, const FilterInfo *filter_info, ContributionInfo *contribution, int rotate)
{
  float scale, support;
  int y;
  int dstY = 0;
  int dstH = destination->rows;

  if (im->height_padding) {
    dstY = im->height_padding;
    dstH = im->height_inner;
  }

  //DEBUG_TRACE("y_factor %.2f\n", y_factor);

  // Apply filter to resize vertically from source to destination
  scale = BLUR * MAX(1.0 / y_factor, 1.0);
  support = scale * filter_info->support;
  if (support <= 0.5) {
    // Reduce to point sampling
    support = 0.5 + EPSILON;
    scale = 1.0;
  }
  scale = 1.0 / scale;

  for (y = dstY; (y < dstY + dstH); y++) {
    float center, density;
    int n, start, stop, x;

    center  = (float)(y - dstY + 0.5) / y_factor;
    start   = (int)MAX(center - support + 0.5, 0);
    stop    = (int)MIN(center + support + 0.5, source->rows);
    density = 0.0;

    //DEBUG_TRACE("y %d: center %.2f, start %d, stop %d\n", y, center, start, stop);

    for (n = 0; n < (stop - start); n++) {
      contribution[n].pixel = start + n;
      contribution[n].weight = filter_info->function(scale * (start + n - center + 0.5), filter_info->support);
      density += contribution[n].weight;
      //DEBUG_TRACE("  contribution[%d].pixel %d, weight %.2f, density %.2f\n", n, contribution[n].pixel, contribution[n].weight, density);
    }

    if ((density != 0.0) && (density != 1.0)) {
      // Normalize
      int i;

      density = 1.0 / density;
      for (i = 0; i < n; i++) {
        contribution[i].weight *= density;
        //DEBUG_TRACE("  normalize contribution[%d].weight to %.2f\n", i, contribution[i].weight);
      }
    }

    for (x = 0; x < destination->columns; x++) {
      float weight;
      float red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
      pix p;
      int j;
      register int i;

      //DEBUG_TRACE("x %d:\n", x);

      if (im->has_alpha) {
        float normalize;

        normalize = 0.0;
        for (i = 0; i < n; i++) {
          j = (int)((contribution[i].pixel * source->columns) + x);
          weight = contribution[i].weight;
          p = source->buf[j];

          // XXX The original GM code weighted based on transparency for some reason,
          // but this produces bad results, so we use only the weight
          //transparency_coeff = weight * ((float)COL_ALPHA(p) / 255);

          /*
          DEBUG_TRACE("    merging with pix (%d, %d) @ %d (%d %d %d %d) weight %.2f\n",
            x, contribution[i].pixel, j,
            COL_RED(p), COL_GREEN(p), COL_BLUE(p), COL_ALPHA(p),
            weight
          );
          */

          red   += weight * COL_RED(p);
          green += weight * COL_GREEN(p);
          blue  += weight * COL_BLUE(p);
          alpha += weight * COL_ALPHA(p);
          normalize += weight;
        }

        normalize = 1.0 / (ABS(normalize) <= EPSILON ? 1.0 : normalize);
        red   *= normalize;
        green *= normalize;
        blue  *= normalize;
      }
      else {
        for (i = 0; i < n; i++) {
          j = (int)((contribution[i].pixel * source->columns) + x);
          weight = contribution[i].weight;
          p = source->buf[j];

          /*
          DEBUG_TRACE("    merging with pix (%d, %d) @ %d (%d %d %d) weight %.2f\n",
            x, contribution[i].pixel, j,
            COL_RED(p), COL_GREEN(p), COL_BLUE(p),
            weight);
          */

          red   += weight * COL_RED(p);
          green += weight * COL_GREEN(p);
          blue  += weight * COL_BLUE(p);
        }

        alpha = 255.0;
      }

      /*
      DEBUG_TRACE("  -> (%d, %d) @ %d (%d %d %d %d)\n",
        x, y, (y * destination->columns) + x,
        ROUND_FLOAT_TO_INT(red),
        ROUND_FLOAT_TO_INT(green),
        ROUND_FLOAT_TO_INT(blue),
        ROUND_FLOAT_TO_INT(alpha));
      */

      if (rotate && im->orientation != ORIENTATION_NORMAL) {
        int ox, oy; // new destination pixel coordinates after rotating

        image_get_rotated_coords(im, x, y, &ox, &oy);

        if (im->orientation >= 5) {
          // 90 and 270 rotations, width/height are swapped
          destination->buf[(oy * destination->rows) + ox] = COL_FULL(
            ROUND_FLOAT_TO_INT(red),
            ROUND_FLOAT_TO_INT(green),
            ROUND_FLOAT_TO_INT(blue),
            ROUND_FLOAT_TO_INT(alpha)
          );
        }
        else {
          destination->buf[(oy * destination->columns) + ox] = COL_FULL(
            ROUND_FLOAT_TO_INT(red),
            ROUND_FLOAT_TO_INT(green),
            ROUND_FLOAT_TO_INT(blue),
            ROUND_FLOAT_TO_INT(alpha)
          );
        }
      }
      else {
        destination->buf[(y * destination->columns) + x] = COL_FULL(
          ROUND_FLOAT_TO_INT(red),
          ROUND_FLOAT_TO_INT(green),
          ROUND_FLOAT_TO_INT(blue),
          ROUND_FLOAT_TO_INT(alpha)
        );
      }
    }
  }
}

void
image_downsize_gm(image *im)
{
  float x_factor, y_factor;
  float support, x_support, y_support;
  int columns, rows;
  int order;
  int filter;
  ContributionInfo *contribution;
  ImageInfo source, destination;

  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 }
    };

  columns = im->target_width;
  rows = im->target_height;
  filter = im->filter;

  if (!filter) {
    // Lanczos for downsizing, Mitchell for upsizing or if transparent
    if (im->has_alpha || columns > im->width || rows > im->height)
      filter = MitchellFilter;
    else
      filter = LanczosFilter;
  }

  DEBUG_TRACE("Resizing with filter %d\n", filter);

  // Determine which dimension to resize first
  order = (((float)columns * (im->height + rows)) >
         ((float)rows * (im->width + columns)));

  if (im->width_padding)
    x_factor = (float)im->width_inner / im->width;
  else
    x_factor = (float)im->target_width / im->width;

  if (im->height_padding)
    y_factor = (float)im->height_inner / im->height;
  else
    y_factor = (float)im->target_height / im->height;

  x_support = BLUR * MAX(1.0 / x_factor, 1.0) * filters[filter].support;
  y_support = BLUR * MAX(1.0 / y_factor, 1.0) * filters[filter].support;
  support = MAX(x_support, y_support);
  if (support < filters[filter].support)
    support = filters[filter].support;

  DEBUG_TRACE("ContributionInfo allocated for %ld items\n", (size_t)(2.0 * MAX(support, 0.5) + 3));
  New(0, contribution, (size_t)(2.0 * MAX(support, 0.5) + 3), ContributionInfo);

  DEBUG_TRACE("order %d, x_factor %f, y_factor %f, support %f\n", order, x_factor, y_factor, support);

  source.rows    = im->height;
  source.columns = im->width;
  source.buf     = im->pixbuf;

  if (order) {
    DEBUG_TRACE("Allocating temporary buffer size %ld\n", im->target_width * im->height * sizeof(pix));
    New(0, im->tmpbuf, im->target_width * im->height, pix);

    // Fill new space with the bgcolor or zeros
    image_bgcolor_fill(im->tmpbuf, im->target_width * im->height, im->bgcolor);

    // Resize horizontally from source -> tmp
    destination.rows    = im->height;
    destination.columns = im->target_width;
    destination.buf     = im->tmpbuf;
    image_downsize_gm_horizontal_filter(im, &source, &destination, x_factor, &filters[filter], contribution, 0);

    // Resize vertically from tmp -> out
    source.rows    = destination.rows;
    source.columns = destination.columns;
    source.buf     = destination.buf;

    destination.rows = im->target_height;
    destination.buf  = im->outbuf;
    image_downsize_gm_vertical_filter(im, &source, &destination, y_factor, &filters[filter], contribution, 1);
  }
  else {
    DEBUG_TRACE("Allocating temporary buffer size %ld\n", im->width * im->target_height * sizeof(pix));
    New(0, im->tmpbuf, im->width * im->target_height, pix);

    // Fill new space with the bgcolor or zeros
    image_bgcolor_fill(im->tmpbuf, im->width * im->target_height, im->bgcolor);

    // Resize vertically from source -> tmp
    destination.rows    = im->target_height;
    destination.columns = im->width;
    destination.buf     = im->tmpbuf;
    image_downsize_gm_vertical_filter(im, &source, &destination, y_factor, &filters[filter], contribution, 0);

    // Resize horizontally from tmp -> out
    source.rows    = destination.rows;
    source.columns = destination.columns;
    source.buf     = destination.buf;

    destination.columns = im->target_width;
    destination.buf     = im->outbuf;
    image_downsize_gm_horizontal_filter(im, &source, &destination, x_factor, &filters[filter], contribution, 1);
  }

  Safefree(im->tmpbuf);
  Safefree(contribution);
}
