/*
 * By downloading, copying, installing or using the software you agree to this license.
 * If you do not agree to this license, do not download, install,
 * copy or use the software.
 *
 *
 *                           License Agreement
 *                For Open Source Computer Vision Library
 *                        (3-clause BSD License)
 *
 * Copyright (C) 2015, NVIDIA Corporation, all rights reserved.
 * Third party copyrights are property of their respective owners.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *   * Neither the names of the copyright holders nor the names of the contributors
 *     may be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * This software is provided by the copyright holders and contributors "as is" and
 * any express or implied warranties, including, but not limited to, the implied
 * warranties of merchantability and fitness for a particular purpose are disclaimed.
 * In no event shall copyright holders or contributors be liable for any direct,
 * indirect, incidental, special, exemplary, or consequential damages
 * (including, but not limited to, procurement of substitute goods or services;
 * loss of use, data, or profits; or business interruption) however caused
 * and on any theory of liability, whether in contract, strict liability,
 * or tort (including negligence or otherwise) arising in any way out of
 * the use of this software, even if advised of the possibility of such damage.
 */

#include "common.hpp"
#include "vtransform.hpp"

#include <cmath>
#include <vector>
#include <algorithm>

namespace CAROTENE_NS {

bool isResizeNearestNeighborSupported(const Size2D &ssize, u32 elemSize)
{
#if SIZE_MAX <= UINT32_MAX
    (void)ssize;
#endif
    bool supportedElemSize = (elemSize == 1) || (elemSize == 3) || (elemSize == 4);
    return isSupportedConfiguration()
#if SIZE_MAX > UINT32_MAX
           && !(ssize.width > 0xffffFFFF || ssize.height > 0xffffFFFF)// Restrict image size since internally used resizeGeneric performs
                                                                      // index evaluation with u32
#endif
           && supportedElemSize;
}

bool isResizeAreaSupported(f32 wr, f32 hr, u32 channels)
{
    bool supportedRatio = false;

    if (channels == 1)
        supportedRatio = (hr == wr) && ((wr == 2.0f) || (wr == 4.0f) || (wr == 0.5));
    else if (channels == 3)
        supportedRatio = (hr == wr) && ((wr == 2.0f) || (wr == 4.0f) || (wr == 0.5f));
    else if (channels == 4)
        supportedRatio = (hr == wr) && ((wr == 2.0f) || (wr == 4.0f) || (wr == 0.5f));

    return isSupportedConfiguration() && supportedRatio;
}

bool isResizeLinearSupported(const Size2D &ssize, const Size2D &dsize,
                             f32 wr, f32 hr, u32 channels)
{
    if ((wr <= 2.0f) && (hr <= 2.0f))
    {
        bool channelsSupport = (channels == 1) || (channels == 3) || (channels == 4);
        return (ssize.width >= 16) && (dsize.height >= 8) &&
                (dsize.width >= 8) && channelsSupport;
    }

    return false;
}

bool isResizeLinearOpenCVSupported(const Size2D &ssize, const Size2D &dsize, u32 channels)
{
    switch(channels)
    {
    case 1:
        if (ssize.width >= 8
#if SIZE_MAX > UINT32_MAX
            && !(ssize.width > 0xffffFFFF || ssize.height > 0xffffFFFF)// Restrict image size since internal index evaluation
                                                                       // is performed with u32
#endif
            && dsize.width >= 8 && dsize.height >= 8)
            return isSupportedConfiguration();
        return false;
    case 4:
        if (ssize.width >= 2
#if SIZE_MAX > UINT32_MAX
            && !(ssize.width > 0xffffFFFF || ssize.height > 0xffffFFFF)// Restrict image size since internal index evaluation
                                                                       // is performed with u32
#endif
            && dsize.width >= 2 && dsize.height >= 8)
            return isSupportedConfiguration();
    default:
        return false;
    };
}

#ifdef CAROTENE_NEON

namespace {

u32 * calcLUT(size_t size, f32 ratio,
              std::vector<u32> & _ofs)
{
    _ofs.resize(size);
    u32 * ofs = &_ofs[0];

    size_t roiw8 = size >= 7 ? size - 7 : 0;
    size_t roiw4 = size >= 3 ? size - 3 : 0;
    size_t x = 0;

    f32 indeces[4] = { 0, 1, 2, 3 };
    float32x4_t v_index = vld1q_f32(indeces), v_inc = vdupq_n_f32(4);
    float32x4_t v_05 = vdupq_n_f32(0.5f), v_ratio = vdupq_n_f32(ratio);

    for ( ; x < roiw8; x += 8)
    {
        float32x4_t v_dstf = vmulq_f32(vaddq_f32(v_index, v_05), v_ratio);
        vst1q_u32(ofs + x, vcvtq_u32_f32(v_dstf));
        v_index = vaddq_f32(v_index, v_inc);

        v_dstf = vmulq_f32(vaddq_f32(v_index, v_05), v_ratio);
        vst1q_u32(ofs + x + 4, vcvtq_u32_f32(v_dstf));
        v_index = vaddq_f32(v_index, v_inc);
    }

    for ( ; x < roiw4; x += 4)
    {
        float32x4_t v_dstf = vmulq_f32(vaddq_f32(v_index, v_05), v_ratio);
        vst1q_u32(ofs + x, vcvtq_u32_f32(v_dstf));
        v_index = vaddq_f32(v_index, v_inc);
    }

    for ( ; x < size; ++x)
    {
        ofs[x] = static_cast<u32>(floorf((x + 0.5f) * ratio));
    }

    return ofs;
}

template <typename T>
void resizeGeneric(const Size2D &dsize,
                   const void * srcBase, ptrdiff_t srcStride,
                   void * dstBase, ptrdiff_t dstStride,
                   f32 wr, f32 hr)
{
    std::vector<u32> _x_ofs;
    u32 * x_ofs = calcLUT(dsize.width, wr, _x_ofs);//32bit LUT is used so we could get issues on src image dimensions greater than (2^32-1)

    for (size_t dst_y = 0; dst_y < dsize.height; ++dst_y)
    {
        size_t src_y = static_cast<size_t>(floorf((dst_y + 0.5f) * hr));
        const T * src = internal::getRowPtr(static_cast<const T *>(srcBase), srcStride, src_y);
        T * dst = internal::getRowPtr(static_cast<T *>(dstBase), dstStride, dst_y);

        for (size_t dst_x = 0; dst_x < dsize.width; ++dst_x)
        {
            internal::prefetch(src + dst_x);
            dst[dst_x] = src[x_ofs[dst_x]];
        }
    }
}

typedef struct _24bit_
{
    u8 a[3];
} _24bit;

} // namespace


#endif

void resizeNearestNeighbor(const Size2D &ssize, const Size2D &dsize,
                           const void * srcBase, ptrdiff_t srcStride,
                           void * dstBase, ptrdiff_t dstStride,
                           f32 wr, f32 hr, u32 elemSize)
{
    internal::assertSupportedConfiguration(wr > 0 && hr > 0 &&
                                           (dsize.width - 0.5) * wr < ssize.width &&
                                           (dsize.height - 0.5) * hr < ssize.height &&  // Ensure we have enough source data
                                           (dsize.width + 0.5) * wr >= ssize.width &&
                                           (dsize.height + 0.5) * hr >= ssize.height && // Ensure source isn't too big
                                           isResizeNearestNeighborSupported(ssize, elemSize));
#ifdef CAROTENE_NEON

    if (elemSize == 1)
    {
        resizeGeneric<u8>(dsize,
                          srcBase, srcStride,
                          dstBase, dstStride,
                          wr, hr);
    }
    else if (elemSize == 3)
    {
        resizeGeneric<_24bit>(dsize,
                              srcBase, srcStride,
                              dstBase, dstStride,
                              wr, hr);
    }
    else if (elemSize == 4)
    {
        resizeGeneric<u32>(dsize,
                           srcBase, srcStride,
                           dstBase, dstStride,
                           wr, hr);
    }

#else
    (void)dsize;
    (void)srcBase;
    (void)srcStride;
    (void)dstBase;
    (void)dstStride;
    (void)wr;
    (void)hr;
#endif
}

#ifdef CAROTENE_NEON
template <bool opencv_like, int shiftsize>
inline uint8x8_t areaDownsamplingDivision(uint16x8_t data)
{
    return vshrn_n_u16(data, shiftsize);
}
template <>
inline uint8x8_t areaDownsamplingDivision<true,2>(uint16x8_t data)
{
    // rounding
    return vrshrn_n_u16(data,2);
}
template <>
inline uint8x8_t areaDownsamplingDivision<true,4>(uint16x8_t data)
{
    // bankers rounding
    return vrshrn_n_u16(vqsubq_u16(data, vshrq_n_u16(vbicq_u16(vdupq_n_u16(1<<4), data), 4)),4);
}

template <bool opencv_like, int shiftsize>
inline u8 areaDownsamplingDivision(u16 data)
{
    return data >> shiftsize;
}
template <>
inline u8 areaDownsamplingDivision<true,2>(u16 data)
{
    // rounding
    return (data + 2) >> 2;
}
template <>
inline u8 areaDownsamplingDivision<true,4>(u16 data)
{
    // bankers rounding
    return (data - (((1<<4) & ~data) >> 4) + 8) >> 4;
}
#endif

template <bool opencv_like>
inline void resizeAreaRounding(const Size2D &ssize, const Size2D &dsize,
                               const u8 * srcBase, ptrdiff_t srcStride,
                               u8 * dstBase, ptrdiff_t dstStride,
                               f32 wr, f32 hr, u32 channels)
{
    internal::assertSupportedConfiguration(isResizeAreaSupported(wr, hr, channels) &&
                                           std::abs(dsize.width  * wr -  ssize.width) < 0.1 &&
                                           std::abs(dsize.height * hr - ssize.height) < 0.1);
#ifdef CAROTENE_NEON
    if (channels == 1)
    {
        if ((wr == 2.0f) && (hr == 2.0f))
        {
            size_t roiw8 = dsize.width >= 7 ? dsize.width - 7 : 0;

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 1);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 1) + 1);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

                for ( ; dj < roiw8; dj += 8, sj += 16)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);

                    uint16x8_t vSum1 = vpaddlq_u8(vld1q_u8(src0_row + sj));
                    uint16x8_t vSum2 = vpaddlq_u8(vld1q_u8(src1_row + sj));
                    uint8x8_t vRes1 = areaDownsamplingDivision<opencv_like,2>(vaddq_u16(vSum1, vSum2));

                    vst1_u8(dst_row + dj, vRes1);
                }

                for ( ; dj < dsize.width; ++dj, sj += 2)
                {
                    dst_row[dj] = areaDownsamplingDivision<opencv_like,2>(
                                      (u16)src0_row[sj] + src0_row[sj + 1] +
                                      src1_row[sj] + src1_row[sj + 1]);
                }
            }
        }
        else if ((wr == 0.5f) && (hr == 0.5f))
        {
            size_t roiw32 = dsize.width >= 31 ? dsize.width - 31 : 0;
            size_t roiw16 = dsize.width >= 15 ? dsize.width - 15 : 0;

            for (size_t i = 0; i < dsize.height; i += 2)
            {
                const u8 * src_row = internal::getRowPtr(srcBase, srcStride, i >> 1);
                u8 * dst0_row = internal::getRowPtr(dstBase, dstStride, i);
                u8 * dst1_row = internal::getRowPtr(dstBase, dstStride, std::min(i + 1, dsize.height - 1));
                size_t sj = 0, dj = 0;

                for ( ; dj < roiw32; dj += 32, sj += 16)
                {
                    internal::prefetch(src_row + sj);

                    uint8x16x2_t v_dst;
                    v_dst.val[0] = v_dst.val[1] = vld1q_u8(src_row + sj);

                    vst2q_u8(dst0_row + dj, v_dst);
                    vst2q_u8(dst1_row + dj, v_dst);
                }

                for ( ; dj < roiw16; dj += 16, sj += 8)
                {
                    uint8x8x2_t v_dst;
                    v_dst.val[0] = v_dst.val[1] = vld1_u8(src_row + sj);

                    vst2_u8(dst0_row + dj, v_dst);
                    vst2_u8(dst1_row + dj, v_dst);
                }

                for ( ; dj < dsize.width; dj += 2, ++sj)
                {
                    u8 src_val = src_row[sj];
                    dst0_row[dj] = dst0_row[dj + 1] = src_val;
                    dst1_row[dj] = dst1_row[dj + 1] = src_val;
                }
            }
        }
        else //if ((wr == 4.0f) && (hr == 4.0f)) //the only scale that lasts after isSupported check
        {
#ifndef ANDROID
            size_t roiw16 = dsize.width >= 15 ? dsize.width - 15 : 0;
#endif
            size_t roiw8 = dsize.width >= 7 ? dsize.width - 7 : 0;

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 2);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 1);
                const u8 * src2_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 2);
                const u8 * src3_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 3);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw16; dj += 16, sj += 64)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);
                    internal::prefetch(src2_row + sj);
                    internal::prefetch(src3_row + sj);

                    uint8x16x4_t vLane1 = vld4q_u8(src0_row + sj);
                    uint8x16x4_t vLane2 = vld4q_u8(src1_row + sj);
                    uint8x16x4_t vLane3 = vld4q_u8(src2_row + sj);
                    uint8x16x4_t vLane4 = vld4q_u8(src3_row + sj);

                    uint16x8_t vSum_0 = vaddl_u8(vget_low_u8(vLane1.val[0]), vget_low_u8(vLane1.val[1]));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane1.val[2]), vget_low_u8(vLane1.val[3])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane2.val[0]), vget_low_u8(vLane2.val[1])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane2.val[2]), vget_low_u8(vLane2.val[3])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane3.val[0]), vget_low_u8(vLane3.val[1])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane3.val[2]), vget_low_u8(vLane3.val[3])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane4.val[0]), vget_low_u8(vLane4.val[1])));
                    vSum_0 = vaddq_u16(vSum_0, vaddl_u8(vget_low_u8(vLane4.val[2]), vget_low_u8(vLane4.val[3])));

                    uint16x8_t vSum_1 = vaddl_u8(vget_high_u8(vLane1.val[0]), vget_high_u8(vLane1.val[1]));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane1.val[2]), vget_high_u8(vLane1.val[3])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane2.val[0]), vget_high_u8(vLane2.val[1])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane2.val[2]), vget_high_u8(vLane2.val[3])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane3.val[0]), vget_high_u8(vLane3.val[1])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane3.val[2]), vget_high_u8(vLane3.val[3])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane4.val[0]), vget_high_u8(vLane4.val[1])));
                    vSum_1 = vaddq_u16(vSum_1, vaddl_u8(vget_high_u8(vLane4.val[2]), vget_high_u8(vLane4.val[3])));

                    uint8x8_t vRes_0 = areaDownsamplingDivision<opencv_like,4>(vSum_0);
                    uint8x8_t vRes_1 = areaDownsamplingDivision<opencv_like,4>(vSum_1);

                    vst1q_u8(dst_row + dj, vcombine_u8(vRes_0, vRes_1));
                }
#endif

                for ( ; dj < roiw8; dj += 8, sj += 32)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);
                    internal::prefetch(src2_row + sj);
                    internal::prefetch(src3_row + sj);

                    uint8x8x4_t vLane1 = vld4_u8(src0_row + sj);
                    uint8x8x4_t vLane2 = vld4_u8(src1_row + sj);
                    uint8x8x4_t vLane3 = vld4_u8(src2_row + sj);
                    uint8x8x4_t vLane4 = vld4_u8(src3_row + sj);

                    uint16x8_t vSum = vaddl_u8(vLane1.val[0], vLane1.val[1]);
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane1.val[2], vLane1.val[3]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane2.val[0], vLane2.val[1]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane2.val[2], vLane2.val[3]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane3.val[0], vLane3.val[1]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane3.val[2], vLane3.val[3]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane4.val[0], vLane4.val[1]));
                    vSum = vaddq_u16(vSum, vaddl_u8(vLane4.val[2], vLane4.val[3]));

                    vst1_u8(dst_row + dj, areaDownsamplingDivision<opencv_like,4>(vSum));
                }

                for ( ; dj < dsize.width; ++dj, sj += 4)
                {
                    dst_row[dj] = areaDownsamplingDivision<opencv_like,4>(
                                      (u16)src0_row[sj] + src0_row[sj + 1] + src0_row[sj + 2] + src0_row[sj + 3] +
                                      src1_row[sj] + src1_row[sj + 1] + src1_row[sj + 2] + src1_row[sj + 3] +
                                      src2_row[sj] + src2_row[sj + 1] + src2_row[sj + 2] + src2_row[sj + 3] +
                                      src3_row[sj] + src3_row[sj + 1] + src3_row[sj + 2] + src3_row[sj + 3]);
                }
            }
        }
    }
    else if (channels == 4)
    {
        if ((wr == 2.0f) && (hr == 2.0f))
        {
#ifndef ANDROID
            size_t roiw4 = dsize.width >= 3 ? (dsize.width - 3) << 2 : 0;
#endif
            size_t roiw2 = dsize.width >= 1 ? (dsize.width - 1) << 2 : 0;

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 1);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 1) + 1);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw4; dj += 16, sj += 32)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);

                    uint8x8_t vRes_0, vRes_1;

                    {
                        uint8x16_t vLane1 = vld1q_u8(src0_row + sj);
                        uint8x16_t vLane2 = vld1q_u8(src1_row + sj);

                        uint16x8_t vLane_l = vaddl_u8(vget_low_u8(vLane1), vget_low_u8(vLane2));
                        uint16x8_t vLane_h = vaddl_u8(vget_high_u8(vLane1), vget_high_u8(vLane2));

                        uint16x4_t vSum_l = vadd_u16(vget_low_u16(vLane_l), vget_high_u16(vLane_l));
                        uint16x4_t vSum_h = vadd_u16(vget_low_u16(vLane_h), vget_high_u16(vLane_h));

                        vRes_0 = areaDownsamplingDivision<opencv_like,2>(vcombine_u16(vSum_l, vSum_h));
                    }

                    {
                        uint8x16_t vLane1 = vld1q_u8(src0_row + sj + 16);
                        uint8x16_t vLane2 = vld1q_u8(src1_row + sj + 16);

                        uint16x8_t vLane_l = vaddl_u8(vget_low_u8(vLane1), vget_low_u8(vLane2));
                        uint16x8_t vLane_h = vaddl_u8(vget_high_u8(vLane1), vget_high_u8(vLane2));

                        uint16x4_t vSum_l = vadd_u16(vget_low_u16(vLane_l), vget_high_u16(vLane_l));
                        uint16x4_t vSum_h = vadd_u16(vget_low_u16(vLane_h), vget_high_u16(vLane_h));

                        vRes_1 = areaDownsamplingDivision<opencv_like,2>(vcombine_u16(vSum_l, vSum_h));
                    }

                    vst1q_u8(dst_row + dj, vcombine_u8(vRes_0, vRes_1));
                }
#endif

                for ( ; dj < roiw2; dj += 8, sj += 16)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);

                    uint8x16_t vLane1 = vld1q_u8(src0_row + sj);
                    uint8x16_t vLane2 = vld1q_u8(src1_row + sj);

                    uint16x8_t vLane_l = vaddl_u8(vget_low_u8(vLane1), vget_low_u8(vLane2));
                    uint16x8_t vLane_h = vaddl_u8(vget_high_u8(vLane1), vget_high_u8(vLane2));

                    uint16x4_t vSum_l = vadd_u16(vget_low_u16(vLane_l), vget_high_u16(vLane_l));
                    uint16x4_t vSum_h = vadd_u16(vget_low_u16(vLane_h), vget_high_u16(vLane_h));

                    uint8x8_t vRes = areaDownsamplingDivision<opencv_like,2>(vcombine_u16(vSum_l, vSum_h));
                    vst1_u8(dst_row + dj, vRes);
                }

                for (size_t dwidth = dsize.width << 2; dj < dwidth; dj += 4, sj += 8)
                {
                    dst_row[dj    ] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj    ] + src0_row[sj + 4] +
                                               src1_row[sj    ] + src1_row[sj + 4]);
                    dst_row[dj + 1] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj + 1] + src0_row[sj + 5] +
                                               src1_row[sj + 1] + src1_row[sj + 5]);
                    dst_row[dj + 2] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj + 2] + src0_row[sj + 6] +
                                               src1_row[sj + 2] + src1_row[sj + 6]);
                    dst_row[dj + 3] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj + 3] + src0_row[sj + 7] +
                                               src1_row[sj + 3] + src1_row[sj + 7]);
                }
            }
        }
        else if ((wr == 0.5f) && (hr == 0.5f))
        {
#ifndef ANDROID
            size_t roiw32 = dsize.width >= 31 ? (dsize.width - 31) << 2 : 0;
#endif
            size_t roiw16 = dsize.width >= 15 ? (dsize.width - 15) << 2 : 0;

            for (size_t i = 0; i < dsize.height; i += 2)
            {
                const u8 * src_row = internal::getRowPtr(srcBase, srcStride, i >> 1);
                u8 * dst0_row = internal::getRowPtr(dstBase, dstStride, i);
                u8 * dst1_row = internal::getRowPtr(dstBase, dstStride, std::min(i + 1, dsize.height - 1));
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw32; dj += 128, sj += 64)
                {
                    internal::prefetch(src_row + sj);

                    uint8x16x4_t v_src = vld4q_u8(src_row + sj);
                    uint8x16x2_t v_c0 = vzipq_u8(v_src.val[0], v_src.val[0]);
                    uint8x16x2_t v_c1 = vzipq_u8(v_src.val[1], v_src.val[1]);
                    uint8x16x2_t v_c2 = vzipq_u8(v_src.val[2], v_src.val[2]);
                    uint8x16x2_t v_c3 = vzipq_u8(v_src.val[3], v_src.val[3]);

                    uint8x16x4_t v_dst;
                    v_dst.val[0] = v_c0.val[0];
                    v_dst.val[1] = v_c1.val[0];
                    v_dst.val[2] = v_c2.val[0];
                    v_dst.val[3] = v_c3.val[0];
                    vst4q_u8(dst0_row + dj, v_dst);
                    vst4q_u8(dst1_row + dj, v_dst);

                    v_dst.val[0] = v_c0.val[1];
                    v_dst.val[1] = v_c1.val[1];
                    v_dst.val[2] = v_c2.val[1];
                    v_dst.val[3] = v_c3.val[1];
                    vst4q_u8(dst0_row + dj + 64, v_dst);
                    vst4q_u8(dst1_row + dj + 64, v_dst);
                }
#endif

                for ( ; dj < roiw16; dj += 64, sj += 32)
                {
                    internal::prefetch(src_row + sj);

                    uint8x8x4_t v_src = vld4_u8(src_row + sj);
                    uint8x8x2_t v_c0 = vzip_u8(v_src.val[0], v_src.val[0]);
                    uint8x8x2_t v_c1 = vzip_u8(v_src.val[1], v_src.val[1]);
                    uint8x8x2_t v_c2 = vzip_u8(v_src.val[2], v_src.val[2]);
                    uint8x8x2_t v_c3 = vzip_u8(v_src.val[3], v_src.val[3]);

                    uint8x16x4_t v_dst;
                    v_dst.val[0] = vcombine_u8(v_c0.val[0], v_c0.val[1]);
                    v_dst.val[1] = vcombine_u8(v_c1.val[0], v_c1.val[1]);
                    v_dst.val[2] = vcombine_u8(v_c2.val[0], v_c2.val[1]);
                    v_dst.val[3] = vcombine_u8(v_c3.val[0], v_c3.val[1]);
                    vst4q_u8(dst0_row + dj, v_dst);
                    vst4q_u8(dst1_row + dj, v_dst);
                }

                for (size_t dwidth = dsize.width << 2; dj < dwidth; dj += 8, sj += 4)
                {
                    u8 src_val = src_row[sj];
                    dst0_row[dj] = dst0_row[dj + 4] = src_val;
                    dst1_row[dj] = dst1_row[dj + 4] = src_val;

                    src_val = src_row[sj + 1];
                    dst0_row[dj + 1] = dst0_row[dj + 5] = src_val;
                    dst1_row[dj + 1] = dst1_row[dj + 5] = src_val;

                    src_val = src_row[sj + 2];
                    dst0_row[dj + 2] = dst0_row[dj + 6] = src_val;
                    dst1_row[dj + 2] = dst1_row[dj + 6] = src_val;

                    src_val = src_row[sj + 3];
                    dst0_row[dj + 3] = dst0_row[dj + 7] = src_val;
                    dst1_row[dj + 3] = dst1_row[dj + 7] = src_val;
                }
            }
        }
        else //if ((hr == 4.0f) && (wr == 4.0f)) //the only scale that lasts after isSupported check
        {
            size_t roiw4 = dsize.width >= 3 ? (dsize.width - 3) << 2 : 0;
            size_t roiw2 = dsize.width >= 1 ? (dsize.width - 1) << 2 : 0;

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 2);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 1);
                const u8 * src2_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 2);
                const u8 * src3_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 3);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

                for ( ; dj < roiw4; dj += 16, sj += 64)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);
                    internal::prefetch(src2_row + sj);
                    internal::prefetch(src3_row + sj);

                    uint8x16_t vLane10 = vld1q_u8(src0_row + sj), vLane11 = vld1q_u8(src0_row + sj + 16);
                    uint8x16_t vLane20 = vld1q_u8(src1_row + sj), vLane21 = vld1q_u8(src1_row + sj + 16);
                    uint8x16_t vLane30 = vld1q_u8(src2_row + sj), vLane31 = vld1q_u8(src2_row + sj + 16);
                    uint8x16_t vLane40 = vld1q_u8(src3_row + sj), vLane41 = vld1q_u8(src3_row + sj + 16);

                    uint16x8_t v_part_0, v_part_1;
                    {
                        uint16x8_t v_sum0 = vaddl_u8(vget_low_u8(vLane10), vget_high_u8(vLane10));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane20), vget_high_u8(vLane20)));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane30), vget_high_u8(vLane30)));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane40), vget_high_u8(vLane40)));

                        uint16x8_t v_sum1 = vaddl_u8(vget_low_u8(vLane11), vget_high_u8(vLane11));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane21), vget_high_u8(vLane21)));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane31), vget_high_u8(vLane31)));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane41), vget_high_u8(vLane41)));

                        v_part_0 = vcombine_u16(vadd_u16(vget_low_u16(v_sum0), vget_high_u16(v_sum0)),
                                                vadd_u16(vget_low_u16(v_sum1), vget_high_u16(v_sum1)));
                    }

                    vLane10 = vld1q_u8(src0_row + sj + 32);
                    vLane11 = vld1q_u8(src0_row + sj + 48);
                    vLane20 = vld1q_u8(src1_row + sj + 32);
                    vLane21 = vld1q_u8(src1_row + sj + 48);
                    vLane30 = vld1q_u8(src2_row + sj + 32);
                    vLane31 = vld1q_u8(src2_row + sj + 48);
                    vLane40 = vld1q_u8(src3_row + sj + 32);
                    vLane41 = vld1q_u8(src3_row + sj + 48);

                    {
                        uint16x8_t v_sum0 = vaddl_u8(vget_low_u8(vLane10), vget_high_u8(vLane10));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane20), vget_high_u8(vLane20)));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane30), vget_high_u8(vLane30)));
                        v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane40), vget_high_u8(vLane40)));

                        uint16x8_t v_sum1 = vaddl_u8(vget_low_u8(vLane11), vget_high_u8(vLane11));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane21), vget_high_u8(vLane21)));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane31), vget_high_u8(vLane31)));
                        v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane41), vget_high_u8(vLane41)));

                        v_part_1 = vcombine_u16(vadd_u16(vget_low_u16(v_sum0), vget_high_u16(v_sum0)),
                                                vadd_u16(vget_low_u16(v_sum1), vget_high_u16(v_sum1)));
                    }

                    vst1q_u8(dst_row + dj, vcombine_u8(areaDownsamplingDivision<opencv_like,4>(v_part_0),
                                                       areaDownsamplingDivision<opencv_like,4>(v_part_1)));
                }

                for ( ; dj < roiw2; dj += 8, sj += 32)
                {
                    uint8x16_t vLane10 = vld1q_u8(src0_row + sj), vLane11 = vld1q_u8(src0_row + sj + 16);
                    uint8x16_t vLane20 = vld1q_u8(src1_row + sj), vLane21 = vld1q_u8(src1_row + sj + 16);
                    uint8x16_t vLane30 = vld1q_u8(src2_row + sj), vLane31 = vld1q_u8(src2_row + sj + 16);
                    uint8x16_t vLane40 = vld1q_u8(src3_row + sj), vLane41 = vld1q_u8(src3_row + sj + 16);

                    uint16x8_t v_sum0 = vaddl_u8(vget_low_u8(vLane10), vget_high_u8(vLane10));
                    v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane20), vget_high_u8(vLane20)));
                    v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane30), vget_high_u8(vLane30)));
                    v_sum0 = vaddq_u16(v_sum0, vaddl_u8(vget_low_u8(vLane40), vget_high_u8(vLane40)));

                    uint16x8_t v_sum1 = vaddl_u8(vget_low_u8(vLane11), vget_high_u8(vLane11));
                    v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane21), vget_high_u8(vLane21)));
                    v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane31), vget_high_u8(vLane31)));
                    v_sum1 = vaddq_u16(v_sum1, vaddl_u8(vget_low_u8(vLane41), vget_high_u8(vLane41)));

                    uint16x8_t v_sum = vcombine_u16(vadd_u16(vget_low_u16(v_sum0), vget_high_u16(v_sum0)),
                                                    vadd_u16(vget_low_u16(v_sum1), vget_high_u16(v_sum1)));

                    vst1_u8(dst_row + dj, areaDownsamplingDivision<opencv_like,4>(v_sum));
                }

                for (size_t dwidth = dsize.width << 2; dj < dwidth; dj += 4, sj += 16)
                {
                    dst_row[dj    ] = areaDownsamplingDivision<opencv_like,4>(
                                            (u16)src0_row[sj     ] + src0_row[sj +  4] +
                                                 src0_row[sj +  8] + src0_row[sj + 12] +
                                                 src1_row[sj     ] + src1_row[sj +  4] +
                                                 src1_row[sj +  8] + src1_row[sj + 12] +
                                                 src2_row[sj     ] + src2_row[sj +  4] +
                                                 src2_row[sj +  8] + src2_row[sj + 12] +
                                                 src3_row[sj     ] + src3_row[sj +  4] +
                                                 src3_row[sj +  8] + src3_row[sj + 12]);

                    dst_row[dj + 1] = areaDownsamplingDivision<opencv_like,4>(
                                            (u16)src0_row[sj +  1] + src0_row[sj +  5] +
                                                 src0_row[sj +  9] + src0_row[sj + 13] +
                                                 src1_row[sj +  1] + src1_row[sj +  5] +
                                                 src1_row[sj +  9] + src1_row[sj + 13] +
                                                 src2_row[sj +  1] + src2_row[sj +  5] +
                                                 src2_row[sj +  9] + src2_row[sj + 13] +
                                                 src3_row[sj +  1] + src3_row[sj +  5] +
                                                 src3_row[sj +  9] + src3_row[sj + 13]);

                    dst_row[dj + 2] = areaDownsamplingDivision<opencv_like,4>(
                                            (u16)src0_row[sj +  2] + src0_row[sj +  6] +
                                                 src0_row[sj + 10] + src0_row[sj + 14] +
                                                 src1_row[sj +  2] + src1_row[sj +  6] +
                                                 src1_row[sj + 10] + src1_row[sj + 14] +
                                                 src2_row[sj +  2] + src2_row[sj +  6] +
                                                 src2_row[sj + 10] + src2_row[sj + 14] +
                                                 src3_row[sj +  2] + src3_row[sj +  6] +
                                                 src3_row[sj + 10] + src3_row[sj + 14]);

                    dst_row[dj + 3] = areaDownsamplingDivision<opencv_like,4>(
                                            (u16)src0_row[sj +  3] + src0_row[sj +  7] +
                                                 src0_row[sj + 11] + src0_row[sj + 15] +
                                                 src1_row[sj +  3] + src1_row[sj +  7] +
                                                 src1_row[sj + 11] + src1_row[sj + 15] +
                                                 src2_row[sj +  3] + src2_row[sj +  7] +
                                                 src2_row[sj + 11] + src2_row[sj + 15] +
                                                 src3_row[sj +  3] + src3_row[sj +  7] +
                                                 src3_row[sj + 11] + src3_row[sj + 15]);
                }
            }
        }
    }
    else if (channels == 3)
    {
        if ((wr == 2.0f) && (wr == 2.0f))
        {
#ifndef ANDROID
            size_t roiw16 = dsize.width >= 15 ? (dsize.width - 15) * 3 : 0;
#endif
            size_t roiw8 = dsize.width >= 7 ? (dsize.width - 7) * 3 : 0;

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 1);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 1) + 1);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw16; dj += 48, sj += 96)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);

                    uint8x16x3_t vLane1 = vld3q_u8(src0_row + sj);
                    uint8x16x3_t vLane2 = vld3q_u8(src1_row + sj);

                    uint8x8x3_t v_dst0, v_dst1;
                    {
                        uint16x8_t v_el0 = vpaddlq_u8(vLane1.val[0]);
                        uint16x8_t v_el1 = vpaddlq_u8(vLane1.val[1]);
                        uint16x8_t v_el2 = vpaddlq_u8(vLane1.val[2]);
                        v_el0 = vpadalq_u8(v_el0, vLane2.val[0]);
                        v_el1 = vpadalq_u8(v_el1, vLane2.val[1]);
                        v_el2 = vpadalq_u8(v_el2, vLane2.val[2]);

                        v_dst0.val[0] = areaDownsamplingDivision<opencv_like,2>(v_el0);
                        v_dst0.val[1] = areaDownsamplingDivision<opencv_like,2>(v_el1);
                        v_dst0.val[2] = areaDownsamplingDivision<opencv_like,2>(v_el2);
                    }

                    vLane1 = vld3q_u8(src0_row + sj + 48);
                    vLane2 = vld3q_u8(src1_row + sj + 48);
                    {
                        uint16x8_t v_el0 = vpaddlq_u8(vLane1.val[0]);
                        uint16x8_t v_el1 = vpaddlq_u8(vLane1.val[1]);
                        uint16x8_t v_el2 = vpaddlq_u8(vLane1.val[2]);
                        v_el0 = vpadalq_u8(v_el0, vLane2.val[0]);
                        v_el1 = vpadalq_u8(v_el1, vLane2.val[1]);
                        v_el2 = vpadalq_u8(v_el2, vLane2.val[2]);

                        v_dst1.val[0] = areaDownsamplingDivision<opencv_like,2>(v_el0);
                        v_dst1.val[1] = areaDownsamplingDivision<opencv_like,2>(v_el1);
                        v_dst1.val[2] = areaDownsamplingDivision<opencv_like,2>(v_el2);
                    }

                    uint8x16x3_t v_dst;
                    v_dst.val[0] = vcombine_u8(v_dst0.val[0], v_dst1.val[0]);
                    v_dst.val[1] = vcombine_u8(v_dst0.val[1], v_dst1.val[1]);
                    v_dst.val[2] = vcombine_u8(v_dst0.val[2], v_dst1.val[2]);

                    vst3q_u8(dst_row + dj, v_dst);
                }
#endif

                for ( ; dj < roiw8; dj += 24, sj += 48)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);

                    uint8x16x3_t vLane1 = vld3q_u8(src0_row + sj);
                    uint8x16x3_t vLane2 = vld3q_u8(src1_row + sj);

                    uint16x8_t v_el0 = vpaddlq_u8(vLane1.val[0]);
                    uint16x8_t v_el1 = vpaddlq_u8(vLane1.val[1]);
                    uint16x8_t v_el2 = vpaddlq_u8(vLane1.val[2]);
                    v_el0 = vpadalq_u8(v_el0, vLane2.val[0]);
                    v_el1 = vpadalq_u8(v_el1, vLane2.val[1]);
                    v_el2 = vpadalq_u8(v_el2, vLane2.val[2]);

                    uint8x8x3_t v_dst;
                    v_dst.val[0] = areaDownsamplingDivision<opencv_like,2>(v_el0);
                    v_dst.val[1] = areaDownsamplingDivision<opencv_like,2>(v_el1);
                    v_dst.val[2] = areaDownsamplingDivision<opencv_like,2>(v_el2);

                    vst3_u8(dst_row + dj, v_dst);
                }

                for (size_t dwidth = dsize.width * 3; dj < dwidth; dj += 3, sj += 6)
                {
                    dst_row[dj    ] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj    ] + src0_row[sj + 3] +
                                               src1_row[sj    ] + src1_row[sj + 3]);
                    dst_row[dj + 1] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj + 1] + src0_row[sj + 4] +
                                               src1_row[sj + 1] + src1_row[sj + 4]);
                    dst_row[dj + 2] = areaDownsamplingDivision<opencv_like,2>(
                                          (u16)src0_row[sj + 2] + src0_row[sj + 5] +
                                               src1_row[sj + 2] + src1_row[sj + 5]);
                }
            }
        }
        else if ((wr == 0.5f) && (hr == 0.5f))
        {
#ifndef ANDROID
            size_t roiw32 = dsize.width >= 31 ? (dsize.width - 31) * 3 : 0;
#endif
            size_t roiw16 = dsize.width >= 15 ? (dsize.width - 15) * 3 : 0;

            for (size_t i = 0; i < dsize.height; i += 2)
            {
                const u8 * src_row = internal::getRowPtr(srcBase, srcStride, i >> 1);
                u8 * dst0_row = internal::getRowPtr(dstBase, dstStride, i);
                u8 * dst1_row = internal::getRowPtr(dstBase, dstStride, std::min(i + 1, dsize.height - 1));
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw32; dj += 96, sj += 48)
                {
                    internal::prefetch(src_row + sj);

                    uint8x16x3_t v_src = vld3q_u8(src_row + sj);
                    uint8x16x2_t v_c0 = vzipq_u8(v_src.val[0], v_src.val[0]);
                    uint8x16x2_t v_c1 = vzipq_u8(v_src.val[1], v_src.val[1]);
                    uint8x16x2_t v_c2 = vzipq_u8(v_src.val[2], v_src.val[2]);

                    uint8x16x3_t v_dst;
                    v_dst.val[0] = v_c0.val[0];
                    v_dst.val[1] = v_c1.val[0];
                    v_dst.val[2] = v_c2.val[0];
                    vst3q_u8(dst0_row + dj, v_dst);
                    vst3q_u8(dst1_row + dj, v_dst);

                    v_dst.val[0] = v_c0.val[1];
                    v_dst.val[1] = v_c1.val[1];
                    v_dst.val[2] = v_c2.val[1];
                    vst3q_u8(dst0_row + dj + 48, v_dst);
                    vst3q_u8(dst1_row + dj + 48, v_dst);
                }
#endif

                for ( ; dj < roiw16; dj += 48, sj += 24)
                {
                    internal::prefetch(src_row + sj);

                    uint8x8x3_t v_src = vld3_u8(src_row + sj);
                    uint8x8x2_t v_c0 = vzip_u8(v_src.val[0], v_src.val[0]);
                    uint8x8x2_t v_c1 = vzip_u8(v_src.val[1], v_src.val[1]);
                    uint8x8x2_t v_c2 = vzip_u8(v_src.val[2], v_src.val[2]);

                    uint8x16x3_t v_dst;
                    v_dst.val[0] = vcombine_u8(v_c0.val[0], v_c0.val[1]);
                    v_dst.val[1] = vcombine_u8(v_c1.val[0], v_c1.val[1]);
                    v_dst.val[2] = vcombine_u8(v_c2.val[0], v_c2.val[1]);
                    vst3q_u8(dst0_row + dj, v_dst);
                    vst3q_u8(dst1_row + dj, v_dst);
                }

                for (size_t dwidth = dsize.width * 3; dj < dwidth; dj += 6, sj += 3)
                {
                    u8 src_val = src_row[sj];
                    dst0_row[dj] = dst0_row[dj + 3] = src_val;
                    dst1_row[dj] = dst1_row[dj + 3] = src_val;

                    src_val = src_row[sj + 1];
                    dst0_row[dj + 1] = dst0_row[dj + 4] = src_val;
                    dst1_row[dj + 1] = dst1_row[dj + 4] = src_val;

                    src_val = src_row[sj + 2];
                    dst0_row[dj + 2] = dst0_row[dj + 5] = src_val;
                    dst1_row[dj + 2] = dst1_row[dj + 5] = src_val;
                }
            }
        }
        else //if ((hr == 4.0f) && (wr == 4.0f)) //the only scale that lasts after isSupported check
        {
#ifndef ANDROID
            size_t roiw8 = dsize.width >= 7 ? (dsize.width - 7) * 3 : 0;
#endif

            for (size_t i = 0; i < dsize.height; ++i)
            {
                const u8 * src0_row = internal::getRowPtr(srcBase, srcStride, i << 2);
                const u8 * src1_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 1);
                const u8 * src2_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 2);
                const u8 * src3_row = internal::getRowPtr(srcBase, srcStride, (i << 2) + 3);
                u8 * dst_row = internal::getRowPtr(dstBase, dstStride, i);
                size_t sj = 0, dj = 0;

#ifndef ANDROID
                for ( ; dj < roiw8; dj += 24, sj += 96)
                {
                    internal::prefetch(src0_row + sj);
                    internal::prefetch(src1_row + sj);
                    internal::prefetch(src2_row + sj);
                    internal::prefetch(src3_row + sj);

                    uint8x16x3_t vLane10 = vld3q_u8(src0_row + sj), vLane11 = vld3q_u8(src0_row + sj + 48);
                    uint8x16x3_t vLane20 = vld3q_u8(src1_row + sj), vLane21 = vld3q_u8(src1_row + sj + 48);
                    uint8x16x3_t vLane30 = vld3q_u8(src2_row + sj), vLane31 = vld3q_u8(src2_row + sj + 48);
                    uint8x16x3_t vLane40 = vld3q_u8(src3_row + sj), vLane41 = vld3q_u8(src3_row + sj + 48);

                    uint8x8x3_t v_dst;

                    // channel 0
                    {
                        uint16x8_t v_lane0 = vpaddlq_u8(vLane10.val[0]);
                        uint16x8_t v_lane1 = vpaddlq_u8(vLane20.val[0]);
                        uint16x8_t v_lane2 = vpaddlq_u8(vLane30.val[0]);
                        uint16x8_t v_lane3 = vpaddlq_u8(vLane40.val[0]);
                        v_lane0 = vaddq_u16(v_lane0, v_lane1);
                        v_lane0 = vaddq_u16(v_lane0, v_lane2);
                        v_lane0 = vaddq_u16(v_lane0, v_lane3);

                        uint16x8_t v_lane0_ = vpaddlq_u8(vLane11.val[0]);
                        uint16x8_t v_lane1_ = vpaddlq_u8(vLane21.val[0]);
                        uint16x8_t v_lane2_ = vpaddlq_u8(vLane31.val[0]);
                        uint16x8_t v_lane3_ = vpaddlq_u8(vLane41.val[0]);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane1_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane2_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane3_);

                        v_dst.val[0] = areaDownsamplingDivision<opencv_like,4>(
                                           vcombine_u16(vmovn_u32(vpaddlq_u16(v_lane0)),
                                                        vmovn_u32(vpaddlq_u16(v_lane0_))));
                    }

                    // channel 1
                    {
                        uint16x8_t v_lane0 = vpaddlq_u8(vLane10.val[1]);
                        uint16x8_t v_lane1 = vpaddlq_u8(vLane20.val[1]);
                        uint16x8_t v_lane2 = vpaddlq_u8(vLane30.val[1]);
                        uint16x8_t v_lane3 = vpaddlq_u8(vLane40.val[1]);
                        v_lane0 = vaddq_u16(v_lane0, v_lane1);
                        v_lane0 = vaddq_u16(v_lane0, v_lane2);
                        v_lane0 = vaddq_u16(v_lane0, v_lane3);

                        uint16x8_t v_lane0_ = vpaddlq_u8(vLane11.val[1]);
                        uint16x8_t v_lane1_ = vpaddlq_u8(vLane21.val[1]);
                        uint16x8_t v_lane2_ = vpaddlq_u8(vLane31.val[1]);
                        uint16x8_t v_lane3_ = vpaddlq_u8(vLane41.val[1]);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane1_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane2_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane3_);

                        v_dst.val[1] = areaDownsamplingDivision<opencv_like,4>(
                                           vcombine_u16(vmovn_u32(vpaddlq_u16(v_lane0)),
                                                        vmovn_u32(vpaddlq_u16(v_lane0_))));
                    }

                    // channel 2
                    {
                        uint16x8_t v_lane0 = vpaddlq_u8(vLane10.val[2]);
                        uint16x8_t v_lane1 = vpaddlq_u8(vLane20.val[2]);
                        uint16x8_t v_lane2 = vpaddlq_u8(vLane30.val[2]);
                        uint16x8_t v_lane3 = vpaddlq_u8(vLane40.val[2]);
                        v_lane0 = vaddq_u16(v_lane0, v_lane1);
                        v_lane0 = vaddq_u16(v_lane0, v_lane2);
                        v_lane0 = vaddq_u16(v_lane0, v_lane3);

                        uint16x8_t v_lane0_ = vpaddlq_u8(vLane11.val[2]);
                        uint16x8_t v_lane1_ = vpaddlq_u8(vLane21.val[2]);
                        uint16x8_t v_lane2_ = vpaddlq_u8(vLane31.val[2]);
                        uint16x8_t v_lane3_ = vpaddlq_u8(vLane41.val[2]);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane1_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane2_);
                        v_lane0_ = vaddq_u16(v_lane0_, v_lane3_);

                        v_dst.val[2] = areaDownsamplingDivision<opencv_like,4>(
                                           vcombine_u16(vmovn_u32(vpaddlq_u16(v_lane0)),
                                                        vmovn_u32(vpaddlq_u16(v_lane0_))));
                    }

                    vst3_u8(dst_row + dj, v_dst);
                }
#endif

                for (size_t dwidth = dsize.width * 3; dj < dwidth; dj += 3, sj += 12)
                {
                    dst_row[dj    ] = areaDownsamplingDivision<opencv_like,4>(
                                          (u16)src0_row[sj    ] + src0_row[sj +  3] +
                                               src0_row[sj + 6] + src0_row[sj +  9] +
                                               src1_row[sj    ] + src1_row[sj +  3] +
                                               src1_row[sj + 6] + src1_row[sj +  9] +
                                               src2_row[sj    ] + src2_row[sj +  3] +
                                               src2_row[sj + 6] + src2_row[sj +  9] +
                                               src3_row[sj    ] + src3_row[sj +  3] +
                                               src3_row[sj + 6] + src3_row[sj +  9]);

                    dst_row[dj + 1] = areaDownsamplingDivision<opencv_like,4>(
                                          (u16)src0_row[sj + 1] + src0_row[sj +  4] +
                                               src0_row[sj + 7] + src0_row[sj + 10] +
                                               src1_row[sj + 1] + src1_row[sj +  4] +
                                               src1_row[sj + 7] + src1_row[sj + 10] +
                                               src2_row[sj + 1] + src2_row[sj +  4] +
                                               src2_row[sj + 7] + src2_row[sj + 10] +
                                               src3_row[sj + 1] + src3_row[sj +  4] +
                                               src3_row[sj + 7] + src3_row[sj + 10]);

                    dst_row[dj + 2] = areaDownsamplingDivision<opencv_like,4>(
                                          (u16)src0_row[sj + 2] + src0_row[sj +  5] +
                                               src0_row[sj + 8] + src0_row[sj + 11] +
                                               src1_row[sj + 2] + src1_row[sj +  5] +
                                               src1_row[sj + 8] + src1_row[sj + 11] +
                                               src2_row[sj + 2] + src2_row[sj +  5] +
                                               src2_row[sj + 8] + src2_row[sj + 11] +
                                               src3_row[sj + 2] + src3_row[sj +  5] +
                                               src3_row[sj + 8] + src3_row[sj + 11]);
                }
            }
        }
    }
#else
    (void)dsize;
    (void)srcBase;
    (void)srcStride;
    (void)dstBase;
    (void)dstStride;
    (void)wr;
    (void)hr;
#endif
    (void)ssize;
}

void resizeAreaOpenCV(const Size2D &ssize, const Size2D &dsize,
                const u8 * srcBase, ptrdiff_t srcStride,
                u8 * dstBase, ptrdiff_t dstStride,
                f32 wr, f32 hr, u32 channels)
{
   resizeAreaRounding<true>(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr, channels);
}

void resizeArea(const Size2D &ssize, const Size2D &dsize,
                const u8 * srcBase, ptrdiff_t srcStride,
                u8 * dstBase, ptrdiff_t dstStride,
                f32 wr, f32 hr, u32 channels)
{
   resizeAreaRounding<false>(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr, channels);
}

#ifdef CAROTENE_NEON

namespace {

uint8x8_t resizeLinearStep(uint8x16_t vr1, uint8x16_t vr2,
                           uint8x8_t vlutl, uint8x8_t vluth,
                           float32x4_t vrw, float32x4_t vcw0, float32x4_t vcw1)
{
    uint8x8_t vr1l = internal::vqtbl1_u8(vr1, vlutl);
    uint8x8_t vr1h = internal::vqtbl1_u8(vr1, vluth);
    uint8x8_t vr2l = internal::vqtbl1_u8(vr2, vlutl);
    uint8x8_t vr2h = internal::vqtbl1_u8(vr2, vluth);

    uint16x8_t v1hw = vmovl_u8(vr1h);
    uint16x8_t v2hw = vmovl_u8(vr2h);

    int16x8_t v1df = vreinterpretq_s16_u16(vsubl_u8(vr1l, vr1h));
    int16x8_t v2df = vreinterpretq_s16_u16(vsubl_u8(vr2l, vr2h));

    float32x4_t v1L = vcvtq_f32_u32(vmovl_u16(vget_low_u16(v1hw)));
    float32x4_t v1H = vcvtq_f32_u32(vmovl_u16(vget_high_u16(v1hw)));
    float32x4_t v2L = vcvtq_f32_u32(vmovl_u16(vget_low_u16(v2hw)));
    float32x4_t v2H = vcvtq_f32_u32(vmovl_u16(vget_high_u16(v2hw)));

    v1L = vmlaq_f32(v1L, vcvtq_f32_s32(vmovl_s16(vget_low_s16(v1df))), vcw0);
    v1H = vmlaq_f32(v1H, vcvtq_f32_s32(vmovl_s16(vget_high_s16(v1df))), vcw1);
    v2L = vmlaq_f32(v2L, vcvtq_f32_s32(vmovl_s16(vget_low_s16(v2df))), vcw0);
    v2H = vmlaq_f32(v2H, vcvtq_f32_s32(vmovl_s16(vget_high_s16(v2df))), vcw1);

    float32x4_t vdiffL = vsubq_f32(v1L, v2L);
    float32x4_t vdiffH = vsubq_f32(v1H, v2H);

    float32x4_t vL = vmlaq_f32(v2L, vdiffL, vrw);
    float32x4_t vH = vmlaq_f32(v2H, vdiffH, vrw);
    uint16x4_t vL_ = vmovn_u32(vcvtq_u32_f32(vL));
    uint16x4_t vH_ = vmovn_u32(vcvtq_u32_f32(vH));
    return vmovn_u16(vcombine_u16(vL_, vH_));
}

} // namespace

namespace {

void resize_bilinear_rows(const Size2D &ssize, const Size2D &dsize,
                        const u8 * srcBase, ptrdiff_t srcStride,
                        u8 * dstBase, ptrdiff_t dstStride,
                        f32 hr, const u8** gcols, u8* gcweight, u8* buf)
{
    f32 scale_y_offset = 0.5f * hr - 0.5f;

    size_t dst_h8 = dsize.height & ~7;
    size_t dst_w8 = dsize.width & ~7;
    size_t src_w8 = ssize.width & ~7;

    size_t r = 0;
    for (; r < dst_h8; r += 8)
    {
resize8u_xystretch:
        const u8* rows[16];
        u8 rweight[8];

        for (u32 i = 0; i < 8; ++i)
        {
            f32 w = (i + r) * hr + scale_y_offset;
            ptrdiff_t src_row = floorf(w);
            ptrdiff_t src_row2 = src_row + 1;

            rweight[i] = (u8)((src_row2-w) * 128);

            if (src_row < 0)
                src_row = 0;
            if (src_row2 >= (ptrdiff_t)ssize.height)
                src_row2 = ssize.height-1;

            rows[2 * i] = srcBase + src_row * srcStride;
            rows[2 * i + 1] = srcBase + src_row2 * srcStride;
        }

        uint8x8_t vr0w = vdup_n_u8(rweight[0]);
        uint8x8_t vr1w = vdup_n_u8(rweight[1]);
        uint8x8_t vr2w = vdup_n_u8(rweight[2]);
        uint8x8_t vr3w = vdup_n_u8(rweight[3]);
        uint8x8_t vr4w = vdup_n_u8(rweight[4]);
        uint8x8_t vr5w = vdup_n_u8(rweight[5]);
        uint8x8_t vr6w = vdup_n_u8(rweight[6]);
        uint8x8_t vr7w = vdup_n_u8(rweight[7]);

        uint8x8_t vr0w2 = vdup_n_u8(128 - rweight[0]);
        uint8x8_t vr1w2 = vdup_n_u8(128 - rweight[1]);
        uint8x8_t vr2w2 = vdup_n_u8(128 - rweight[2]);
        uint8x8_t vr3w2 = vdup_n_u8(128 - rweight[3]);
        uint8x8_t vr4w2 = vdup_n_u8(128 - rweight[4]);
        uint8x8_t vr5w2 = vdup_n_u8(128 - rweight[5]);
        uint8x8_t vr6w2 = vdup_n_u8(128 - rweight[6]);
        uint8x8_t vr7w2 = vdup_n_u8(128 - rweight[7]);

        size_t col = 0;
        for(; col < src_w8; col += 8)
        {
            internal::prefetch(rows[3] + col);
            internal::prefetch(rows[7] + col);
            internal::prefetch(rows[11] + col);
            internal::prefetch(rows[15] + col);
resize8u_ystretch:
            uint8x8_t vsrc0l1 = vld1_u8(rows[0] + col);
            uint8x8_t vsrc0l2 = vld1_u8(rows[1] + col);
            uint8x8_t vsrc1l1 = vld1_u8(rows[2] + col);
            uint8x8_t vsrc1l2 = vld1_u8(rows[3] + col);

            // (l1 * w + l2 * (128 - w) + 64) / 128
            uint16x8_t vdst0l = vmull_u8(vsrc0l1, vr0w);
            uint16x8_t vdst1l = vmull_u8(vsrc1l1, vr1w);

            uint8x8_t vsrc2l1 = vld1_u8(rows[4] + col);
            uint8x8_t vsrc2l2 = vld1_u8(rows[5] + col);
            uint8x8_t vsrc3l1 = vld1_u8(rows[6] + col);
            uint8x8_t vsrc3l2 = vld1_u8(rows[7] + col);

            vdst0l = vmlal_u8(vdst0l, vsrc0l2, vr0w2);
            vdst1l = vmlal_u8(vdst1l, vsrc1l2, vr1w2);
            uint16x8_t vdst2l = vmull_u8(vsrc2l1, vr2w);
            uint16x8_t vdst3l = vmull_u8(vsrc3l1, vr3w);

            uint8x8_t vsrc4l1 = vld1_u8(rows[8] + col);
            uint8x8_t vsrc4l2 = vld1_u8(rows[9] + col);
            uint8x8_t vsrc5l1 = vld1_u8(rows[10] + col);
            uint8x8_t vsrc5l2 = vld1_u8(rows[11] + col);

            vdst2l = vmlal_u8(vdst2l, vsrc2l2, vr2w2);
            vdst3l = vmlal_u8(vdst3l, vsrc3l2, vr3w2);
            uint16x8_t vdst4l = vmull_u8(vsrc4l1, vr4w);
            uint16x8_t vdst5l = vmull_u8(vsrc5l1, vr5w);

            uint8x8_t vsrc6l1 = vld1_u8(rows[12] + col);
            uint8x8_t vsrc6l2 = vld1_u8(rows[13] + col);
            uint8x8_t vsrc7l1 = vld1_u8(rows[14] + col);
            uint8x8_t vsrc7l2 = vld1_u8(rows[15] + col);

            uint8x8_t vdst0 = vrshrn_n_u16(vdst0l, 7);
            uint8x8_t vdst1 = vrshrn_n_u16(vdst1l, 7);
            vdst4l = vmlal_u8(vdst4l, vsrc4l2, vr4w2);
            vdst5l = vmlal_u8(vdst5l, vsrc5l2, vr5w2);
            uint16x8_t vdst6l = vmull_u8(vsrc6l1, vr6w);
            uint16x8_t vdst7l = vmull_u8(vsrc7l1, vr7w);

            uint8x8_t vdst2 = vrshrn_n_u16(vdst2l, 7);
            uint8x8_t vdst3 = vrshrn_n_u16(vdst3l, 7);
            vdst6l = vmlal_u8(vdst6l, vsrc6l2, vr6w2);
            vdst7l = vmlal_u8(vdst7l, vsrc7l2, vr7w2);

            uint8x8_t vdst4 = vrshrn_n_u16(vdst4l, 7);
            uint8x8_t vdst5 = vrshrn_n_u16(vdst5l, 7);
            uint8x8_t vdst6 = vrshrn_n_u16(vdst6l, 7);
            uint8x8_t vdst7 = vrshrn_n_u16(vdst7l, 7);

            // == 8x8 matrix transpose ==

            //00 01 02 03 04 05 06 07   d0
            //10 11 12 13 14 15 16 17   d1
            //20 21 22 23 24 25 26 27   d2
            //30 31 32 33 34 35 36 37   d3
            //40 41 42 43 44 45 46 47   d4
            //50 51 52 53 54 55 56 57   d5
            //60 61 62 63 64 65 66 67   d6
            //70 71 72 73 74 75 76 77   d7

            uint8x8x2_t vdst10t = vtrn_u8(vdst0, vdst1);
            uint8x8x2_t vdst32t = vtrn_u8(vdst2, vdst3);
            uint8x8x2_t vdst54t = vtrn_u8(vdst4, vdst5);
            uint8x8x2_t vdst76t = vtrn_u8(vdst6, vdst7);

            uint8x16_t vd1d0 = vcombine_u8(vdst10t.val[0], vdst10t.val[1]);
            uint8x16_t vd3d2 = vcombine_u8(vdst32t.val[0], vdst32t.val[1]);
            uint8x16_t vd5d4 = vcombine_u8(vdst54t.val[0], vdst54t.val[1]);
            uint8x16_t vd7d6 = vcombine_u8(vdst76t.val[0], vdst76t.val[1]);

            //00 10 02 12 04 14 06 16   d0
            //01 11 03 13 05 15 07 17   d1
            //20 30 22 32 24 34 26 36   d2
            //21 31 23 33 25 35 27 37   d3
            //40 50 42 52 44 54 46 56   d4
            //41 51 43 53 45 55 47 57   d5
            //60 70 62 72 64 74 66 76   d6
            //61 71 63 73 65 75 67 77   d7

            uint16x8x2_t vq1q0t = vtrnq_u16((uint16x8_t)vd1d0, (uint16x8_t)vd3d2);
            uint16x8x2_t vq3q2t = vtrnq_u16((uint16x8_t)vd5d4, (uint16x8_t)vd7d6);

            //00 10 20 30 04 14 24 34   d0
            //01 11 21 31 05 15 25 35   d1
            //02 12 22 32 06 16 26 36   d2
            //03 13 23 33 07 17 27 37   d3
            //40 50 60 70 44 54 64 74   d4
            //41 51 61 71 45 55 65 75   d5
            //42 52 62 72 46 56 66 76   d6
            //43 53 63 73 47 57 67 77   d7

            uint32x4x2_t vq2q0t = vtrnq_u32((uint32x4_t)vq1q0t.val[0], (uint32x4_t)vq3q2t.val[0]);
            uint32x4x2_t vq3q1t = vtrnq_u32((uint32x4_t)vq1q0t.val[1], (uint32x4_t)vq3q2t.val[1]);

            //00 10 20 30 40 50 60 70   d0
            //01 11 21 31 41 51 61 71   d1
            //02 12 22 32 42 52 62 72   d2
            //03 13 23 33 43 53 63 73   d3
            //04 14 24 34 44 54 64 74   d4
            //05 15 25 35 45 55 65 75   d5
            //06 16 26 36 46 56 66 76   d6
            //07 17 27 37 47 57 67 77   d7

            vst1q_u8(buf + col * 8 +  0, (uint8x16_t)vq2q0t.val[0]);
            vst1q_u8(buf + col * 8 + 16, (uint8x16_t)vq3q1t.val[0]);
            vst1q_u8(buf + col * 8 + 32, (uint8x16_t)vq2q0t.val[1]);
            vst1q_u8(buf + col * 8 + 48, (uint8x16_t)vq3q1t.val[1]);
        }

        if (col < ssize.width)
        {
            col = ssize.width - 8;
            goto resize8u_ystretch;
        }

        u8* dst_data = dstBase + r * dstStride;
        const u8** cols = gcols;
        u8* cweight = gcweight;

        size_t dcol = 0;
        for (; dcol < dst_w8; dcol += 8, cols += 16, cweight += 8)
        {
            internal::prefetch(cols[0], 64*4);
resize8u_xstretch:
            uint8x8_t vc0w = vdup_n_u8(cweight[0]);
            uint8x8_t vc1w = vdup_n_u8(cweight[1]);
            uint8x8_t vc2w = vdup_n_u8(cweight[2]);
            uint8x8_t vc3w = vdup_n_u8(cweight[3]);
            uint8x8_t vc4w = vdup_n_u8(cweight[4]);
            uint8x8_t vc5w = vdup_n_u8(cweight[5]);
            uint8x8_t vc6w = vdup_n_u8(cweight[6]);
            uint8x8_t vc7w = vdup_n_u8(cweight[7]);

            uint8x8_t vc0w2 = vdup_n_u8(128 - cweight[0]);
            uint8x8_t vc1w2 = vdup_n_u8(128 - cweight[1]);
            uint8x8_t vc2w2 = vdup_n_u8(128 - cweight[2]);
            uint8x8_t vc3w2 = vdup_n_u8(128 - cweight[3]);
            uint8x8_t vc4w2 = vdup_n_u8(128 - cweight[4]);
            uint8x8_t vc5w2 = vdup_n_u8(128 - cweight[5]);
            uint8x8_t vc6w2 = vdup_n_u8(128 - cweight[6]);
            uint8x8_t vc7w2 = vdup_n_u8(128 - cweight[7]);

            uint8x8_t vsrc0l1 = vld1_u8(cols[0]);
            uint8x8_t vsrc0l2 = vld1_u8(cols[1]);
            uint8x8_t vsrc1l1 = vld1_u8(cols[2]);
            uint8x8_t vsrc1l2 = vld1_u8(cols[3]);
            uint8x8_t vsrc2l1 = vld1_u8(cols[4]);
            uint8x8_t vsrc2l2 = vld1_u8(cols[5]);
            uint8x8_t vsrc3l1 = vld1_u8(cols[6]);
            uint8x8_t vsrc3l2 = vld1_u8(cols[7]);
            uint8x8_t vsrc4l1 = vld1_u8(cols[8]);
            uint8x8_t vsrc4l2 = vld1_u8(cols[9]);
            uint8x8_t vsrc5l1 = vld1_u8(cols[10]);
            uint8x8_t vsrc5l2 = vld1_u8(cols[11]);
            uint8x8_t vsrc6l1 = vld1_u8(cols[12]);
            uint8x8_t vsrc6l2 = vld1_u8(cols[13]);
            uint8x8_t vsrc7l1 = vld1_u8(cols[14]);
            uint8x8_t vsrc7l2 = vld1_u8(cols[15]);

            // (l1 * w + l2 * (128 - w) + 64) / 128
            uint16x8_t vdst0l = vmull_u8(vsrc0l1, vc0w);
            uint16x8_t vdst1l = vmull_u8(vsrc1l1, vc1w);
            uint16x8_t vdst2l = vmull_u8(vsrc2l1, vc2w);
            uint16x8_t vdst3l = vmull_u8(vsrc3l1, vc3w);
            uint16x8_t vdst4l = vmull_u8(vsrc4l1, vc4w);
            uint16x8_t vdst5l = vmull_u8(vsrc5l1, vc5w);
            uint16x8_t vdst6l = vmull_u8(vsrc6l1, vc6w);
            uint16x8_t vdst7l = vmull_u8(vsrc7l1, vc7w);

            vdst0l = vmlal_u8(vdst0l, vsrc0l2, vc0w2);
            vdst1l = vmlal_u8(vdst1l, vsrc1l2, vc1w2);
            vdst2l = vmlal_u8(vdst2l, vsrc2l2, vc2w2);
            vdst3l = vmlal_u8(vdst3l, vsrc3l2, vc3w2);
            vdst4l = vmlal_u8(vdst4l, vsrc4l2, vc4w2);
            vdst5l = vmlal_u8(vdst5l, vsrc5l2, vc5w2);
            vdst6l = vmlal_u8(vdst6l, vsrc6l2, vc6w2);
            vdst7l = vmlal_u8(vdst7l, vsrc7l2, vc7w2);

            uint8x8_t vdst0 = vrshrn_n_u16(vdst0l, 7);
            uint8x8_t vdst1 = vrshrn_n_u16(vdst1l, 7);
            uint8x8_t vdst2 = vrshrn_n_u16(vdst2l, 7);
            uint8x8_t vdst3 = vrshrn_n_u16(vdst3l, 7);
            uint8x8_t vdst4 = vrshrn_n_u16(vdst4l, 7);
            uint8x8_t vdst5 = vrshrn_n_u16(vdst5l, 7);
            uint8x8_t vdst6 = vrshrn_n_u16(vdst6l, 7);
            uint8x8_t vdst7 = vrshrn_n_u16(vdst7l, 7);

            // == 8x8 matrix transpose ==
            uint8x8x2_t vdst10t = vtrn_u8(vdst0, vdst1);
            uint8x8x2_t vdst32t = vtrn_u8(vdst2, vdst3);
            uint8x8x2_t vdst54t = vtrn_u8(vdst4, vdst5);
            uint8x8x2_t vdst76t = vtrn_u8(vdst6, vdst7);
            uint8x16_t vd1d0 = vcombine_u8(vdst10t.val[0], vdst10t.val[1]);
            uint8x16_t vd3d2 = vcombine_u8(vdst32t.val[0], vdst32t.val[1]);
            uint8x16_t vd5d4 = vcombine_u8(vdst54t.val[0], vdst54t.val[1]);
            uint8x16_t vd7d6 = vcombine_u8(vdst76t.val[0], vdst76t.val[1]);
            uint16x8x2_t vq1q0t = vtrnq_u16((uint16x8_t)vd1d0, (uint16x8_t)vd3d2);
            uint16x8x2_t vq3q2t = vtrnq_u16((uint16x8_t)vd5d4, (uint16x8_t)vd7d6);
            uint32x4x2_t vq2q0t = vtrnq_u32((uint32x4_t)vq1q0t.val[0], (uint32x4_t)vq3q2t.val[0]);
            uint32x4x2_t vq3q1t = vtrnq_u32((uint32x4_t)vq1q0t.val[1], (uint32x4_t)vq3q2t.val[1]);

            //save results
            vst1_u8(dst_data + 0 * dstStride + dcol, (uint8x8_t)vget_low_u32(vq2q0t.val[0]));
            vst1_u8(dst_data + 1 * dstStride + dcol, (uint8x8_t)vget_high_u32(vq2q0t.val[0]));
            vst1_u8(dst_data + 2 * dstStride + dcol, (uint8x8_t)vget_low_u32(vq3q1t.val[0]));
            vst1_u8(dst_data + 3 * dstStride + dcol, (uint8x8_t)vget_high_u32(vq3q1t.val[0]));
            vst1_u8(dst_data + 4 * dstStride + dcol, (uint8x8_t)vget_low_u32(vq2q0t.val[1]));
            vst1_u8(dst_data + 5 * dstStride + dcol, (uint8x8_t)vget_high_u32(vq2q0t.val[1]));
            vst1_u8(dst_data + 6 * dstStride + dcol, (uint8x8_t)vget_low_u32(vq3q1t.val[1]));
            vst1_u8(dst_data + 7 * dstStride + dcol, (uint8x8_t)vget_high_u32(vq3q1t.val[1]));
        }

        if (dcol < dsize.width)
        {
            dcol = dsize.width - 8;
            cols = gcols + dcol * 2;
            cweight = gcweight + dcol;
            goto resize8u_xstretch;
        }
    }

    if (r < dsize.height)
    {
        r = dsize.height - 8;
        goto resize8u_xystretch;
    }
}

template <int channels> struct resizeLinearInternals;
template <> struct resizeLinearInternals<1>
{
    int32x4_t vc_upd;
    int32x4_t vc0;
    int32x4_t vcmax;

    inline resizeLinearInternals(int32x4_t & vi, u32 srccols)
    {
        vc_upd = vdupq_n_s32(4);
        vc0 = vdupq_n_s32(0);
        vcmax = vdupq_n_s32(srccols-1);

        s32 tmp0123[] = {0, 1, 2, 3 };
        vi = vld1q_s32(tmp0123);
    }
    inline void updateIndexes(int32x4_t & vi, int32x4_t & vsrch, int32x4_t & vsrcl)
    {
        vsrch = vminq_s32(vsrch, vcmax);
        vsrcl = vmaxq_s32(vsrcl, vc0);
        vsrcl = vminq_s32(vsrcl, vcmax);//for safe tail
        vsrch = vshlq_n_s32(vsrch, 3);
        vsrcl = vshlq_n_s32(vsrcl, 3);
        vi = vaddq_s32(vi, vc_upd);
    }
};
template <> struct resizeLinearInternals<4>
{
    int32x4_t vc_upd;
    int32x4_t vc0;
    int32x4_t vcmax;
    int32x4_t v0123x8;

    inline resizeLinearInternals(int32x4_t & vi, u32 srccols)
    {
        vc_upd = vdupq_n_s32(1);
        vc0 = vdupq_n_s32(0);
        vcmax = vdupq_n_s32(srccols-1);
        s32 tmp0123x8[] = {0, 8, 16, 24};
        v0123x8 = vld1q_s32(tmp0123x8);

        vi = vc0;
    }
    inline void updateIndexes(int32x4_t & vi, int32x4_t & vsrch, int32x4_t & vsrcl)
    {
        vsrch = vminq_s32(vsrch, vcmax);
        vsrcl = vmaxq_s32(vsrcl, vc0);
        vsrch = vshlq_n_s32(vsrch, 5);
        vsrcl = vshlq_n_s32(vsrcl, 5);
        vsrch = vaddq_s32(vsrch, v0123x8);
        vsrcl = vaddq_s32(vsrcl, v0123x8);
        vi = vaddq_s32(vi, vc_upd);
    }
};

template <int channels>
void resizeLinearOpenCVchan(const Size2D &_ssize, const Size2D &_dsize,
                            const u8 * srcBase, ptrdiff_t srcStride,
                            u8 * dstBase, ptrdiff_t dstStride,
                            f32 wr, f32 hr)
{
    float scale_x_offset = 0.5f * wr - 0.5f;

    Size2D ssize(_ssize.width*channels, _ssize.height);
    Size2D dsize(_dsize.width*channels, _dsize.height);

    std::vector<u8> gcweight((dsize.width + 7) & ~7);
    std::vector<const u8*> gcols(((dsize.width + 7) & ~7) * 2);
    std::vector<u8> buf(((ssize.width + 7) & ~7) * 8); // (8 rows) x (width of src)

    float32x4_t vscale_x = vdupq_n_f32(wr);
    float32x4_t vscale_x_offset = vdupq_n_f32(scale_x_offset);
    int32x4_t vc1 = vdupq_n_s32(1);
    float32x4_t vc128f = vdupq_n_f32(128.0f);

    int32x4_t vi;
    resizeLinearInternals<channels> indexes(vi, _ssize.width);//u32 is used to store indexes
                                                              //so we could get issues on src image dimensions greater than (2^32-1)

    for (size_t dcol = 0; dcol < dsize.width; dcol += 8)
    {
        s32 idx[16];

        float32x4_t vif = vcvtq_f32_s32(vi);
        float32x4_t vw = vmlaq_f32(vscale_x_offset, vscale_x, vif);
        int32x4_t vwi = vcvtq_s32_f32(vw);
        float32x4_t vwif = vcvtq_f32_s32(vwi);
        int32x4_t vmask = (int32x4_t)vcltq_f32(vwif, vw);
        int32x4_t vsrch = vsubq_s32(vwi, vmask);
        int32x4_t vsrcl = vsubq_s32(vsrch, vc1);
        float32x4_t vsrchf = vcvtq_f32_s32(vsrch);
        float32x4_t vw2 = vsubq_f32(vsrchf, vw);

        vw2 = vmulq_f32(vw2, vc128f);
        uint32x4_t vw32u = vcvtq_u32_f32(vw2);
        uint16x4_t vw16ul = vmovn_u32(vw32u);
        indexes.updateIndexes(vi, vsrch, vsrcl);

        vst1q_s32(idx + 0, vsrcl);
        vst1q_s32(idx + 8, vsrch);

        vif = vcvtq_f32_s32(vi);
        vw = vmlaq_f32(vscale_x_offset, vscale_x, vif);
        vwi = vcvtq_s32_f32(vw);
        vwif = vcvtq_f32_s32(vwi);
        vmask = (int32x4_t)vcltq_f32(vwif, vw);
        vsrch = vsubq_s32(vwi, vmask);
        vsrcl = vsubq_s32(vsrch, vc1);
        vsrchf = vcvtq_f32_s32(vsrch);
        vw2 = vsubq_f32(vsrchf, vw);

        vw2 = vmulq_f32(vw2, vc128f);
        vw32u = vcvtq_u32_f32(vw2);
        indexes.updateIndexes(vi, vsrch, vsrcl);

        uint16x4_t vw16uh = vmovn_u32(vw32u);

        vst1q_s32(idx + 4, vsrcl);
        vst1q_s32(idx + 12, vsrch);

        uint8x8_t vw8u = vmovn_u16(vcombine_u16(vw16ul, vw16uh));

        for (u32 i = 0; i < 8; ++i)
        {
            gcols[dcol * 2 + i*2] = &buf[idx[i]];
            gcols[dcol * 2 + i*2 + 1] = &buf[idx[i + 8]];
        }

        vst1_u8(&gcweight[dcol], vw8u);
    }

    resize_bilinear_rows(ssize, dsize, srcBase, srcStride, dstBase, dstStride, hr, &gcols[0], &gcweight[0], &buf[0]);
}

void downsample_bilinear_8uc1(const Size2D &ssize, const Size2D &dsize,
                              const u8 * srcBase, ptrdiff_t srcStride,
                              u8 * dstBase, ptrdiff_t dstStride,
                              f32 wr, f32 hr)
{
    internal::assertSupportedConfiguration(wr <= 2.f && hr <= 2.f);

    enum { SHIFT_BITS = 11 };

    f32 scale_x_offset = 0.5f * wr - 0.5f;
    f32 scale_y_offset = 0.5f * hr - 0.5f;

    std::vector<s32> _buf(dsize.height*(2*(sizeof(ptrdiff_t)/sizeof(s32))+1)+1);
    ptrdiff_t* buf = (ptrdiff_t*)&_buf[0];
    s32* buf2 = (s32*)buf+2*(sizeof(ptrdiff_t)/sizeof(s32))*dsize.height;
    for(size_t row = 0; row < (size_t)dsize.height; ++row)
    {
        f32 r = row * hr + scale_y_offset;
        ptrdiff_t src_row = floorf(r);
        ptrdiff_t src_row2 = src_row + 1;

        f32 rweight = src_row2 - r;
        buf2[row] = floorf(rweight * (1 << SHIFT_BITS) + 0.5f);
        buf[0 * dsize.height + row] = std::max<ptrdiff_t>(0, src_row);
        buf[1 * dsize.height + row] = std::min((ptrdiff_t)ssize.height-1, src_row2);
    }

#define USE_CORRECT_VERSION 0

    ptrdiff_t col = 0;
/***********************************************/
    for(; col <= (ptrdiff_t)dsize.width-16; col+=16)
    {
        ptrdiff_t col1[16];
        ptrdiff_t col2[16];
        s16 cwi[16];

        for(s32 k = 0; k < 16; ++k)
        {
            f32 c = (col + k) * wr + scale_x_offset;
            col1[k] = (ptrdiff_t)c;
            col2[k] = col1[k] + 1;

            cwi[k] = (short)floorf((col2[k] - c) * (1 << SHIFT_BITS) + 0.5f);

            if(col1[k] < 0) col1[k] = 0;
            if(col2[k] >= (ptrdiff_t)ssize.width) col2[k] = ssize.width-1;
        }

        ptrdiff_t x = std::min(col1[0], (ptrdiff_t)ssize.width-16);
        ptrdiff_t y = std::min(col1[8], (ptrdiff_t)ssize.width-16);
        u8 lutl[16];
        u8 luth[16];
        for(s32 k = 0; k < 8; ++k)
        {
            lutl[k] = (u8)(col1[k] - x);
            luth[k] = (u8)(col2[k] - x);
            lutl[k+8] = (u8)(col1[k+8] - y);
            luth[k+8] = (u8)(col2[k+8] - y);
        }

        uint8x8_t vlutl = vld1_u8(lutl);
        uint8x8_t vluth = vld1_u8(luth);
        int16x8_t vcw = vld1q_s16(cwi);

        uint8x8_t vlutl_ = vld1_u8(lutl+8);
        uint8x8_t vluth_ = vld1_u8(luth+8);
        int16x8_t vcw_ = vld1q_s16(cwi+8);

        for(ptrdiff_t row = 0; row < (ptrdiff_t)dsize.height; ++row)
        {
#if USE_CORRECT_VERSION
            int32x4_t vrw = vdupq_n_s32(buf2[row]);
#else
            int16x8_t vrw = vdupq_n_s16((int16_t)buf2[row]);
            int16x8_t vrW = vdupq_n_s16((int16_t)((1 << SHIFT_BITS) - buf2[row]));
#endif

            internal::prefetch(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x, 2*srcStride);
            internal::prefetch(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x, 3*srcStride);

            {
                union { uint8x16_t v; uint8x8x2_t w; } vr1 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[0*dsize.height + row]) + x) };
                union { uint8x16_t v; uint8x8x2_t w; } vr2 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x) };

                uint8x8_t vr1l = vtbl2_u8(vr1.w, vlutl);
                uint8x8_t vr1h = vtbl2_u8(vr1.w, vluth);
                uint8x8_t vr2l = vtbl2_u8(vr2.w, vlutl);
                uint8x8_t vr2h = vtbl2_u8(vr2.w, vluth);

                uint16x8_t v1hw = vmovl_u8(vr1h);
                uint16x8_t v2hw = vmovl_u8(vr2h);

                int16x8_t v1df = vreinterpretq_s16_u16(vsubl_u8(vr1l, vr1h));
                int16x8_t v2df = vreinterpretq_s16_u16(vsubl_u8(vr2l, vr2h));

                int32x4_t v1L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v1hw),  SHIFT_BITS));
                int32x4_t v1H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v1hw), SHIFT_BITS));
                int32x4_t v2L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v2hw),  SHIFT_BITS));
                int32x4_t v2H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v2hw), SHIFT_BITS));

                v1L = vmlal_s16(v1L, vget_low_s16(v1df), vget_low_s16(vcw));
                v1H = vmlal_s16(v1H, vget_high_s16(v1df), vget_high_s16(vcw));
                v2L = vmlal_s16(v2L, vget_low_s16(v2df), vget_low_s16(vcw));
                v2H = vmlal_s16(v2H, vget_high_s16(v2df), vget_high_s16(vcw));

#if USE_CORRECT_VERSION
                /* correct version */
                int32x4_t vL = vshlq_n_s32(v2L, SHIFT_BITS);
                int32x4_t vH = vshlq_n_s32(v2H, SHIFT_BITS);
                int32x4_t vdiffL = vsubq_s32(v1L, v2L);
                int32x4_t vdiffH = vsubq_s32(v1H, v2H);

                vL = vmlaq_s32(vL, vdiffL, vrw);
                vH = vmlaq_s32(vH, vdiffH, vrw);
                uint16x4_t vL_ = vqrshrun_n_s32(vL, 2*SHIFT_BITS - 8);
                uint16x4_t vH_ = vqrshrun_n_s32(vH, 2*SHIFT_BITS - 8);
                uint8x8_t vres = vrshrn_n_u16(vcombine_u16(vL_, vH_), 8);
                vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col, vres);
#else
                /* ugly version matching to OpenCV's SSE optimization */
                int16x4_t v1Ls = vshrn_n_s32(v1L, 4);
                int16x4_t v1Hs = vshrn_n_s32(v1H, 4);
                int16x4_t v2Ls = vshrn_n_s32(v2L, 4);
                int16x4_t v2Hs = vshrn_n_s32(v2H, 4);

                int16x8_t v1s = vqdmulhq_s16(vcombine_s16(v1Ls, v1Hs), vrw);
                int16x8_t v2s = vqdmulhq_s16(vcombine_s16(v2Ls, v2Hs), vrW);

                int16x8_t vsum = vaddq_s16(vshrq_n_s16(v1s,1), vshrq_n_s16(v2s,1));
                uint8x8_t vres = vqrshrun_n_s16(vsum, 2);

                vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col, vres);
#endif
            }

            {
                union { uint8x16_t v; uint8x8x2_t w; } vr1 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[0*dsize.height + row]) + y) };
                union { uint8x16_t v; uint8x8x2_t w; } vr2 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + y) };

                uint8x8_t vr1l = vtbl2_u8(vr1.w, vlutl_);
                uint8x8_t vr1h = vtbl2_u8(vr1.w, vluth_);
                uint8x8_t vr2l = vtbl2_u8(vr2.w, vlutl_);
                uint8x8_t vr2h = vtbl2_u8(vr2.w, vluth_);

                uint16x8_t v1hw = vmovl_u8(vr1h);
                uint16x8_t v2hw = vmovl_u8(vr2h);

                int16x8_t v1df = vreinterpretq_s16_u16(vsubl_u8(vr1l, vr1h));
                int16x8_t v2df = vreinterpretq_s16_u16(vsubl_u8(vr2l, vr2h));

                int32x4_t v1L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v1hw),  SHIFT_BITS));
                int32x4_t v1H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v1hw), SHIFT_BITS));
                int32x4_t v2L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v2hw),  SHIFT_BITS));
                int32x4_t v2H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v2hw), SHIFT_BITS));

                v1L = vmlal_s16(v1L, vget_low_s16(v1df), vget_low_s16(vcw_));
                v1H = vmlal_s16(v1H, vget_high_s16(v1df), vget_high_s16(vcw_));
                v2L = vmlal_s16(v2L, vget_low_s16(v2df), vget_low_s16(vcw_));
                v2H = vmlal_s16(v2H, vget_high_s16(v2df), vget_high_s16(vcw_));

#if USE_CORRECT_VERSION
                /* correct version */
                int32x4_t vL = vshlq_n_s32(v2L, SHIFT_BITS);
                int32x4_t vH = vshlq_n_s32(v2H, SHIFT_BITS);
                int32x4_t vdiffL = vsubq_s32(v1L, v2L);
                int32x4_t vdiffH = vsubq_s32(v1H, v2H);

                vL = vmlaq_s32(vL, vdiffL, vrw);
                vH = vmlaq_s32(vH, vdiffH, vrw);
                uint16x4_t vL_ = vqrshrun_n_s32(vL, 2*SHIFT_BITS - 8);
                uint16x4_t vH_ = vqrshrun_n_s32(vH, 2*SHIFT_BITS - 8);
                uint8x8_t vres = vrshrn_n_u16(vcombine_u16(vL_, vH_), 8);
                vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col + 8, vres);
#else
                /* ugly version matching to OpenCV's SSE optimization */
                int16x4_t v1Ls = vshrn_n_s32(v1L, 4);
                int16x4_t v1Hs = vshrn_n_s32(v1H, 4);
                int16x4_t v2Ls = vshrn_n_s32(v2L, 4);
                int16x4_t v2Hs = vshrn_n_s32(v2H, 4);

                int16x8_t v1s = vqdmulhq_s16(vcombine_s16(v1Ls, v1Hs), vrw);
                int16x8_t v2s = vqdmulhq_s16(vcombine_s16(v2Ls, v2Hs), vrW);

                int16x8_t vsum = vaddq_s16(vshrq_n_s16(v1s,1), vshrq_n_s16(v2s,1));
                uint8x8_t vres = vqrshrun_n_s16(vsum, 2);

                vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col + 8, vres);
#endif
            }
        }
    }
/***********************************************/
    for(; col <= (ptrdiff_t)dsize.width-8; col+=8)
    {
downsample_bilinear_8uc1_col_loop8:
        ptrdiff_t col1[8];
        ptrdiff_t col2[8];
        s16 cwi[8];

        for(s32 k = 0; k < 8; ++k)
        {
            f32 c = (col + k) * wr + scale_x_offset;
            col1[k] = (ptrdiff_t)c;
            col2[k] = col1[k] + 1;

            cwi[k] = (s16)floorf((col2[k] - c) * (1 << SHIFT_BITS) + 0.5f);

            if(col1[k] < 0) col1[k] = 0;
            if(col2[k] >= (ptrdiff_t)ssize.width) col2[k] = (ptrdiff_t)ssize.width-1;
        }

        ptrdiff_t x = std::min(col1[0], (ptrdiff_t)ssize.width-16);
        u8 lutl[8];
        u8 luth[8];
        for(s32 k = 0; k < 8; ++k)
        {
            lutl[k] = (u8)(col1[k] - x);
            luth[k] = (u8)(col2[k] - x);
        }

        uint8x8_t vlutl = vld1_u8(lutl);
        uint8x8_t vluth = vld1_u8(luth);
        int16x8_t vcw = vld1q_s16(cwi);

        for(ptrdiff_t row = 0; row < (ptrdiff_t)dsize.height; ++row)
        {
#if USE_CORRECT_VERSION
            int32x4_t vrw = vdupq_n_s32(buf2[row]);
#else
            int16x8_t vrw = vdupq_n_s16((int16_t)buf2[row]);
            int16x8_t vrW = vdupq_n_s16((int16_t)((1 << SHIFT_BITS) - buf2[row]));
#endif

            internal::prefetch(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x, 2*srcStride);
            internal::prefetch(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x, 3*srcStride);

            union { uint8x16_t v; uint8x8x2_t w; } vr1 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[0*dsize.height + row]) + x) };
            union { uint8x16_t v; uint8x8x2_t w; } vr2 = { vld1q_u8(internal::getRowPtr(srcBase, srcStride, buf[1*dsize.height + row]) + x) };

            uint8x8_t vr1l = vtbl2_u8(vr1.w, vlutl);
            uint8x8_t vr1h = vtbl2_u8(vr1.w, vluth);
            uint8x8_t vr2l = vtbl2_u8(vr2.w, vlutl);
            uint8x8_t vr2h = vtbl2_u8(vr2.w, vluth);

            uint16x8_t v1hw = vmovl_u8(vr1h);
            uint16x8_t v2hw = vmovl_u8(vr2h);

            int16x8_t v1df = vreinterpretq_s16_u16(vsubl_u8(vr1l, vr1h));
            int16x8_t v2df = vreinterpretq_s16_u16(vsubl_u8(vr2l, vr2h));

            int32x4_t v1L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v1hw),  SHIFT_BITS));
            int32x4_t v1H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v1hw), SHIFT_BITS));
            int32x4_t v2L = vreinterpretq_s32_u32(vshll_n_u16(vget_low_u16(v2hw),  SHIFT_BITS));
            int32x4_t v2H = vreinterpretq_s32_u32(vshll_n_u16(vget_high_u16(v2hw), SHIFT_BITS));

            v1L = vmlal_s16(v1L, vget_low_s16(v1df), vget_low_s16(vcw));
            v1H = vmlal_s16(v1H, vget_high_s16(v1df), vget_high_s16(vcw));
            v2L = vmlal_s16(v2L, vget_low_s16(v2df), vget_low_s16(vcw));
            v2H = vmlal_s16(v2H, vget_high_s16(v2df), vget_high_s16(vcw));

#if USE_CORRECT_VERSION
            /* correct version */
            int32x4_t vL = vshlq_n_s32(v2L, SHIFT_BITS);
            int32x4_t vH = vshlq_n_s32(v2H, SHIFT_BITS);
            int32x4_t vdiffL = vsubq_s32(v1L, v2L);
            int32x4_t vdiffH = vsubq_s32(v1H, v2H);

            vL = vmlaq_s32(vL, vdiffL, vrw);
            vH = vmlaq_s32(vH, vdiffH, vrw);
            uint16x4_t vL_ = vqrshrun_n_s32(vL, 2*SHIFT_BITS - 8);
            uint16x4_t vH_ = vqrshrun_n_s32(vH, 2*SHIFT_BITS - 8);
            uint8x8_t vres = vrshrn_n_u16(vcombine_u16(vL_, vH_), 8);
            vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col, vres);
#else
            /* ugly version matching to OpenCV's SSE optimization */
            int16x4_t v1Ls = vshrn_n_s32(v1L, 4);
            int16x4_t v1Hs = vshrn_n_s32(v1H, 4);
            int16x4_t v2Ls = vshrn_n_s32(v2L, 4);
            int16x4_t v2Hs = vshrn_n_s32(v2H, 4);

            int16x8_t v1s = vqdmulhq_s16(vcombine_s16(v1Ls, v1Hs), vrw);
            int16x8_t v2s = vqdmulhq_s16(vcombine_s16(v2Ls, v2Hs), vrW);

            int16x8_t vsum = vaddq_s16(vshrq_n_s16(v1s,1), vshrq_n_s16(v2s,1));
            uint8x8_t vres = vqrshrun_n_s16(vsum, 2);

            vst1_u8(internal::getRowPtr(dstBase, dstStride, row) + col, vres);
#endif
        }
    }
    if (col < (ptrdiff_t)dsize.width)
    {
        col = dsize.width - 8;
        goto downsample_bilinear_8uc1_col_loop8;
    }
}

} // namespace

#endif

void resizeLinearOpenCV(const Size2D &ssize, const Size2D &dsize,
                        const u8 * srcBase, ptrdiff_t srcStride,
                        u8 * dstBase, ptrdiff_t dstStride,
                        f32 wr, f32 hr, u32 channels)
{
    internal::assertSupportedConfiguration(wr > 0 && hr > 0 &&
                                           (dsize.width - 0.5) * wr - 0.5 < ssize.width &&
                                           (dsize.height - 0.5) * hr - 0.5 < ssize.height &&  // Ensure we have enough source data
                                           (dsize.width + 0.5) * wr + 0.5 >= ssize.width &&
                                           (dsize.height + 0.5) * hr + 0.5 >= ssize.height && // Ensure source isn't too big
                                           isResizeLinearOpenCVSupported(ssize, dsize, channels));
#ifdef CAROTENE_NEON
        if(1 == channels)
        {
            if (wr <= 1.f && hr <= 1.f)
                resizeLinearOpenCVchan<1>(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr);
            else if (wr <= 2.0f && hr <= 2.0f && ssize.width >= 16)
                downsample_bilinear_8uc1(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr);
            else
                resizeLinearOpenCVchan<1>(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr);
        }
        else if(4 == channels)
            resizeLinearOpenCVchan<4>(ssize, dsize, srcBase, srcStride, dstBase, dstStride, wr, hr);
#else
    (void)ssize;
    (void)dsize;
    (void)srcBase;
    (void)srcStride;
    (void)dstBase;
    (void)dstStride;
    (void)wr;
    (void)hr;
    (void)channels;
#endif
}

void resizeLinear(const Size2D &ssize, const Size2D &dsize,
                  const u8 * srcBase, ptrdiff_t srcStride,
                  u8 * dstBase, ptrdiff_t dstStride,
                  f32 wr, f32 hr, u32 channels)
{
    internal::assertSupportedConfiguration(wr > 0 && hr > 0 &&
                                           (dsize.width - 0.5) * wr - 0.5 < ssize.width &&
                                           (dsize.height - 0.5) * hr - 0.5 < ssize.height &&  // Ensure we have enough source data
                                           (dsize.width + 0.5) * wr + 0.5 >= ssize.width &&
                                           (dsize.height + 0.5) * hr + 0.5 >= ssize.height && // Ensure source isn't too big
                                           isResizeLinearSupported(ssize, dsize,
                                                                   wr, hr, channels));
#ifdef CAROTENE_NEON
    f32 scale_x = wr;
    f32 scale_x_offset = 0.5f * scale_x - 0.5f;
    f32 scale_y = hr;
    f32 scale_y_offset = 0.5f * scale_y - 0.5f;

    std::vector<ptrdiff_t> _buf(dsize.height * 3 + 1);
    std::vector<f32> coeff(dsize.height);
    ptrdiff_t * buf = &_buf[0];

    for (size_t row = 0; row < dsize.height; ++row)
    {
        f32 r = row * scale_y + scale_y_offset;
        ptrdiff_t src_row = floorf(r);
        ptrdiff_t src_row2 = src_row + 1;

        f32 rweight = src_row2 - r;
        buf[0 * dsize.height + row] = std::max<ptrdiff_t>(0, src_row);
        buf[1 * dsize.height + row] = std::min<ptrdiff_t>(ssize.height - 1, src_row2);
        coeff[row] = rweight;
    }

    size_t col = 0;
    for ( ; col + 16 <= dsize.width; col += 16)
    {
        ptrdiff_t col1[16], col2[16];
        f32 cwi[16];

        for(s32 k = 0; k < 16; ++k)
        {
            f32 c = (col + k) * scale_x + scale_x_offset;
            col1[k] = floorf(c);
            col2[k] = col1[k] + 1;

            cwi[k] = col2[k] - c;

            if (col1[k] < 0)
                col1[k] = 0;
            if (col2[k] >= (ptrdiff_t)ssize.width)
                col2[k] = ssize.width - 1;
        }

        ptrdiff_t x = std::min<ptrdiff_t>(col1[0], ssize.width - 16);
        ptrdiff_t y = std::min<ptrdiff_t>(col1[8], ssize.width - 16);
        u8 lutl[16], luth[16];

        for (s32 k = 0; k < 8; ++k)
        {
            lutl[k] = (u8)(col1[k] - x);
            luth[k] = (u8)(col2[k] - x);
            lutl[k + 8] = (u8)(col1[k + 8] - y);
            luth[k + 8] = (u8)(col2[k + 8] - y);
        }

        uint8x8_t vlutl = vld1_u8(lutl);
        uint8x8_t vluth = vld1_u8(luth);
        float32x4_t vcw0 = vld1q_f32(cwi);
        float32x4_t vcw1 = vld1q_f32(cwi + 4);

        uint8x8_t vlutl_ = vld1_u8(lutl + 8);
        uint8x8_t vluth_ = vld1_u8(luth + 8);
        float32x4_t vcw0_ = vld1q_f32(cwi + 8);
        float32x4_t vcw1_ = vld1q_f32(cwi + 12);

        if (channels == 1)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x8_t vres0 = resizeLinearStep(vld1q_u8(srow0 + x), vld1q_u8(srow1 + x),
                                                   vlutl, vluth,
                                                   vrw, vcw0, vcw1);

                uint8x8_t vres1 = resizeLinearStep(vld1q_u8(srow0 + y), vld1q_u8(srow1 + y),
                                                   vlutl_, vluth_,
                                                   vrw, vcw0_, vcw1_);

                vst1q_u8(drow + col, vcombine_u8(vres0, vres1));
            }
        }
        else if (channels == 3)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x16x3_t v_src10 = vld3q_u8(srow0 + (x * 3));
                uint8x16x3_t v_src20 = vld3q_u8(srow1 + (x * 3));

                uint8x16x3_t v_src11 = vld3q_u8(srow0 + (y * 3));
                uint8x16x3_t v_src21 = vld3q_u8(srow1 + (y * 3));

                uint8x16x3_t v_dst;

                v_dst.val[0] = vcombine_u8(resizeLinearStep(v_src10.val[0], v_src20.val[0], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[0], v_src21.val[0], vlutl_, vluth_, vrw, vcw0_, vcw1_));
                v_dst.val[1] = vcombine_u8(resizeLinearStep(v_src10.val[1], v_src20.val[1], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[1], v_src21.val[1], vlutl_, vluth_, vrw, vcw0_, vcw1_));
                v_dst.val[2] = vcombine_u8(resizeLinearStep(v_src10.val[2], v_src20.val[2], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[2], v_src21.val[2], vlutl_, vluth_, vrw, vcw0_, vcw1_));

                vst3q_u8(drow + (col * 3), v_dst);
            }
        }
        else if (channels == 4)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x16x4_t v_src10 = vld4q_u8(srow0 + (x << 2));
                uint8x16x4_t v_src20 = vld4q_u8(srow1 + (x << 2));

                uint8x16x4_t v_src11 = vld4q_u8(srow0 + (y << 2));
                uint8x16x4_t v_src21 = vld4q_u8(srow1 + (y << 2));

                uint8x16x4_t v_dst;

                v_dst.val[0] = vcombine_u8(resizeLinearStep(v_src10.val[0], v_src20.val[0], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[0], v_src21.val[0], vlutl_, vluth_, vrw, vcw0_, vcw1_));
                v_dst.val[1] = vcombine_u8(resizeLinearStep(v_src10.val[1], v_src20.val[1], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[1], v_src21.val[1], vlutl_, vluth_, vrw, vcw0_, vcw1_));
                v_dst.val[2] = vcombine_u8(resizeLinearStep(v_src10.val[2], v_src20.val[2], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[2], v_src21.val[2], vlutl_, vluth_, vrw, vcw0_, vcw1_));
                v_dst.val[3] = vcombine_u8(resizeLinearStep(v_src10.val[3], v_src20.val[3], vlutl, vluth, vrw, vcw0, vcw1),
                                           resizeLinearStep(v_src11.val[3], v_src21.val[3], vlutl_, vluth_, vrw, vcw0_, vcw1_));

                vst4q_u8(drow + (col << 2), v_dst);
            }
        }
    }

    for ( ; col + 8 <= dsize.width; col += 8)
    {
downsample_bilinear_8uc1_col_loop8:
        ptrdiff_t col1[8], col2[8];
        f32 cwi[8];

        for (s32 k = 0; k < 8; ++k)
        {
            f32 c = (col + k) * scale_x + scale_x_offset;
            col1[k] = floorf(c);
            col2[k] = col1[k] + 1;

            cwi[k] = col2[k] - c;

            if (col1[k] < 0)
                col1[k] = 0;
            if (col2[k] >= (ptrdiff_t)ssize.width)
                col2[k] = ssize.width - 1;
        }

        ptrdiff_t x = std::min<ptrdiff_t>(col1[0], ssize.width - 16);
        u8 lutl[8], luth[8];
        for (s32 k = 0; k < 8; ++k)
        {
            lutl[k] = (u8)(col1[k] - x);
            luth[k] = (u8)(col2[k] - x);
        }

        uint8x8_t vlutl = vld1_u8(lutl);
        uint8x8_t vluth = vld1_u8(luth);
        float32x4_t vcw0 = vld1q_f32(cwi);
        float32x4_t vcw1 = vld1q_f32(cwi + 4);

        if (channels == 1)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x8_t vres = resizeLinearStep(vld1q_u8(srow0 + x), vld1q_u8(srow1 + x),
                                                  vlutl, vluth,
                                                  vrw, vcw0, vcw1);
                vst1_u8(drow + col, vres);
            }
        }
        else if (channels == 3)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x16x3_t v_src1 = vld3q_u8(srow0 + (x * 3));
                uint8x16x3_t v_src2 = vld3q_u8(srow1 + (x * 3));

                uint8x8x3_t v_dst;

                v_dst.val[0] = resizeLinearStep(v_src1.val[0], v_src2.val[0], vlutl, vluth, vrw, vcw0, vcw1);
                v_dst.val[1] = resizeLinearStep(v_src1.val[1], v_src2.val[1], vlutl, vluth, vrw, vcw0, vcw1);
                v_dst.val[2] = resizeLinearStep(v_src1.val[2], v_src2.val[2], vlutl, vluth, vrw, vcw0, vcw1);

                vst3_u8(drow + (col * 3), v_dst);
            }
        }
        else if (channels == 4)
        {
            for (size_t row = 0; row < dsize.height; ++row)
            {
                float32x4_t vrw = vdupq_n_f32(coeff[row]);

                const u8 * srow0 = internal::getRowPtr(srcBase, srcStride, buf[0 * dsize.height + row]);
                const u8 * srow1 = internal::getRowPtr(srcBase, srcStride, buf[1 * dsize.height + row]);
                u8 * drow = internal::getRowPtr(dstBase, dstStride, row);

                internal::prefetch(srow0 + x + 2 * srcStride);
                internal::prefetch(srow1 + x + 2 * srcStride);

                uint8x16x4_t v_src1 = vld4q_u8(srow0 + (x << 2));
                uint8x16x4_t v_src2 = vld4q_u8(srow1 + (x << 2));

                uint8x8x4_t v_dst;

                v_dst.val[0] = resizeLinearStep(v_src1.val[0], v_src2.val[0], vlutl, vluth, vrw, vcw0, vcw1);
                v_dst.val[1] = resizeLinearStep(v_src1.val[1], v_src2.val[1], vlutl, vluth, vrw, vcw0, vcw1);
                v_dst.val[2] = resizeLinearStep(v_src1.val[2], v_src2.val[2], vlutl, vluth, vrw, vcw0, vcw1);
                v_dst.val[3] = resizeLinearStep(v_src1.val[3], v_src2.val[3], vlutl, vluth, vrw, vcw0, vcw1);

                vst4_u8(drow + (col << 2), v_dst);
            }
        }
    }

    if (col < dsize.width)
    {
        col = dsize.width - 8;
        goto downsample_bilinear_8uc1_col_loop8;
    }

#else
    (void)ssize;
    (void)dsize;
    (void)srcBase;
    (void)srcStride;
    (void)dstBase;
    (void)dstStride;
    (void)wr;
    (void)hr;
    (void)channels;
#endif
}

} // namespace CAROTENE_NS
