/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkImageBSplineInternals.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/

// This code has been modified from the original C code from Thevenaz.
// The functions have been converted into static C++ class methods,
// and two new boundary conditions have been added: clamped and repeat.

/*****************************************************************************
 *    Date: January 5, 2009
 *----------------------------------------------------------------------------
 *    This C program is based on the following paper:
 *        P. Thevenaz, T. Blu, M. Unser, "Interpolation Revisited,"
 *        IEEE Transactions on Medical Imaging,
 *        vol. 19, no. 7, pp. 739-758, July 2000.
 *----------------------------------------------------------------------------
 *    Philippe Thevenaz
 *    EPFL/STI/IMT/LIB/BM.4.137
 *    Station 17
 *    CH-1015 Lausanne VD
 *----------------------------------------------------------------------------
 *    phone (CET):    +41(21)693.51.61
 *    fax:            +41(21)693.37.01
 *    RFC-822:        philippe.thevenaz@epfl.ch
 *    X-400:            /C=ch/A=400net/P=switch/O=epfl/S=thevenaz/G=philippe/
 *    URL:            http://bigwww.epfl.ch/
 ****************************************************************************/

#include "vtkImageBSplineInternals.h"
#include "vtkAbstractImageInterpolator.h"
#include <cmath>
#include <cstddef>

/*--------------------------------------------------------------------------*/
void vtkImageBSplineInternals::ConvertToInterpolationCoefficients(
  double c[],                /* input samples --> output coefficients */
  long DataLength,           /* number of samples or coefficients */
  vtkImageBorderMode Border, /* border mode */
  double z[],                /* poles */
  long NbPoles,              /* number of poles */
  double Tolerance           /* admissible relative error */
)

{ /* begin ConvertToInterpolationCoefficients */

  double Lambda = 1.0;
  long n, k;

  /* special case required by mirror boundaries */
  if (DataLength == 1L)
  {
    return;
  }
  /* compute the overall gain */
  for (k = 0L; k < NbPoles; k++)
  {
    Lambda = Lambda * (1.0 - z[k]) * (1.0 - 1.0 / z[k]);
  }
  /* apply the gain */
  for (n = 0L; n < DataLength; n++)
  {
    c[n] *= Lambda;
  }
  /* loop over all poles */
  for (k = 0L; k < NbPoles; k++)
  {
    /* causal initialization */
    c[0] = InitialCausalCoefficient(c, DataLength, Border, z[k], Tolerance);
    /* causal recursion */
    for (n = 1L; n < DataLength; n++)
    {
      c[n] += z[k] * c[n - 1L];
    }
    /* anticausal initialization */
    c[DataLength - 1L] = InitialAntiCausalCoefficient(c, DataLength, Border, z[k], Tolerance);
    /* anticausal recursion */
    for (n = DataLength - 2L; 0 <= n; n--)
    {
      c[n] = z[k] * (c[n + 1L] - c[n]);
    }
  }
} /* end ConvertToInterpolationCoefficients */

/*--------------------------------------------------------------------------*/
double vtkImageBSplineInternals::InitialCausalCoefficient(double c[], /* coefficients */
  long DataLength,                                                    /* number of coefficients */
  vtkImageBorderMode Border,                                          /* border mode */
  double z,                                                           /* actual pole */
  double Tolerance /* admissible relative error */
)
{ /* begin InitialCausalCoefficient */

  double Sum, zn, z2n, iz;
  long n, Horizon;

  switch (Border)
  {
    case VTK_IMAGE_BORDER_CLAMP:
      /* this initialization corresponds to repeating edge pixels */
      Horizon = DataLength;
      if (Tolerance > 0.0 && DataLength > 16)
      {
        Horizon = (long)(ceil(log(Tolerance) / log(fabs(z))));
      }
      if (Horizon < DataLength)
      {
        /* accelerated loop */
        zn = z;
        Sum = c[0];
        for (n = 0; n < Horizon; n++)
        {
          Sum += zn * c[n];
          zn *= z;
        }
        return (Sum);
      }
      else
      {
        /* full loop */
        zn = z;
        iz = 1.0 / z;
        z2n = pow(z, (double)DataLength);
        Sum = z * c[0] + z2n * z2n * c[0];
        z2n *= z2n * iz;
        for (n = 1L; n <= DataLength - 1L; n++)
        {
          zn *= z;
          Sum += (zn + z2n) * c[n];
          z2n *= iz;
        }
        return (c[0] + Sum / (1.0 - zn * zn));
      }
      /* break; (unnecessary because of return) */

    case VTK_IMAGE_BORDER_MIRROR:
      /* this initialization corresponds to mirror boundaries */
      Horizon = DataLength;
      if (Tolerance > 0.0 && DataLength > 16)
      {
        Horizon = (long)(ceil(log(Tolerance) / log(fabs(z))));
      }
      if (Horizon < DataLength)
      {
        /* accelerated loop */
        zn = z;
        Sum = c[0];
        for (n = 1L; n < Horizon; n++)
        {
          Sum += zn * c[n];
          zn *= z;
        }
        return (Sum);
      }
      else
      {
        /* full loop */
        zn = z;
        iz = 1.0 / z;
        z2n = pow(z, (double)(DataLength - 1L));
        Sum = c[0] + z2n * c[DataLength - 1L];
        z2n *= z2n * iz;
        for (n = 1L; n <= DataLength - 2L; n++)
        {
          Sum += (zn + z2n) * c[n];
          zn *= z;
          z2n *= iz;
        }
        return (Sum / (1.0 - zn * zn));
      }
      /* break; (unnecessary because of return) */

    case VTK_IMAGE_BORDER_REPEAT:
      /* this initialization corresponds to periodic boundaries */
      Horizon = DataLength;
      if (Tolerance > 0.0 && DataLength > 16)
      {
        Horizon = (long)(ceil(log(Tolerance) / log(fabs(z))));
      }
      if (Horizon < DataLength)
      {
        /* accelerated loop */
        zn = z;
        Sum = c[0];
        for (n = 1L; n < Horizon; n++)
        {
          Sum += zn * c[DataLength - n];
          zn *= z;
        }
        return (Sum);
      }
      else
      {
        /* full loop */
        zn = z;
        Sum = c[0];
        for (n = 1L; n < DataLength; n++)
        {
          Sum += zn * c[DataLength - n];
          zn *= z;
        }
        return (Sum / (1.0 - zn));
      }
      /* break; (unnecessary because of return) */
  }

  return (0.0);
} /* end InitialCausalCoefficient */

/*--------------------------------------------------------------------------*/
double vtkImageBSplineInternals::InitialAntiCausalCoefficient(double c[], /* coefficients */
  long DataLength,           /* number of samples or coefficients */
  vtkImageBorderMode Border, /* border mode */
  double z,                  /* actual pole */
  double Tolerance           /* admissible relative error */
)
{ /* begin InitialAntiCausalCoefficient */
  double Sum;
  double zn;
  long n, Horizon;

  switch (Border)
  {
    case VTK_IMAGE_BORDER_CLAMP:
      /* this initialization corresponds to repeating edge pixels */
      return ((z / (z - 1.0)) * c[DataLength - 1L]);
    case VTK_IMAGE_BORDER_MIRROR:
      /* this initialization corresponds to mirror boundaries */
      return ((z / (z * z - 1.0)) * (z * c[DataLength - 2L] + c[DataLength - 1L]));
    case VTK_IMAGE_BORDER_REPEAT:
      /* this initialization corresponds to periodic boundaries */
      Horizon = DataLength;
      if (Tolerance > 0.0 && DataLength > 16)
      {
        Horizon = (long)(ceil(log(Tolerance) / log(fabs(z))));
      }
      if (Horizon < DataLength)
      {
        /* accelerated loop */
        zn = z;
        Sum = c[0];
        for (n = 1L; n < Horizon; n++)
        {
          Sum += zn * c[n];
          zn *= z;
        }
        return (-Sum * z * z - z * c[DataLength - 1L]);
      }
      else
      {
        /* full loop */
        zn = z;
        Sum = c[0];
        for (n = 1L; n < DataLength; n++)
        {
          Sum += zn * c[n];
          zn *= z;
        }
        return (Sum * z * z / (zn - 1.0) - z * c[DataLength - 1L]);
      }
  }
  return (0.0);
} /* end InitialAntiCausalCoefficient */

/*--------------------------------------------------------------------------*/
int vtkImageBSplineInternals::GetPoleValues(double Pole[4], /* poles for b-spline */
  long& NbPoles,    /* number of poles (will be four, at maximum) */
  long SplineDegree /* degree of the spline model */
)

{ /* begin GetPoleValues */

  /* recover the poles from a lookup table */
  switch (SplineDegree)
  {
    case 0L:
      NbPoles = 0L;
      break;
    case 1L:
      NbPoles = 0L;
      break;
    case 2L:
      NbPoles = 1L;
      Pole[0] = sqrt(8.0) - 3.0;
      break;
    case 3L:
      NbPoles = 1L;
      Pole[0] = sqrt(3.0) - 2.0;
      break;
    case 4L:
      NbPoles = 2L;
      Pole[0] = sqrt(664.0 - sqrt(438976.0)) + sqrt(304.0) - 19.0;
      Pole[1] = sqrt(664.0 + sqrt(438976.0)) - sqrt(304.0) - 19.0;
      break;
    case 5L:
      NbPoles = 2L;
      Pole[0] = sqrt(135.0 / 2.0 - sqrt(17745.0 / 4.0)) + sqrt(105.0 / 4.0) - 13.0 / 2.0;
      Pole[1] = sqrt(135.0 / 2.0 + sqrt(17745.0 / 4.0)) - sqrt(105.0 / 4.0) - 13.0 / 2.0;
      break;
    case 6L:
      NbPoles = 3L;
      Pole[0] = -0.48829458930304475513011803888378906211227916123938;
      Pole[1] = -0.081679271076237512597937765737059080653379610398148;
      Pole[2] = -0.0014141518083258177510872439765585925278641690553467;
      break;
    case 7L:
      NbPoles = 3L;
      Pole[0] = -0.53528043079643816554240378168164607183392315234269;
      Pole[1] = -0.12255461519232669051527226435935734360548654942730;
      Pole[2] = -0.0091486948096082769285930216516478534156925639545994;
      break;
    case 8L:
      NbPoles = 4L;
      Pole[0] = -0.57468690924876543053013930412874542429066157804125;
      Pole[1] = -0.16303526929728093524055189686073705223476814550830;
      Pole[2] = -0.023632294694844850023403919296361320612665920854629;
      Pole[3] = -0.00015382131064169091173935253018402160762964054070043;
      break;
    case 9L:
      NbPoles = 4L;
      Pole[0] = -0.60799738916862577900772082395428976943963471853991;
      Pole[1] = -0.20175052019315323879606468505597043468089886575747;
      Pole[2] = -0.043222608540481752133321142979429688265852380231497;
      Pole[3] = -0.0021213069031808184203048965578486234220548560988624;
      break;
    default:
      NbPoles = 0L;
      return (1);
  }

  return (0);
} /* end GetPoleValues */

/*--------------------------------------------------------------------------*/
namespace
{

template <class T>
int vtkImageBSplineGetInterpolationWeights(T xWeight[10], /* weights, size is SplineDegree + 1 */
  double w,                                               /* offset, value between 0 and 1 */
  long SplineDegree                                       /* degree of spline, between 2 and 9 */
)
{ /* begin GetInterpolationWeights */
  double w2, w4, t, t0, t1;

  /* compute the interpolation weights */
  switch (SplineDegree)
  {
    case 0L:
      xWeight[0] = 1.0;
      break;
    case 1L:
      xWeight[0] = 1.0 - w;
      xWeight[1] = w;
      break;
    case 2L:
      xWeight[1] = 3.0 / 4.0 - w * w;
      xWeight[2] = (1.0 / 2.0) * (w - xWeight[1] + 1.0);
      xWeight[0] = 1.0 - xWeight[1] - xWeight[2];
      break;
    case 3L:
      xWeight[3] = (1.0 / 6.0) * w * w * w;
      xWeight[0] = (1.0 / 6.0) + (1.0 / 2.0) * w * (w - 1.0) - xWeight[3];
      xWeight[2] = w + xWeight[0] - 2.0 * xWeight[3];
      xWeight[1] = 1.0 - xWeight[0] - xWeight[2] - xWeight[3];
      break;
    case 4L:
      w2 = w * w;
      t = (1.0 / 6.0) * w2;
      xWeight[0] = 1.0 / 2.0 - w;
      xWeight[0] *= xWeight[0];
      xWeight[0] *= (1.0 / 24.0) * xWeight[0];
      t0 = w * (t - 11.0 / 24.0);
      t1 = 19.0 / 96.0 + w2 * (1.0 / 4.0 - t);
      xWeight[1] = t1 + t0;
      xWeight[3] = t1 - t0;
      xWeight[4] = xWeight[0] + t0 + (1.0 / 2.0) * w;
      xWeight[2] = 1.0 - xWeight[0] - xWeight[1] - xWeight[3] - xWeight[4];
      break;
    case 5L:
      w2 = w * w;
      xWeight[5] = (1.0 / 120.0) * w * w2 * w2;
      w2 -= w;
      w4 = w2 * w2;
      w -= 1.0 / 2.0;
      t = w2 * (w2 - 3.0);
      xWeight[0] = (1.0 / 24.0) * (1.0 / 5.0 + w2 + w4) - xWeight[5];
      t0 = (1.0 / 24.0) * (w2 * (w2 - 5.0) + 46.0 / 5.0);
      t1 = (-1.0 / 12.0) * w * (t + 4.0);
      xWeight[2] = t0 + t1;
      xWeight[3] = t0 - t1;
      t0 = (1.0 / 16.0) * (9.0 / 5.0 - t);
      t1 = (1.0 / 24.0) * w * (w4 - w2 - 5.0);
      xWeight[1] = t0 + t1;
      xWeight[4] = t0 - t1;
      break;
    case 6L:
      xWeight[0] = 1.0 / 2.0 - w;
      xWeight[0] *= xWeight[0] * xWeight[0];
      xWeight[0] *= xWeight[0] / 720.0;
      xWeight[1] =
        (361.0 / 192.0 -
          w *
            (59.0 / 8.0 +
              w * (-185.0 / 16.0 + w * (25.0 / 3.0 + w * (-5.0 / 2.0 + w) * (1.0 / 2.0 + w))))) /
        120.0;
      xWeight[2] =
        (10543.0 / 960.0 +
          w *
            (-289.0 / 16.0 +
              w * (79.0 / 16.0 + w * (43.0 / 6.0 + w * (-17.0 / 4.0 + w * (-1.0 + w)))))) /
        48.0;
      w2 = w * w;
      xWeight[3] = (5887.0 / 320.0 - w2 * (231.0 / 16.0 - w2 * (21.0 / 4.0 - w2))) / 36.0;
      xWeight[4] =
        (10543.0 / 960.0 +
          w *
            (289.0 / 16.0 +
              w * (79.0 / 16.0 + w * (-43.0 / 6.0 + w * (-17.0 / 4.0 + w * (1.0 + w)))))) /
        48.0;
      xWeight[6] = 1.0 / 2.0 + w;
      xWeight[6] *= xWeight[6] * xWeight[6];
      xWeight[6] *= xWeight[6] / 720.0;
      xWeight[5] =
        1.0 - xWeight[0] - xWeight[1] - xWeight[2] - xWeight[3] - xWeight[4] - xWeight[6];
      break;
    case 7L:
      xWeight[0] = 1.0 - w;
      xWeight[0] *= xWeight[0];
      xWeight[0] *= xWeight[0] * xWeight[0];
      xWeight[0] *= (1.0 - w) / 5040.0;
      w2 = w * w;
      xWeight[1] =
        (120.0 / 7.0 + w * (-56.0 + w * (72.0 + w * (-40.0 + w2 * (12.0 + w * (-6.0 + w)))))) /
        720.0;
      xWeight[2] =
        (397.0 / 7.0 -
          w *
            (245.0 / 3.0 +
              w * (-15.0 + w * (-95.0 / 3.0 + w * (15.0 + w * (5.0 + w * (-5.0 + w))))))) /
        240.0;
      xWeight[3] = (2416.0 / 35.0 + w2 * (-48.0 + w2 * (16.0 + w2 * (-4.0 + w)))) / 144.0;
      xWeight[4] =
        (1191.0 / 35.0 - w * (-49.0 + w * (-9.0 + w * (19.0 + w * (-3.0 + w) * (-3.0 + w2))))) /
        144.0;
      xWeight[5] =
        (40.0 / 7.0 +
          w * (56.0 / 3.0 + w * (24.0 + w * (40.0 / 3.0 + w2 * (-4.0 + w * (-2.0 + w)))))) /
        240.0;
      xWeight[7] = w2;
      xWeight[7] *= xWeight[7] * xWeight[7];
      xWeight[7] *= w / 5040.0;
      xWeight[6] = 1.0 - xWeight[0] - xWeight[1] - xWeight[2] - xWeight[3] - xWeight[4] -
        xWeight[5] - xWeight[7];
      break;
    case 8L:
      xWeight[0] = 1.0 / 2.0 - w;
      xWeight[0] *= xWeight[0];
      xWeight[0] *= xWeight[0];
      xWeight[0] *= xWeight[0] / 40320.0;
      w2 = w * w;
      xWeight[1] = (39.0 / 16.0 - w * (6.0 + w * (-9.0 / 2.0 + w2))) *
        (21.0 / 16.0 + w * (-15.0 / 4.0 + w * (9.0 / 2.0 + w * (-3.0 + w)))) / 5040.0;
      xWeight[2] =
        (82903.0 / 1792.0 +
          w *
            (-4177.0 / 32.0 +
              w *
                (2275.0 / 16.0 +
                  w *
                    (-487.0 / 8.0 +
                      w * (-85.0 / 8.0 + w * (41.0 / 2.0 + w * (-5.0 + w * (-2.0 + w)))))))) /
        1440.0;
      xWeight[3] =
        (310661.0 / 1792.0 -
          w *
            (14219.0 / 64.0 +
              w *
                (-199.0 / 8.0 +
                  w *
                    (-1327.0 / 16.0 +
                      w * (245.0 / 8.0 + w * (53.0 / 4.0 + w * (-8.0 + w * (-1.0 + w)))))))) /
        720.0;
      xWeight[4] =
        (2337507.0 / 8960.0 + w2 * (-2601.0 / 16.0 + w2 * (387.0 / 8.0 + w2 * (-9.0 + w2)))) /
        576.0;
      xWeight[5] =
        (310661.0 / 1792.0 -
          w *
            (-14219.0 / 64.0 +
              w *
                (-199.0 / 8.0 +
                  w *
                    (1327.0 / 16.0 +
                      w * (245.0 / 8.0 + w * (-53.0 / 4.0 + w * (-8.0 + w * (1.0 + w)))))))) /
        720.0;
      xWeight[7] = (39.0 / 16.0 - w * (-6.0 + w * (-9.0 / 2.0 + w2))) *
        (21.0 / 16.0 + w * (15.0 / 4.0 + w * (9.0 / 2.0 + w * (3.0 + w)))) / 5040.0;
      xWeight[8] = 1.0 / 2.0 + w;
      xWeight[8] *= xWeight[8];
      xWeight[8] *= xWeight[8];
      xWeight[8] *= xWeight[8] / 40320.0;
      xWeight[6] = 1.0 - xWeight[0] - xWeight[1] - xWeight[2] - xWeight[3] - xWeight[4] -
        xWeight[5] - xWeight[7] - xWeight[8];
      break;
    case 9L:
      xWeight[0] = 1.0 - w;
      xWeight[0] *= xWeight[0];
      xWeight[0] *= xWeight[0];
      xWeight[0] *= xWeight[0] * (1.0 - w) / 362880.0;
      xWeight[1] =
        (502.0 / 9.0 +
          w *
            (-246.0 +
              w *
                (472.0 +
                  w *
                    (-504.0 +
                      w *
                        (308.0 +
                          w * (-84.0 + w * (-56.0 / 3.0 + w * (24.0 + w * (-8.0 + w))))))))) /
        40320.0;
      xWeight[2] =
        (3652.0 / 9.0 -
          w *
            (2023.0 / 2.0 +
              w *
                (-952.0 +
                  w *
                    (938.0 / 3.0 +
                      w *
                        (112.0 +
                          w * (-119.0 + w * (56.0 / 3.0 + w * (14.0 + w * (-7.0 + w))))))))) /
        10080.0;
      xWeight[3] =
        (44117.0 / 42.0 +
          w *
            (-2427.0 / 2.0 +
              w *
                (66.0 +
                  w *
                    (434.0 +
                      w * (-129.0 + w * (-69.0 + w * (34.0 + w * (6.0 + w * (-6.0 + w))))))))) /
        4320.0;
      w2 = w * w;
      xWeight[4] =
        (78095.0 / 63.0 - w2 * (700.0 + w2 * (-190.0 + w2 * (100.0 / 3.0 + w2 * (-5.0 + w))))) /
        2880.0;
      xWeight[5] =
        (44117.0 / 63.0 +
          w *
            (809.0 +
              w *
                (44.0 +
                  w *
                    (-868.0 / 3.0 +
                      w *
                        (-86.0 + w * (46.0 + w * (68.0 / 3.0 + w * (-4.0 + w * (-4.0 + w))))))))) /
        2880.0;
      xWeight[6] =
        (3652.0 / 21.0 -
          w *
            (-867.0 / 2.0 +
              w *
                (-408.0 +
                  w *
                    (-134.0 +
                      w * (48.0 + w * (51.0 + w * (-4.0 + w) * (-1.0 + w) * (2.0 + w))))))) /
        4320.0;
      xWeight[7] =
        (251.0 / 18.0 +
          w *
            (123.0 / 2.0 +
              w *
                (118.0 +
                  w *
                    (126.0 +
                      w *
                        (77.0 + w * (21.0 + w * (-14.0 / 3.0 + w * (-6.0 + w * (-2.0 + w))))))))) /
        10080.0;
      xWeight[9] = w2 * w2;
      xWeight[9] *= xWeight[9] * w / 362880.0;
      xWeight[8] = 1.0 - xWeight[0] - xWeight[1] - xWeight[2] - xWeight[3] - xWeight[4] -
        xWeight[5] - xWeight[6] - xWeight[7] - xWeight[9];
      break;
    default:
      return (0);
  }
  return (1);
} /* end GetInterpolationWeights */

/*--------------------------------------------------------------------------*/
template <class T>
int vtkImageBSplineInterpolatedValue(const T* Bcoeff, /* input B-spline array of coefficients */
  T* v,                                               /* resulting pixel value */
  long Width,                                         /* width of the image */
  long Height,                                        /* height of the image */
  long Slices,                                        /* number of slices of the image */
  long Depth,                                         /* number of samples per pixel */
  double x,                                           /* x coordinate where to interpolate */
  double y,                                           /* y coordinate where to interpolate */
  double z,                                           /* y coordinate where to interpolate */
  long SplineDegree,                                  /* degree of the spline model */
  vtkImageBorderMode Border                           /* what to do at the border */
)
{ /* begin InterpolatedValue */

  const T *p1, *p2, *p3;
  T xWeight[10], yWeight[10], zWeight[10];
  double interpolated;
  double w, u;
  double s, t, r;
  long xIndex[10], yIndex[10], zIndex[10];
  ptrdiff_t xIncrement, yIncrement, zIncrement;
  long Width2 = 2L * Width - 2L;
  long Height2 = 2L * Height - 2L;
  long Slices2 = 2L * Slices - 2L;
  long CentralIndex = SplineDegree / 2L;
  long i, j, k, l, c;
  long imax, jmax, kmax;

  if (SplineDegree < 0 || SplineDegree > 9)
  {
    return 0;
  }

  /* check for 1D and 2D images */
  imax = SplineDegree;
  jmax = SplineDegree;
  kmax = SplineDegree;
  if (Width == 1)
  {
    imax = 0;
  }
  if (Height == 1)
  {
    jmax = 0;
  }
  if (Slices == 1)
  {
    kmax = 0;
  }

  /* compute the interpolation indexes and fractional offsets*/
  if (SplineDegree & 1L)
  {
    i = (long)(floor(x));
    j = (long)(floor(y));
    k = (long)(floor(z));
  }
  else
  {
    i = (long)(floor(x + 0.5));
    j = (long)(floor(y + 0.5));
    k = (long)(floor(z + 0.5));
  }

  s = x - i;
  t = y - j;
  r = z - k;

  i -= CentralIndex;
  j -= CentralIndex;
  k -= CentralIndex;

  for (l = 0L; l <= SplineDegree; l++)
  {
    xIndex[l] = i++;
    yIndex[l] = j++;
    zIndex[l] = k++;
  }

  /* get the interpolation weights */
  xWeight[0] = 1.0;
  yWeight[0] = 1.0;
  zWeight[0] = 1.0;

  if (Width > 1)
  {
    vtkImageBSplineInternals::GetInterpolationWeights(xWeight, s, SplineDegree);
  }
  if (Height > 1)
  {
    vtkImageBSplineInternals::GetInterpolationWeights(yWeight, t, SplineDegree);
  }
  if (Slices > 1)
  {
    vtkImageBSplineInternals::GetInterpolationWeights(zWeight, r, SplineDegree);
  }

  switch (Border)
  {
    case VTK_IMAGE_BORDER_CLAMP:
      /* apply the constant boundary conditions */
      for (l = 0L; l <= SplineDegree; l++)
      {
        if (xIndex[l] < 0)
        {
          xIndex[l] = 0;
        }
        else if (xIndex[l] >= Width)
        {
          xIndex[l] = Width - 1;
        }
      }
      for (l = 0L; l <= SplineDegree; l++)
      {
        if (yIndex[l] < 0)
        {
          yIndex[l] = 0;
        }
        else if (yIndex[l] >= Height)
        {
          yIndex[l] = Height - 1;
        }
      }
      for (l = 0L; l <= SplineDegree; l++)
      {
        if (zIndex[l] < 0)
        {
          zIndex[l] = 0;
        }
        else if (zIndex[l] >= Slices)
        {
          zIndex[l] = Slices - 1;
        }
      }
      break;

    case VTK_IMAGE_BORDER_MIRROR:
      /* apply the mirror boundary conditions */
      for (l = 0L; l <= SplineDegree; l++)
      {
        xIndex[l] = (Width == 1L)
          ? (0L)
          : ((xIndex[l] < 0L) ? (-xIndex[l] - Width2 * ((-xIndex[l]) / Width2))
                              : (xIndex[l] - Width2 * (xIndex[l] / Width2)));
        if (Width <= xIndex[l])
        {
          xIndex[l] = Width2 - xIndex[l];
        }
        yIndex[l] = (Height == 1L)
          ? (0L)
          : ((yIndex[l] < 0L) ? (-yIndex[l] - Height2 * ((-yIndex[l]) / Height2))
                              : (yIndex[l] - Height2 * (yIndex[l] / Height2)));
        if (Height <= yIndex[l])
        {
          yIndex[l] = Height2 - yIndex[l];
        }
        zIndex[l] = (Slices == 1L)
          ? (0L)
          : ((zIndex[l] < 0L) ? (-zIndex[l] - Slices2 * ((-zIndex[l]) / Slices2))
                              : (zIndex[l] - Slices2 * (zIndex[l] / Slices2)));
        if (Slices <= zIndex[l])
        {
          zIndex[l] = Slices2 - zIndex[l];
        }
      }
      break;

    case VTK_IMAGE_BORDER_REPEAT:
      /* apply the repeat boundary conditions */
      for (l = 0L; l <= SplineDegree; l++)
      {
        if ((xIndex[l] %= Width) < 0)
        {
          xIndex[l] += Width;
        }
        if ((yIndex[l] %= Height) < 0)
        {
          yIndex[l] += Height;
        }
        if ((zIndex[l] %= Slices) < 0)
        {
          zIndex[l] += Slices;
        }
      }
      break;
  }

  /* precompute the increments in each direction */
  xIncrement = Depth;
  yIncrement = xIncrement * Width;
  zIncrement = yIncrement * Height;

  /* perform interpolation */
  for (c = 0L; c < Depth; c++)
  {
    p1 = Bcoeff + c;
    interpolated = 0.0;
    for (k = 0L; k <= kmax; k++)
    {
      p2 = p1 + (zIndex[k] * zIncrement);
      u = 0.0;
      for (j = 0L; j <= jmax; j++)
      {
        p3 = p2 + (yIndex[j] * yIncrement);
        w = 0.0;
        for (i = 0L; i <= imax; i++)
        {
          w += xWeight[i] * p3[xIndex[i] * xIncrement];
        }
        u += yWeight[j] * w;
      }
      interpolated += zWeight[k] * u;
    }
    v[c] = interpolated;
  }

  return (1);
} /* end InterpolateValue */

}

/*--------------------------------------------------------------------------*/
int vtkImageBSplineInternals::GetInterpolationWeights(float weights[10], double w, long degree)
{
  return vtkImageBSplineGetInterpolationWeights(weights, w, degree);
}

int vtkImageBSplineInternals::GetInterpolationWeights(double weights[10], double w, long degree)
{
  return vtkImageBSplineGetInterpolationWeights(weights, w, degree);
}

/*--------------------------------------------------------------------------*/
int vtkImageBSplineInternals::InterpolatedValue(const double* coeffs, double* value, long width,
  long height, long slices, long depth, double x, double y, double z, long degree,
  vtkImageBorderMode border)
{
  return vtkImageBSplineInterpolatedValue(
    coeffs, value, width, height, slices, depth, x, y, z, degree, border);
}

int vtkImageBSplineInternals::InterpolatedValue(const float* coeffs, float* value, long width,
  long height, long slices, long depth, double x, double y, double z, long degree,
  vtkImageBorderMode border)
{
  return vtkImageBSplineInterpolatedValue(
    coeffs, value, width, height, slices, depth, x, y, z, degree, border);
}
