// File: crn_resampler.h
// RG: This is public domain code, originally derived from Graphics Gems 3, see: http://code.google.com/p/imageresampler/
#include "crn_core.h"
#include "crn_resampler.h"
#include "crn_resample_filters.h"

namespace crnlib {
#define resampler_assert CRNLIB_ASSERT

static inline int resampler_range_check(int v, int h) {
  (void)h;
  resampler_assert((v >= 0) && (v < h));
  return v;
}

#ifndef max
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a, b) (((a) < (b)) ? (a) : (b))
#endif

#ifndef TRUE
#define TRUE (1)
#endif

#ifndef FALSE
#define FALSE (0)
#endif

#define RESAMPLER_DEBUG 0

// (x mod y) with special handling for negative x values.
static inline int posmod(int x, int y) {
  if (x >= 0)
    return (x % y);
  else {
    int m = (-x) % y;

    if (m != 0)
      m = y - m;

    return (m);
  }
}

// Float to int cast with truncation.
static inline int cast_to_int(Resample_Real i) {
  return (int)i;
}

/* Ensure that the contributing source sample is
   * within bounds. If not, reflect, clamp, or wrap.
   */
int Resampler::reflect(const int j, const int src_x, const Boundary_Op boundary_op) {
  int n;

  if (j < 0) {
    if (boundary_op == BOUNDARY_REFLECT) {
      n = -j;

      if (n >= src_x)
        n = src_x - 1;
    } else if (boundary_op == BOUNDARY_WRAP)
      n = posmod(j, src_x);
    else
      n = 0;
  } else if (j >= src_x) {
    if (boundary_op == BOUNDARY_REFLECT) {
      n = (src_x - j) + (src_x - 1);

      if (n < 0)
        n = 0;
    } else if (boundary_op == BOUNDARY_WRAP)
      n = posmod(j, src_x);
    else
      n = src_x - 1;
  } else
    n = j;

  return n;
}

// The make_clist() method generates, for all destination samples,
// the list of all source samples with non-zero weighted contributions.
Resampler::Contrib_List* Resampler::make_clist(
    int src_x, int dst_x, Boundary_Op boundary_op,
    Resample_Real (*Pfilter)(Resample_Real),
    Resample_Real filter_support,
    Resample_Real filter_scale,
    Resample_Real src_ofs) {
  typedef struct
  {
    // The center of the range in DISCRETE coordinates (pixel center = 0.0f).
    Resample_Real center;
    int left, right;
  } Contrib_Bounds;

  int i, j, k, n, left, right;
  Resample_Real total_weight;
  Resample_Real xscale, center, half_width, weight;
  Contrib_List* Pcontrib;
  Contrib* Pcpool;
  Contrib* Pcpool_next;
  Contrib_Bounds* Pcontrib_bounds;

  if ((Pcontrib = (Contrib_List*)crnlib_calloc(dst_x, sizeof(Contrib_List))) == NULL)
    return NULL;

  Pcontrib_bounds = (Contrib_Bounds*)crnlib_calloc(dst_x, sizeof(Contrib_Bounds));
  if (!Pcontrib_bounds) {
    crnlib_free(Pcontrib);
    return (NULL);
  }

  const Resample_Real oo_filter_scale = 1.0f / filter_scale;

  const Resample_Real NUDGE = 0.5f;
  xscale = dst_x / (Resample_Real)src_x;

  if (xscale < 1.0f) {
    int total;
    (void)total;

    /* Handle case when there are fewer destination
         * samples than source samples (downsampling/minification).
         */

    // stretched half width of filter
    half_width = (filter_support / xscale) * filter_scale;

    // Find the range of source sample(s) that will contribute to each destination sample.

    for (i = 0, n = 0; i < dst_x; i++) {
      // Convert from discrete to continuous coordinates, scale, then convert back to discrete.
      center = ((Resample_Real)i + NUDGE) / xscale;
      center -= NUDGE;
      center += src_ofs;

      left = cast_to_int((Resample_Real)floor(center - half_width));
      right = cast_to_int((Resample_Real)ceil(center + half_width));

      Pcontrib_bounds[i].center = center;
      Pcontrib_bounds[i].left = left;
      Pcontrib_bounds[i].right = right;

      n += (right - left + 1);
    }

    /* Allocate memory for contributors. */

    if ((n == 0) || ((Pcpool = (Contrib*)crnlib_calloc(n, sizeof(Contrib))) == NULL)) {
      crnlib_free(Pcontrib);
      crnlib_free(Pcontrib_bounds);
      return NULL;
    }
    total = n;

    Pcpool_next = Pcpool;

    /* Create the list of source samples which
         * contribute to each destination sample.
         */

    for (i = 0; i < dst_x; i++) {
      int max_k = -1;
      Resample_Real max_w = -1e+20f;

      center = Pcontrib_bounds[i].center;
      left = Pcontrib_bounds[i].left;
      right = Pcontrib_bounds[i].right;

      Pcontrib[i].n = 0;
      Pcontrib[i].p = Pcpool_next;
      Pcpool_next += (right - left + 1);
      resampler_assert((Pcpool_next - Pcpool) <= total);

      total_weight = 0;

      for (j = left; j <= right; j++)
        total_weight += (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale);
      const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);

      total_weight = 0;

#if RESAMPLER_DEBUG
      printf("%i: ", i);
#endif

      for (j = left; j <= right; j++) {
        weight = (*Pfilter)((center - (Resample_Real)j) * xscale * oo_filter_scale) * norm;
        if (weight == 0.0f)
          continue;

        n = reflect(j, src_x, boundary_op);

#if RESAMPLER_DEBUG
        printf("%i(%f), ", n, weight);
#endif

        /* Increment the number of source
               * samples which contribute to the
               * current destination sample.
               */

        k = Pcontrib[i].n++;

        Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */
        Pcontrib[i].p[k].weight = weight;           /* store src sample weight */

        total_weight += weight; /* total weight of all contributors */

        if (weight > max_w) {
          max_w = weight;
          max_k = k;
        }
      }

#if RESAMPLER_DEBUG
      printf("\n\n");
#endif

      //resampler_assert(Pcontrib[i].n);
      //resampler_assert(max_k != -1);
      if ((max_k == -1) || (Pcontrib[i].n == 0)) {
        crnlib_free(Pcpool);
        crnlib_free(Pcontrib);
        crnlib_free(Pcontrib_bounds);
        return NULL;
      }

      if (total_weight != 1.0f)
        Pcontrib[i].p[max_k].weight += 1.0f - total_weight;
    }
  } else {
    /* Handle case when there are more
         * destination samples than source
         * samples (upsampling).
         */

    half_width = filter_support * filter_scale;

    // Find the source sample(s) that contribute to each destination sample.

    for (i = 0, n = 0; i < dst_x; i++) {
      // Convert from discrete to continuous coordinates, scale, then convert back to discrete.
      center = ((Resample_Real)i + NUDGE) / xscale;
      center -= NUDGE;
      center += src_ofs;

      left = cast_to_int((Resample_Real)floor(center - half_width));
      right = cast_to_int((Resample_Real)ceil(center + half_width));

      Pcontrib_bounds[i].center = center;
      Pcontrib_bounds[i].left = left;
      Pcontrib_bounds[i].right = right;

      n += (right - left + 1);
    }

    /* Allocate memory for contributors. */

    int total = n;
    if ((total == 0) || ((Pcpool = (Contrib*)crnlib_calloc(total, sizeof(Contrib))) == NULL)) {
      crnlib_free(Pcontrib);
      crnlib_free(Pcontrib_bounds);
      return NULL;
    }

    Pcpool_next = Pcpool;

    /* Create the list of source samples which
         * contribute to each destination sample.
         */

    for (i = 0; i < dst_x; i++) {
      int max_k = -1;
      Resample_Real max_w = -1e+20f;

      center = Pcontrib_bounds[i].center;
      left = Pcontrib_bounds[i].left;
      right = Pcontrib_bounds[i].right;

      Pcontrib[i].n = 0;
      Pcontrib[i].p = Pcpool_next;
      Pcpool_next += (right - left + 1);
      resampler_assert((Pcpool_next - Pcpool) <= total);

      total_weight = 0;
      for (j = left; j <= right; j++)
        total_weight += (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale);

      const Resample_Real norm = static_cast<Resample_Real>(1.0f / total_weight);

      total_weight = 0;

#if RESAMPLER_DEBUG
      printf("%i: ", i);
#endif

      for (j = left; j <= right; j++) {
        weight = (*Pfilter)((center - (Resample_Real)j) * oo_filter_scale) * norm;
        if (weight == 0.0f)
          continue;

        n = reflect(j, src_x, boundary_op);

#if RESAMPLER_DEBUG
        printf("%i(%f), ", n, weight);
#endif

        /* Increment the number of source
               * samples which contribute to the
               * current destination sample.
               */

        k = Pcontrib[i].n++;

        Pcontrib[i].p[k].pixel = (unsigned short)n; /* store src sample number */
        Pcontrib[i].p[k].weight = weight;           /* store src sample weight */

        total_weight += weight; /* total weight of all contributors */

        if (weight > max_w) {
          max_w = weight;
          max_k = k;
        }
      }

#if RESAMPLER_DEBUG
      printf("\n\n");
#endif

      //resampler_assert(Pcontrib[i].n);
      //resampler_assert(max_k != -1);

      if ((max_k == -1) || (Pcontrib[i].n == 0)) {
        crnlib_free(Pcpool);
        crnlib_free(Pcontrib);
        crnlib_free(Pcontrib_bounds);
        return NULL;
      }

      if (total_weight != 1.0f)
        Pcontrib[i].p[max_k].weight += 1.0f - total_weight;
    }
  }

#if RESAMPLER_DEBUG
  printf("*******\n");
#endif

  crnlib_free(Pcontrib_bounds);

  return Pcontrib;
}

void Resampler::resample_x(Sample* Pdst, const Sample* Psrc) {
  resampler_assert(Pdst);
  resampler_assert(Psrc);

  int i, j;
  Sample total;
  Contrib_List* Pclist = m_Pclist_x;
  Contrib* p;

  for (i = m_resample_dst_x; i > 0; i--, Pclist++) {
#if CRNLIB_RESAMPLER_DEBUG_OPS
    total_ops += Pclist->n;
#endif

    for (j = Pclist->n, p = Pclist->p, total = 0; j > 0; j--, p++)
      total += Psrc[p->pixel] * p->weight;

    *Pdst++ = total;
  }
}

void Resampler::scale_y_mov(Sample* Ptmp, const Sample* Psrc, Resample_Real weight, int dst_x) {
  int i;

#if CRNLIB_RESAMPLER_DEBUG_OPS
  total_ops += dst_x;
#endif

  // Not += because temp buf wasn't cleared.
  for (i = dst_x; i > 0; i--)
    *Ptmp++ = *Psrc++ * weight;
}

void Resampler::scale_y_add(Sample* Ptmp, const Sample* Psrc, Resample_Real weight, int dst_x) {
#if CRNLIB_RESAMPLER_DEBUG_OPS
  total_ops += dst_x;
#endif

  for (int i = dst_x; i > 0; i--)
    (*Ptmp++) += *Psrc++ * weight;
}

void Resampler::clamp(Sample* Pdst, int n) {
  while (n > 0) {
    Sample x = *Pdst;
    *Pdst++ = clamp_sample(x);
    n--;
  }
}

void Resampler::resample_y(Sample* Pdst) {
  int i, j;
  Sample* Psrc;
  Contrib_List* Pclist = &m_Pclist_y[m_cur_dst_y];

  Sample* Ptmp = m_delay_x_resample ? m_Ptmp_buf : Pdst;
  resampler_assert(Ptmp);

  /* Process each contributor. */

  for (i = 0; i < Pclist->n; i++) {
    /* locate the contributor's location in the scan
         * buffer -- the contributor must always be found!
         */

    for (j = 0; j < MAX_SCAN_BUF_SIZE; j++)
      if (m_Pscan_buf->scan_buf_y[j] == Pclist->p[i].pixel)
        break;

    resampler_assert(j < MAX_SCAN_BUF_SIZE);

    Psrc = m_Pscan_buf->scan_buf_l[j];

    if (!i)
      scale_y_mov(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);
    else
      scale_y_add(Ptmp, Psrc, Pclist->p[i].weight, m_intermediate_x);

    /* If this source line doesn't contribute to any
         * more destination lines then mark the scanline buffer slot
         * which holds this source line as free.
         * (The max. number of slots used depends on the Y
         * axis sampling factor and the scaled filter width.)
         */

    if (--m_Psrc_y_count[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] == 0) {
      m_Psrc_y_flag[resampler_range_check(Pclist->p[i].pixel, m_resample_src_y)] = FALSE;
      m_Pscan_buf->scan_buf_y[j] = -1;
    }
  }

  /* Now generate the destination line */

  if (m_delay_x_resample)  // Was X resampling delayed until after Y resampling?
  {
    resampler_assert(Pdst != Ptmp);
    resample_x(Pdst, Ptmp);
  } else {
    resampler_assert(Pdst == Ptmp);
  }

  if (m_lo < m_hi)
    clamp(Pdst, m_resample_dst_x);
}

bool Resampler::put_line(const Sample* Psrc) {
  int i;

  if (m_cur_src_y >= m_resample_src_y)
    return false;

  /* Does this source line contribute
      * to any destination line? if not,
      * exit now.
      */

  if (!m_Psrc_y_count[resampler_range_check(m_cur_src_y, m_resample_src_y)]) {
    m_cur_src_y++;
    return true;
  }

  /* Find an empty slot in the scanline buffer. (FIXME: Perf. is terrible here with extreme scaling ratios.) */

  for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
    if (m_Pscan_buf->scan_buf_y[i] == -1)
      break;

  /* If the buffer is full, exit with an error. */

  if (i == MAX_SCAN_BUF_SIZE) {
    m_status = STATUS_SCAN_BUFFER_FULL;
    return false;
  }

  m_Psrc_y_flag[resampler_range_check(m_cur_src_y, m_resample_src_y)] = TRUE;
  m_Pscan_buf->scan_buf_y[i] = m_cur_src_y;

  /* Does this slot have any memory allocated to it? */

  if (!m_Pscan_buf->scan_buf_l[i]) {
    if ((m_Pscan_buf->scan_buf_l[i] = (Sample*)crnlib_malloc(m_intermediate_x * sizeof(Sample))) == NULL) {
      m_status = STATUS_OUT_OF_MEMORY;
      return false;
    }
  }

  // Resampling on the X axis first?
  if (m_delay_x_resample) {
    resampler_assert(m_intermediate_x == m_resample_src_x);

    // Y-X resampling order
    memcpy(m_Pscan_buf->scan_buf_l[i], Psrc, m_intermediate_x * sizeof(Sample));
  } else {
    resampler_assert(m_intermediate_x == m_resample_dst_x);

    // X-Y resampling order
    resample_x(m_Pscan_buf->scan_buf_l[i], Psrc);
  }

  m_cur_src_y++;

  return true;
}

const Resampler::Sample* Resampler::get_line() {
  int i;

  /* If all the destination lines have been
      * generated, then always return NULL.
      */

  if (m_cur_dst_y == m_resample_dst_y)
    return NULL;

  /* Check to see if all the required
      * contributors are present, if not,
      * return NULL.
      */

  for (i = 0; i < m_Pclist_y[m_cur_dst_y].n; i++)
    if (!m_Psrc_y_flag[resampler_range_check(m_Pclist_y[m_cur_dst_y].p[i].pixel, m_resample_src_y)])
      return NULL;

  resample_y(m_Pdst_buf);

  m_cur_dst_y++;

  return m_Pdst_buf;
}

Resampler::~Resampler() {
  int i;

#if CRNLIB_RESAMPLER_DEBUG_OPS
  printf("actual ops: %i\n", total_ops);
#endif

  crnlib_free(m_Pdst_buf);
  m_Pdst_buf = NULL;

  if (m_Ptmp_buf) {
    crnlib_free(m_Ptmp_buf);
    m_Ptmp_buf = NULL;
  }

  /* Don't deallocate a contibutor list
      * if the user passed us one of their own.
      */

  if ((m_Pclist_x) && (!m_clist_x_forced)) {
    crnlib_free(m_Pclist_x->p);
    crnlib_free(m_Pclist_x);
    m_Pclist_x = NULL;
  }

  if ((m_Pclist_y) && (!m_clist_y_forced)) {
    crnlib_free(m_Pclist_y->p);
    crnlib_free(m_Pclist_y);
    m_Pclist_y = NULL;
  }

  crnlib_free(m_Psrc_y_count);
  m_Psrc_y_count = NULL;

  crnlib_free(m_Psrc_y_flag);
  m_Psrc_y_flag = NULL;

  if (m_Pscan_buf) {
    for (i = 0; i < MAX_SCAN_BUF_SIZE; i++)
      crnlib_free(m_Pscan_buf->scan_buf_l[i]);

    crnlib_free(m_Pscan_buf);
    m_Pscan_buf = NULL;
  }
}

void Resampler::restart() {
  if (STATUS_OKAY != m_status)
    return;

  m_cur_src_y = m_cur_dst_y = 0;

  int i, j;
  for (i = 0; i < m_resample_src_y; i++) {
    m_Psrc_y_count[i] = 0;
    m_Psrc_y_flag[i] = FALSE;
  }

  for (i = 0; i < m_resample_dst_y; i++) {
    for (j = 0; j < m_Pclist_y[i].n; j++)
      m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;
  }

  for (i = 0; i < MAX_SCAN_BUF_SIZE; i++) {
    m_Pscan_buf->scan_buf_y[i] = -1;

    crnlib_free(m_Pscan_buf->scan_buf_l[i]);
    m_Pscan_buf->scan_buf_l[i] = NULL;
  }
}

Resampler::Resampler(int src_x, int src_y,
                     int dst_x, int dst_y,
                     Boundary_Op boundary_op,
                     Resample_Real sample_low, Resample_Real sample_high,
                     const char* Pfilter_name,
                     Contrib_List* Pclist_x,
                     Contrib_List* Pclist_y,
                     Resample_Real filter_x_scale,
                     Resample_Real filter_y_scale,
                     Resample_Real src_x_ofs,
                     Resample_Real src_y_ofs) {
  int i, j;
  Resample_Real support, (*func)(Resample_Real);

  resampler_assert(src_x > 0);
  resampler_assert(src_y > 0);
  resampler_assert(dst_x > 0);
  resampler_assert(dst_y > 0);

#if CRNLIB_RESAMPLER_DEBUG_OPS
  total_ops = 0;
#endif

  m_lo = sample_low;
  m_hi = sample_high;

  m_delay_x_resample = false;
  m_intermediate_x = 0;
  m_Pdst_buf = NULL;
  m_Ptmp_buf = NULL;
  m_clist_x_forced = false;
  m_Pclist_x = NULL;
  m_clist_y_forced = false;
  m_Pclist_y = NULL;
  m_Psrc_y_count = NULL;
  m_Psrc_y_flag = NULL;
  m_Pscan_buf = NULL;
  m_status = STATUS_OKAY;

  m_resample_src_x = src_x;
  m_resample_src_y = src_y;
  m_resample_dst_x = dst_x;
  m_resample_dst_y = dst_y;

  m_boundary_op = boundary_op;

  if ((m_Pdst_buf = (Sample*)crnlib_malloc(m_resample_dst_x * sizeof(Sample))) == NULL) {
    m_status = STATUS_OUT_OF_MEMORY;
    return;
  }

  // Find the specified filter.

  if (Pfilter_name == NULL)
    Pfilter_name = CRNLIB_RESAMPLER_DEFAULT_FILTER;

  for (i = 0; i < g_num_resample_filters; i++)
    if (strcmp(Pfilter_name, g_resample_filters[i].name) == 0)
      break;

  if (i == g_num_resample_filters) {
    m_status = STATUS_BAD_FILTER_NAME;
    return;
  }

  func = g_resample_filters[i].func;
  support = g_resample_filters[i].support;

  /* Create contributor lists, unless the user supplied custom lists. */

  if (!Pclist_x) {
    m_Pclist_x = make_clist(m_resample_src_x, m_resample_dst_x, m_boundary_op, func, support, filter_x_scale, src_x_ofs);
    if (!m_Pclist_x) {
      m_status = STATUS_OUT_OF_MEMORY;
      return;
    }
  } else {
    m_Pclist_x = Pclist_x;
    m_clist_x_forced = true;
  }

  if (!Pclist_y) {
    m_Pclist_y = make_clist(m_resample_src_y, m_resample_dst_y, m_boundary_op, func, support, filter_y_scale, src_y_ofs);
    if (!m_Pclist_y) {
      m_status = STATUS_OUT_OF_MEMORY;
      return;
    }
  } else {
    m_Pclist_y = Pclist_y;
    m_clist_y_forced = true;
  }

  if ((m_Psrc_y_count = (int*)crnlib_calloc(m_resample_src_y, sizeof(int))) == NULL) {
    m_status = STATUS_OUT_OF_MEMORY;
    return;
  }

  if ((m_Psrc_y_flag = (unsigned char*)crnlib_calloc(m_resample_src_y, sizeof(unsigned char))) == NULL) {
    m_status = STATUS_OUT_OF_MEMORY;
    return;
  }

  /* Count how many times each source line
      * contributes to a destination line.
      */

  for (i = 0; i < m_resample_dst_y; i++)
    for (j = 0; j < m_Pclist_y[i].n; j++)
      m_Psrc_y_count[resampler_range_check(m_Pclist_y[i].p[j].pixel, m_resample_src_y)]++;

  if ((m_Pscan_buf = (Scan_Buf*)crnlib_malloc(sizeof(Scan_Buf))) == NULL) {
    m_status = STATUS_OUT_OF_MEMORY;
    return;
  }

  for (i = 0; i < MAX_SCAN_BUF_SIZE; i++) {
    m_Pscan_buf->scan_buf_y[i] = -1;
    m_Pscan_buf->scan_buf_l[i] = NULL;
  }

  m_cur_src_y = m_cur_dst_y = 0;
  {
    // Determine which axis to resample first by comparing the number of multiplies required
    // for each possibility.
    int x_ops = count_ops(m_Pclist_x, m_resample_dst_x);
    int y_ops = count_ops(m_Pclist_y, m_resample_dst_y);

    // Hack 10/2000: Weight Y axis ops a little more than X axis ops.
    // (Y axis ops use more cache resources.)
    int xy_ops = x_ops * m_resample_src_y +
                 (4 * y_ops * m_resample_dst_x) / 3;

    int yx_ops = (4 * y_ops * m_resample_src_x) / 3 +
                 x_ops * m_resample_dst_y;

#if CRNLIB_RESAMPLER_DEBUG_OPS
    printf("src: %i %i\n", m_resample_src_x, m_resample_src_y);
    printf("dst: %i %i\n", m_resample_dst_x, m_resample_dst_y);
    printf("x_ops: %i\n", x_ops);
    printf("y_ops: %i\n", y_ops);
    printf("xy_ops: %i\n", xy_ops);
    printf("yx_ops: %i\n", yx_ops);
#endif

    // Now check which resample order is better. In case of a tie, choose the order
    // which buffers the least amount of data.
    if ((xy_ops > yx_ops) ||
        ((xy_ops == yx_ops) && (m_resample_src_x < m_resample_dst_x))) {
      m_delay_x_resample = true;
      m_intermediate_x = m_resample_src_x;
    } else {
      m_delay_x_resample = false;
      m_intermediate_x = m_resample_dst_x;
    }
#if CRNLIB_RESAMPLER_DEBUG_OPS
    printf("delaying: %i\n", m_delay_x_resample);
#endif
  }

  if (m_delay_x_resample) {
    if ((m_Ptmp_buf = (Sample*)crnlib_malloc(m_intermediate_x * sizeof(Sample))) == NULL) {
      m_status = STATUS_OUT_OF_MEMORY;
      return;
    }
  }
}

void Resampler::get_clists(Contrib_List** ptr_clist_x, Contrib_List** ptr_clist_y) {
  if (ptr_clist_x)
    *ptr_clist_x = m_Pclist_x;

  if (ptr_clist_y)
    *ptr_clist_y = m_Pclist_y;
}

int Resampler::get_filter_num() {
  return g_num_resample_filters;
}

const char* Resampler::get_filter_name(int filter_num) {
  if ((filter_num < 0) || (filter_num >= g_num_resample_filters))
    return NULL;
  else
    return g_resample_filters[filter_num].name;
}

}  // namespace crnlib
