#include "precomp.hpp"
#include <float.h>
#include <limits.h>

#ifdef HAVE_TEGRA_OPTIMIZATION
#include "tegra.hpp"
#endif

using namespace cv;

namespace cvtest
{

const char* getTypeName( int type )
{
    static const char* type_names[] = { "8u", "8s", "16u", "16s", "32s", "32f", "64f", "ptr" };
    return type_names[CV_MAT_DEPTH(type)];
}

int typeByName( const char* name )
{
    int i;
    for( i = 0; i < CV_DEPTH_MAX; i++ )
        if( strcmp(name, getTypeName(i)) == 0 )
            return i;
    return -1;
}

string vec2str( const string& sep, const int* v, size_t nelems )
{
    char buf[32];
    string result = "";
    for( size_t i = 0; i < nelems; i++ )
    {
        sprintf(buf, "%d", v[i]);
        result += string(buf);
        if( i < nelems - 1 )
            result += sep;
    }
    return result;
}


Size randomSize(RNG& rng, double maxSizeLog)
{
    double width_log = rng.uniform(0., maxSizeLog);
    double height_log = rng.uniform(0., maxSizeLog - width_log);
    if( (unsigned)rng % 2 != 0 )
        std::swap(width_log, height_log);
    Size sz;
    sz.width = cvRound(exp(width_log));
    sz.height = cvRound(exp(height_log));
    return sz;
}

void randomSize(RNG& rng, int minDims, int maxDims, double maxSizeLog, vector<int>& sz)
{
    int i, dims = rng.uniform(minDims, maxDims+1);
    sz.resize(dims);
    for( i = 0; i < dims; i++ )
    {
        double v = rng.uniform(0., maxSizeLog);
        maxSizeLog -= v;
        sz[i] = cvRound(exp(v));
    }
    for( i = 0; i < dims; i++ )
    {
        int j = rng.uniform(0, dims);
        int k = rng.uniform(0, dims);
        std::swap(sz[j], sz[k]);
    }
}

int randomType(RNG& rng, int typeMask, int minChannels, int maxChannels)
{
    int channels = rng.uniform(minChannels, maxChannels+1);
    int depth = 0;
    CV_Assert((typeMask & DEPTH_MASK_ALL) != 0);
    for(;;)
    {
        depth = rng.uniform(CV_8U, CV_64F+1);
        if( ((1 << depth) & typeMask) != 0 )
            break;
    }
    return CV_MAKETYPE(depth, channels);
}

double getMinVal(int depth)
{
    depth = CV_MAT_DEPTH(depth);
    double val = depth == CV_8U ? 0 : depth == CV_8S ? SCHAR_MIN : depth == CV_16U ? 0 :
    depth == CV_16S ? SHRT_MIN : depth == CV_32S ? INT_MIN :
    depth == CV_32F ? -FLT_MAX : depth == CV_64F ? -DBL_MAX : -1;
    CV_Assert(val != -1);
    return val;
}

double getMaxVal(int depth)
{
    depth = CV_MAT_DEPTH(depth);
    double val = depth == CV_8U ? UCHAR_MAX : depth == CV_8S ? SCHAR_MAX : depth == CV_16U ? USHRT_MAX :
    depth == CV_16S ? SHRT_MAX : depth == CV_32S ? INT_MAX :
    depth == CV_32F ? FLT_MAX : depth == CV_64F ? DBL_MAX : -1;
    CV_Assert(val != -1);
    return val;
}

Mat randomMat(RNG& rng, Size size, int type, double minVal, double maxVal, bool useRoi)
{
    Size size0 = size;
    if( useRoi )
    {
        size0.width += std::max(rng.uniform(0, 10) - 5, 0);
        size0.height += std::max(rng.uniform(0, 10) - 5, 0);
    }

    Mat m(size0, type);

    rng.fill(m, RNG::UNIFORM, minVal, maxVal);
    if( size0 == size )
        return m;
    return m(Rect((size0.width-size.width)/2, (size0.height-size.height)/2, size.width, size.height));
}

Mat randomMat(RNG& rng, const vector<int>& size, int type, double minVal, double maxVal, bool useRoi)
{
    int i, dims = (int)size.size();
    vector<int> size0(dims);
    vector<Range> r(dims);
    bool eqsize = true;
    for( i = 0; i < dims; i++ )
    {
        size0[i] = size[i];
        r[i] = Range::all();
        if( useRoi )
        {
            size0[i] += std::max(rng.uniform(0, 5) - 2, 0);
            r[i] = Range((size0[i] - size[i])/2, (size0[i] - size[i])/2 + size[i]);
        }
        eqsize = eqsize && size[i] == size0[i];
    }

    Mat m(dims, &size0[0], type);

    rng.fill(m, RNG::UNIFORM, minVal, maxVal);
    if( eqsize )
        return m;
    return m(&r[0]);
}

void add(const Mat& _a, double alpha, const Mat& _b, double beta,
        Scalar gamma, Mat& c, int ctype, bool calcAbs)
{
    Mat a = _a, b = _b;
    if( a.empty() || alpha == 0 )
    {
        // both alpha and beta can be 0, but at least one of a and b must be non-empty array,
        // otherwise we do not know the size of the output (and may be type of the output, when ctype<0)
        CV_Assert( !a.empty() || !b.empty() );
        if( !b.empty() )
        {
            a = b;
            alpha = beta;
            b = Mat();
            beta = 0;
        }
    }
    if( b.empty() || beta == 0 )
    {
        b = Mat();
        beta = 0;
    }
    else
        CV_Assert(a.size == b.size);

    if( ctype < 0 )
        ctype = a.depth();
    ctype = CV_MAKETYPE(CV_MAT_DEPTH(ctype), a.channels());
    c.create(a.dims, &a.size[0], ctype);
    const Mat *arrays[] = {&a, &b, &c, 0};
    Mat planes[3], buf[3];

    NAryMatIterator it(arrays, planes);
    size_t i, nplanes = it.nplanes;
    int cn=a.channels();
    int total = (int)planes[0].total(), maxsize = std::min(12*12*std::max(12/cn, 1), total);

    CV_Assert(planes[0].rows == 1);
    buf[0].create(1, maxsize, CV_64FC(cn));
    if(!b.empty())
        buf[1].create(1, maxsize, CV_64FC(cn));
    buf[2].create(1, maxsize, CV_64FC(cn));
    scalarToRawData(gamma, buf[2].data, CV_64FC(cn), (int)(maxsize*cn));

    for( i = 0; i < nplanes; i++, ++it)
    {
        for( int j = 0; j < total; j += maxsize )
        {
            int j2 = std::min(j + maxsize, total);
            Mat apart0 = planes[0].colRange(j, j2);
            Mat cpart0 = planes[2].colRange(j, j2);
            Mat apart = buf[0].colRange(0, j2 - j);

            apart0.convertTo(apart, apart.type(), alpha);
            size_t k, n = (j2 - j)*cn;
            double* aptr = (double*)apart.data;
            const double* gptr = (const double*)buf[2].data;

            if( b.empty() )
            {
                for( k = 0; k < n; k++ )
                    aptr[k] += gptr[k];
            }
            else
            {
                Mat bpart0 = planes[1].colRange((int)j, (int)j2);
                Mat bpart = buf[1].colRange(0, (int)(j2 - j));
                bpart0.convertTo(bpart, bpart.type(), beta);
                const double* bptr = (const double*)bpart.data;

                for( k = 0; k < n; k++ )
                    aptr[k] += bptr[k] + gptr[k];
            }
            if( calcAbs )
                for( k = 0; k < n; k++ )
                    aptr[k] = fabs(aptr[k]);
            apart.convertTo(cpart0, cpart0.type(), 1, 0);
        }
    }
}


template<typename _Tp1, typename _Tp2> inline void
convert_(const _Tp1* src, _Tp2* dst, size_t total, double alpha, double beta)
{
    size_t i;
    if( alpha == 1 && beta == 0 )
        for( i = 0; i < total; i++ )
            dst[i] = saturate_cast<_Tp2>(src[i]);
    else if( beta == 0 )
        for( i = 0; i < total; i++ )
            dst[i] = saturate_cast<_Tp2>(src[i]*alpha);
    else
        for( i = 0; i < total; i++ )
            dst[i] = saturate_cast<_Tp2>(src[i]*alpha + beta);
}

template<typename _Tp> inline void
convertTo(const _Tp* src, void* dst, int dtype, size_t total, double alpha, double beta)
{
    switch( CV_MAT_DEPTH(dtype) )
    {
    case CV_8U:
        convert_(src, (uchar*)dst, total, alpha, beta);
        break;
    case CV_8S:
        convert_(src, (schar*)dst, total, alpha, beta);
        break;
    case CV_16U:
        convert_(src, (ushort*)dst, total, alpha, beta);
        break;
    case CV_16S:
        convert_(src, (short*)dst, total, alpha, beta);
        break;
    case CV_32S:
        convert_(src, (int*)dst, total, alpha, beta);
        break;
    case CV_32F:
        convert_(src, (float*)dst, total, alpha, beta);
        break;
    case CV_64F:
        convert_(src, (double*)dst, total, alpha, beta);
        break;
    default:
        CV_Assert(0);
    }
}

void convert(const Mat& src, Mat& dst, int dtype, double alpha, double beta)
{
    dtype = CV_MAKETYPE(CV_MAT_DEPTH(dtype), src.channels());
    dst.create(src.dims, &src.size[0], dtype);
    if( alpha == 0 )
    {
        set( dst, Scalar::all(beta) );
        return;
    }
    if( dtype == src.type() && alpha == 1 && beta == 0 )
    {
        copy( src, dst );
        return;
    }

    const Mat *arrays[]={&src, &dst, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels();
    size_t i, nplanes = it.nplanes;

    for( i = 0; i < nplanes; i++, ++it)
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data;

        switch( src.depth() )
        {
        case CV_8U:
            convertTo((const uchar*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_8S:
            convertTo((const schar*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_16U:
            convertTo((const ushort*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_16S:
            convertTo((const short*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_32S:
            convertTo((const int*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_32F:
            convertTo((const float*)sptr, dptr, dtype, total, alpha, beta);
            break;
        case CV_64F:
            convertTo((const double*)sptr, dptr, dtype, total, alpha, beta);
            break;
        }
    }
}


void copy(const Mat& src, Mat& dst, const Mat& mask, bool invertMask)
{
    dst.create(src.dims, &src.size[0], src.type());

    if(mask.empty())
    {
        const Mat* arrays[] = {&src, &dst, 0};
        Mat planes[2];
        NAryMatIterator it(arrays, planes);
        size_t i, nplanes = it.nplanes;
        size_t planeSize = planes[0].total()*src.elemSize();

        for( i = 0; i < nplanes; i++, ++it )
            memcpy(planes[1].data, planes[0].data, planeSize);

        return;
    }

    CV_Assert( src.size == mask.size && mask.type() == CV_8U );

    const Mat *arrays[]={&src, &dst, &mask, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t j, k, elemSize = src.elemSize(), total = planes[0].total();
    size_t i, nplanes = it.nplanes;

    for( i = 0; i < nplanes; i++, ++it)
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data;
        const uchar* mptr = planes[2].data;

        for( j = 0; j < total; j++, sptr += elemSize, dptr += elemSize )
        {
            if( (mptr[j] != 0) ^ invertMask )
                for( k = 0; k < elemSize; k++ )
                    dptr[k] = sptr[k];
        }
    }
}


void set(Mat& dst, const Scalar& gamma, const Mat& mask)
{
    double buf[12];
    scalarToRawData(gamma, &buf, dst.type(), dst.channels());
    const uchar* gptr = (const uchar*)&buf[0];

    if(mask.empty())
    {
        const Mat* arrays[] = {&dst, 0};
        Mat plane;
        NAryMatIterator it(arrays, &plane);
        size_t i, nplanes = it.nplanes;
        size_t j, k, elemSize = dst.elemSize(), planeSize = plane.total()*elemSize;

        for( k = 1; k < elemSize; k++ )
            if( gptr[k] != gptr[0] )
                break;
        bool uniform = k >= elemSize;

        for( i = 0; i < nplanes; i++, ++it )
        {
            uchar* dptr = plane.data;
            if( uniform )
                memset( dptr, gptr[0], planeSize );
            else if( i == 0 )
            {
                for( j = 0; j < planeSize; j += elemSize, dptr += elemSize )
                    for( k = 0; k < elemSize; k++ )
                        dptr[k] = gptr[k];
            }
            else
                memcpy(dptr, dst.data, planeSize);
        }
        return;
    }

    CV_Assert( dst.size == mask.size && mask.type() == CV_8U );

    const Mat *arrays[]={&dst, &mask, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t j, k, elemSize = dst.elemSize(), total = planes[0].total();
    size_t i, nplanes = it.nplanes;

    for( i = 0; i < nplanes; i++, ++it)
    {
        uchar* dptr = planes[0].data;
        const uchar* mptr = planes[1].data;

        for( j = 0; j < total; j++, dptr += elemSize )
        {
            if( mptr[j] )
                for( k = 0; k < elemSize; k++ )
                    dptr[k] = gptr[k];
        }
    }
}


void insert(const Mat& src, Mat& dst, int coi)
{
    CV_Assert( dst.size == src.size && src.depth() == dst.depth() &&
              0 <= coi && coi < dst.channels() );

    const Mat* arrays[] = {&src, &dst, 0};
    Mat planes[2];
    NAryMatIterator it(arrays, planes);
    size_t i, nplanes = it.nplanes;
    size_t j, k, size0 = src.elemSize(), size1 = dst.elemSize(), total = planes[0].total();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data + coi*size0;

        for( j = 0; j < total; j++, sptr += size0, dptr += size1 )
        {
            for( k = 0; k < size0; k++ )
                dptr[k] = sptr[k];
        }
    }
}


void extract(const Mat& src, Mat& dst, int coi)
{
    dst.create( src.dims, &src.size[0], src.depth() );
    CV_Assert( 0 <= coi && coi < src.channels() );

    const Mat* arrays[] = {&src, &dst, 0};
    Mat planes[2];
    NAryMatIterator it(arrays, planes);
    size_t i, nplanes = it.nplanes;
    size_t j, k, size0 = src.elemSize(), size1 = dst.elemSize(), total = planes[0].total();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data + coi*size1;
        uchar* dptr = planes[1].data;

        for( j = 0; j < total; j++, sptr += size0, dptr += size1 )
        {
            for( k = 0; k < size1; k++ )
                dptr[k] = sptr[k];
        }
    }
}


void transpose(const Mat& src, Mat& dst)
{
    CV_Assert(src.dims == 2);
    dst.create(src.cols, src.rows, src.type());
    int i, j, k, esz = (int)src.elemSize();

    for( i = 0; i < dst.rows; i++ )
    {
        const uchar* sptr = src.ptr(0) + i*esz;
        uchar* dptr = dst.ptr(i);

        for( j = 0; j < dst.cols; j++, sptr += src.step[0], dptr += esz )
        {
            for( k = 0; k < esz; k++ )
                dptr[k] = sptr[k];
        }
    }
}


template<typename _Tp> static void
randUniInt_(RNG& rng, _Tp* data, size_t total, int cn, const Scalar& scale, const Scalar& delta)
{
    for( size_t i = 0; i < total; i += cn )
        for( int k = 0; k < cn; k++ )
        {
            int val = cvFloor( randInt(rng)*scale[k] + delta[k] );
            data[i + k] = saturate_cast<_Tp>(val);
        }
}


template<typename _Tp> static void
randUniFlt_(RNG& rng, _Tp* data, size_t total, int cn, const Scalar& scale, const Scalar& delta)
{
    for( size_t i = 0; i < total; i += cn )
        for( int k = 0; k < cn; k++ )
        {
            double val = randReal(rng)*scale[k] + delta[k];
            data[i + k] = saturate_cast<_Tp>(val);
        }
}


void randUni( RNG& rng, Mat& a, const Scalar& param0, const Scalar& param1 )
{
    Scalar scale = param0;
    Scalar delta = param1;
    double C = a.depth() < CV_32F ? 1./(65536.*65536.) : 1.;

    for( int k = 0; k < 4; k++ )
    {
        double s = scale.val[k] - delta.val[k];
        if( s >= 0 )
            scale.val[k] = s;
        else
        {
            delta.val[k] = scale.val[k];
            scale.val[k] = -s;
        }
        scale.val[k] *= C;
    }

    const Mat *arrays[]={&a, 0};
    Mat plane;

    NAryMatIterator it(arrays, &plane);
    size_t i, nplanes = it.nplanes;
    int depth = a.depth(), cn = a.channels();
    size_t total = plane.total()*cn;

    for( i = 0; i < nplanes; i++, ++it )
    {
        switch( depth )
        {
        case CV_8U:
            randUniInt_(rng, plane.ptr<uchar>(), total, cn, scale, delta);
            break;
        case CV_8S:
            randUniInt_(rng, plane.ptr<schar>(), total, cn, scale, delta);
            break;
        case CV_16U:
            randUniInt_(rng, plane.ptr<ushort>(), total, cn, scale, delta);
            break;
        case CV_16S:
            randUniInt_(rng, plane.ptr<short>(), total, cn, scale, delta);
            break;
        case CV_32S:
            randUniInt_(rng, plane.ptr<int>(), total, cn, scale, delta);
            break;
        case CV_32F:
            randUniFlt_(rng, plane.ptr<float>(), total, cn, scale, delta);
            break;
        case CV_64F:
            randUniFlt_(rng, plane.ptr<double>(), total, cn, scale, delta);
            break;
        default:
            CV_Assert(0);
        }
    }
}


template<typename _Tp> static void
erode_(const Mat& src, Mat& dst, const vector<int>& ofsvec)
{
    int width = dst.cols*src.channels(), n = (int)ofsvec.size();
    const int* ofs = &ofsvec[0];

    for( int y = 0; y < dst.rows; y++ )
    {
        const _Tp* sptr = src.ptr<_Tp>(y);
        _Tp* dptr = dst.ptr<_Tp>(y);

        for( int x = 0; x < width; x++ )
        {
            _Tp result = sptr[x + ofs[0]];
            for( int i = 1; i < n; i++ )
                result = std::min(result, sptr[x + ofs[i]]);
            dptr[x] = result;
        }
    }
}


template<typename _Tp> static void
dilate_(const Mat& src, Mat& dst, const vector<int>& ofsvec)
{
    int width = dst.cols*src.channels(), n = (int)ofsvec.size();
    const int* ofs = &ofsvec[0];

    for( int y = 0; y < dst.rows; y++ )
    {
        const _Tp* sptr = src.ptr<_Tp>(y);
        _Tp* dptr = dst.ptr<_Tp>(y);

        for( int x = 0; x < width; x++ )
        {
            _Tp result = sptr[x + ofs[0]];
            for( int i = 1; i < n; i++ )
                result = std::max(result, sptr[x + ofs[i]]);
            dptr[x] = result;
        }
    }
}


void erode(const Mat& _src, Mat& dst, const Mat& _kernel, Point anchor,
           int borderType, const Scalar& _borderValue)
{
    //if( _src.type() == CV_16UC3 && _src.size() == Size(1, 2) )
    //    putchar('*');
    Mat kernel = _kernel, src;
    Scalar borderValue = _borderValue;
    if( kernel.empty() )
        kernel = Mat::ones(3, 3, CV_8U);
    else
    {
        CV_Assert( kernel.type() == CV_8U );
    }
    if( anchor == Point(-1,-1) )
        anchor = Point(kernel.cols/2, kernel.rows/2);
    if( borderType == IPL_BORDER_CONSTANT )
        borderValue = getMaxVal(src.depth());
    copyMakeBorder(_src, src, anchor.y, kernel.rows - anchor.y - 1,
                   anchor.x, kernel.cols - anchor.x - 1,
                   borderType, borderValue);
    dst.create( _src.size(), src.type() );

    vector<int> ofs;
    int step = (int)(src.step/src.elemSize1()), cn = src.channels();
    for( int i = 0; i < kernel.rows; i++ )
        for( int j = 0; j < kernel.cols; j++ )
            if( kernel.at<uchar>(i, j) != 0 )
                ofs.push_back(i*step + j*cn);
    if( ofs.empty() )
        ofs.push_back(anchor.y*step + anchor.x*cn);

    switch( src.depth() )
    {
    case CV_8U:
        erode_<uchar>(src, dst, ofs);
        break;
    case CV_8S:
        erode_<schar>(src, dst, ofs);
        break;
    case CV_16U:
        erode_<ushort>(src, dst, ofs);
        break;
    case CV_16S:
        erode_<short>(src, dst, ofs);
        break;
    case CV_32S:
        erode_<int>(src, dst, ofs);
        break;
    case CV_32F:
        erode_<float>(src, dst, ofs);
        break;
    case CV_64F:
        erode_<double>(src, dst, ofs);
        break;
    default:
        CV_Assert(0);
    }
}

void dilate(const Mat& _src, Mat& dst, const Mat& _kernel, Point anchor,
            int borderType, const Scalar& _borderValue)
{
    Mat kernel = _kernel, src;
    Scalar borderValue = _borderValue;
    if( kernel.empty() )
        kernel = Mat::ones(3, 3, CV_8U);
    else
    {
        CV_Assert( kernel.type() == CV_8U );
    }
    if( anchor == Point(-1,-1) )
        anchor = Point(kernel.cols/2, kernel.rows/2);
    if( borderType == IPL_BORDER_CONSTANT )
        borderValue = getMinVal(src.depth());
    copyMakeBorder(_src, src, anchor.y, kernel.rows - anchor.y - 1,
                   anchor.x, kernel.cols - anchor.x - 1,
                   borderType, borderValue);
    dst.create( _src.size(), src.type() );

    vector<int> ofs;
    int step = (int)(src.step/src.elemSize1()), cn = src.channels();
    for( int i = 0; i < kernel.rows; i++ )
        for( int j = 0; j < kernel.cols; j++ )
            if( kernel.at<uchar>(i, j) != 0 )
                ofs.push_back(i*step + j*cn);
    if( ofs.empty() )
        ofs.push_back(anchor.y*step + anchor.x*cn);

    switch( src.depth() )
    {
    case CV_8U:
        dilate_<uchar>(src, dst, ofs);
        break;
    case CV_8S:
        dilate_<schar>(src, dst, ofs);
        break;
    case CV_16U:
        dilate_<ushort>(src, dst, ofs);
        break;
    case CV_16S:
        dilate_<short>(src, dst, ofs);
        break;
    case CV_32S:
        dilate_<int>(src, dst, ofs);
        break;
    case CV_32F:
        dilate_<float>(src, dst, ofs);
        break;
    case CV_64F:
        dilate_<double>(src, dst, ofs);
        break;
    default:
        CV_Assert(0);
    }
}


template<typename _Tp> static void
filter2D_(const Mat& src, Mat& dst, const vector<int>& ofsvec, const vector<double>& coeffvec)
{
    const int* ofs = &ofsvec[0];
    const double* coeff = &coeffvec[0];
    int width = dst.cols*dst.channels(), ncoeffs = (int)ofsvec.size();

    for( int y = 0; y < dst.rows; y++ )
    {
        const _Tp* sptr = src.ptr<_Tp>(y);
        double* dptr = dst.ptr<double>(y);

        for( int x = 0; x < width; x++ )
        {
            double s = 0;
            for( int i = 0; i < ncoeffs; i++ )
                s += sptr[x + ofs[i]]*coeff[i];
            dptr[x] = s;
        }
    }
}


void filter2D(const Mat& _src, Mat& dst, int ddepth, const Mat& kernel,
              Point anchor, double delta, int borderType, const Scalar& _borderValue)
{
    Mat src, _dst;
    Scalar borderValue = _borderValue;
    CV_Assert( kernel.type() == CV_32F || kernel.type() == CV_64F );
    if( anchor == Point(-1,-1) )
        anchor = Point(kernel.cols/2, kernel.rows/2);
    if( borderType == IPL_BORDER_CONSTANT )
        borderValue = getMinVal(src.depth());
    copyMakeBorder(_src, src, anchor.y, kernel.rows - anchor.y - 1,
                   anchor.x, kernel.cols - anchor.x - 1,
                   borderType, borderValue);
    _dst.create( _src.size(), CV_MAKETYPE(CV_64F, src.channels()) );

    vector<int> ofs;
    vector<double> coeff(kernel.rows*kernel.cols);
    Mat cmat(kernel.rows, kernel.cols, CV_64F, &coeff[0]);
    convert(kernel, cmat, cmat.type());

    int step = (int)(src.step/src.elemSize1()), cn = src.channels();
    for( int i = 0; i < kernel.rows; i++ )
        for( int j = 0; j < kernel.cols; j++ )
                ofs.push_back(i*step + j*cn);

    switch( src.depth() )
    {
    case CV_8U:
        filter2D_<uchar>(src, _dst, ofs, coeff);
        break;
    case CV_8S:
        filter2D_<schar>(src, _dst, ofs, coeff);
        break;
    case CV_16U:
        filter2D_<ushort>(src, _dst, ofs, coeff);
        break;
    case CV_16S:
        filter2D_<short>(src, _dst, ofs, coeff);
        break;
    case CV_32S:
        filter2D_<int>(src, _dst, ofs, coeff);
        break;
    case CV_32F:
        filter2D_<float>(src, _dst, ofs, coeff);
        break;
    case CV_64F:
        filter2D_<double>(src, _dst, ofs, coeff);
        break;
    default:
        CV_Assert(0);
    }

    convert(_dst, dst, ddepth, 1, delta);
}


static int borderInterpolate( int p, int len, int borderType )
{
    if( (unsigned)p < (unsigned)len )
        ;
    else if( borderType == IPL_BORDER_REPLICATE )
        p = p < 0 ? 0 : len - 1;
    else if( borderType == IPL_BORDER_REFLECT || borderType == IPL_BORDER_REFLECT_101 )
    {
        int delta = borderType == IPL_BORDER_REFLECT_101;
        if( len == 1 )
            return 0;
        do
        {
            if( p < 0 )
                p = -p - 1 + delta;
            else
                p = len - 1 - (p - len) - delta;
        }
        while( (unsigned)p >= (unsigned)len );
    }
    else if( borderType == IPL_BORDER_WRAP )
    {
        if( p < 0 )
            p -= ((p-len+1)/len)*len;
        if( p >= len )
            p %= len;
    }
    else if( borderType == IPL_BORDER_CONSTANT )
        p = -1;
    else
        CV_Error( CV_StsBadArg, "Unknown/unsupported border type" );
    return p;
}


void copyMakeBorder(const Mat& src, Mat& dst, int top, int bottom, int left, int right,
                    int borderType, const Scalar& borderValue)
{
    dst.create(src.rows + top + bottom, src.cols + left + right, src.type());
    int i, j, k, esz = (int)src.elemSize();
    int width = src.cols*esz, width1 = dst.cols*esz;

    if( borderType == IPL_BORDER_CONSTANT )
    {
        vector<uchar> valvec((src.cols + left + right)*esz);
        uchar* val = &valvec[0];
        scalarToRawData(borderValue, val, src.type(), (src.cols + left + right)*src.channels());

        left *= esz;
        right *= esz;
        for( i = 0; i < src.rows; i++ )
        {
            const uchar* sptr = src.ptr(i);
            uchar* dptr = dst.ptr(i + top) + left;
            for( j = 0; j < left; j++ )
                dptr[j - left] = val[j];
            if( dptr != sptr )
                for( j = 0; j < width; j++ )
                    dptr[j] = sptr[j];
            for( j = 0; j < right; j++ )
                dptr[j + width] = val[j];
        }

        for( i = 0; i < top; i++ )
        {
            uchar* dptr = dst.ptr(i);
            for( j = 0; j < width1; j++ )
                dptr[j] = val[j];
        }

        for( i = 0; i < bottom; i++ )
        {
            uchar* dptr = dst.ptr(i + top + src.rows);
            for( j = 0; j < width1; j++ )
                dptr[j] = val[j];
        }
    }
    else
    {
        vector<int> tabvec((left + right)*esz + 1);
        int* ltab = &tabvec[0];
        int* rtab = &tabvec[left*esz];
        for( i = 0; i < left; i++ )
        {
            j = borderInterpolate(i - left, src.cols, borderType)*esz;
            for( k = 0; k < esz; k++ )
                ltab[i*esz + k] = j + k;
        }
        for( i = 0; i < right; i++ )
        {
            j = borderInterpolate(src.cols + i, src.cols, borderType)*esz;
            for( k = 0; k < esz; k++ )
                rtab[i*esz + k] = j + k;
        }

        left *= esz;
        right *= esz;
        for( i = 0; i < src.rows; i++ )
        {
            const uchar* sptr = src.ptr(i);
            uchar* dptr = dst.ptr(i + top);

            for( j = 0; j < left; j++ )
                dptr[j] = sptr[ltab[j]];
            if( dptr + left != sptr )
            {
                for( j = 0; j < width; j++ )
                    dptr[j + left] = sptr[j];
            }
            for( j = 0; j < right; j++ )
                dptr[j + left + width] = sptr[rtab[j]];
        }

        for( i = 0; i < top; i++ )
        {
            j = borderInterpolate(i - top, src.rows, borderType);
            const uchar* sptr = dst.ptr(j + top);
            uchar* dptr = dst.ptr(i);

            for( k = 0; k < width1; k++ )
                dptr[k] = sptr[k];
        }

        for( i = 0; i < bottom; i++ )
        {
            j = borderInterpolate(i + src.rows, src.rows, borderType);
            const uchar* sptr = dst.ptr(j + top);
            uchar* dptr = dst.ptr(i + top + src.rows);

            for( k = 0; k < width1; k++ )
                dptr[k] = sptr[k];
        }
    }
}


template<typename _Tp> static void
minMaxLoc_(const _Tp* src, size_t total, size_t startidx,
           double* _minval, double* _maxval,
           size_t* _minpos, size_t* _maxpos,
           const uchar* mask)
{
    _Tp maxval = saturate_cast<_Tp>(*_maxval), minval = saturate_cast<_Tp>(*_minval);
    size_t minpos = *_minpos, maxpos = *_maxpos;

    if( !mask )
    {
        for( size_t i = 0; i < total; i++ )
        {
            _Tp val = src[i];
            if( minval > val )
            {
                minval = val;
                minpos = startidx + i;
            }
            if( maxval < val )
            {
                maxval = val;
                maxpos = startidx + i;
            }
        }
    }
    else
    {
        for( size_t i = 0; i < total; i++ )
        {
            _Tp val = src[i];
            if( minval > val && mask[i] )
            {
                minval = val;
                minpos = startidx + i;
            }
            if( maxval < val && mask[i] )
            {
                maxval = val;
                maxpos = startidx + i;
            }
        }
    }

    *_maxval = maxval;
    *_minval = minval;
    *_maxpos = maxpos;
    *_minpos = minpos;
}


static void setpos( const Mat& mtx, vector<int>& pos, size_t idx )
{
    pos.resize(mtx.dims);
    if( idx > 0 )
    {
        idx--;
        for( int i = mtx.dims-1; i >= 0; i-- )
        {
            int sz = mtx.size[i]*(i == mtx.dims-1 ? mtx.channels() : 1);
            pos[i] = (int)(idx % sz);
            idx /= sz;
        }
    }
    else
    {
        for( int i = mtx.dims-1; i >= 0; i-- )
            pos[i] = -1;
    }
}

void minMaxLoc(const Mat& src, double* _minval, double* _maxval,
               vector<int>* _minloc, vector<int>* _maxloc,
               const Mat& mask)
{
    CV_Assert( src.channels() == 1 );
    const Mat *arrays[]={&src, &mask, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t startidx = 1, total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src.depth();
    double maxval = depth < CV_32F ? INT_MIN : depth == CV_32F ? -FLT_MAX : -DBL_MAX;
    double minval = depth < CV_32F ? INT_MAX : depth == CV_32F ? FLT_MAX : DBL_MAX;
    size_t maxidx = 0, minidx = 0;

    for( i = 0; i < nplanes; i++, ++it, startidx += total )
    {
        const uchar* sptr = planes[0].data;
        const uchar* mptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            minMaxLoc_((const uchar*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_8S:
            minMaxLoc_((const schar*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_16U:
            minMaxLoc_((const ushort*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_16S:
            minMaxLoc_((const short*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_32S:
            minMaxLoc_((const int*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_32F:
            minMaxLoc_((const float*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        case CV_64F:
            minMaxLoc_((const double*)sptr, total, startidx,
                       &minval, &maxval, &minidx, &maxidx, mptr);
            break;
        default:
            CV_Assert(0);
        }
    }

    if( minidx == 0 )
        minval = maxval = 0;

    if( _maxval )
        *_maxval = maxval;
    if( _minval )
        *_minval = minval;
    if( _maxloc )
        setpos( src, *_maxloc, maxidx );
    if( _minloc )
        setpos( src, *_minloc, minidx );
}


static int
normHamming(const uchar* src, size_t total, int cellSize)
{
    int result = 0;
    int mask = cellSize == 1 ? 1 : cellSize == 2 ? 3 : cellSize == 4 ? 15 : -1;
    CV_Assert( mask >= 0 );

    for( size_t i = 0; i < total; i++ )
    {
        unsigned a = src[i];
        for( ; a != 0; a >>= cellSize )
            result += (a & mask) != 0;
    }
    return result;
}


template<typename _Tp> static double
norm_(const _Tp* src, size_t total, int cn, int normType, double startval, const uchar* mask)
{
    size_t i;
    double result = startval;
    if( !mask )
        total *= cn;

    if( normType == NORM_INF )
    {
        if( !mask )
            for( i = 0; i < total; i++ )
                result = std::max(result, (double)std::abs(0+src[i]));// trick with 0 used to quiet gcc warning
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                        result = std::max(result, (double)std::abs(0+src[i*cn + c]));
            }
    }
    else if( normType == NORM_L1 )
    {
        if( !mask )
            for( i = 0; i < total; i++ )
                result += std::abs(0+src[i]);
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                        result += std::abs(0+src[i*cn + c]);
            }
    }
    else
    {
        if( !mask )
            for( i = 0; i < total; i++ )
            {
                double v = src[i];
                result += v*v;
            }
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                    {
                        double v = src[i*cn + c];
                        result += v*v;
                    }
            }
    }
    return result;
}


template<typename _Tp> static double
norm_(const _Tp* src1, const _Tp* src2, size_t total, int cn, int normType, double startval, const uchar* mask)
{
    size_t i;
    double result = startval;
    if( !mask )
        total *= cn;

    if( normType == NORM_INF )
    {
        if( !mask )
            for( i = 0; i < total; i++ )
                result = std::max(result, (double)std::abs(src1[i] - src2[i]));
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                        result = std::max(result, (double)std::abs(src1[i*cn + c] - src2[i*cn + c]));
            }
    }
    else if( normType == NORM_L1 )
    {
        if( !mask )
            for( i = 0; i < total; i++ )
                result += std::abs(src1[i] - src2[i]);
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                        result += std::abs(src1[i*cn + c] - src2[i*cn + c]);
            }
    }
    else
    {
        if( !mask )
            for( i = 0; i < total; i++ )
            {
                double v = src1[i] - src2[i];
                result += v*v;
            }
        else
            for( int c = 0; c < cn; c++ )
            {
                for( i = 0; i < total; i++ )
                    if( mask[i] )
                    {
                        double v = src1[i*cn + c] - src2[i*cn + c];
                        result += v*v;
                    }
            }
    }
    return result;
}


double norm(const Mat& src, int normType, const Mat& mask)
{
    if( normType == NORM_HAMMING || normType == NORM_HAMMING2 )
    {
        if( !mask.empty() )
        {
            Mat temp;
            bitwise_and(src, mask, temp);
            return norm(temp, normType, Mat());
        }

        CV_Assert( src.depth() == CV_8U );

        const Mat *arrays[]={&src, 0};
        Mat planes[1];

        NAryMatIterator it(arrays, planes);
        size_t total = planes[0].total();
        size_t i, nplanes = it.nplanes;
        double result = 0;
        int cellSize = normType == NORM_HAMMING ? 1 : 2;

        for( i = 0; i < nplanes; i++, ++it )
            result += normHamming(planes[0].data, total, cellSize);
        return result;
    }
    int normType0 = normType;
    normType = normType == NORM_L2SQR ? NORM_L2 : normType;

    CV_Assert( mask.empty() || (src.size == mask.size && mask.type() == CV_8U) );
    CV_Assert( normType == NORM_INF || normType == NORM_L1 || normType == NORM_L2 );

    const Mat *arrays[]={&src, &mask, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src.depth(), cn = planes[0].channels();
    double result = 0;

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        const uchar* mptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            result = norm_((const uchar*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_8S:
            result = norm_((const schar*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_16U:
            result = norm_((const ushort*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_16S:
            result = norm_((const short*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_32S:
            result = norm_((const int*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_32F:
            result = norm_((const float*)sptr, total, cn, normType, result, mptr);
            break;
        case CV_64F:
            result = norm_((const double*)sptr, total, cn, normType, result, mptr);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        };
    }
    if( normType0 == NORM_L2 )
        result = sqrt(result);
    return result;
}


double norm(const Mat& src1, const Mat& src2, int normType, const Mat& mask)
{
    if( normType == NORM_HAMMING || normType == NORM_HAMMING2 )
    {
        Mat temp;
        bitwise_xor(src1, src2, temp);
        if( !mask.empty() )
            bitwise_and(temp, mask, temp);

        CV_Assert( temp.depth() == CV_8U );

        const Mat *arrays[]={&temp, 0};
        Mat planes[1];

        NAryMatIterator it(arrays, planes);
        size_t total = planes[0].total();
        size_t i, nplanes = it.nplanes;
        double result = 0;
        int cellSize = normType == NORM_HAMMING ? 1 : 2;

        for( i = 0; i < nplanes; i++, ++it )
            result += normHamming(planes[0].data, total, cellSize);
        return result;
    }
    int normType0 = normType;
    normType = normType == NORM_L2SQR ? NORM_L2 : normType;

    CV_Assert( src1.type() == src2.type() && src1.size == src2.size );
    CV_Assert( mask.empty() || (src1.size == mask.size && mask.type() == CV_8U) );
    CV_Assert( normType == NORM_INF || normType == NORM_L1 || normType == NORM_L2 );
    const Mat *arrays[]={&src1, &src2, &mask, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src1.depth(), cn = planes[0].channels();
    double result = 0;

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        const uchar* mptr = planes[2].data;

        switch( depth )
        {
        case CV_8U:
            result = norm_((const uchar*)sptr1, (const uchar*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_8S:
            result = norm_((const schar*)sptr1, (const schar*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_16U:
            result = norm_((const ushort*)sptr1, (const ushort*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_16S:
            result = norm_((const short*)sptr1, (const short*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_32S:
            result = norm_((const int*)sptr1, (const int*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_32F:
            result = norm_((const float*)sptr1, (const float*)sptr2, total, cn, normType, result, mptr);
            break;
        case CV_64F:
            result = norm_((const double*)sptr1, (const double*)sptr2, total, cn, normType, result, mptr);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        };
    }
    if( normType0 == NORM_L2 )
        result = sqrt(result);
    return result;
}


template<typename _Tp> static double
crossCorr_(const _Tp* src1, const _Tp* src2, size_t total)
{
    double result = 0;
    for( size_t i = 0; i < total; i++ )
        result += (double)src1[i]*src2[i];
    return result;
}

double crossCorr(const Mat& src1, const Mat& src2)
{
    CV_Assert( src1.size == src2.size && src1.type() == src2.type() );
    const Mat *arrays[]={&src1, &src2, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels();
    size_t i, nplanes = it.nplanes;
    int depth = src1.depth();
    double result = 0;

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            result += crossCorr_((const uchar*)sptr1, (const uchar*)sptr2, total);
            break;
        case CV_8S:
            result += crossCorr_((const schar*)sptr1, (const schar*)sptr2, total);
            break;
        case CV_16U:
            result += crossCorr_((const ushort*)sptr1, (const ushort*)sptr2, total);
            break;
        case CV_16S:
            result += crossCorr_((const short*)sptr1, (const short*)sptr2, total);
            break;
        case CV_32S:
            result += crossCorr_((const int*)sptr1, (const int*)sptr2, total);
            break;
        case CV_32F:
            result += crossCorr_((const float*)sptr1, (const float*)sptr2, total);
            break;
        case CV_64F:
            result += crossCorr_((const double*)sptr1, (const double*)sptr2, total);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        };
    }
    return result;
}


static void
logicOp_(const uchar* src1, const uchar* src2, uchar* dst, size_t total, char c)
{
    size_t i;
    if( c == '&' )
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] & src2[i];
    else if( c == '|' )
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] | src2[i];
    else
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] ^ src2[i];
}

static void
logicOpS_(const uchar* src, const uchar* scalar, uchar* dst, size_t total, char c)
{
    const size_t blockSize = 96;
    size_t i, j;
    if( c == '&' )
        for( i = 0; i < total; i += blockSize, dst += blockSize, src += blockSize )
        {
            size_t sz = MIN(total - i, blockSize);
            for( j = 0; j < sz; j++ )
                dst[j] = src[j] & scalar[j];
        }
    else if( c == '|' )
        for( i = 0; i < total; i += blockSize, dst += blockSize, src += blockSize )
        {
            size_t sz = MIN(total - i, blockSize);
            for( j = 0; j < sz; j++ )
                dst[j] = src[j] | scalar[j];
        }
    else if( c == '^' )
    {
        for( i = 0; i < total; i += blockSize, dst += blockSize, src += blockSize )
        {
            size_t sz = MIN(total - i, blockSize);
            for( j = 0; j < sz; j++ )
                dst[j] = src[j] ^ scalar[j];
        }
    }
    else
        for( i = 0; i < total; i++ )
            dst[i] = ~src[i];
}


void logicOp( const Mat& src1, const Mat& src2, Mat& dst, char op )
{
    CV_Assert( op == '&' || op == '|' || op == '^' );
    CV_Assert( src1.type() == src2.type() && src1.size == src2.size );
    dst.create( src1.dims, &src1.size[0], src1.type() );
    const Mat *arrays[]={&src1, &src2, &dst, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].elemSize();
    size_t i, nplanes = it.nplanes;

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        uchar* dptr = planes[2].data;

        logicOp_(sptr1, sptr2, dptr, total, op);
    }
}


void logicOp(const Mat& src, const Scalar& s, Mat& dst, char op)
{
    CV_Assert( op == '&' || op == '|' || op == '^' || op == '~' );
    dst.create( src.dims, &src.size[0], src.type() );
    const Mat *arrays[]={&src, &dst, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].elemSize();
    size_t i, nplanes = it.nplanes;
    double buf[12];
    scalarToRawData(s, buf, src.type(), (int)(96/planes[0].elemSize1()));

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data;

        logicOpS_(sptr, (uchar*)&buf[0], dptr, total, op);
    }
}


template<typename _Tp> static void
compare_(const _Tp* src1, const _Tp* src2, uchar* dst, size_t total, int cmpop)
{
    size_t i;
    switch( cmpop )
    {
    case CMP_LT:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] < src2[i] ? 255 : 0;
        break;
    case CMP_LE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] <= src2[i] ? 255 : 0;
        break;
    case CMP_EQ:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] == src2[i] ? 255 : 0;
        break;
    case CMP_NE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] != src2[i] ? 255 : 0;
        break;
    case CMP_GE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] >= src2[i] ? 255 : 0;
        break;
    case CMP_GT:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] > src2[i] ? 255 : 0;
        break;
    default:
        CV_Error(CV_StsBadArg, "Unknown comparison operation");
    }
}


template<typename _Tp, typename _WTp> static void
compareS_(const _Tp* src1, _WTp value, uchar* dst, size_t total, int cmpop)
{
    size_t i;
    switch( cmpop )
    {
    case CMP_LT:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] < value ? 255 : 0;
        break;
    case CMP_LE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] <= value ? 255 : 0;
        break;
    case CMP_EQ:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] == value ? 255 : 0;
        break;
    case CMP_NE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] != value ? 255 : 0;
        break;
    case CMP_GE:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] >= value ? 255 : 0;
        break;
    case CMP_GT:
        for( i = 0; i < total; i++ )
            dst[i] = src1[i] > value ? 255 : 0;
        break;
    default:
        CV_Error(CV_StsBadArg, "Unknown comparison operation");
    }
}


void compare(const Mat& src1, const Mat& src2, Mat& dst, int cmpop)
{
    CV_Assert( src1.type() == src2.type() && src1.channels() == 1 && src1.size == src2.size );
    dst.create( src1.dims, &src1.size[0], CV_8U );
    const Mat *arrays[]={&src1, &src2, &dst, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src1.depth();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        uchar* dptr = planes[2].data;

        switch( depth )
        {
        case CV_8U:
            compare_((const uchar*)sptr1, (const uchar*)sptr2, dptr, total, cmpop);
            break;
        case CV_8S:
            compare_((const schar*)sptr1, (const schar*)sptr2, dptr, total, cmpop);
            break;
        case CV_16U:
            compare_((const ushort*)sptr1, (const ushort*)sptr2, dptr, total, cmpop);
            break;
        case CV_16S:
            compare_((const short*)sptr1, (const short*)sptr2, dptr, total, cmpop);
            break;
        case CV_32S:
            compare_((const int*)sptr1, (const int*)sptr2, dptr, total, cmpop);
            break;
        case CV_32F:
            compare_((const float*)sptr1, (const float*)sptr2, dptr, total, cmpop);
            break;
        case CV_64F:
            compare_((const double*)sptr1, (const double*)sptr2, dptr, total, cmpop);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}

void compare(const Mat& src, double value, Mat& dst, int cmpop)
{
    CV_Assert( src.channels() == 1 );
    dst.create( src.dims, &src.size[0], CV_8U );
    const Mat *arrays[]={&src, &dst, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src.depth();
    int ivalue = saturate_cast<int>(value);

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            compareS_((const uchar*)sptr, ivalue, dptr, total, cmpop);
            break;
        case CV_8S:
            compareS_((const schar*)sptr, ivalue, dptr, total, cmpop);
            break;
        case CV_16U:
            compareS_((const ushort*)sptr, ivalue, dptr, total, cmpop);
            break;
        case CV_16S:
            compareS_((const short*)sptr, ivalue, dptr, total, cmpop);
            break;
        case CV_32S:
            compareS_((const int*)sptr, ivalue, dptr, total, cmpop);
            break;
        case CV_32F:
            compareS_((const float*)sptr, value, dptr, total, cmpop);
            break;
        case CV_64F:
            compareS_((const double*)sptr, value, dptr, total, cmpop);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}


template<typename _Tp> double
cmpUlpsInt_(const _Tp* src1, const _Tp* src2, size_t total, int imaxdiff,
           size_t startidx, size_t& idx)
{
    size_t i;
    int realmaxdiff = 0;
    for( i = 0; i < total; i++ )
    {
        int diff = std::abs(src1[i] - src2[i]);
        if( realmaxdiff < diff )
        {
            realmaxdiff = diff;
            if( diff > imaxdiff && idx == 0 )
                idx = i + startidx;
        }
    }
    return realmaxdiff;
}


template<> double cmpUlpsInt_<int>(const int* src1, const int* src2,
                                          size_t total, int imaxdiff,
                                          size_t startidx, size_t& idx)
{
    size_t i;
    double realmaxdiff = 0;
    for( i = 0; i < total; i++ )
    {
        double diff = fabs((double)src1[i] - (double)src2[i]);
        if( realmaxdiff < diff )
        {
            realmaxdiff = diff;
            if( diff > imaxdiff && idx == 0 )
                idx = i + startidx;
        }
    }
    return realmaxdiff;
}


static double
cmpUlpsFlt_(const int* src1, const int* src2, size_t total, int imaxdiff, size_t startidx, size_t& idx)
{
    const int C = 0x7fffffff;
    int realmaxdiff = 0;
    size_t i;
    for( i = 0; i < total; i++ )
    {
        int a = src1[i], b = src2[i];
        if( a < 0 ) a ^= C; if( b < 0 ) b ^= C;
        int diff = std::abs(a - b);
        if( realmaxdiff < diff )
        {
            realmaxdiff = diff;
            if( diff > imaxdiff && idx == 0 )
                idx = i + startidx;
        }
    }
    return realmaxdiff;
}


static double
cmpUlpsFlt_(const int64* src1, const int64* src2, size_t total, int imaxdiff, size_t startidx, size_t& idx)
{
    const int64 C = CV_BIG_INT(0x7fffffffffffffff);
    double realmaxdiff = 0;
    size_t i;
    for( i = 0; i < total; i++ )
    {
        int64 a = src1[i], b = src2[i];
        if( a < 0 ) a ^= C; if( b < 0 ) b ^= C;
        double diff = fabs((double)a - (double)b);
        if( realmaxdiff < diff )
        {
            realmaxdiff = diff;
            if( diff > imaxdiff && idx == 0 )
                idx = i + startidx;
        }
    }
    return realmaxdiff;
}

bool cmpUlps(const Mat& src1, const Mat& src2, int imaxDiff, double* _realmaxdiff, vector<int>* loc)
{
    CV_Assert( src1.type() == src2.type() && src1.size == src2.size );
    const Mat *arrays[]={&src1, &src2, 0};
    Mat planes[2];
    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels();
    size_t i, nplanes = it.nplanes;
    int depth = src1.depth();
    size_t startidx = 1, idx = 0;
    if(_realmaxdiff)
        *_realmaxdiff = 0;

    for( i = 0; i < nplanes; i++, ++it, startidx += total )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        double realmaxdiff = 0;

        switch( depth )
        {
        case CV_8U:
            realmaxdiff = cmpUlpsInt_((const uchar*)sptr1, (const uchar*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_8S:
            realmaxdiff = cmpUlpsInt_((const schar*)sptr1, (const schar*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_16U:
            realmaxdiff = cmpUlpsInt_((const ushort*)sptr1, (const ushort*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_16S:
            realmaxdiff = cmpUlpsInt_((const short*)sptr1, (const short*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_32S:
            realmaxdiff = cmpUlpsInt_((const int*)sptr1, (const int*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_32F:
            realmaxdiff = cmpUlpsFlt_((const int*)sptr1, (const int*)sptr2, total, imaxDiff, startidx, idx);
            break;
        case CV_64F:
            realmaxdiff = cmpUlpsFlt_((const int64*)sptr1, (const int64*)sptr2, total, imaxDiff, startidx, idx);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }

        if(_realmaxdiff)
            *_realmaxdiff = std::max(*_realmaxdiff, realmaxdiff);
    }
    if(idx > 0 && loc)
        setpos(src1, *loc, idx);
    return idx == 0;
}


template<typename _Tp> static void
checkInt_(const _Tp* a, size_t total, int imin, int imax, size_t startidx, size_t& idx)
{
    for( size_t i = 0; i < total; i++ )
    {
        int val = a[i];
        if( val < imin || val > imax )
        {
            idx = i + startidx;
            break;
        }
    }
}


template<typename _Tp> static void
checkFlt_(const _Tp* a, size_t total, double fmin, double fmax, size_t startidx, size_t& idx)
{
    for( size_t i = 0; i < total; i++ )
    {
        double val = a[i];
        if( cvIsNaN(val) || cvIsInf(val) || val < fmin || val > fmax )
        {
            idx = i + startidx;
            break;
        }
    }
}


// checks that the array does not have NaNs and/or Infs and all the elements are
// within [min_val,max_val). idx is the index of the first "bad" element.
int check( const Mat& a, double fmin, double fmax, vector<int>* _idx )
{
    const Mat *arrays[]={&a, 0};
    Mat plane;
    NAryMatIterator it(arrays, &plane);
    size_t total = plane.total()*plane.channels();
    size_t i, nplanes = it.nplanes;
    int depth = a.depth();
    size_t startidx = 1, idx = 0;
    int imin = 0, imax = 0;

    if( depth <= CV_32S )
    {
        imin = cvCeil(fmin);
        imax = cvFloor(fmax);
    }

    for( i = 0; i < nplanes; i++, ++it, startidx += total )
    {
        const uchar* aptr = plane.data;

        switch( depth )
        {
            case CV_8U:
                checkInt_((const uchar*)aptr, total, imin, imax, startidx, idx);
                break;
            case CV_8S:
                checkInt_((const schar*)aptr, total, imin, imax, startidx, idx);
                break;
            case CV_16U:
                checkInt_((const ushort*)aptr, total, imin, imax, startidx, idx);
                break;
            case CV_16S:
                checkInt_((const short*)aptr, total, imin, imax, startidx, idx);
                break;
            case CV_32S:
                checkInt_((const int*)aptr, total, imin, imax, startidx, idx);
                break;
            case CV_32F:
                checkFlt_((const float*)aptr, total, fmin, fmax, startidx, idx);
                break;
            case CV_64F:
                checkFlt_((const double*)aptr, total, fmin, fmax, startidx, idx);
                break;
            default:
                CV_Error(CV_StsUnsupportedFormat, "");
        }

        if( idx != 0 )
            break;
    }

    if(idx != 0 && _idx)
        setpos(a, *_idx, idx);
    return idx == 0 ? 0 : -1;
}

#define CMP_EPS_OK 0
#define CMP_EPS_BIG_DIFF -1
#define CMP_EPS_INVALID_TEST_DATA -2 // there is NaN or Inf value in test data
#define CMP_EPS_INVALID_REF_DATA -3 // there is NaN or Inf value in reference data

// compares two arrays. max_diff is the maximum actual difference,
// success_err_level is maximum allowed difference, idx is the index of the first
// element for which difference is >success_err_level
// (or index of element with the maximum difference)
int cmpEps( const Mat& arr, const Mat& refarr, double* _realmaxdiff,
            double success_err_level, vector<int>* _idx,
            bool element_wise_relative_error )
{
    CV_Assert( arr.type() == refarr.type() && arr.size == refarr.size );

    int ilevel = refarr.depth() <= CV_32S ? cvFloor(success_err_level) : 0;
    int result = CMP_EPS_OK;

    const Mat *arrays[]={&arr, &refarr, 0};
    Mat planes[2];
    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels(), j = total;
    size_t i, nplanes = it.nplanes;
    int depth = arr.depth();
    size_t startidx = 1, idx = 0;
    double realmaxdiff = 0, maxval = 0;

    if(_realmaxdiff)
        *_realmaxdiff = 0;

    if( refarr.depth() >= CV_32F && !element_wise_relative_error )
    {
        maxval = cvtest::norm( refarr, NORM_INF );
        maxval = MAX(maxval, 1.);
    }

    for( i = 0; i < nplanes; i++, ++it, startidx += total )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            realmaxdiff = cmpUlpsInt_((const uchar*)sptr1, (const uchar*)sptr2, total, ilevel, startidx, idx);
            break;
        case CV_8S:
            realmaxdiff = cmpUlpsInt_((const schar*)sptr1, (const schar*)sptr2, total, ilevel, startidx, idx);
            break;
        case CV_16U:
            realmaxdiff = cmpUlpsInt_((const ushort*)sptr1, (const ushort*)sptr2, total, ilevel, startidx, idx);
            break;
        case CV_16S:
            realmaxdiff = cmpUlpsInt_((const short*)sptr1, (const short*)sptr2, total, ilevel, startidx, idx);
            break;
        case CV_32S:
            realmaxdiff = cmpUlpsInt_((const int*)sptr1, (const int*)sptr2, total, ilevel, startidx, idx);
            break;
        case CV_32F:
            for( j = 0; j < total; j++ )
            {
                double a_val = ((float*)sptr1)[j];
                double b_val = ((float*)sptr2)[j];
                double threshold;
                if( ((int*)sptr1)[j] == ((int*)sptr2)[j] )
                    continue;
                if( cvIsNaN(a_val) || cvIsInf(a_val) )
                {
                    result = CMP_EPS_INVALID_TEST_DATA;
                    idx = startidx + j;
                    break;
                }
                if( cvIsNaN(b_val) || cvIsInf(b_val) )
                {
                    result = CMP_EPS_INVALID_REF_DATA;
                    idx = startidx + j;
                    break;
                }
                a_val = fabs(a_val - b_val);
                threshold = element_wise_relative_error ? fabs(b_val) + 1 : maxval;
                if( a_val > threshold*success_err_level )
                {
                    realmaxdiff = a_val/threshold;
                    if( idx == 0 )
                        idx = startidx + j;
                    break;
                }
            }
            break;
        case CV_64F:
            for( j = 0; j < total; j++ )
            {
                double a_val = ((double*)sptr1)[j];
                double b_val = ((double*)sptr2)[j];
                double threshold;
                if( ((int64*)sptr1)[j] == ((int64*)sptr2)[j] )
                    continue;
                if( cvIsNaN(a_val) || cvIsInf(a_val) )
                {
                    result = CMP_EPS_INVALID_TEST_DATA;
                    idx = startidx + j;
                    break;
                }
                if( cvIsNaN(b_val) || cvIsInf(b_val) )
                {
                    result = CMP_EPS_INVALID_REF_DATA;
                    idx = startidx + j;
                    break;
                }
                a_val = fabs(a_val - b_val);
                threshold = element_wise_relative_error ? fabs(b_val) + 1 : maxval;
                if( a_val > threshold*success_err_level )
                {
                    realmaxdiff = a_val/threshold;
                    idx = startidx + j;
                    break;
                }
            }
            break;
        default:
            assert(0);
            return CMP_EPS_BIG_DIFF;
        }
        if(_realmaxdiff)
            *_realmaxdiff = MAX(*_realmaxdiff, realmaxdiff);
        if( idx != 0 )
            break;
    }

    if( result == 0 && idx != 0 )
        result = CMP_EPS_BIG_DIFF;

    if( result < -1 && _realmaxdiff )
        *_realmaxdiff = exp(1000.);
    if(idx > 0 && _idx)
        setpos(arr, *_idx, idx);

    return result;
}


int cmpEps2( TS* ts, const Mat& a, const Mat& b, double success_err_level,
             bool element_wise_relative_error, const char* desc )
{
    char msg[100];
    double diff = 0;
    vector<int> idx;
    int code = cmpEps( a, b, &diff, success_err_level, &idx, element_wise_relative_error );

    switch( code )
    {
    case CMP_EPS_BIG_DIFF:
        sprintf( msg, "%s: Too big difference (=%g)", desc, diff );
        code = TS::FAIL_BAD_ACCURACY;
        break;
    case CMP_EPS_INVALID_TEST_DATA:
        sprintf( msg, "%s: Invalid output", desc );
        code = TS::FAIL_INVALID_OUTPUT;
        break;
    case CMP_EPS_INVALID_REF_DATA:
        sprintf( msg, "%s: Invalid reference output", desc );
        code = TS::FAIL_INVALID_OUTPUT;
        break;
    default:
        ;
    }

    if( code < 0 )
    {
        if( a.total() == 1 )
        {
            ts->printf( TS::LOG, "%s\n", msg );
        }
        else if( a.dims == 2 && (a.rows == 1 || a.cols == 1) )
        {
            ts->printf( TS::LOG, "%s at element %d\n", msg, idx[0] + idx[1] );
        }
        else
        {
            string idxstr = vec2str(", ", &idx[0], idx.size());
            ts->printf( TS::LOG, "%s at (%s)\n", msg, idxstr.c_str() );
        }
    }

    return code;
}


int cmpEps2_64f( TS* ts, const double* val, const double* refval, int len,
             double eps, const char* param_name )
{
    Mat _val(1, len, CV_64F, (void*)val);
    Mat _refval(1, len, CV_64F, (void*)refval);

    return cmpEps2( ts, _val, _refval, eps, true, param_name );
}


template<typename _Tp> static void
GEMM_(const _Tp* a_data0, int a_step, int a_delta,
      const _Tp* b_data0, int b_step, int b_delta,
      const _Tp* c_data0, int c_step, int c_delta,
      _Tp* d_data, int d_step,
      int d_rows, int d_cols, int a_cols, int cn,
      double alpha, double beta)
{
    for( int i = 0; i < d_rows; i++, d_data += d_step, c_data0 += c_step, a_data0 += a_step )
    {
        for( int j = 0; j < d_cols; j++ )
        {
            const _Tp* a_data = a_data0;
            const _Tp* b_data = b_data0 + j*b_delta;
            const _Tp* c_data = c_data0 + j*c_delta;

            if( cn == 1 )
            {
                double s = 0;
                for( int k = 0; k < a_cols; k++ )
                {
                    s += ((double)a_data[0])*b_data[0];
                    a_data += a_delta;
                    b_data += b_step;
                }
                d_data[j] = (_Tp)(s*alpha + (c_data ? c_data[0]*beta : 0));
            }
            else
            {
                double s_re = 0, s_im = 0;

                for( int k = 0; k < a_cols; k++ )
                {
                    s_re += ((double)a_data[0])*b_data[0] - ((double)a_data[1])*b_data[1];
                    s_im += ((double)a_data[0])*b_data[1] + ((double)a_data[1])*b_data[0];
                    a_data += a_delta;
                    b_data += b_step;
                }

                s_re *= alpha;
                s_im *= alpha;

                if( c_data )
                {
                    s_re += c_data[0]*beta;
                    s_im += c_data[1]*beta;
                }

                d_data[j*2] = (_Tp)s_re;
                d_data[j*2+1] = (_Tp)s_im;
            }
        }
    }
}


void gemm( const Mat& _a, const Mat& _b, double alpha,
           const Mat& _c, double beta, Mat& d, int flags )
{
    Mat a = _a, b = _b, c = _c;

    if( a.data == d.data )
        a = a.clone();

    if( b.data == d.data )
        b = b.clone();

    if( !c.empty() && c.data == d.data && (flags & cv::GEMM_3_T) )
        c = c.clone();

    int a_rows = a.rows, a_cols = a.cols, b_rows = b.rows, b_cols = b.cols;
    int cn = a.channels();
    int a_step = (int)a.step1(), a_delta = cn;
    int b_step = (int)b.step1(), b_delta = cn;
    int c_rows = 0, c_cols = 0, c_step = 0, c_delta = 0;

    CV_Assert( a.type() == b.type() && a.dims == 2 && b.dims == 2 && cn <= 2 );

    if( flags & cv::GEMM_1_T )
    {
        std::swap( a_rows, a_cols );
        std::swap( a_step, a_delta );
    }

    if( flags & cv::GEMM_2_T )
    {
        std::swap( b_rows, b_cols );
        std::swap( b_step, b_delta );
    }

    if( !c.empty() )
    {
        c_rows = c.rows;
        c_cols = c.cols;
        c_step = (int)c.step1();
        c_delta = cn;

        if( flags & cv::GEMM_3_T )
        {
            std::swap( c_rows, c_cols );
            std::swap( c_step, c_delta );
        }

        CV_Assert( c.dims == 2 && c.type() == a.type() && c_rows == a_rows && c_cols == b_cols );
    }

    d.create(a_rows, b_cols, a.type());

    if( a.depth() == CV_32F )
        GEMM_(a.ptr<float>(), a_step, a_delta, b.ptr<float>(), b_step, b_delta,
              !c.empty() ? c.ptr<float>() : 0, c_step, c_delta, d.ptr<float>(),
              (int)d.step1(), a_rows, b_cols, a_cols, cn, alpha, beta );
    else
        GEMM_(a.ptr<double>(), a_step, a_delta, b.ptr<double>(), b_step, b_delta,
              !c.empty() ? c.ptr<double>() : 0, c_step, c_delta, d.ptr<double>(),
              (int)d.step1(), a_rows, b_cols, a_cols, cn, alpha, beta );
}


template<typename _Tp> static void
transform_(const _Tp* sptr, _Tp* dptr, size_t total, int scn, int dcn, const double* mat)
{
    for( size_t i = 0; i < total; i++, sptr += scn, dptr += dcn )
    {
        for( int j = 0; j < dcn; j++ )
        {
            double s = mat[j*(scn + 1) + scn];
            for( int k = 0; k < scn; k++ )
                s += mat[j*(scn + 1) + k]*sptr[k];
            dptr[j] = saturate_cast<_Tp>(s);
        }
    }
}


void transform( const Mat& src, Mat& dst, const Mat& transmat, const Mat& _shift )
{
    double mat[20];

    int scn = src.channels();
    int dcn = dst.channels();
    int depth = src.depth();
    int mattype = transmat.depth();
    Mat shift = _shift.reshape(1, 0);
    bool haveShift = !shift.empty();

    CV_Assert( scn <= 4 && dcn <= 4 &&
              (mattype == CV_32F || mattype == CV_64F) &&
              (!haveShift || (shift.type() == mattype && (shift.rows == 1 || shift.cols == 1))) );

    // prepare cn x (cn + 1) transform matrix
    if( mattype == CV_32F )
    {
        for( int i = 0; i < transmat.rows; i++ )
        {
            mat[i*(scn+1)+scn] = 0.;
            for( int j = 0; j < transmat.cols; j++ )
                mat[i*(scn+1)+j] = transmat.at<float>(i,j);
            if( haveShift )
                mat[i*(scn+1)+scn] = shift.at<float>(i);
        }
    }
    else
    {
        for( int i = 0; i < transmat.rows; i++ )
        {
            mat[i*(scn+1)+scn] = 0.;
            for( int j = 0; j < transmat.cols; j++ )
                mat[i*(scn+1)+j] = transmat.at<double>(i,j);
            if( haveShift )
                mat[i*(scn+1)+scn] = shift.at<double>(i);
        }
    }

    const Mat *arrays[]={&src, &dst, 0};
    Mat planes[2];
    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        uchar* dptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            transform_((const uchar*)sptr, (uchar*)dptr, total, scn, dcn, mat);
            break;
        case CV_8S:
            transform_((const schar*)sptr, (schar*)dptr, total, scn, dcn, mat);
            break;
        case CV_16U:
            transform_((const ushort*)sptr, (ushort*)dptr, total, scn, dcn, mat);
            break;
        case CV_16S:
            transform_((const short*)sptr, (short*)dptr, total, scn, dcn, mat);
            break;
        case CV_32S:
            transform_((const int*)sptr, (int*)dptr, total, scn, dcn, mat);
            break;
        case CV_32F:
            transform_((const float*)sptr, (float*)dptr, total, scn, dcn, mat);
            break;
        case CV_64F:
            transform_((const double*)sptr, (double*)dptr, total, scn, dcn, mat);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}

template<typename _Tp> static void
minmax_(const _Tp* src1, const _Tp* src2, _Tp* dst, size_t total, char op)
{
    if( op == 'M' )
        for( size_t i = 0; i < total; i++ )
            dst[i] = std::max(src1[i], src2[i]);
    else
        for( size_t i = 0; i < total; i++ )
            dst[i] = std::min(src1[i], src2[i]);
}

static void minmax(const Mat& src1, const Mat& src2, Mat& dst, char op)
{
    dst.create(src1.dims, src1.size, src1.type());
    CV_Assert( src1.type() == src2.type() && src1.size == src2.size );
    const Mat *arrays[]={&src1, &src2, &dst, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels();
    size_t i, nplanes = it.nplanes, depth = src1.depth();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        uchar* dptr = planes[2].data;

        switch( depth )
        {
        case CV_8U:
            minmax_((const uchar*)sptr1, (const uchar*)sptr2, (uchar*)dptr, total, op);
            break;
        case CV_8S:
            minmax_((const schar*)sptr1, (const schar*)sptr2, (schar*)dptr, total, op);
            break;
        case CV_16U:
            minmax_((const ushort*)sptr1, (const ushort*)sptr2, (ushort*)dptr, total, op);
            break;
        case CV_16S:
            minmax_((const short*)sptr1, (const short*)sptr2, (short*)dptr, total, op);
            break;
        case CV_32S:
            minmax_((const int*)sptr1, (const int*)sptr2, (int*)dptr, total, op);
            break;
        case CV_32F:
            minmax_((const float*)sptr1, (const float*)sptr2, (float*)dptr, total, op);
            break;
        case CV_64F:
            minmax_((const double*)sptr1, (const double*)sptr2, (double*)dptr, total, op);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}


void min(const Mat& src1, const Mat& src2, Mat& dst)
{
    minmax( src1, src2, dst, 'm' );
}

void max(const Mat& src1, const Mat& src2, Mat& dst)
{
    minmax( src1, src2, dst, 'M' );
}


template<typename _Tp> static void
minmax_(const _Tp* src1, _Tp val, _Tp* dst, size_t total, char op)
{
    if( op == 'M' )
        for( size_t i = 0; i < total; i++ )
            dst[i] = std::max(src1[i], val);
    else
        for( size_t i = 0; i < total; i++ )
            dst[i] = std::min(src1[i], val);
}

static void minmax(const Mat& src1, double val, Mat& dst, char op)
{
    dst.create(src1.dims, src1.size, src1.type());
    const Mat *arrays[]={&src1, &dst, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total()*planes[0].channels();
    size_t i, nplanes = it.nplanes, depth = src1.depth();
    int ival = saturate_cast<int>(val);

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        uchar* dptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            minmax_((const uchar*)sptr1, saturate_cast<uchar>(ival), (uchar*)dptr, total, op);
            break;
        case CV_8S:
            minmax_((const schar*)sptr1, saturate_cast<schar>(ival), (schar*)dptr, total, op);
            break;
        case CV_16U:
            minmax_((const ushort*)sptr1, saturate_cast<ushort>(ival), (ushort*)dptr, total, op);
            break;
        case CV_16S:
            minmax_((const short*)sptr1, saturate_cast<short>(ival), (short*)dptr, total, op);
            break;
        case CV_32S:
            minmax_((const int*)sptr1, saturate_cast<int>(ival), (int*)dptr, total, op);
            break;
        case CV_32F:
            minmax_((const float*)sptr1, saturate_cast<float>(val), (float*)dptr, total, op);
            break;
        case CV_64F:
            minmax_((const double*)sptr1, saturate_cast<double>(val), (double*)dptr, total, op);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}


void min(const Mat& src1, double val, Mat& dst)
{
    minmax( src1, val, dst, 'm' );
}

void max(const Mat& src1, double val, Mat& dst)
{
    minmax( src1, val, dst, 'M' );
}


template<typename _Tp> static void
muldiv_(const _Tp* src1, const _Tp* src2, _Tp* dst, size_t total, double scale, char op)
{
    if( op == '*' )
        for( size_t i = 0; i < total; i++ )
            dst[i] = saturate_cast<_Tp>((scale*src1[i])*src2[i]);
    else if( src1 )
        for( size_t i = 0; i < total; i++ )
            dst[i] = src2[i] ? saturate_cast<_Tp>((scale*src1[i])/src2[i]) : 0;
    else
        for( size_t i = 0; i < total; i++ )
            dst[i] = src2[i] ? saturate_cast<_Tp>(scale/src2[i]) : 0;
}

static void muldiv(const Mat& src1, const Mat& src2, Mat& dst, double scale, char op)
{
    dst.create(src2.dims, src2.size, src2.type());
    CV_Assert( src1.empty() || (src1.type() == src2.type() && src1.size == src2.size) );
    const Mat *arrays[]={&src1, &src2, &dst, 0};
    Mat planes[3];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[1].total()*planes[1].channels();
    size_t i, nplanes = it.nplanes, depth = src2.depth();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr1 = planes[0].data;
        const uchar* sptr2 = planes[1].data;
        uchar* dptr = planes[2].data;

        switch( depth )
        {
        case CV_8U:
            muldiv_((const uchar*)sptr1, (const uchar*)sptr2, (uchar*)dptr, total, scale, op);
            break;
        case CV_8S:
            muldiv_((const schar*)sptr1, (const schar*)sptr2, (schar*)dptr, total, scale, op);
            break;
        case CV_16U:
            muldiv_((const ushort*)sptr1, (const ushort*)sptr2, (ushort*)dptr, total, scale, op);
            break;
        case CV_16S:
            muldiv_((const short*)sptr1, (const short*)sptr2, (short*)dptr, total, scale, op);
            break;
        case CV_32S:
            muldiv_((const int*)sptr1, (const int*)sptr2, (int*)dptr, total, scale, op);
            break;
        case CV_32F:
            muldiv_((const float*)sptr1, (const float*)sptr2, (float*)dptr, total, scale, op);
            break;
        case CV_64F:
            muldiv_((const double*)sptr1, (const double*)sptr2, (double*)dptr, total, scale, op);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }
}


void multiply(const Mat& src1, const Mat& src2, Mat& dst, double scale)
{
    muldiv( src1, src2, dst, scale, '*' );
}

void divide(const Mat& src1, const Mat& src2, Mat& dst, double scale)
{
    muldiv( src1, src2, dst, scale, '/' );
}


template<typename _Tp> static void
mean_(const _Tp* src, const uchar* mask, size_t total, int cn, Scalar& sum, int& nz)
{
    if( !mask )
    {
        nz += (int)total;
        total *= cn;
        for( size_t i = 0; i < total; i += cn )
        {
            for( int c = 0; c < cn; c++ )
                sum[c] += src[i + c];
        }
    }
    else
    {
        for( size_t i = 0; i < total; i++ )
            if( mask[i] )
            {
                nz++;
                for( int c = 0; c < cn; c++ )
                    sum[c] += src[i*cn + c];
            }
    }
}

Scalar mean(const Mat& src, const Mat& mask)
{
    CV_Assert(mask.empty() || (mask.type() == CV_8U && mask.size == src.size));
    Scalar sum;
    int nz = 0;

    const Mat *arrays[]={&src, &mask, 0};
    Mat planes[2];

    NAryMatIterator it(arrays, planes);
    size_t total = planes[0].total();
    size_t i, nplanes = it.nplanes;
    int depth = src.depth(), cn = src.channels();

    for( i = 0; i < nplanes; i++, ++it )
    {
        const uchar* sptr = planes[0].data;
        const uchar* mptr = planes[1].data;

        switch( depth )
        {
        case CV_8U:
            mean_((const uchar*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_8S:
            mean_((const schar*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_16U:
            mean_((const ushort*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_16S:
            mean_((const short*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_32S:
            mean_((const int*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_32F:
            mean_((const float*)sptr, mptr, total, cn, sum, nz);
            break;
        case CV_64F:
            mean_((const double*)sptr, mptr, total, cn, sum, nz);
            break;
        default:
            CV_Error(CV_StsUnsupportedFormat, "");
        }
    }

    return sum * (1./std::max(nz, 1));
}


void  patchZeros( Mat& mat, double level )
{
    int j, ncols = mat.cols * mat.channels();
    int depth = mat.depth();
    CV_Assert( depth == CV_32F || depth == CV_64F );

    for( int i = 0; i < mat.rows; i++ )
    {
        if( depth == CV_32F )
        {
            float* data = mat.ptr<float>(i);
            for( j = 0; j < ncols; j++ )
                if( fabs(data[j]) < level )
                    data[j] += 1;
        }
        else
        {
            double* data = mat.ptr<double>(i);
            for( j = 0; j < ncols; j++ )
                if( fabs(data[j]) < level )
                    data[j] += 1;
        }
    }
}


static void calcSobelKernel1D( int order, int _aperture_size, int size, vector<int>& kernel )
{
    int i, j, oldval, newval;
    kernel.resize(size + 1);

    if( _aperture_size < 0 )
    {
        static const int scharr[] = { 3, 10, 3, -1, 0, 1 };
        assert( size == 3 );
        for( i = 0; i < size; i++ )
            kernel[i] = scharr[order*3 + i];
        return;
    }

    for( i = 1; i <= size; i++ )
        kernel[i] = 0;
    kernel[0] = 1;

    for( i = 0; i < size - order - 1; i++ )
    {
        oldval = kernel[0];
        for( j = 1; j <= size; j++ )
        {
            newval = kernel[j] + kernel[j-1];
            kernel[j-1] = oldval;
            oldval = newval;
        }
    }

    for( i = 0; i < order; i++ )
    {
        oldval = -kernel[0];
        for( j = 1; j <= size; j++ )
        {
            newval = kernel[j-1] - kernel[j];
            kernel[j-1] = oldval;
            oldval = newval;
        }
    }
}


Mat calcSobelKernel2D( int dx, int dy, int _aperture_size, int origin )
{
    CV_Assert( (_aperture_size == -1 || (_aperture_size >= 1 && _aperture_size % 2 == 1)) &&
              dx >= 0 && dy >= 0 && dx + dy <= 3 );
    Size ksize = _aperture_size == -1 ? Size(3,3) : _aperture_size > 1 ?
        Size(_aperture_size, _aperture_size) : dx > 0 ? Size(3, 1) : Size(1, 3);

    Mat kernel(ksize, CV_32F);
    vector<int> kx, ky;

    calcSobelKernel1D( dx, _aperture_size, ksize.width, kx );
    calcSobelKernel1D( dy, _aperture_size, ksize.height, ky );

    for( int i = 0; i < kernel.rows; i++ )
    {
        float ay = (float)ky[i]*(origin && (dy & 1) ? -1 : 1);
        for( int j = 0; j < kernel.cols; j++ )
            kernel.at<float>(i, j) = kx[j]*ay;
    }

    return kernel;
}


Mat calcLaplaceKernel2D( int aperture_size )
{
    int ksize = aperture_size == 1 ? 3 : aperture_size;
    Mat kernel(ksize, ksize, CV_32F);

    vector<int> kx, ky;

    calcSobelKernel1D( 2, aperture_size, ksize, kx );
    if( aperture_size > 1 )
        calcSobelKernel1D( 0, aperture_size, ksize, ky );
    else
    {
        ky.resize(3);
        ky[0] = ky[2] = 0; ky[1] = 1;
    }

    for( int i = 0; i < ksize; i++ )
        for( int j = 0; j < ksize; j++ )
            kernel.at<float>(i, j) = (float)(kx[j]*ky[i] + kx[i]*ky[j]);

    return kernel;
}


void initUndistortMap( const Mat& _a0, const Mat& _k0, Size sz, Mat& _mapx, Mat& _mapy )
{
    _mapx.create(sz, CV_32F);
    _mapy.create(sz, CV_32F);

    double a[9], k[5]={0,0,0,0,0};
    Mat _a(3, 3, CV_64F, a);
    Mat _k(_k0.rows,_k0.cols, CV_MAKETYPE(CV_64F,_k0.channels()),k);
    double fx, fy, cx, cy, ifx, ify, cxn, cyn;

    _a0.convertTo(_a, CV_64F);
    _k0.convertTo(_k, CV_64F);
    fx = a[0]; fy = a[4]; cx = a[2]; cy = a[5];
    ifx = 1./fx; ify = 1./fy;
    cxn = cx;
    cyn = cy;

    for( int v = 0; v < sz.height; v++ )
    {
        for( int u = 0; u < sz.width; u++ )
        {
            double x = (u - cxn)*ifx;
            double y = (v - cyn)*ify;
            double x2 = x*x, y2 = y*y;
            double r2 = x2 + y2;
            double cdist = 1 + (k[0] + (k[1] + k[4]*r2)*r2)*r2;
            double x1 = x*cdist + k[2]*2*x*y + k[3]*(r2 + 2*x2);
            double y1 = y*cdist + k[3]*2*x*y + k[2]*(r2 + 2*y2);

            _mapy.at<float>(v, u) = (float)(y1*fy + cy);
            _mapx.at<float>(v, u) = (float)(x1*fx + cx);
        }
    }
}


std::ostream& operator << (std::ostream& out, const MatInfo& m)
{
    if( !m.m || m.m->empty() )
        out << "<Empty>";
    else
    {
        static const char* depthstr[] = {"8u", "8s", "16u", "16s", "32s", "32f", "64f", "?"};
        out << depthstr[m.m->depth()] << "C" << m.m->channels() << " " << m.m->dims << "-dim (";
        for( int i = 0; i < m.m->dims; i++ )
            out << m.m->size[i] << (i < m.m->dims-1 ? " x " : ")");
    }
    return out;
}


static Mat getSubArray(const Mat& m, int border, vector<int>& ofs0, vector<int>& ofs)
{
    ofs.resize(ofs0.size());
    if( border < 0 )
    {
        std::copy(ofs0.begin(), ofs0.end(), ofs.begin());
        return m;
    }
    int i, d = m.dims;
    CV_Assert(d == (int)ofs.size());
    vector<Range> r(d);
    for( i = 0; i < d; i++ )
    {
        r[i].start = std::max(0, ofs0[i] - border);
        r[i].end = std::min(ofs0[i] + 1 + border, m.size[i]);
        ofs[i] = std::min(ofs0[i], border);
    }
    return m(&r[0]);
}

template<typename _Tp, typename _WTp> static void
writeElems(std::ostream& out, const void* data, int nelems, int starpos)
{
    for(int i = 0; i < nelems; i++)
    {
        if( i == starpos )
            out << "*";
        out << (_WTp)((_Tp*)data)[i];
        if( i == starpos )
            out << "*";
        out << (i+1 < nelems ? ", " : "");
    }
}


static void writeElems(std::ostream& out, const void* data, int nelems, int depth, int starpos)
{
    if(depth == CV_8U)
        writeElems<uchar, int>(out, data, nelems, starpos);
    else if(depth == CV_8S)
        writeElems<schar, int>(out, data, nelems, starpos);
    else if(depth == CV_16U)
        writeElems<ushort, int>(out, data, nelems, starpos);
    else if(depth == CV_16S)
        writeElems<short, int>(out, data, nelems, starpos);
    else if(depth == CV_32S)
        writeElems<int, int>(out, data, nelems, starpos);
    else if(depth == CV_32F)
    {
        std::streamsize pp = out.precision();
        out.precision(8);
        writeElems<float, float>(out, data, nelems, starpos);
        out.precision(pp);
    }
    else if(depth == CV_64F)
    {
        std::streamsize pp = out.precision();
        out.precision(16);
        writeElems<double, double>(out, data, nelems, starpos);
        out.precision(pp);
    }
    else
        CV_Error(CV_StsUnsupportedFormat, "");
}


struct MatPart
{
    MatPart(const Mat& _m, const vector<int>* _loc)
    : m(&_m), loc(_loc) {}
    const Mat* m;
    const vector<int>* loc;
};

static std::ostream& operator << (std::ostream& out, const MatPart& m)
{
    CV_Assert( !m.loc || ((int)m.loc->size() == m.m->dims && m.m->dims <= 2) );
    if( !m.loc )
        out << *m.m;
    else
    {
        int i, depth = m.m->depth(), cn = m.m->channels(), width = m.m->cols*cn;
        for( i = 0; i < m.m->rows; i++ )
        {
            writeElems(out, m.m->ptr(i), width, depth, i == (*m.loc)[0] ? (*m.loc)[1] : -1);
            out << (i < m.m->rows-1 ? ";\n" : "");
        }
    }
    return out;
}

MatComparator::MatComparator(double _maxdiff, int _context)
    : maxdiff(_maxdiff), context(_context) {}

::testing::AssertionResult
MatComparator::operator()(const char* expr1, const char* expr2,
                          const Mat& m1, const Mat& m2)
{
    if( m1.type() != m2.type() || m1.size != m2.size )
        return ::testing::AssertionFailure()
        << "The reference and the actual output arrays have different type or size:\n"
        << expr1 << " ~ " << MatInfo(m1) << "\n"
        << expr2 << " ~ " << MatInfo(m2) << "\n";

    //bool ok = cvtest::cmpUlps(m1, m2, maxdiff, &realmaxdiff, &loc0);
    int code = cmpEps( m1, m2, &realmaxdiff, maxdiff, &loc0, true);

    if(code >= 0)
        return ::testing::AssertionSuccess();

    Mat m[] = {m1.reshape(1,0), m2.reshape(1,0)};
    int dims = m[0].dims;
    vector<int> loc;
    int border = dims <= 2 ? context : 0;

    Mat m1part, m2part;
    if( border == 0 )
    {
        loc = loc0;
        m1part = Mat(1, 1, m[0].depth(), m[0].ptr(&loc[0]));
        m2part = Mat(1, 1, m[1].depth(), m[1].ptr(&loc[0]));
    }
    else
    {
        m1part = getSubArray(m[0], border, loc0, loc);
        m2part = getSubArray(m[1], border, loc0, loc);
    }

    return ::testing::AssertionFailure()
    << "too big relative difference (" << realmaxdiff << " > "
    << maxdiff << ") between "
    << MatInfo(m1) << " '" << expr1 << "' and '" << expr2 << "' at " << Mat(loc0) << ".\n\n"
    << "'" << expr1 << "': " << MatPart(m1part, border > 0 ? &loc : 0) << ".\n\n"
    << "'" << expr2 << "': " << MatPart(m2part, border > 0 ? &loc : 0) << ".\n";
}

void printVersionInfo(bool useStdOut)
{
    ::testing::Test::RecordProperty("cv_version", CV_VERSION);
    if(useStdOut) std::cout << "OpenCV version: " << CV_VERSION << std::endl;

    std::string buildInfo( cv::getBuildInformation() );

    size_t pos1 = buildInfo.find("Version control");
    size_t pos2 = buildInfo.find('\n', pos1);
    if(pos1 != std::string::npos && pos2 != std::string::npos)
    {
        size_t value_start = buildInfo.rfind(' ', pos2) + 1;
        std::string ver( buildInfo.substr(value_start, pos2 - value_start) );
        ::testing::Test::RecordProperty("cv_vcs_version", ver);
        if (useStdOut) std::cout << "OpenCV VCS version: " << ver << std::endl;
    }

    pos1 = buildInfo.find("inner version");
    pos2 = buildInfo.find('\n', pos1);
    if(pos1 != std::string::npos && pos2 != std::string::npos)
    {
        size_t value_start = buildInfo.rfind(' ', pos2) + 1;
        std::string ver( buildInfo.substr(value_start, pos2 - value_start) );
        ::testing::Test::RecordProperty("cv_inner_vcs_version", ver);
        if(useStdOut) std::cout << "Inner VCS version: " << ver << std::endl;
    }

    const char * build_type =
#ifdef _DEBUG
        "debug";
#else
        "release";
#endif

    ::testing::Test::RecordProperty("cv_build_type", build_type);
    if (useStdOut) std::cout << "Build type: " << build_type << std::endl;

    const char* parallel_framework = currentParallelFramework();

    if (parallel_framework) {
        ::testing::Test::RecordProperty("cv_parallel_framework", parallel_framework);
        if (useStdOut) std::cout << "Parallel framework: " << parallel_framework << std::endl;
    }

    std::string cpu_features;

#if CV_SSE
    if (checkHardwareSupport(CV_CPU_SSE)) cpu_features += " sse";
#endif
#if CV_SSE2
    if (checkHardwareSupport(CV_CPU_SSE2)) cpu_features += " sse2";
#endif
#if CV_SSE3
    if (checkHardwareSupport(CV_CPU_SSE3)) cpu_features += " sse3";
#endif
#if CV_SSSE3
    if (checkHardwareSupport(CV_CPU_SSSE3)) cpu_features += " ssse3";
#endif
#if CV_SSE4_1
    if (checkHardwareSupport(CV_CPU_SSE4_1)) cpu_features += " sse4.1";
#endif
#if CV_SSE4_2
    if (checkHardwareSupport(CV_CPU_SSE4_2)) cpu_features += " sse4.2";
#endif
#if CV_AVX
    if (checkHardwareSupport(CV_CPU_AVX)) cpu_features += " avx";
#endif
#if CV_AVX2
    if (checkHardwareSupport(CV_CPU_AVX2)) cpu_features += " avx2";
#endif
#if CV_NEON
    cpu_features += " neon"; // NEON is currently not checked at runtime
#endif

    cpu_features.erase(0, 1); // erase initial space

    ::testing::Test::RecordProperty("cv_cpu_features", cpu_features);
    if (useStdOut) std::cout << "CPU features: " << cpu_features << std::endl;

#ifdef HAVE_TEGRA_OPTIMIZATION
    const char * tegra_optimization = tegra::isDeviceSupported() ? "enabled" : "disabled";
    ::testing::Test::RecordProperty("cv_tegra_optimization", tegra_optimization);
    if (useStdOut) std::cout << "Tegra optimization: " << tegra_optimization << std::endl;
#endif
}

} //namespace cvtest

void cvTsConvert( const CvMat* src, CvMat* dst )
{
    Mat _src = cvarrToMat(src), _dst = cvarrToMat(dst);
    cvtest::convert(_src, _dst, _dst.depth());
}

void cvTsZero( CvMat* dst, const CvMat* mask )
{
    Mat _dst = cvarrToMat(dst), _mask = mask ? cvarrToMat(mask) : Mat();
    cvtest::set(_dst, Scalar::all(0), _mask);
}
