/*
* Copyright (c) 2012-2019 Fredrik Mellbin
*
* This file is part of VapourSynth.
*
* VapourSynth is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* VapourSynth 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with VapourSynth; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <VSHelper4.h>
#include "generic.h"

namespace {

template <class T>
T limit(T x, uint16_t maxval)
{
    return static_cast<T>(std::min(static_cast<uint16_t>(x), maxval));
}

template <>
float limit(float x, uint16_t) { return x; }

template <class T>
T xrint(float x)
{
    return static_cast<T>(std::lrint(std::min(std::max(x, static_cast<float>(std::numeric_limits<T>::min())), static_cast<float>(std::numeric_limits<T>::max()))));
}

template <>
float xrint(float x) { return x; }

template <class T>
T *line_ptr(T *ptr, unsigned i, ptrdiff_t stride)
{
    return (T *)(((unsigned char *)ptr) + static_cast<ptrdiff_t>(i) * stride);
}


template <class T, bool Sobel>
struct PrewittSobelOp {
    typedef T type;

    float scale;

    explicit PrewittSobelOp(const vs_generic_params &params) : scale{ params.scale } {}

    T op(T a00, T a01, T a02, T a10, T a11, T a12, T a20, T a21, T a22) const
    {
        typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Signed;
        constexpr Signed two = 2;

        Signed gx, gy;

        if (Sobel) {
            gx = static_cast<Signed>(a20) + two * a21 + a22 - a00 - two * a01 - a02;
            gy = static_cast<Signed>(a02) + two * a12 + a22 - a00 - two * a10 - a20;
        } else {
            gx = static_cast<Signed>(a20) + a21 + a22 - a00 - a01 - a02;
            gy = static_cast<Signed>(a02) + a12 + a22 - a00 - a10 - a20;
        }

        float tmp = std::sqrt(static_cast<float>(gx) * gx + static_cast<float>(gy) * gy) * scale;
        return xrint<T>(tmp);
    }
};

template <class T, bool Max>
struct MinMaxOp {
    typedef T type;

    typename std::conditional<std::is_integral<T>::value, int32_t, float>::type threshold;
    uint8_t stencil[8];

    explicit MinMaxOp(const vs_generic_params &params) :
        threshold{ std::is_integral<T>::value ? static_cast<T>(params.threshold) : static_cast<T>(params.thresholdf) },
        stencil{}
    {
        for (unsigned i = 0; i < 8; ++i) {
            stencil[i] = (params.stencil & (1U << i)) ? 0xFF : 0;
        }
    }

    static T reduce(T lhs, T rhs)
    {
        return Max ? std::max(lhs, rhs) : std::min(lhs, rhs);
    }

    T op(T a00, T a01, T a02, T a10, T a11, T a12, T a20, T a21, T a22) const
    {
        typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Signed;

        T val = a11;
        val = reduce(val, stencil[0] ? a00 : val);
        val = reduce(val, stencil[1] ? a01 : val);
        val = reduce(val, stencil[2] ? a02 : val);
        val = reduce(val, stencil[3] ? a10 : val);
        val = reduce(val, stencil[4] ? a12 : val);
        val = reduce(val, stencil[5] ? a20 : val);
        val = reduce(val, stencil[6] ? a21 : val);
        val = reduce(val, stencil[7] ? a22 : val);

        Signed minval = std::is_integral<T>::value ? Signed{} : static_cast<Signed>(-INFINITY);
        Signed limit = Max ? static_cast<Signed>(a11) + threshold : std::max(static_cast<Signed>(a11) - threshold, minval);
        return Max ? std::min(static_cast<Signed>(val), limit) : std::max(static_cast<Signed>(val), limit);
    }
};

template <class T>
struct MedianOp {
    typedef T type;

    explicit MedianOp(const vs_generic_params &) {}

    static void compare_exchange(T &lhs, T &rhs)
    {
        T x = lhs;
        T y = rhs;
        lhs = std::min(x, y);
        rhs = std::max(x, y);
    }

    T op(T a00, T a01, T a02, T a10, T a11, T a12, T a20, T a21, T a22) const
    {
        compare_exchange(a00, a01);
        compare_exchange(a02, a10);
        compare_exchange(a12, a20);
        compare_exchange(a21, a22);

        compare_exchange(a00, a02);
        compare_exchange(a01, a10);
        compare_exchange(a12, a21);
        compare_exchange(a20, a22);

        compare_exchange(a01, a02);
        compare_exchange(a20, a21);

        compare_exchange(a00, a12);
        compare_exchange(a01, a20);
        compare_exchange(a02, a21);
        compare_exchange(a10, a22);

        compare_exchange(a02, a12);
        compare_exchange(a10, a20);

        compare_exchange(a10, a12);

        compare_exchange(a10, a11);
        compare_exchange(a11, a12);
        return a11;
    }
};

template <class T, bool Inflate>
struct DeflateInflateOp {
    typedef T type;

    typename std::conditional<std::is_integral<T>::value, int32_t, float>::type threshold;

    explicit DeflateInflateOp(const vs_generic_params &params) :
        threshold{ std::is_integral<T>::value ? static_cast<T>(params.threshold) : static_cast<T>(params.thresholdf) }
    {}

    T op(T a00, T a01, T a02, T a10, T a11, T a12, T a20, T a21, T a22) const
    {
        typedef typename std::conditional<std::is_integral<T>::value, uint32_t, float>::type U;
        typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Signed;

        U accum = static_cast<U>(a00) + a01 + a02 + a10 + a12 + a20 + a21 + a22;
        accum = std::is_integral<T>::value ? accum + 4 : accum;

        Signed val = static_cast<Signed>(accum / 8);
        val = Inflate ? std::max(val, static_cast<Signed>(a11)) : std::min(val, static_cast<Signed>(a11));

        Signed minval = std::is_integral<T>::value ? Signed{} : static_cast<Signed>(-INFINITY);
        Signed limit = Inflate ? static_cast<Signed>(a11) + threshold : std::max(static_cast<Signed>(a11) - threshold, minval);
        return Inflate ? std::min(val, limit) : std::max(val, limit);
    }
};

template <class T>
struct ConvolutionOp {
    typedef T type;

    std::array<typename std::conditional<std::is_integral<T>::value, int16_t, float>::type, 9> coeffs;
    float div;
    float bias;
    uint8_t saturate;

    explicit ConvolutionOp(const vs_generic_params &params) :
        coeffs{},
        div{ params.div },
        bias{ params.bias },
        saturate{ params.saturate }
    {
        typedef typename std::conditional<std::is_integral<T>::value, int16_t, float>::type coeff_type;

        for (unsigned i = 0; i < 9; ++i) {
            coeffs[i] = std::is_integral<T>::value ? static_cast<coeff_type>(params.matrix[i]) : static_cast<coeff_type>(params.matrixf[i]);
        }
    }

    T op(T a00, T a01, T a02, T a10, T a11, T a12, T a20, T a21, T a22) const
    {
        typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Signed;

        Signed accum = coeffs[0] * a00;
        accum += coeffs[1] * a01;
        accum += coeffs[2] * a02;
        accum += coeffs[3] * a10;
        accum += coeffs[4] * a11;
        accum += coeffs[5] * a12;
        accum += coeffs[6] * a20;
        accum += coeffs[7] * a21;
        accum += coeffs[8] * a22;

        float tmp = static_cast<float>(accum) * div + bias;
        tmp = saturate ? tmp : std::fabs(tmp);
        return xrint<T>(tmp);
    }
};


template <class Traits>
void filter_plane_3x3(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params &params, unsigned width, unsigned height)
{
    typedef typename Traits::type T;

    Traits traits{ params };
    uint16_t maxval = params.maxval;

    for (unsigned i = 0; i < height; ++i) {
        unsigned above_idx = i == 0 ? std::min(1U, height - 1) : i - 1;
        unsigned below_idx = i == height - 1 ? height - std::min(2U, height) : i + 1;

        const T *srcp0 = static_cast<const T *>(line_ptr(src, above_idx, src_stride));
        const T *srcp1 = static_cast<const T *>(line_ptr(src, i, src_stride));
        const T *srcp2 = static_cast<const T *>(line_ptr(src, below_idx, src_stride));
        T *dstp = static_cast<T *>(line_ptr(dst, i, dst_stride));

        {
            unsigned a = width > 1 ? 1 : 0;
            unsigned b = 0;
            unsigned c = width > 1 ? 1 : 0;

            T x = traits.op(srcp0[a], srcp0[b], srcp0[c], srcp1[a], srcp1[b], srcp1[c], srcp2[a], srcp2[b], srcp2[c]);
            dstp[0] = limit(x, maxval);
        }

        for (unsigned i = 1; i < width - 1; ++i) {
            unsigned a = i - 1;
            unsigned b = i;
            unsigned c = i + 1;

            T x = traits.op(srcp0[a], srcp0[b], srcp0[c], srcp1[a], srcp1[b], srcp1[c], srcp2[a], srcp2[b], srcp2[c]);
            dstp[i] = limit(x, maxval);
        }

        if (width > 1) {
            unsigned a = width - 2;
            unsigned b = width - 1;
            unsigned c = width - 2;
            T x = traits.op(srcp0[a], srcp0[b], srcp0[c], srcp1[a], srcp1[b], srcp1[c], srcp2[a], srcp2[b], srcp2[c]);
            dstp[width - 1] = limit(x, maxval);
        }
    }
}

template <class T>
void conv_plane_5x5(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params &params, unsigned width, unsigned height)
{
    typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Accum;
    typedef typename std::conditional<std::is_integral<T>::value, int16_t, float>::type Weight;

    const Weight *coeffs = std::is_integral<T>::value ? (const Weight *)params.matrix : (const Weight *)params.matrixf;
    uint16_t maxval = params.maxval;
    float div = params.div;
    float bias = params.bias;
    bool saturate = params.saturate;

    for (unsigned i = 0; i < height; ++i) {
        unsigned dist_from_bottom = height - 1 - i;

        unsigned above2_idx = i < 2 ? std::min(2 - i, height - 1) : i - 2;
        unsigned above1_idx = i < 1 ? std::min(1 - i, height - 1) : i - 1;
        unsigned below1_idx = dist_from_bottom < 1 ? i - std::min(1 - dist_from_bottom, i) : i + 1;
        unsigned below2_idx = dist_from_bottom < 2 ? i - std::min(2 - dist_from_bottom, i) : i + 2;

        const T *srcp0 = static_cast<const T *>(line_ptr(src, above2_idx, src_stride));
        const T *srcp1 = static_cast<const T *>(line_ptr(src, above1_idx, src_stride));
        const T *srcp2 = static_cast<const T *>(line_ptr(src, i, src_stride));
        const T *srcp3 = static_cast<const T *>(line_ptr(src, below1_idx, src_stride));
        const T *srcp4 = static_cast<const T *>(line_ptr(src, below2_idx, src_stride));
        T *dst_p = static_cast<T *>(line_ptr(dst, i, dst_stride));

        for (unsigned j = 0; j < std::min(width, 2U); ++j) {
            unsigned dist_from_right = width - 1 - i;
            unsigned idx[5];

            idx[0] = j < 2 ? std::min(2 - j, width - 1) : j - 2;
            idx[1] = j < 1 ? std::min(1 - j, width - 1) : j - 1;
            idx[2] = j;
            idx[3] = dist_from_right < 1 ? j - std::min(1 - dist_from_right, j) : j + 1;
            idx[4] = dist_from_right < 2 ? j - std::min(2 - dist_from_right, j) : j + 2;

            Accum accum = 0;

            for (unsigned k = 0; k < 5; ++k) {
                accum += coeffs[5 * 0 + k] * static_cast<Accum>(srcp0[idx[k]]);
                accum += coeffs[5 * 1 + k] * static_cast<Accum>(srcp1[idx[k]]);
                accum += coeffs[5 * 2 + k] * static_cast<Accum>(srcp2[idx[k]]);
                accum += coeffs[5 * 3 + k] * static_cast<Accum>(srcp3[idx[k]]);
                accum += coeffs[5 * 4 + k] * static_cast<Accum>(srcp4[idx[k]]);
            }

            float tmp = static_cast<float>(accum) * div + bias;
            tmp = saturate ? tmp : std::fabs(tmp);
            dst_p[j] = limit(xrint<T>(tmp), maxval);
        }

        for (unsigned j = 2; j < width - std::min(width, 2U); ++j) {
            Accum accum = 0;

            for (unsigned k = 0; k < 5; ++k) {
                accum += coeffs[5 * 0 + k] * static_cast<Accum>(srcp0[j - 2 + k]);
                accum += coeffs[5 * 1 + k] * static_cast<Accum>(srcp1[j - 2 + k]);
                accum += coeffs[5 * 2 + k] * static_cast<Accum>(srcp2[j - 2 + k]);
                accum += coeffs[5 * 3 + k] * static_cast<Accum>(srcp3[j - 2 + k]);
                accum += coeffs[5 * 4 + k] * static_cast<Accum>(srcp4[j - 2 + k]);
            }

            float tmp = static_cast<float>(accum) * div + bias;
            tmp = saturate ? tmp : std::fabs(tmp);
            dst_p[j] = limit(xrint<T>(tmp), maxval);
        }

        for (unsigned j = std::max(2U, width - std::min(width, 2U)); j < width; ++j) {
            unsigned dist_from_right = width - 1 - i;
            unsigned idx[5];

            idx[0] = j < 2 ? std::min(2 - j, width - 1) : j - 2;
            idx[1] = j < 1 ? std::min(1 - j, width - 1) : j - 1;
            idx[2] = j;
            idx[3] = dist_from_right < 1 ? j - std::min(1 - dist_from_right, j) : j + 1;
            idx[4] = dist_from_right < 2 ? j - std::min(2 - dist_from_right, j) : j + 2;

            Accum accum = 0;

            for (unsigned k = 0; k < 5; ++k) {
                accum += coeffs[5 * 0 + k] * static_cast<Accum>(srcp0[idx[k]]);
                accum += coeffs[5 * 1 + k] * static_cast<Accum>(srcp1[idx[k]]);
                accum += coeffs[5 * 2 + k] * static_cast<Accum>(srcp2[idx[k]]);
                accum += coeffs[5 * 3 + k] * static_cast<Accum>(srcp3[idx[k]]);
                accum += coeffs[5 * 4 + k] * static_cast<Accum>(srcp4[idx[k]]);
            }

            float tmp = static_cast<float>(accum) * div + bias;
            tmp = saturate ? tmp : std::fabs(tmp);
            dst_p[j] = limit(xrint<T>(tmp), maxval);
        }
    }
}

template <class T>
void conv_scanline_h(const void *src, void *dst, const vs_generic_params &params, unsigned width)
{
    typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Accum;
    typedef typename std::conditional<std::is_integral<T>::value, int16_t, float>::type Weight;

    const Weight *coeffs = std::is_integral<T>::value ? (const Weight *)params.matrix : (const Weight *)params.matrixf;
    unsigned fwidth = params.matrixsize;
    unsigned support = fwidth / 2;

    uint16_t maxval = params.maxval;
    float div = params.div;
    float bias = params.bias;
    bool saturate = params.saturate;

    const T *srcp = static_cast<const T *>(src);
    T *dstp = static_cast<T *>(dst);

    for (unsigned j = 0; j < std::min(width, support); ++j) {
        unsigned dist_from_right = width - 1 - j;

        Accum accum = 0;

        for (unsigned k = 0; k < support; ++k) {
            unsigned idx = j < support - k ? std::min(support - k - j - 1, width - 1) : j - support + k;
            accum += coeffs[k] * static_cast<Accum>(srcp[idx]);
        }
        for (unsigned k = support; k < fwidth; ++k) {
            unsigned idx = dist_from_right < k - support ? width - std::min(k - support - dist_from_right, width) : j - support + k;
            accum += coeffs[k] * static_cast<Accum>(srcp[idx]);
        }

        float tmp = static_cast<float>(accum) * div + bias;
        tmp = saturate ? tmp : std::fabs(tmp);
        dstp[j] = limit(xrint<T>(tmp), maxval);
    }

    for (unsigned j = support; j < width - std::min(width, support); ++j) {
        Accum accum = 0;

        for (unsigned k = 0; k < fwidth; ++k) {
            accum += coeffs[k] * static_cast<Accum>(srcp[j - support + k]);
        }

        float tmp = static_cast<float>(accum) * div + bias;
        tmp = saturate ? tmp : std::fabs(tmp);
        dstp[j] = limit(xrint<T>(tmp), maxval);
    }

    for (unsigned j = std::max(support, width - std::min(width, support)); j < width; ++j) {
        unsigned dist_from_right = width - 1 - j;

        Accum accum = 0;

        for (unsigned k = 0; k < support; ++k) {
            unsigned idx = j < support - k ? std::min(support - k - j - 1, width - 1) : j - support + k;
            accum += coeffs[k] * static_cast<Accum>(srcp[idx]);
        }
        for (unsigned k = support; k < fwidth; ++k) {
            unsigned idx = dist_from_right < k - support ? width - std::min(k - support - dist_from_right, width) : j - support + k;
            accum += coeffs[k] * static_cast<Accum>(srcp[idx]);
        }

        float tmp = static_cast<float>(accum) * div + bias;
        tmp = saturate ? tmp : std::fabs(tmp);
        dstp[j] = limit(xrint<T>(tmp), maxval);
    }
}

template <class T>
void conv_scanline_v(const void * const src[], void *dst, const vs_generic_params &params, unsigned width)
{
    typedef typename std::conditional<std::is_integral<T>::value, int32_t, float>::type Accum;
    typedef typename std::conditional<std::is_integral<T>::value, int16_t, float>::type Weight;

    const Weight *coeffs = std::is_integral<T>::value ? (const Weight *)params.matrix : (const Weight *)params.matrixf;
    unsigned fwidth = params.matrixsize;

    uint16_t maxval = params.maxval;
    float div = params.div;
    float bias = params.bias;
    bool saturate = params.saturate;

    T *dstp = static_cast<T *>(dst);

    for (unsigned j = 0; j < width; ++j) {
        Accum accum = 0;

        for (unsigned k = 0; k < fwidth; ++k) {
            accum += coeffs[k] * static_cast<Accum>(static_cast<const T *>(src[k])[j]);
        }

        float tmp = static_cast<float>(accum) * div + bias;
        tmp = saturate ? tmp : std::fabs(tmp);
        dstp[j] = limit(xrint<T>(tmp), maxval);
    }
}

template <class T>
void conv_plane_h(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params &params, unsigned width, unsigned height)
{
    for (unsigned i = 0; i < height; ++i) {
        const void *srcp = line_ptr(src, i, src_stride);
        void *dstp = line_ptr(dst, i, dst_stride);

        conv_scanline_h<T>(srcp, dstp, params, width);
    }
}

template <class T>
void conv_plane_v(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params &params, unsigned width, unsigned height)
{
    unsigned fwidth = params.matrixsize;
    unsigned support = fwidth / 2;

    for (unsigned i = 0; i < height; ++i) {
        const void *srcp[25];
        void *dstp = line_ptr(dst, i, dst_stride);

        unsigned dist_from_bottom = height - 1 - i;

        for (unsigned k = 0; k < support; ++k) {
            unsigned row = i < support - k ? std::min(support - k - i - 1, height - 1) : i - support + k;
            srcp[k] = line_ptr(src, row, src_stride);
        }
        for (unsigned k = support; k < fwidth; ++k) {
            unsigned row = dist_from_bottom < k - support ? height - std::min(k - support - dist_from_bottom, i) : i - support + k;
            srcp[k] = line_ptr(src, row, src_stride);
        }

        conv_scanline_v<T>(srcp, dstp, params, width);
    }
}

template <class T>
void conv_plane_x(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params &params, unsigned width, unsigned height)
{
    void *tmp = vsh::vsh_aligned_malloc(width * sizeof(T), 64);
    unsigned fwidth = params.matrixsize;
    unsigned support = fwidth / 2;

    for (unsigned i = 0; i < height; ++i) {
        const void *srcp[25];
        void *dstp = line_ptr(dst, i, dst_stride);

        unsigned dist_from_bottom = height - 1 - i;

        for (unsigned k = 0; k < support; ++k) {
            unsigned row = i < support - k ? std::min(support - k - i - 1, height - 1) : i - support + k;
            srcp[k] = line_ptr(src, row, src_stride);
        }
        for (unsigned k = support; k < fwidth; ++k) {
            unsigned row = dist_from_bottom < k - support ? height - std::min(k - support - dist_from_bottom, i) : i - support + k;
            srcp[k] = line_ptr(src, row, src_stride);
        }

        conv_scanline_v<T>(srcp, tmp, params, width);
        conv_scanline_h<T>(tmp, dstp, params, width);
    }

    vsh::vsh_aligned_free(tmp);
}

} // namespace


void vs_generic_3x3_prewitt_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<uint8_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_prewitt_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<uint16_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_prewitt_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<float, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_sobel_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<uint8_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_sobel_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<uint16_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_sobel_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<PrewittSobelOp<float, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_min_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<uint8_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_min_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<uint16_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_min_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<float, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_max_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<uint8_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_max_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<uint16_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_max_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MinMaxOp<float, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_median_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MedianOp<uint8_t>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_median_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MedianOp<uint16_t>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_median_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<MedianOp<float>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_deflate_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<uint8_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_deflate_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<uint16_t, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_deflate_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<float, false>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_inflate_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<uint8_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_inflate_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<uint16_t, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_inflate_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<DeflateInflateOp<float, true>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_conv_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<ConvolutionOp<uint8_t>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_conv_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<ConvolutionOp<uint16_t>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_3x3_conv_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const vs_generic_params *params, unsigned width, unsigned height)
{
    filter_plane_3x3<ConvolutionOp<float>>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_5x5_conv_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_5x5<uint8_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_5x5_conv_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_5x5<uint16_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_5x5_conv_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_5x5<float>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_h_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_h<uint8_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_h_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_h<uint16_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_h_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_h<float>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_v_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_v<uint8_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_v_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_v<uint16_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_1d_conv_v_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_v<float>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_2d_conv_sep_byte_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_x<uint8_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_2d_conv_sep_word_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_x<uint16_t>(src, src_stride, dst, dst_stride, *params, width, height);
}

void vs_generic_2d_conv_sep_float_c(const void *src, ptrdiff_t src_stride, void *dst, ptrdiff_t dst_stride, const struct vs_generic_params *params, unsigned width, unsigned height)
{
    conv_plane_x<float>(src, src_stride, dst, dst_stride, *params, width, height);
}

