/* GStreamer
 * Copyright (C) <2014> Wim Taymans <wim.taymans@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <string.h>
#include <stdio.h>
#include <math.h>

/**
 * SECTION:gstvideoscaler
 * @title: GstVideoScaler
 * @short_description: Utility object for rescaling video frames
 *
 * #GstVideoScaler is a utility object for rescaling and resampling
 * video frames using various interpolation / sampling methods.
 *
 */

#ifndef DISABLE_ORC
#include <orc/orcfunctions.h>
#else
#define orc_memcpy memcpy
#endif

#include "video-orc.h"
#include "video-scaler.h"

#ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT ensure_debug_category()
static GstDebugCategory *
ensure_debug_category (void)
{
  static gsize cat_gonce = 0;

  if (g_once_init_enter (&cat_gonce)) {
    gsize cat_done;

    cat_done = (gsize) _gst_debug_category_new ("video-scaler", 0,
        "video-scaler object");

    g_once_init_leave (&cat_gonce, cat_done);
  }

  return (GstDebugCategory *) cat_gonce;
}

#else
#define ensure_debug_category() /* NOOP */
#endif /* GST_DISABLE_GST_DEBUG */

#define SCALE_U8          12
#define SCALE_U8_ROUND    (1 << (SCALE_U8 -1))
#define SCALE_U8_LQ       6
#define SCALE_U8_LQ_ROUND (1 << (SCALE_U8_LQ -1))
#define SCALE_U16         12
#define SCALE_U16_ROUND   (1 << (SCALE_U16 -1))

#define LQ

typedef void (*GstVideoScalerHFunc) (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems);
typedef void (*GstVideoScalerVFunc) (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems);

struct _GstVideoScaler
{
  GstVideoResamplerMethod method;
  GstVideoScalerFlags flags;

  GstVideoResampler resampler;

  gboolean merged;
  gint in_y_offset;
  gint out_y_offset;

  /* cached integer coefficients */
  gint16 *taps_s16;
  gint16 *taps_s16_4;
  guint32 *offset_n;
  /* for ORC */
  gint inc;

  gint tmpwidth;
  gpointer tmpline1;
  gpointer tmpline2;
};

static void
resampler_zip (GstVideoResampler * resampler, const GstVideoResampler * r1,
    const GstVideoResampler * r2)
{
  guint i, out_size, max_taps, n_phases;
  gdouble *taps;
  guint32 *offset, *phase;

  g_return_if_fail (r1->max_taps == r2->max_taps);

  out_size = r1->out_size + r2->out_size;
  max_taps = r1->max_taps;
  n_phases = out_size;
  offset = g_malloc (sizeof (guint32) * out_size);
  phase = g_malloc (sizeof (guint32) * n_phases);
  taps = g_malloc (sizeof (gdouble) * max_taps * n_phases);

  resampler->in_size = r1->in_size + r2->in_size;
  resampler->out_size = out_size;
  resampler->max_taps = max_taps;
  resampler->n_phases = n_phases;
  resampler->offset = offset;
  resampler->phase = phase;
  resampler->n_taps = g_malloc (sizeof (guint32) * out_size);
  resampler->taps = taps;

  for (i = 0; i < out_size; i++) {
    guint idx = i / 2;
    const GstVideoResampler *r;

    r = (i & 1) ? r2 : r1;

    offset[i] = r->offset[idx] * 2 + (i & 1);
    phase[i] = i;

    memcpy (taps + i * max_taps, r->taps + r->phase[idx] * max_taps,
        max_taps * sizeof (gdouble));
  }
}

static void
realloc_tmplines (GstVideoScaler * scale, gint n_elems, gint width)
{
  gint n_taps = scale->resampler.max_taps;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    n_taps *= 2;

  scale->tmpline1 =
      g_realloc (scale->tmpline1, sizeof (gint32) * width * n_elems * n_taps);
  scale->tmpline2 =
      g_realloc (scale->tmpline2, sizeof (gint32) * width * n_elems);
  scale->tmpwidth = width;
}

static void
scaler_dump (GstVideoScaler * scale)
{
#if 0
  gint i, j, in_size, out_size, max_taps;
  guint32 *offset, *phase;
  gdouble *taps;
  GstVideoResampler *r = &scale->resampler;

  in_size = r->in_size;
  out_size = r->out_size;
  offset = r->offset;
  phase = r->phase;
  max_taps = r->max_taps;
  taps = r->taps;

  g_print ("in %d, out %d, max_taps %d, n_phases %d\n", in_size, out_size,
      max_taps, r->n_phases);

  for (i = 0; i < out_size; i++) {
    g_print ("%d: \t%d \t%d:", i, offset[i], phase[i]);

    for (j = 0; j < max_taps; j++) {
      g_print ("\t%f", taps[i * max_taps + j]);
    }
    g_print ("\n");
  }
#endif
}

#define INTERLACE_SHIFT 0.5

/**
 * gst_video_scaler_new: (skip)
 * @method: a #GstVideoResamplerMethod
 * @flags: #GstVideoScalerFlags
 * @n_taps: number of taps to use
 * @in_size: number of source elements
 * @out_size: number of destination elements
 * @options: (allow-none): extra options
 *
 * Make a new @method video scaler. @in_size source lines/pixels will
 * be scaled to @out_size destination lines/pixels.
 *
 * @n_taps specifies the amount of pixels to use from the source for one output
 * pixel. If n_taps is 0, this function chooses a good value automatically based
 * on the @method and @in_size/@out_size.
 *
 * Returns: a #GstVideoScaler
 */
GstVideoScaler *
gst_video_scaler_new (GstVideoResamplerMethod method, GstVideoScalerFlags flags,
    guint n_taps, guint in_size, guint out_size, GstStructure * options)
{
  GstVideoScaler *scale;

  g_return_val_if_fail (in_size != 0, NULL);
  g_return_val_if_fail (out_size != 0, NULL);

  scale = g_slice_new0 (GstVideoScaler);

  GST_DEBUG ("%d %u  %u->%u", method, n_taps, in_size, out_size);

  scale->method = method;
  scale->flags = flags;

  if (flags & GST_VIDEO_SCALER_FLAG_INTERLACED) {
    GstVideoResampler tresamp, bresamp;
    gdouble shift;

    shift = (INTERLACE_SHIFT * out_size) / in_size;

    gst_video_resampler_init (&tresamp, method,
        GST_VIDEO_RESAMPLER_FLAG_HALF_TAPS, (out_size + 1) / 2, n_taps, shift,
        (in_size + 1) / 2, (out_size + 1) / 2, options);

    n_taps = tresamp.max_taps;

    gst_video_resampler_init (&bresamp, method, 0, out_size - tresamp.out_size,
        n_taps, -shift, in_size - tresamp.in_size,
        out_size - tresamp.out_size, options);

    resampler_zip (&scale->resampler, &tresamp, &bresamp);
    gst_video_resampler_clear (&tresamp);
    gst_video_resampler_clear (&bresamp);
  } else {
    gst_video_resampler_init (&scale->resampler, method,
        GST_VIDEO_RESAMPLER_FLAG_NONE, out_size, n_taps, 0.0, in_size, out_size,
        options);
  }

  if (out_size == 1)
    scale->inc = 0;
  else
    scale->inc = ((in_size - 1) << 16) / (out_size - 1) - 1;

  scaler_dump (scale);
  GST_DEBUG ("max_taps %d", scale->resampler.max_taps);

  return scale;
}

/**
 * gst_video_scaler_free:
 * @scale: a #GstVideoScaler
 *
 * Free a previously allocated #GstVideoScaler @scale.
 */
void
gst_video_scaler_free (GstVideoScaler * scale)
{
  g_return_if_fail (scale != NULL);

  gst_video_resampler_clear (&scale->resampler);
  g_free (scale->taps_s16);
  g_free (scale->taps_s16_4);
  g_free (scale->offset_n);
  g_free (scale->tmpline1);
  g_free (scale->tmpline2);
  g_slice_free (GstVideoScaler, scale);
}

/**
 * gst_video_scaler_get_max_taps:
 * @scale: a #GstVideoScaler
 *
 * Get the maximum number of taps for @scale.
 *
 * Returns: the maximum number of taps
 */
guint
gst_video_scaler_get_max_taps (GstVideoScaler * scale)
{
  g_return_val_if_fail (scale != NULL, 0);

  return scale->resampler.max_taps;
}

/**
 * gst_video_scaler_get_coeff:
 * @scale: a #GstVideoScaler
 * @out_offset: an output offset
 * @in_offset: result input offset
 * @n_taps: result n_taps
 *
 * For a given pixel at @out_offset, get the first required input pixel at
 * @in_offset and the @n_taps filter coefficients.
 *
 * Note that for interlaced content, @in_offset needs to be incremented with
 * 2 to get the next input line.
 *
 * Returns: an array of @n_tap gdouble values with filter coefficients.
 */
const gdouble *
gst_video_scaler_get_coeff (GstVideoScaler * scale,
    guint out_offset, guint * in_offset, guint * n_taps)
{
  guint offset, phase;

  g_return_val_if_fail (scale != NULL, NULL);
  g_return_val_if_fail (out_offset < scale->resampler.out_size, NULL);

  offset = scale->resampler.offset[out_offset];
  phase = scale->resampler.phase[out_offset];

  if (in_offset)
    *in_offset = offset;
  if (n_taps) {
    *n_taps = scale->resampler.max_taps;
    if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
      *n_taps *= 2;
  }
  return scale->resampler.taps + phase * scale->resampler.max_taps;
}

static gboolean
resampler_convert_coeff (const gdouble * src,
    gpointer dest, guint n, guint bits, guint precision)
{
  gdouble multiplier;
  gint i, j;
  gdouble offset, l_offset, h_offset;
  gboolean exact = FALSE;

  multiplier = (1 << precision);

  /* Round to integer, but with an adjustable bias that we use to
   * eliminate the DC error. */
  l_offset = 0.0;
  h_offset = 1.0;
  offset = 0.5;

  for (i = 0; i < 64; i++) {
    gint sum = 0;

    for (j = 0; j < n; j++) {
      gint16 tap = floor (offset + src[j] * multiplier);

      ((gint16 *) dest)[j] = tap;

      sum += tap;
    }
    if (sum == (1 << precision)) {
      exact = TRUE;
      break;
    }

    if (l_offset == h_offset)
      break;

    if (sum < (1 << precision)) {
      if (offset > l_offset)
        l_offset = offset;
      offset += (h_offset - l_offset) / 2;
    } else {
      if (offset < h_offset)
        h_offset = offset;
      offset -= (h_offset - l_offset) / 2;
    }
  }

  if (!exact)
    GST_WARNING ("can't find exact taps");

  return exact;
}

static void
make_s16_taps (GstVideoScaler * scale, gint n_elems, gint precision)
{
  gint i, j, max_taps, n_phases, out_size, src_inc;
  gint16 *taps_s16, *taps_s16_4;
  gdouble *taps;
  guint32 *phase, *offset, *offset_n;

  n_phases = scale->resampler.n_phases;
  max_taps = scale->resampler.max_taps;

  taps = scale->resampler.taps;
  taps_s16 = scale->taps_s16 = g_malloc (sizeof (gint16) * n_phases * max_taps);

  for (i = 0; i < n_phases; i++) {
    resampler_convert_coeff (taps, taps_s16, max_taps, 16, precision);

    taps += max_taps;
    taps_s16 += max_taps;
  }

  out_size = scale->resampler.out_size;

  taps_s16 = scale->taps_s16;
  phase = scale->resampler.phase;
  offset = scale->resampler.offset;

  taps_s16_4 = scale->taps_s16_4 =
      g_malloc (sizeof (gint16) * out_size * max_taps * 4);
  offset_n = scale->offset_n =
      g_malloc (sizeof (guint32) * out_size * max_taps);

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  for (j = 0; j < max_taps; j++) {
    for (i = 0; i < out_size; i++) {
      gint16 tap;

      if (scale->merged) {
        if ((i & 1) == scale->out_y_offset)
          offset_n[j * out_size + i] = offset[i] + (2 * j);
        else
          offset_n[j * out_size + i] = offset[i] + (4 * j);
      } else {
        offset_n[j * out_size + i] = offset[i] + j * src_inc;
      }
      tap = taps_s16[phase[i] * max_taps + j];
      taps_s16_4[(j * out_size + i) * n_elems + 0] = tap;
      if (n_elems > 1)
        taps_s16_4[(j * out_size + i) * n_elems + 1] = tap;
      if (n_elems > 2)
        taps_s16_4[(j * out_size + i) * n_elems + 2] = tap;
      if (n_elems > 3)
        taps_s16_4[(j * out_size + i) * n_elems + 3] = tap;
    }
  }
}

#undef ACC_SCALE

static void
video_scale_h_near_u8 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint8 *s, *d;
  gint i;

  d = (guint8 *) dest + dest_offset;
  s = (guint8 *) src;

  {
#ifndef ACC_SCALE
    guint32 *offset = scale->resampler.offset + dest_offset;

    for (i = 0; i < width; i++)
      d[i] = s[offset[i]];
#else
    gint acc = 0;

    for (i = 0; i < width; i++) {
      gint j = (acc + 0x8000) >> 16;
      d[i] = s[j];
      acc += scale->inc;
    }
#endif
  }
}

static void
video_scale_h_near_3u8 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint8 *s, *d;
  gint i;

  d = (guint8 *) dest + dest_offset;
  s = (guint8 *) src;

  {
#ifndef ACC_SCALE
    guint32 *offset = scale->resampler.offset + dest_offset;

    for (i = 0; i < width; i++) {
      gint j = offset[i] * 3;

      d[i * 3 + 0] = s[j + 0];
      d[i * 3 + 1] = s[j + 1];
      d[i * 3 + 2] = s[j + 2];
    }
#else
    gint acc = 0;

    for (i = 0; i < width; i++) {
      gint j = ((acc + 0x8000) >> 16) * 3;

      d[i * 3 + 0] = s[j + 0];
      d[i * 3 + 1] = s[j + 1];
      d[i * 3 + 2] = s[j + 2];
      acc += scale->inc;
    }
#endif
  }
}

static void
video_scale_h_near_u16 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint16 *s, *d;
  gint i;

  d = (guint16 *) dest + dest_offset;
  s = (guint16 *) src;

  {
#ifndef ACC_SCALE
    guint32 *offset = scale->resampler.offset + dest_offset;

    for (i = 0; i < width; i++)
      d[i] = s[offset[i]];
#else
    gint acc = 0;

    for (i = 0; i < width; i++) {
      gint j = (acc + 0x8000) >> 16;
      d[i] = s[j];
      acc += scale->inc;
    }
#endif
  }
}

static void
video_scale_h_near_u32 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint32 *s, *d;

  d = (guint32 *) dest + dest_offset;
  s = (guint32 *) src;

#if 0
  /* ORC is slower on this */
  video_orc_resample_h_near_u32_lq (d, s, 0, scale->inc, width);
#elif 0
  video_orc_resample_h_near_u32 (d, s, offset, width);
#else
  {
    gint i;
#ifndef ACC_SCALE
    guint32 *offset = scale->resampler.offset + dest_offset;

    for (i = 0; i < width; i++)
      d[i] = s[offset[i]];
#else
    gint acc = 0;

    for (i = 0; i < width; i++) {
      gint j = (acc + 0x8000) >> 16;
      d[i] = s[j];
      acc += scale->inc;
    }
#endif
  }
#endif
}

static void
video_scale_h_near_u64 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint64 *s, *d;
  gint i;
  guint32 *offset;

  d = (guint64 *) dest + dest_offset;
  s = (guint64 *) src;

  offset = scale->resampler.offset + dest_offset;
  for (i = 0; i < width; i++)
    d[i] = s[offset[i]];
}

static void
video_scale_h_2tap_1u8 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint8 *s, *d;

  d = (guint8 *) dest + dest_offset;
  s = (guint8 *) src;

  video_orc_resample_h_2tap_1u8_lq (d, s, 0, scale->inc, width);
}

static void
video_scale_h_2tap_4u8 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  guint32 *s, *d;

  d = (guint32 *) dest + dest_offset;
  s = (guint32 *) src;

  video_orc_resample_h_2tap_4u8_lq (d, s, 0, scale->inc, width);
}

static void
video_scale_h_ntap_u8 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  gint16 *taps;
  gint i, max_taps, count;
  gpointer d;
  guint32 *offset_n;
  guint8 *pixels;
  gint16 *temp;

  if (scale->taps_s16 == NULL)
#ifdef LQ
    make_s16_taps (scale, n_elems, SCALE_U8_LQ);
#else
    make_s16_taps (scale, n_elems, SCALE_U8);
#endif

  max_taps = scale->resampler.max_taps;
  offset_n = scale->offset_n;

  pixels = (guint8 *) scale->tmpline1;

  /* prepare the arrays */
  count = width * max_taps;
  switch (n_elems) {
    case 1:
    {
      guint8 *s = (guint8 *) src;

      for (i = 0; i < count; i++)
        pixels[i] = s[offset_n[i]];

      d = (guint8 *) dest + dest_offset;
      break;
    }
    case 2:
    {
      guint16 *p16 = (guint16 *) pixels;
      guint16 *s = (guint16 *) src;

      for (i = 0; i < count; i++)
        p16[i] = s[offset_n[i]];

      d = (guint16 *) dest + dest_offset;
      break;
    }
    case 3:
    {
      guint8 *s = (guint8 *) src;

      for (i = 0; i < count; i++) {
        gint j = offset_n[i] * 3;
        pixels[i * 3 + 0] = s[j + 0];
        pixels[i * 3 + 1] = s[j + 1];
        pixels[i * 3 + 2] = s[j + 2];
      }
      d = (guint8 *) dest + dest_offset * 3;
      break;
    }
    case 4:
    {
      guint32 *p32 = (guint32 *) pixels;
      guint32 *s = (guint32 *) src;
#if 0
      video_orc_resample_h_near_u32 (p32, s, offset_n, count);
#else
      for (i = 0; i < count; i++)
        p32[i] = s[offset_n[i]];
#endif
      d = (guint32 *) dest + dest_offset;
      break;
    }
    default:
      return;
  }
  temp = (gint16 *) scale->tmpline2;
  taps = scale->taps_s16_4;
  count = width * n_elems;

#ifdef LQ
  if (max_taps == 2) {
    video_orc_resample_h_2tap_u8_lq (d, pixels, pixels + count, taps,
        taps + count, count);
  } else {
    /* first pixels with first tap to temp */
    if (max_taps >= 3) {
      video_orc_resample_h_multaps3_u8_lq (temp, pixels, pixels + count,
          pixels + count * 2, taps, taps + count, taps + count * 2, count);
      max_taps -= 3;
      pixels += count * 3;
      taps += count * 3;
    } else {
      gint first = max_taps % 3;

      video_orc_resample_h_multaps_u8_lq (temp, pixels, taps, count);
      video_orc_resample_h_muladdtaps_u8_lq (temp, 0, pixels + count, count,
          taps + count, count * 2, count, first - 1);
      max_taps -= first;
      pixels += count * first;
      taps += count * first;
    }
    while (max_taps > 3) {
      if (max_taps >= 6) {
        video_orc_resample_h_muladdtaps3_u8_lq (temp, pixels, pixels + count,
            pixels + count * 2, taps, taps + count, taps + count * 2, count);
        max_taps -= 3;
        pixels += count * 3;
        taps += count * 3;
      } else {
        video_orc_resample_h_muladdtaps_u8_lq (temp, 0, pixels, count,
            taps, count * 2, count, max_taps - 3);
        pixels += count * (max_taps - 3);
        taps += count * (max_taps - 3);
        max_taps = 3;
      }
    }
    if (max_taps == 3) {
      video_orc_resample_h_muladdscaletaps3_u8_lq (d, pixels, pixels + count,
          pixels + count * 2, taps, taps + count, taps + count * 2, temp,
          count);
    } else {
      if (max_taps) {
        /* add other pixels with other taps to t4 */
        video_orc_resample_h_muladdtaps_u8_lq (temp, 0, pixels, count,
            taps, count * 2, count, max_taps);
      }
      /* scale and write final result */
      video_orc_resample_scaletaps_u8_lq (d, temp, count);
    }
  }
#else
  /* first pixels with first tap to t4 */
  video_orc_resample_h_multaps_u8 (temp, pixels, taps, count);
  /* add other pixels with other taps to t4 */
  video_orc_resample_h_muladdtaps_u8 (temp, 0, pixels + count, count,
      taps + count, count * 2, count, max_taps - 1);
  /* scale and write final result */
  video_orc_resample_scaletaps_u8 (d, temp, count);
#endif
}

static void
video_scale_h_ntap_u16 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width, guint n_elems)
{
  gint16 *taps;
  gint i, max_taps, count;
  gpointer d;
  guint32 *offset_n;
  guint16 *pixels;
  gint32 *temp;

  if (scale->taps_s16 == NULL)
    make_s16_taps (scale, n_elems, SCALE_U16);

  max_taps = scale->resampler.max_taps;
  offset_n = scale->offset_n;

  pixels = (guint16 *) scale->tmpline1;
  /* prepare the arrays FIXME, we can add this into ORC */
  count = width * max_taps;
  switch (n_elems) {
    case 1:
    {
      guint16 *s = (guint16 *) src;

      for (i = 0; i < count; i++)
        pixels[i] = s[offset_n[i]];

      d = (guint16 *) dest + dest_offset;
      break;
    }
    case 4:
    {
      guint64 *p64 = (guint64 *) pixels;
      guint64 *s = (guint64 *) src;
#if 0
      video_orc_resample_h_near_u32 (p32, s, offset_n, count);
#else
      for (i = 0; i < count; i++)
        p64[i] = s[offset_n[i]];
#endif
      d = (guint64 *) dest + dest_offset;
      break;
    }
    default:
      return;
  }

  temp = (gint32 *) scale->tmpline2;
  taps = scale->taps_s16_4;
  count = width * n_elems;

  if (max_taps == 2) {
    video_orc_resample_h_2tap_u16 (d, pixels, pixels + count, taps,
        taps + count, count);
  } else {
    /* first pixels with first tap to t4 */
    video_orc_resample_h_multaps_u16 (temp, pixels, taps, count);
    /* add other pixels with other taps to t4 */
    video_orc_resample_h_muladdtaps_u16 (temp, 0, pixels + count, count * 2,
        taps + count, count * 2, count, max_taps - 1);
    /* scale and write final result */
    video_orc_resample_scaletaps_u16 (d, temp, count);
  }
}

static void
video_scale_v_near_u8 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  if (dest != srcs[0])
    memcpy (dest, srcs[0], n_elems * width);
}

static void
video_scale_v_near_u16 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  if (dest != srcs[0])
    memcpy (dest, srcs[0], n_elems * 2 * width);
}

static void
video_scale_v_2tap_u8 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  gint max_taps, src_inc;
  guint8 *s1, *s2, *d;
  gint16 p1;

  if (scale->taps_s16 == NULL)
#ifdef LQ
    make_s16_taps (scale, n_elems, SCALE_U8_LQ + 2);
#else
    make_s16_taps (scale, n_elems, SCALE_U8);
#endif

  max_taps = scale->resampler.max_taps;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  d = (guint8 *) dest;
  s1 = (guint8 *) srcs[0 * src_inc];
  s2 = (guint8 *) srcs[1 * src_inc];
  p1 = scale->taps_s16[dest_offset * max_taps + 1];

#ifdef LQ
  video_orc_resample_v_2tap_u8_lq (d, s1, s2, p1, width * n_elems);
#else
  video_orc_resample_v_2tap_u8 (d, s1, s2, p1, width * n_elems);
#endif
}

static void
video_scale_v_2tap_u16 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  gint max_taps, src_inc;
  guint16 *s1, *s2, *d;
  gint16 p1;

  if (scale->taps_s16 == NULL)
    make_s16_taps (scale, n_elems, SCALE_U16);

  max_taps = scale->resampler.max_taps;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  d = (guint16 *) dest;
  s1 = (guint16 *) srcs[0 * src_inc];
  s2 = (guint16 *) srcs[1 * src_inc];
  p1 = scale->taps_s16[dest_offset * max_taps + 1];

  video_orc_resample_v_2tap_u16 (d, s1, s2, p1, width * n_elems);
}

#if 0
static void
video_scale_h_4tap_8888 (GstVideoScaler * scale,
    gpointer src, gpointer dest, guint dest_offset, guint width)
{
  gint16 *taps;
  gint i, max_taps, count;
  guint8 *d;
  guint32 *offset_n;
  guint32 *pixels;

  if (scale->taps_s16 == NULL)
    make_s16_taps (scale, n_elems, S16_SCALE);

  max_taps = scale->resampler.max_taps;
  offset_n = scale->offset_n;

  d = (guint8 *) dest + 4 * dest_offset;

  /* prepare the arrays FIXME, we can add this into ORC */
  count = width * max_taps;
  pixels = (guint32 *) scale->tmpline1;
  for (i = 0; i < count; i++)
    pixels[i] = ((guint32 *) src)[offset_n[i]];

  taps = scale->taps_s16_4;
  count = width * 4;

  video_orc_resample_h_4tap_8 (d, pixels, pixels + width, pixels + 2 * width,
      pixels + 3 * width, taps, taps + count, taps + 2 * count,
      taps + 3 * count, count);
}
#endif

static void
video_scale_v_4tap_u8 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  gint max_taps;
  guint8 *s1, *s2, *s3, *s4, *d;
  gint p1, p2, p3, p4, src_inc;
  gint16 *taps;

  if (scale->taps_s16 == NULL)
#ifdef LQ
    make_s16_taps (scale, n_elems, SCALE_U8_LQ);
#else
    make_s16_taps (scale, n_elems, SCALE_U8);
#endif

  max_taps = scale->resampler.max_taps;
  taps = scale->taps_s16 + dest_offset * max_taps;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  d = (guint8 *) dest;
  s1 = (guint8 *) srcs[0 * src_inc];
  s2 = (guint8 *) srcs[1 * src_inc];
  s3 = (guint8 *) srcs[2 * src_inc];
  s4 = (guint8 *) srcs[3 * src_inc];
  p1 = taps[0];
  p2 = taps[1];
  p3 = taps[2];
  p4 = taps[3];

#ifdef LQ
  video_orc_resample_v_4tap_u8_lq (d, s1, s2, s3, s4, p1, p2, p3, p4,
      width * n_elems);
#else
  video_orc_resample_v_4tap_u8 (d, s1, s2, s3, s4, p1, p2, p3, p4,
      width * n_elems);
#endif
}

static void
video_scale_v_ntap_u8 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  gint16 *taps;
  gint i, max_taps, count, src_inc;
  gpointer d;
  gint16 *temp;

  if (scale->taps_s16 == NULL)
#ifdef LQ
    make_s16_taps (scale, n_elems, SCALE_U8_LQ);
#else
    make_s16_taps (scale, n_elems, SCALE_U8);
#endif

  max_taps = scale->resampler.max_taps;
  taps = scale->taps_s16 + (scale->resampler.phase[dest_offset] * max_taps);

  d = (guint32 *) dest;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  temp = (gint16 *) scale->tmpline2;
  count = width * n_elems;

#ifdef LQ
  if (max_taps >= 4) {
    video_orc_resample_v_multaps4_u8_lq (temp, srcs[0], srcs[1 * src_inc],
        srcs[2 * src_inc], srcs[3 * src_inc], taps[0], taps[1], taps[2],
        taps[3], count);
    max_taps -= 4;
    srcs += 4 * src_inc;
    taps += 4;
  } else {
    gint first = (max_taps % 4);

    video_orc_resample_v_multaps_u8_lq (temp, srcs[0], taps[0], count);
    for (i = 1; i < first; i++) {
      video_orc_resample_v_muladdtaps_u8_lq (temp, srcs[i * src_inc], taps[i],
          count);
    }
    max_taps -= first;
    srcs += first * src_inc;
    taps += first;
  }
  while (max_taps > 4) {
    if (max_taps >= 8) {
      video_orc_resample_v_muladdtaps4_u8_lq (temp, srcs[0], srcs[1 * src_inc],
          srcs[2 * src_inc], srcs[3 * src_inc], taps[0], taps[1], taps[2],
          taps[3], count);
      max_taps -= 4;
      srcs += 4 * src_inc;
      taps += 4;
    } else {
      for (i = 0; i < max_taps - 4; i++)
        video_orc_resample_v_muladdtaps_u8_lq (temp, srcs[i * src_inc], taps[i],
            count);
      srcs += (max_taps - 4) * src_inc;
      taps += (max_taps - 4);
      max_taps = 4;
    }
  }
  if (max_taps == 4) {
    video_orc_resample_v_muladdscaletaps4_u8_lq (d, srcs[0], srcs[1 * src_inc],
        srcs[2 * src_inc], srcs[3 * src_inc], temp, taps[0], taps[1], taps[2],
        taps[3], count);
  } else {
    for (i = 0; i < max_taps; i++)
      video_orc_resample_v_muladdtaps_u8_lq (temp, srcs[i * src_inc], taps[i],
          count);
    video_orc_resample_scaletaps_u8_lq (d, temp, count);
  }

#else
  video_orc_resample_v_multaps_u8 (temp, srcs[0], taps[0], count);
  for (i = 1; i < max_taps; i++) {
    video_orc_resample_v_muladdtaps_u8 (temp, srcs[i * src_inc], taps[i],
        count);
  }
  video_orc_resample_scaletaps_u8 (d, temp, count);
#endif
}

static void
video_scale_v_ntap_u16 (GstVideoScaler * scale,
    gpointer srcs[], gpointer dest, guint dest_offset, guint width,
    guint n_elems)
{
  gint16 *taps;
  gint i, max_taps, count, src_inc;
  gpointer d;
  gint32 *temp;

  if (scale->taps_s16 == NULL)
    make_s16_taps (scale, n_elems, SCALE_U16);

  max_taps = scale->resampler.max_taps;
  taps = scale->taps_s16 + (scale->resampler.phase[dest_offset] * max_taps);

  d = (guint16 *) dest;

  if (scale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED)
    src_inc = 2;
  else
    src_inc = 1;

  temp = (gint32 *) scale->tmpline2;
  count = width * n_elems;

  video_orc_resample_v_multaps_u16 (temp, srcs[0], taps[0], count);
  for (i = 1; i < max_taps; i++) {
    video_orc_resample_v_muladdtaps_u16 (temp, srcs[i * src_inc], taps[i],
        count);
  }
  video_orc_resample_scaletaps_u16 (d, temp, count);
}

static gint
get_y_offset (GstVideoFormat format)
{
  switch (format) {
    case GST_VIDEO_FORMAT_YUY2:
    case GST_VIDEO_FORMAT_YVYU:
      return 0;
    default:
    case GST_VIDEO_FORMAT_UYVY:
      return 1;
  }
}

/**
 * gst_video_scaler_combine_packed_YUV: (skip)
 * @y_scale: a scaler for the Y component
 * @uv_scale: a scaler for the U and V components
 * @in_format: the input video format
 * @out_format: the output video format
 *
 * Combine a scaler for Y and UV into one scaler for the packed @format.
 *
 * Returns: a new horizontal videoscaler for @format.
 *
 * Since: 1.6
 */
GstVideoScaler *
gst_video_scaler_combine_packed_YUV (GstVideoScaler * y_scale,
    GstVideoScaler * uv_scale, GstVideoFormat in_format,
    GstVideoFormat out_format)
{
  GstVideoScaler *scale;
  GstVideoResampler *resampler;
  guint i, out_size, max_taps, n_phases;
  gdouble *taps;
  guint32 *offset, *phase;

  g_return_val_if_fail (y_scale != NULL, NULL);
  g_return_val_if_fail (uv_scale != NULL, NULL);
  g_return_val_if_fail (uv_scale->resampler.max_taps ==
      y_scale->resampler.max_taps, NULL);

  scale = g_slice_new0 (GstVideoScaler);

  scale->method = y_scale->method;
  scale->flags = y_scale->flags;
  scale->merged = TRUE;

  resampler = &scale->resampler;

  out_size = GST_ROUND_UP_4 (y_scale->resampler.out_size * 2);
  max_taps = y_scale->resampler.max_taps;
  n_phases = out_size;
  offset = g_malloc (sizeof (guint32) * out_size);
  phase = g_malloc (sizeof (guint32) * n_phases);
  taps = g_malloc (sizeof (gdouble) * max_taps * n_phases);

  resampler->in_size = y_scale->resampler.in_size * 2;
  resampler->out_size = out_size;
  resampler->max_taps = max_taps;
  resampler->n_phases = n_phases;
  resampler->offset = offset;
  resampler->phase = phase;
  resampler->n_taps = g_malloc (sizeof (guint32) * out_size);
  resampler->taps = taps;

  scale->in_y_offset = get_y_offset (in_format);
  scale->out_y_offset = get_y_offset (out_format);
  scale->inc = y_scale->inc;

  for (i = 0; i < out_size; i++) {
    gint ic;

    if ((i & 1) == scale->out_y_offset) {
      ic = MIN (i / 2, y_scale->resampler.out_size - 1);
      offset[i] = y_scale->resampler.offset[ic] * 2 + scale->in_y_offset;
      memcpy (taps + i * max_taps, y_scale->resampler.taps +
          y_scale->resampler.phase[ic] * max_taps, max_taps * sizeof (gdouble));
    } else {
      ic = MIN (i / 4, uv_scale->resampler.out_size - 1);
      offset[i] = uv_scale->resampler.offset[ic] * 4 + (i & 3);
      memcpy (taps + i * max_taps, uv_scale->resampler.taps +
          uv_scale->resampler.phase[ic] * max_taps,
          max_taps * sizeof (gdouble));
    }
    phase[i] = i;
  }

  scaler_dump (scale);

  return scale;
}

static gboolean
get_functions (GstVideoScaler * hscale, GstVideoScaler * vscale,
    GstVideoFormat format,
    GstVideoScalerHFunc * hfunc, GstVideoScalerVFunc * vfunc,
    gint * n_elems, guint * width, gint * bits)
{
  gboolean mono = FALSE;

  switch (format) {
    case GST_VIDEO_FORMAT_GRAY8:
      *bits = 8;
      *n_elems = 1;
      mono = TRUE;
      break;
    case GST_VIDEO_FORMAT_YUY2:
    case GST_VIDEO_FORMAT_YVYU:
    case GST_VIDEO_FORMAT_UYVY:
      *bits = 8;
      *n_elems = 1;
      *width = GST_ROUND_UP_4 (*width * 2);
      break;
    case GST_VIDEO_FORMAT_RGB:
    case GST_VIDEO_FORMAT_BGR:
    case GST_VIDEO_FORMAT_v308:
    case GST_VIDEO_FORMAT_IYU2:
      *bits = 8;
      *n_elems = 3;
      break;
    case GST_VIDEO_FORMAT_AYUV:
    case GST_VIDEO_FORMAT_RGBx:
    case GST_VIDEO_FORMAT_BGRx:
    case GST_VIDEO_FORMAT_xRGB:
    case GST_VIDEO_FORMAT_xBGR:
    case GST_VIDEO_FORMAT_RGBA:
    case GST_VIDEO_FORMAT_BGRA:
    case GST_VIDEO_FORMAT_ARGB:
    case GST_VIDEO_FORMAT_ABGR:
      *bits = 8;
      *n_elems = 4;
      break;
    case GST_VIDEO_FORMAT_ARGB64:
    case GST_VIDEO_FORMAT_ARGB64_LE:
    case GST_VIDEO_FORMAT_ARGB64_BE:
    case GST_VIDEO_FORMAT_RGBA64_BE:
    case GST_VIDEO_FORMAT_RGBA64_LE:
    case GST_VIDEO_FORMAT_BGRA64_BE:
    case GST_VIDEO_FORMAT_BGRA64_LE:
    case GST_VIDEO_FORMAT_ABGR64_BE:
    case GST_VIDEO_FORMAT_ABGR64_LE:
    case GST_VIDEO_FORMAT_AYUV64:
      *bits = 16;
      *n_elems = 4;
      break;
    case GST_VIDEO_FORMAT_GRAY16_LE:
    case GST_VIDEO_FORMAT_GRAY16_BE:
      *bits = 16;
      *n_elems = 1;
      mono = TRUE;
      break;
    case GST_VIDEO_FORMAT_NV12:
    case GST_VIDEO_FORMAT_NV16:
    case GST_VIDEO_FORMAT_NV21:
    case GST_VIDEO_FORMAT_NV24:
    case GST_VIDEO_FORMAT_NV61:
      *bits = 8;
      *n_elems = 2;
      break;
    default:
      return FALSE;
  }
  if (*bits == 8) {
    switch (hscale ? hscale->resampler.max_taps : 0) {
      case 0:
        break;
      case 1:
        if (*n_elems == 1)
          *hfunc = video_scale_h_near_u8;
        else if (*n_elems == 2)
          *hfunc = video_scale_h_near_u16;
        else if (*n_elems == 3)
          *hfunc = video_scale_h_near_3u8;
        else if (*n_elems == 4)
          *hfunc = video_scale_h_near_u32;
        break;
      case 2:
        if (*n_elems == 1 && mono)
          *hfunc = video_scale_h_2tap_1u8;
        else if (*n_elems == 4)
          *hfunc = video_scale_h_2tap_4u8;
        else
          *hfunc = video_scale_h_ntap_u8;
        break;
      default:
        *hfunc = video_scale_h_ntap_u8;
        break;
    }
    switch (vscale ? vscale->resampler.max_taps : 0) {
      case 0:
        break;
      case 1:
        *vfunc = video_scale_v_near_u8;
        break;
      case 2:
        *vfunc = video_scale_v_2tap_u8;
        break;
      case 4:
        *vfunc = video_scale_v_4tap_u8;
        break;
      default:
        *vfunc = video_scale_v_ntap_u8;
        break;
    }
  } else if (*bits == 16) {
    switch (hscale ? hscale->resampler.max_taps : 0) {
      case 0:
        break;
      case 1:
        if (*n_elems == 1)
          *hfunc = video_scale_h_near_u16;
        else
          *hfunc = video_scale_h_near_u64;
        break;
      default:
        *hfunc = video_scale_h_ntap_u16;
        break;
    }
    switch (vscale ? vscale->resampler.max_taps : 0) {
      case 0:
        break;
      case 1:
        *vfunc = video_scale_v_near_u16;
        break;
      case 2:
        *vfunc = video_scale_v_2tap_u16;
        break;
      default:
        *vfunc = video_scale_v_ntap_u16;
        break;
    }
  }
  return TRUE;
}

/**
 * gst_video_scaler_horizontal:
 * @scale: a #GstVideoScaler
 * @format: a #GstVideoFormat for @src and @dest
 * @src: source pixels
 * @dest: destination pixels
 * @dest_offset: the horizontal destination offset
 * @width: the number of pixels to scale
 *
 * Horizontally scale the pixels in @src to @dest, starting from @dest_offset
 * for @width samples.
 */
void
gst_video_scaler_horizontal (GstVideoScaler * scale, GstVideoFormat format,
    gpointer src, gpointer dest, guint dest_offset, guint width)
{
  gint n_elems, bits;
  GstVideoScalerHFunc func = NULL;

  g_return_if_fail (scale != NULL);
  g_return_if_fail (src != NULL);
  g_return_if_fail (dest != NULL);
  g_return_if_fail (dest_offset + width <= scale->resampler.out_size);

  if (!get_functions (scale, NULL, format, &func, NULL, &n_elems, &width, &bits)
      || func == NULL)
    goto no_func;

  if (scale->tmpwidth < width)
    realloc_tmplines (scale, n_elems, width);

  func (scale, src, dest, dest_offset, width, n_elems);
  return;

no_func:
  {
    GST_WARNING ("no scaler function for format");
  }
}

/**
 * gst_video_scaler_vertical:
 * @scale: a #GstVideoScaler
 * @format: a #GstVideoFormat for @srcs and @dest
 * @src_lines: source pixels lines
 * @dest: destination pixels
 * @dest_offset: the vertical destination offset
 * @width: the number of pixels to scale
 *
 * Vertically combine @width pixels in the lines in @src_lines to @dest.
 * @dest is the location of the target line at @dest_offset and
 * @srcs are the input lines for @dest_offset.
 */
void
gst_video_scaler_vertical (GstVideoScaler * scale, GstVideoFormat format,
    gpointer src_lines[], gpointer dest, guint dest_offset, guint width)
{
  gint n_elems, bits;
  GstVideoScalerVFunc func = NULL;

  g_return_if_fail (scale != NULL);
  g_return_if_fail (src_lines != NULL);
  g_return_if_fail (dest != NULL);
  g_return_if_fail (dest_offset < scale->resampler.out_size);

  if (!get_functions (NULL, scale, format, NULL, &func, &n_elems, &width, &bits)
      || func == NULL)
    goto no_func;

  if (scale->tmpwidth < width)
    realloc_tmplines (scale, n_elems, width);

  func (scale, src_lines, dest, dest_offset, width, n_elems);

  return;

no_func:
  {
    GST_WARNING ("no scaler function for format");
  }
}


/**
 * gst_video_scaler_2d:
 * @hscale: a horizontal #GstVideoScaler
 * @vscale: a vertical #GstVideoScaler
 * @format: a #GstVideoFormat for @srcs and @dest
 * @src: source pixels
 * @src_stride: source pixels stride
 * @dest: destination pixels
 * @dest_stride: destination pixels stride
 * @x: the horizontal destination offset
 * @y: the vertical destination offset
 * @width: the number of output pixels to scale
 * @height: the number of output lines to scale
 *
 * Scale a rectangle of pixels in @src with @src_stride to @dest with
 * @dest_stride using the horizontal scaler @hscaler and the vertical
 * scaler @vscale.
 *
 * One or both of @hscale and @vscale can be NULL to only perform scaling in
 * one dimension or do a copy without scaling.
 *
 * @x and @y are the coordinates in the destination image to process.
 */
void
gst_video_scaler_2d (GstVideoScaler * hscale, GstVideoScaler * vscale,
    GstVideoFormat format, gpointer src, gint src_stride,
    gpointer dest, gint dest_stride, guint x, guint y,
    guint width, guint height)
{
  gint n_elems, bits;
  GstVideoScalerHFunc hfunc = NULL;
  GstVideoScalerVFunc vfunc = NULL;
  gint i;
  gboolean interlaced;

  g_return_if_fail (src != NULL);
  g_return_if_fail (dest != NULL);

  if (!get_functions (hscale, vscale, format, &hfunc, &vfunc, &n_elems, &width,
          &bits))
    goto no_func;

  interlaced = vscale && ! !(vscale->flags & GST_VIDEO_SCALER_FLAG_INTERLACED);

#define LINE(s,ss,i)  ((guint8 *)(s) + ((i) * (ss)))
#define TMP_LINE(s,i) ((guint8 *)((s)->tmpline1) + (i) * (sizeof (gint32) * width * n_elems))

  if (vscale == NULL) {
    if (hscale == NULL) {
      guint xo, xw;
      guint8 *s, *d;

      xo = x * n_elems;
      xw = width * n_elems * (bits / 8);

      s = LINE (src, src_stride, y) + xo;
      d = LINE (dest, dest_stride, y) + xo;

      /* no scaling, do memcpy */
      for (i = y; i < height; i++) {
        memcpy (d, s, xw);
        d += dest_stride;
        s += src_stride;
      }
    } else {
      if (hscale->tmpwidth < width)
        realloc_tmplines (hscale, n_elems, width);

      /* only horizontal scaling */
      for (i = y; i < height; i++) {
        hfunc (hscale, LINE (src, src_stride, i), LINE (dest, dest_stride, i),
            x, width, n_elems);
      }
    }
  } else {
    guint v_taps;
    gpointer *lines;

    if (vscale->tmpwidth < width)
      realloc_tmplines (vscale, n_elems, width);

    v_taps = vscale->resampler.max_taps;

    lines = g_alloca ((interlaced ? 2 : 1) * v_taps * sizeof (gpointer));
    memset (lines, 0, (interlaced ? 2 : 1) * v_taps * sizeof (gpointer));

    if (hscale == NULL) {
      guint src_inc = interlaced ? 2 : 1;

      /* only vertical scaling */
      for (i = y; i < height; i++) {
        guint in, j;

        in = vscale->resampler.offset[i];
        for (j = 0; j < v_taps; j++) {
          guint l = in + j * src_inc;

          g_assert (l < vscale->resampler.in_size);
          lines[j * src_inc] = LINE (src, src_stride, l);
        }

        vfunc (vscale, lines, LINE (dest, dest_stride, i), i, width, n_elems);
      }
    } else {
      gint s1, s2;
      guint *tmpline_lines;

      tmpline_lines = g_newa (guint, (interlaced ? 2 : 1) * v_taps);
      /* initialize with -1 */
      memset (tmpline_lines, 0xff,
          (interlaced ? 2 : 1) * v_taps * sizeof (guint));

      if (hscale->tmpwidth < width)
        realloc_tmplines (hscale, n_elems, width);

      s1 = width * vscale->resampler.offset[height - 1];
      s2 = width * height;

      if (s1 <= s2) {
        for (i = y; i < height; i++) {
          guint in, j;
          guint src_inc = interlaced ? 2 : 1;
          guint f2_offset = (interlaced && (i % 2 == 1)) * v_taps;

          in = vscale->resampler.offset[i];
          for (j = 0; j < v_taps; j++) {
            guint k;
            guint l = in + j * src_inc;

            g_assert (l < vscale->resampler.in_size);

            /* First check if we already have this line in tmplines */
            for (k = f2_offset; k < v_taps + f2_offset; k++) {
              if (tmpline_lines[k] == l) {
                lines[j * src_inc] = TMP_LINE (vscale, k);
                break;
              }
            }
            /* Found */
            if (k < v_taps + f2_offset)
              continue;

            /* Otherwise find an empty line we can clear */
            for (k = f2_offset; k < v_taps + f2_offset; k++) {
              if (tmpline_lines[k] < in || tmpline_lines[k] == -1)
                break;
            }

            /* Must not happen, that would mean we don't have enough space to
             * begin with */
            g_assert (k < v_taps + f2_offset);

            hfunc (hscale, LINE (src, src_stride, l), TMP_LINE (vscale, k), x,
                width, n_elems);
            tmpline_lines[k] = l;
            lines[j * src_inc] = TMP_LINE (vscale, k);
          }

          vfunc (vscale, lines, LINE (dest, dest_stride, i), i, width, n_elems);
        }
      } else {
        guint vx, vw, w1, ws;
        guint h_taps;

        h_taps = hscale->resampler.max_taps;
        w1 = x + width - 1;
        ws = hscale->resampler.offset[w1];

        /* we need to estimate the area that we first need to scale in the
         * vertical direction. Scale x and width to find the lower bound and
         * overshoot the width to find the upper bound */
        vx = (hscale->inc * x) >> 16;
        vx = MIN (vx, hscale->resampler.offset[x]);
        vw = (hscale->inc * (x + width)) >> 16;
        if (hscale->merged) {
          if ((w1 & 1) == hscale->out_y_offset)
            vw = MAX (vw, ws + (2 * h_taps));
          else
            vw = MAX (vw, ws + (4 * h_taps));
        } else {
          vw = MAX (vw, ws + h_taps);
        }
        vw += 1;
        /* but clamp to max size */
        vw = MIN (vw, hscale->resampler.in_size);

        if (vscale->tmpwidth < vw)
          realloc_tmplines (vscale, n_elems, vw);

        for (i = y; i < height; i++) {
          guint in, j;
          guint src_inc = interlaced ? 2 : 1;

          in = vscale->resampler.offset[i];
          for (j = 0; j < v_taps; j++) {
            guint l = in + j * src_inc;

            g_assert (l < vscale->resampler.in_size);
            lines[j * src_inc] =
                LINE (src, src_stride, in + j * src_inc) + vx * n_elems;
          }

          vfunc (vscale, lines, TMP_LINE (vscale, 0) + vx * n_elems, i,
              vw - vx, n_elems);

          hfunc (hscale, TMP_LINE (vscale, 0), LINE (dest, dest_stride,
                  i), x, width, n_elems);
        }
      }
    }
  }
  return;

no_func:
  {
    GST_WARNING ("no scaler function for format");
  }
}

#undef LINE
#undef TMP_LINE
