/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  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.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel 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:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's 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.
//
//   * The name of Intel Corporation may not 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 the Intel Corporation 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.
//
//M*/

#include "_cv.h"

/*
 * This file includes the code, contributed by Simon Perreault
 * (the function icvMedianBlur_8u_CnR_O1)
 *
 * Constant-time median filtering -- http://nomis80.org/ctmf.html
 * Copyright (C) 2006 Simon Perreault
 *
 * Contact:
 *  Laboratoire de vision et systemes numeriques
 *  Pavillon Adrien-Pouliot
 *  Universite Laval
 *  Sainte-Foy, Quebec, Canada
 *  G1K 7P4
 *
 *  perreaul@gel.ulaval.ca
 */

// uncomment the line below to force SSE2 mode
//#define CV_SSE2 1

/****************************************************************************************\
                                         Box Filter
\****************************************************************************************/

static void icvSumRow_8u32s( const uchar* src0, int* dst, void* params );
static void icvSumRow_32f64f( const float* src0, double* dst, void* params );
static void icvSumCol_32s8u( const int** src, uchar* dst, int dst_step,
                             int count, void* params );
static void icvSumCol_32s16s( const int** src, short* dst, int dst_step,
                             int count, void* params );
static void icvSumCol_32s32s( const int** src, int* dst, int dst_step,
                             int count, void* params );
static void icvSumCol_64f32f( const double** src, float* dst, int dst_step,
                              int count, void* params );

CvBoxFilter::CvBoxFilter()
{
    min_depth = CV_32S;
    sum = 0;
    sum_count = 0;
    normalized = false;
}


CvBoxFilter::CvBoxFilter( int _max_width, int _src_type, int _dst_type,
                          bool _normalized, CvSize _ksize,
                          CvPoint _anchor, int _border_mode,
                          CvScalar _border_value )
{
    min_depth = CV_32S;
    sum = 0;
    sum_count = 0;
    normalized = false;
    init( _max_width, _src_type, _dst_type, _normalized,
          _ksize, _anchor, _border_mode, _border_value );
}


CvBoxFilter::~CvBoxFilter()
{
    clear();
}


void CvBoxFilter::init( int _max_width, int _src_type, int _dst_type,
                        bool _normalized, CvSize _ksize,
                        CvPoint _anchor, int _border_mode,
                        CvScalar _border_value )
{
    CV_FUNCNAME( "CvBoxFilter::init" );

    __BEGIN__;
    
    sum = 0;
    normalized = _normalized;

    if( normalized && CV_MAT_TYPE(_src_type) != CV_MAT_TYPE(_dst_type) ||
        !normalized && CV_MAT_CN(_src_type) != CV_MAT_CN(_dst_type))
        CV_ERROR( CV_StsUnmatchedFormats,
        "In case of normalized box filter input and output must have the same type.\n"
        "In case of unnormalized box filter the number of input and output channels must be the same" );

    min_depth = CV_MAT_DEPTH(_src_type) == CV_8U ? CV_32S : CV_64F;

    CvBaseImageFilter::init( _max_width, _src_type, _dst_type, 1, _ksize,
                             _anchor, _border_mode, _border_value );
    
    scale = normalized ? 1./(ksize.width*ksize.height) : 1;

    if( CV_MAT_DEPTH(src_type) == CV_8U )
        x_func = (CvRowFilterFunc)icvSumRow_8u32s;
    else if( CV_MAT_DEPTH(src_type) == CV_32F )
        x_func = (CvRowFilterFunc)icvSumRow_32f64f;
    else
        CV_ERROR( CV_StsUnsupportedFormat, "Unknown/unsupported input image format" );

    if( CV_MAT_DEPTH(dst_type) == CV_8U )
    {
        if( !normalized )
            CV_ERROR( CV_StsBadArg, "Only normalized box filter can be used for 8u->8u transformation" );
        y_func = (CvColumnFilterFunc)icvSumCol_32s8u;
    }
    else if( CV_MAT_DEPTH(dst_type) == CV_16S )
    {
        if( normalized || CV_MAT_DEPTH(src_type) != CV_8U )
            CV_ERROR( CV_StsBadArg, "Only 8u->16s unnormalized box filter is supported in case of 16s output" );
        y_func = (CvColumnFilterFunc)icvSumCol_32s16s;
    }
	else if( CV_MAT_DEPTH(dst_type) == CV_32S )
	{
		if( normalized || CV_MAT_DEPTH(src_type) != CV_8U )
			CV_ERROR( CV_StsBadArg, "Only 8u->32s unnormalized box filter is supported in case of 32s output");

		y_func = (CvColumnFilterFunc)icvSumCol_32s32s;
	}
    else if( CV_MAT_DEPTH(dst_type) == CV_32F )
    {
        if( CV_MAT_DEPTH(src_type) != CV_32F )
            CV_ERROR( CV_StsBadArg, "Only 32f->32f box filter (normalized or not) is supported in case of 32f output" );
        y_func = (CvColumnFilterFunc)icvSumCol_64f32f;
    }
	else{
		CV_ERROR( CV_StsBadArg, "Unknown/unsupported destination image format" );
	}

    __END__;
}


void CvBoxFilter::start_process( CvSlice x_range, int width )
{
    CvBaseImageFilter::start_process( x_range, width );
    int i, psz = CV_ELEM_SIZE(work_type);
    uchar* s;
    buf_end -= buf_step;
    buf_max_count--;
    assert( buf_max_count >= max_ky*2 + 1 );
    s = sum = buf_end + cvAlign((width + ksize.width - 1)*CV_ELEM_SIZE(src_type), ALIGN);
    sum_count = 0;

    width *= psz;
    for( i = 0; i < width; i++ )
        s[i] = (uchar)0;
}


static void
icvSumRow_8u32s( const uchar* src, int* dst, void* params )
{
    const CvBoxFilter* state = (const CvBoxFilter*)params;
    int ksize = state->get_kernel_size().width;
    int width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    int i, k;

    width = (width - 1)*cn; ksize *= cn;

    for( k = 0; k < cn; k++, src++, dst++ )
    {
        int s = 0;
        for( i = 0; i < ksize; i += cn )
            s += src[i];
        dst[0] = s;
        for( i = 0; i < width; i += cn )
        {
            s += src[i+ksize] - src[i];
            dst[i+cn] = s;
        }
    }
}


static void
icvSumRow_32f64f( const float* src, double* dst, void* params )
{
    const CvBoxFilter* state = (const CvBoxFilter*)params;
    int ksize = state->get_kernel_size().width;
    int width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    int i, k;

    width = (width - 1)*cn; ksize *= cn;

    for( k = 0; k < cn; k++, src++, dst++ )
    {
        double s = 0;
        for( i = 0; i < ksize; i += cn )
            s += src[i];
        dst[0] = s;
        for( i = 0; i < width; i += cn )
        {
            s += (double)src[i+ksize] - src[i];
            dst[i+cn] = s;
        }
    }
}


static void
icvSumCol_32s8u( const int** src, uchar* dst,
                 int dst_step, int count, void* params )
{
#define BLUR_SHIFT 24
    CvBoxFilter* state = (CvBoxFilter*)params;
    int ksize = state->get_kernel_size().height;
    int i, width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    double scale = state->get_scale();
    int iscale = cvFloor(scale*(1 << BLUR_SHIFT));
    int* sum = (int*)state->get_sum_buf();
    int* _sum_count = state->get_sum_count_ptr();
    int sum_count = *_sum_count;

    width *= cn;
    src += sum_count;
    count += ksize - 1 - sum_count;

    for( ; count--; src++ )
    {
        const int* sp = src[0];
        if( sum_count+1 < ksize )
        {
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
                sum[i] += sp[i];

            sum_count++;
        }
        else
        {
            const int* sm = src[-ksize+1];
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                int t0 = CV_DESCALE(s0*iscale, BLUR_SHIFT), t1 = CV_DESCALE(s1*iscale, BLUR_SHIFT);
                s0 -= sm[i]; s1 -= sm[i+1];
                sum[i] = s0; sum[i+1] = s1;
                dst[i] = (uchar)t0; dst[i+1] = (uchar)t1;
            }

            for( ; i < width; i++ )
            {
                int s0 = sum[i] + sp[i], t0 = CV_DESCALE(s0*iscale, BLUR_SHIFT);
                sum[i] = s0 - sm[i]; dst[i] = (uchar)t0;
            }
            dst += dst_step;
        }
    }

    *_sum_count = sum_count;
#undef BLUR_SHIFT
}


static void
icvSumCol_32s16s( const int** src, short* dst,
                  int dst_step, int count, void* params )
{
    CvBoxFilter* state = (CvBoxFilter*)params;
    int ksize = state->get_kernel_size().height;
    int ktotal = ksize*state->get_kernel_size().width;
    int i, width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    int* sum = (int*)state->get_sum_buf();
    int* _sum_count = state->get_sum_count_ptr();
    int sum_count = *_sum_count;

    dst_step /= sizeof(dst[0]);
    width *= cn;
    src += sum_count;
    count += ksize - 1 - sum_count;

    for( ; count--; src++ )
    {
        const int* sp = src[0];
        if( sum_count+1 < ksize )
        {
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
                sum[i] += sp[i];

            sum_count++;
        }
        else if( ktotal < 128 )
        {
            const int* sm = src[-ksize+1];
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                dst[i] = (short)s0; dst[i+1] = (short)s1;
                s0 -= sm[i]; s1 -= sm[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
            {
                int s0 = sum[i] + sp[i];
                dst[i] = (short)s0;
                sum[i] = s0 - sm[i];
            }
            dst += dst_step;
        }
        else
        {
            const int* sm = src[-ksize+1];
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                dst[i] = CV_CAST_16S(s0); dst[i+1] = CV_CAST_16S(s1);
                s0 -= sm[i]; s1 -= sm[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
            {
                int s0 = sum[i] + sp[i];
                dst[i] = CV_CAST_16S(s0);
                sum[i] = s0 - sm[i];
            }
            dst += dst_step;
        }
    }

    *_sum_count = sum_count;
}

static void
icvSumCol_32s32s( const int** src, int * dst,
                  int dst_step, int count, void* params )
{
    CvBoxFilter* state = (CvBoxFilter*)params;
    int ksize = state->get_kernel_size().height;
    int i, width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    int* sum = (int*)state->get_sum_buf();
    int* _sum_count = state->get_sum_count_ptr();
    int sum_count = *_sum_count;

    dst_step /= sizeof(dst[0]);
    width *= cn;
    src += sum_count;
    count += ksize - 1 - sum_count;

    for( ; count--; src++ )
    {
        const int* sp = src[0];
        if( sum_count+1 < ksize )
        {
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
                sum[i] += sp[i];

            sum_count++;
        }
        else
        {
            const int* sm = src[-ksize+1];
            for( i = 0; i <= width - 2; i += 2 )
            {
                int s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                dst[i] = s0; dst[i+1] = s1;
                s0 -= sm[i]; s1 -= sm[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
            {
                int s0 = sum[i] + sp[i];
                dst[i] = s0;
                sum[i] = s0 - sm[i];
            }
            dst += dst_step;
        }
    }

    *_sum_count = sum_count;
}


static void
icvSumCol_64f32f( const double** src, float* dst,
                  int dst_step, int count, void* params )
{
    CvBoxFilter* state = (CvBoxFilter*)params;
    int ksize = state->get_kernel_size().height;
    int i, width = state->get_width();
    int cn = CV_MAT_CN(state->get_src_type());
    double scale = state->get_scale();
    bool normalized = state->is_normalized();
    double* sum = (double*)state->get_sum_buf();
    int* _sum_count = state->get_sum_count_ptr();
    int sum_count = *_sum_count;

    dst_step /= sizeof(dst[0]);
    width *= cn;
    src += sum_count;
    count += ksize - 1 - sum_count;

    for( ; count--; src++ )
    {
        const double* sp = src[0];
        if( sum_count+1 < ksize )
        {
            for( i = 0; i <= width - 2; i += 2 )
            {
                double s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                sum[i] = s0; sum[i+1] = s1;
            }

            for( ; i < width; i++ )
                sum[i] += sp[i];

            sum_count++;
        }
        else
        {
            const double* sm = src[-ksize+1];
            if( normalized )
                for( i = 0; i <= width - 2; i += 2 )
                {
                    double s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                    double t0 = s0*scale, t1 = s1*scale;
                    s0 -= sm[i]; s1 -= sm[i+1];
                    dst[i] = (float)t0; dst[i+1] = (float)t1;
                    sum[i] = s0; sum[i+1] = s1;
                }
            else
                for( i = 0; i <= width - 2; i += 2 )
                {
                    double s0 = sum[i] + sp[i], s1 = sum[i+1] + sp[i+1];
                    dst[i] = (float)s0; dst[i+1] = (float)s1;
                    s0 -= sm[i]; s1 -= sm[i+1];
                    sum[i] = s0; sum[i+1] = s1;
                }

            for( ; i < width; i++ )
            {
                double s0 = sum[i] + sp[i], t0 = s0*scale;
                sum[i] = s0 - sm[i]; dst[i] = (float)t0;
            }
            dst += dst_step;
        }
    }

    *_sum_count = sum_count;
}


/****************************************************************************************\
                                      Median Filter
\****************************************************************************************/

#define CV_MINMAX_8U(a,b) \
    (t = CV_FAST_CAST_8U((a) - (b)), (b) += t, a -= t)

#if CV_SSE2 && !defined __SSE2__
#define __SSE2__ 1
#include "emmintrin.h"
#endif

#if defined(__GNUC__)
#define align(x) __attribute__ ((aligned (x)))
#elif CV_SSE2 && (defined(__ICL) || (_MSC_VER >= 1300))
#define align(x) __declspec(align(x))
#else
#define align(x)
#endif

#if _MSC_VER >= 1200
#pragma warning( disable: 4244 )
#endif

/**
 * This structure represents a two-tier histogram. The first tier (known as the
 * "coarse" level) is 4 bit wide and the second tier (known as the "fine" level)
 * is 8 bit wide. Pixels inserted in the fine level also get inserted into the
 * coarse bucket designated by the 4 MSBs of the fine bucket value.
 *
 * The structure is aligned on 16 bits, which is a prerequisite for SIMD
 * instructions. Each bucket is 16 bit wide, which means that extra care must be
 * taken to prevent overflow.
 */
typedef struct align(16)
{
    ushort coarse[16];
    ushort fine[16][16];
} Histogram;

/**
 * HOP is short for Histogram OPeration. This macro makes an operation \a op on
 * histogram \a h for pixel value \a x. It takes care of handling both levels.
 */
#define HOP(h,x,op) \
    h.coarse[x>>4] op; \
    *((ushort*) h.fine + x) op;

#define COP(c,j,x,op) \
    h_coarse[ 16*(n*c+j) + (x>>4) ] op; \
    h_fine[ 16 * (n*(16*c+(x>>4)) + j) + (x & 0xF) ] op;

#if defined __SSE2__ || defined __MMX__ || defined __ALTIVEC__
#define MEDIAN_HAVE_SIMD 1
#else
#define MEDIAN_HAVE_SIMD 0
#endif

/**
 * Adds histograms \a x and \a y and stores the result in \a y. Makes use of
 * SSE2, MMX or Altivec, if available.
 */
#if defined(__SSE2__)
static inline void histogram_add( const ushort x[16], ushort y[16] )
{
    _mm_store_si128( (__m128i*) &y[0], _mm_add_epi16(
        _mm_load_si128((__m128i*) &y[0]), _mm_load_si128((__m128i*) &x[0] )));
    _mm_store_si128( (__m128i*) &y[8], _mm_add_epi16(
        _mm_load_si128((__m128i*) &y[8]), _mm_load_si128((__m128i*) &x[8] )));
}
#elif defined(__MMX__)
static inline void histogram_add( const ushort x[16], ushort y[16] )
{
    *(__m64*) &y[0]  = _mm_add_pi16( *(__m64*) &y[0],  *(__m64*) &x[0]  );
    *(__m64*) &y[4]  = _mm_add_pi16( *(__m64*) &y[4],  *(__m64*) &x[4]  );
    *(__m64*) &y[8]  = _mm_add_pi16( *(__m64*) &y[8],  *(__m64*) &x[8]  );
    *(__m64*) &y[12] = _mm_add_pi16( *(__m64*) &y[12], *(__m64*) &x[12] );
}
#elif defined(__ALTIVEC__)
static inline void histogram_add( const ushort x[16], ushort y[16] )
{
    *(vector ushort*) &y[0] = vec_add( *(vector ushort*) &y[0], *(vector ushort*) &x[0] );
    *(vector ushort*) &y[8] = vec_add( *(vector ushort*) &y[8], *(vector ushort*) &x[8] );
}
#else
static inline void histogram_add( const ushort x[16], ushort y[16] )
{
    int i;
    for( i = 0; i < 16; ++i )
        y[i] = (ushort)(y[i] + x[i]);
}
#endif

/**
 * Subtracts histogram \a x from \a y and stores the result in \a y. Makes use
 * of SSE2, MMX or Altivec, if available.
 */
#if defined(__SSE2__)
static inline void histogram_sub( const ushort x[16], ushort y[16] )
{
    _mm_store_si128( (__m128i*) &y[0], _mm_sub_epi16(
        _mm_load_si128((__m128i*) &y[0]), _mm_load_si128((__m128i*) &x[0] )));
    _mm_store_si128( (__m128i*) &y[8], _mm_sub_epi16(
        _mm_load_si128((__m128i*) &y[8]), _mm_load_si128((__m128i*) &x[8] )));
}
#elif defined(__MMX__)
static inline void histogram_sub( const ushort x[16], ushort y[16] )
{
    *(__m64*) &y[0]  = _mm_sub_pi16( *(__m64*) &y[0],  *(__m64*) &x[0]  );
    *(__m64*) &y[4]  = _mm_sub_pi16( *(__m64*) &y[4],  *(__m64*) &x[4]  );
    *(__m64*) &y[8]  = _mm_sub_pi16( *(__m64*) &y[8],  *(__m64*) &x[8]  );
    *(__m64*) &y[12] = _mm_sub_pi16( *(__m64*) &y[12], *(__m64*) &x[12] );
}
#elif defined(__ALTIVEC__)
static inline void histogram_sub( const ushort x[16], ushort y[16] )
{
    *(vector ushort*) &y[0] = vec_sub( *(vector ushort*) &y[0], *(vector ushort*) &x[0] );
    *(vector ushort*) &y[8] = vec_sub( *(vector ushort*) &y[8], *(vector ushort*) &x[8] );
}
#else
static inline void histogram_sub( const ushort x[16], ushort y[16] )
{
    int i;
    for( i = 0; i < 16; ++i )
        y[i] = (ushort)(y[i] - x[i]);
}
#endif

static inline void histogram_muladd( int a, const ushort x[16],
        ushort y[16] )
{
    int i;
    for ( i = 0; i < 16; ++i )
        y[i] = (ushort)(y[i] + a * x[i]);
}

static CvStatus CV_STDCALL
icvMedianBlur_8u_CnR_O1( uchar* src, int src_step, uchar* dst, int dst_step,
                         CvSize size, int kernel_size, int cn, int pad_left, int pad_right )
{
    int r = (kernel_size-1)/2;
    const int m = size.height, n = size.width;
    int i, j, k, c;
    const unsigned char *p, *q;
    Histogram H[4];
    ushort *h_coarse, *h_fine, luc[4][16];

    if( size.height < r || size.width < r )
        return CV_BADSIZE_ERR;

    assert( src );
    assert( dst );
    assert( r >= 0 );
    assert( size.width >= 2*r+1 );
    assert( size.height >= 2*r+1 );
    assert( src_step != 0 );
    assert( dst_step != 0 );

    h_coarse = (ushort*) cvAlloc(  1 * 16 * n * cn * sizeof(ushort) );
    h_fine   = (ushort*) cvAlloc( 16 * 16 * n * cn * sizeof(ushort) );
    memset( h_coarse, 0,  1 * 16 * n * cn * sizeof(ushort) );
    memset( h_fine,   0, 16 * 16 * n * cn * sizeof(ushort) );

    /* First row initialization */
    for ( j = 0; j < n; ++j ) {
        for ( c = 0; c < cn; ++c ) {
            COP( c, j, src[cn*j+c], += r+1 );
        }
    }
    for ( i = 0; i < r; ++i ) {
        for ( j = 0; j < n; ++j ) {
            for ( c = 0; c < cn; ++c ) {
                COP( c, j, src[src_step*i+cn*j+c], ++ );
            }
        }
    }

    for ( i = 0; i < m; ++i ) {

        /* Update column histograms for entire row. */
        p = src + src_step * MAX( 0, i-r-1 );
        q = p + cn * n;
        for ( j = 0; p != q; ++j ) {
            for ( c = 0; c < cn; ++c, ++p ) {
                COP( c, j, *p, -- );
            }
        }

        p = src + src_step * MIN( m-1, i+r );
        q = p + cn * n;
        for ( j = 0; p != q; ++j ) {
            for ( c = 0; c < cn; ++c, ++p ) {
                COP( c, j, *p, ++ );
            }
        }

        /* First column initialization */
        memset( H, 0, cn*sizeof(H[0]) );
        memset( luc, 0, cn*sizeof(luc[0]) );
        if ( pad_left ) {
            for ( c = 0; c < cn; ++c ) {
                histogram_muladd( r, &h_coarse[16*n*c], H[c].coarse );
            }
        }
        for ( j = 0; j < (pad_left ? r : 2*r); ++j ) {
            for ( c = 0; c < cn; ++c ) {
                histogram_add( &h_coarse[16*(n*c+j)], H[c].coarse );
            }
        }
        for ( c = 0; c < cn; ++c ) {
            for ( k = 0; k < 16; ++k ) {
                histogram_muladd( 2*r+1, &h_fine[16*n*(16*c+k)], &H[c].fine[k][0] );
            }
        }

        for ( j = pad_left ? 0 : r; j < (pad_right ? n : n-r); ++j ) {
            for ( c = 0; c < cn; ++c ) {
                int t = 2*r*r + 2*r, b, sum = 0;
                ushort* segment;

                histogram_add( &h_coarse[16*(n*c + MIN(j+r,n-1))], H[c].coarse );

                /* Find median at coarse level */
                for ( k = 0; k < 16 ; ++k ) {
                    sum += H[c].coarse[k];
                    if ( sum > t ) {
                        sum -= H[c].coarse[k];
                        break;
                    }
                }
                assert( k < 16 );

                /* Update corresponding histogram segment */
                if ( luc[c][k] <= j-r ) {
                    memset( &H[c].fine[k], 0, 16 * sizeof(ushort) );
                    for ( luc[c][k] = j-r; luc[c][k] < MIN(j+r+1,n); ++luc[c][k] ) {
                        histogram_add( &h_fine[16*(n*(16*c+k)+luc[c][k])], H[c].fine[k] );
                    }
                    if ( luc[c][k] < j+r+1 ) {
                        histogram_muladd( j+r+1 - n, &h_fine[16*(n*(16*c+k)+(n-1))], &H[c].fine[k][0] );
                        luc[c][k] = (ushort)(j+r+1);
                    }
                }
                else {
                    for ( ; luc[c][k] < j+r+1; ++luc[c][k] ) {
                        histogram_sub( &h_fine[16*(n*(16*c+k)+MAX(luc[c][k]-2*r-1,0))], H[c].fine[k] );
                        histogram_add( &h_fine[16*(n*(16*c+k)+MIN(luc[c][k],n-1))], H[c].fine[k] );
                    }
                }

                histogram_sub( &h_coarse[16*(n*c+MAX(j-r,0))], H[c].coarse );

                /* Find median in segment */
                segment = H[c].fine[k];
                for ( b = 0; b < 16 ; ++b ) {
                    sum += segment[b];
                    if ( sum > t ) {
                        dst[dst_step*i+cn*j+c] = (uchar)(16*k + b);
                        break;
                    }
                }
                assert( b < 16 );
            }
        }
    }

#if defined(__MMX__)
    _mm_empty();
#endif

    cvFree(&h_coarse);
    cvFree(&h_fine);

#undef HOP
#undef COP
    return CV_OK;
}


#if _MSC_VER >= 1200
#pragma warning( default: 4244 )
#endif


static CvStatus CV_STDCALL
icvMedianBlur_8u_CnR_Om( uchar* src, int src_step, uchar* dst, int dst_step,
                         CvSize size, int m, int cn )
{
    #define N  16
    int     zone0[4][N];
    int     zone1[4][N*N];
    int     x, y;
    int     n2 = m*m/2;
    int     nx = (m + 1)/2 - 1;
    uchar*  src_max = src + size.height*src_step;
    uchar*  src_right = src + size.width*cn;

    #define UPDATE_ACC01( pix, cn, op ) \
    {                                   \
        int p = (pix);                  \
        zone1[cn][p] op;                \
        zone0[cn][p >> 4] op;           \
    }

    if( size.height < nx || size.width < nx )
        return CV_BADSIZE_ERR;

    if( m == 3 )
    {
        size.width *= cn;

        for( y = 0; y < size.height; y++, dst += dst_step )
        {
            const uchar* src0 = src + src_step*(y-1);
            const uchar* src1 = src0 + src_step;
            const uchar* src2 = src1 + src_step;
            if( y == 0 )
                src0 = src1;
            else if( y == size.height - 1 )
                src2 = src1;

            for( x = 0; x < 2*cn; x++ )
            {
                int x0 = x < cn ? x : size.width - 3*cn + x;
                int x2 = x < cn ? x + cn : size.width - 2*cn + x;
                int x1 = x < cn ? x0 : x2, t;

                int p0 = src0[x0], p1 = src0[x1], p2 = src0[x2];
                int p3 = src1[x0], p4 = src1[x1], p5 = src1[x2];
                int p6 = src2[x0], p7 = src2[x1], p8 = src2[x2];

                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p1);
                CV_MINMAX_8U(p3, p4); CV_MINMAX_8U(p6, p7);
                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p3);
                CV_MINMAX_8U(p5, p8); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p3, p6); CV_MINMAX_8U(p1, p4);
                CV_MINMAX_8U(p2, p5); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p4, p2); CV_MINMAX_8U(p6, p4);
                CV_MINMAX_8U(p4, p2);
                dst[x1] = (uchar)p4;
            }

            for( x = cn; x < size.width - cn; x++ )
            {
                int p0 = src0[x-cn], p1 = src0[x], p2 = src0[x+cn];
                int p3 = src1[x-cn], p4 = src1[x], p5 = src1[x+cn];
                int p6 = src2[x-cn], p7 = src2[x], p8 = src2[x+cn];
                int t;

                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p1);
                CV_MINMAX_8U(p3, p4); CV_MINMAX_8U(p6, p7);
                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p3);
                CV_MINMAX_8U(p5, p8); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p3, p6); CV_MINMAX_8U(p1, p4);
                CV_MINMAX_8U(p2, p5); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p4, p2); CV_MINMAX_8U(p6, p4);
                CV_MINMAX_8U(p4, p2);

                dst[x] = (uchar)p4;
            }
        }

        return CV_OK;
    }

    for( x = 0; x < size.width; x++, dst += cn )
    {
        uchar* dst_cur = dst;
        uchar* src_top = src;
        uchar* src_bottom = src;
        int    k, c;
        int    x0 = -1;
        int    src_step1 = src_step, dst_step1 = dst_step;

        if( x % 2 != 0 )
        {
            src_bottom = src_top += src_step*(size.height-1);
            dst_cur += dst_step*(size.height-1);
            src_step1 = -src_step1;
            dst_step1 = -dst_step1;
        }

        if( x <= m/2 )
            nx++;

        if( nx < m )
            x0 = x < m/2 ? 0 : (nx-1)*cn;

        // init accumulator
        memset( zone0, 0, sizeof(zone0[0])*cn );
        memset( zone1, 0, sizeof(zone1[0])*cn );

        for( y = 0; y <= m/2; y++ )
        {
            for( c = 0; c < cn; c++ )
            {
                if( y > 0 )
                {
                    if( x0 >= 0 )
                        UPDATE_ACC01( src_bottom[x0+c], c, += (m - nx) );
                    for( k = 0; k < nx*cn; k += cn )
                        UPDATE_ACC01( src_bottom[k+c], c, ++ );
                }
                else
                {
                    if( x0 >= 0 )
                        UPDATE_ACC01( src_bottom[x0+c], c, += (m - nx)*(m/2+1) );
                    for( k = 0; k < nx*cn; k += cn )
                        UPDATE_ACC01( src_bottom[k+c], c, += m/2+1 );
                }
            }

            if( src_step1 > 0 && y < size.height-1 ||
                src_step1 < 0 && size.height-y-1 > 0 )
                src_bottom += src_step1;
        }

        for( y = 0; y < size.height; y++, dst_cur += dst_step1 )
        {
            // find median
            for( c = 0; c < cn; c++ )
            {
                int s = 0;
                for( k = 0; ; k++ )
                {
                    int t = s + zone0[c][k];
                    if( t > n2 ) break;
                    s = t;
                }

                for( k *= N; ;k++ )
                {
                    s += zone1[c][k];
                    if( s > n2 ) break;
                }

                dst_cur[c] = (uchar)k;
            }

            if( y+1 == size.height )
                break;

            if( cn == 1 )
            {
                for( k = 0; k < nx; k++ )
                {
                    int p = src_top[k];
                    int q = src_bottom[k];
                    zone1[0][p]--;
                    zone0[0][p>>4]--;
                    zone1[0][q]++;
                    zone0[0][q>>4]++;
                }
            }
            else if( cn == 3 )
            {
                for( k = 0; k < nx*3; k += 3 )
                {
                    UPDATE_ACC01( src_top[k], 0, -- );
                    UPDATE_ACC01( src_top[k+1], 1, -- );
                    UPDATE_ACC01( src_top[k+2], 2, -- );

                    UPDATE_ACC01( src_bottom[k], 0, ++ );
                    UPDATE_ACC01( src_bottom[k+1], 1, ++ );
                    UPDATE_ACC01( src_bottom[k+2], 2, ++ );
                }
            }
            else
            {
                assert( cn == 4 );
                for( k = 0; k < nx*4; k += 4 )
                {
                    UPDATE_ACC01( src_top[k], 0, -- );
                    UPDATE_ACC01( src_top[k+1], 1, -- );
                    UPDATE_ACC01( src_top[k+2], 2, -- );
                    UPDATE_ACC01( src_top[k+3], 3, -- );

                    UPDATE_ACC01( src_bottom[k], 0, ++ );
                    UPDATE_ACC01( src_bottom[k+1], 1, ++ );
                    UPDATE_ACC01( src_bottom[k+2], 2, ++ );
                    UPDATE_ACC01( src_bottom[k+3], 3, ++ );
                }
            }

            if( x0 >= 0 )
            {
                for( c = 0; c < cn; c++ )
                {
                    UPDATE_ACC01( src_top[x0+c], c, -= (m - nx) );
                    UPDATE_ACC01( src_bottom[x0+c], c, += (m - nx) );
                }
            }

            if( src_step1 > 0 && src_bottom + src_step1 < src_max ||
                src_step1 < 0 && src_bottom + src_step1 >= src )
                src_bottom += src_step1;

            if( y >= m/2 )
                src_top += src_step1;
        }

        if( x >= m/2 )
            src += cn;
        if( src + nx*cn > src_right ) nx--;
    }
#undef N
#undef UPDATE_ACC
    return CV_OK;
}


/****************************************************************************************\
                                   Bilateral Filtering
\****************************************************************************************/

static CvStatus CV_STDCALL
icvBilateralFiltering_8u_CnR( uchar* src, int srcStep,
                              uchar* dst, int dstStep,
                              CvSize size, double sigma_color,
                              double sigma_space, int channels )
{
    double i2sigma_color = 1./(sigma_color*sigma_color);
    double i2sigma_space = 1./(sigma_space*sigma_space);

    double mean1[3];
    double mean0;
    double w;
    int deltas[8];
    double weight_tab[8];

    int i, j;

#define INIT_C1\
            color = src[0]; \
            mean0 = 1; mean1[0] = color;

#define COLOR_DISTANCE_C1(c1, c2)\
            (c1 - c2)*(c1 - c2)
#define KERNEL_ELEMENT_C1(k)\
            temp_color = src[deltas[k]];\
            w = weight_tab[k] + COLOR_DISTANCE_C1(color, temp_color)*i2sigma_color;\
            w = 1./(w*w + 1); \
            mean0 += w;\
            mean1[0] += temp_color*w;

#define INIT_C3\
            mean0 = 1; mean1[0] = src[0];mean1[1] = src[1];mean1[2] = src[2];

#define UPDATE_OUTPUT_C1                   \
            dst[i] = (uchar)cvRound(mean1[0]/mean0);

#define COLOR_DISTANCE_C3(c1, c2)\
            ((c1[0] - c2[0])*(c1[0] - c2[0]) + \
             (c1[1] - c2[1])*(c1[1] - c2[1]) + \
             (c1[2] - c2[2])*(c1[2] - c2[2]))
#define KERNEL_ELEMENT_C3(k)\
            temp_color = src + deltas[k];\
            w = weight_tab[k] + COLOR_DISTANCE_C3(src, temp_color)*i2sigma_color;\
            w = 1./(w*w + 1); \
            mean0 += w;\
            mean1[0] += temp_color[0]*w; \
            mean1[1] += temp_color[1]*w; \
            mean1[2] += temp_color[2]*w;

#define UPDATE_OUTPUT_C3\
            mean0 = 1./mean0;\
            dst[i*3 + 0] = (uchar)cvRound(mean1[0]*mean0); \
            dst[i*3 + 1] = (uchar)cvRound(mean1[1]*mean0); \
            dst[i*3 + 2] = (uchar)cvRound(mean1[2]*mean0);

    CV_INIT_3X3_DELTAS( deltas, srcStep, channels );

    weight_tab[0] = weight_tab[2] = weight_tab[4] = weight_tab[6] = i2sigma_space;
    weight_tab[1] = weight_tab[3] = weight_tab[5] = weight_tab[7] = i2sigma_space*2;

    if( channels == 1 )
    {
        int color, temp_color;

        for( i = 0; i < size.width; i++, src++ )
        {
            INIT_C1;
            KERNEL_ELEMENT_C1(6);
            if( i > 0 )
            {
                KERNEL_ELEMENT_C1(5);
                KERNEL_ELEMENT_C1(4);
            }
            if( i < size.width - 1 )
            {
                KERNEL_ELEMENT_C1(7);
                KERNEL_ELEMENT_C1(0);
            }
            UPDATE_OUTPUT_C1;
        }

        src += srcStep - size.width;
        dst += dstStep;

        for( j = 1; j < size.height - 1; j++, dst += dstStep )
        {
            i = 0;
            INIT_C1;
            KERNEL_ELEMENT_C1(0);
            KERNEL_ELEMENT_C1(1);
            KERNEL_ELEMENT_C1(2);
            KERNEL_ELEMENT_C1(6);
            KERNEL_ELEMENT_C1(7);
            UPDATE_OUTPUT_C1;

            for( i = 1, src++; i < size.width - 1; i++, src++ )
            {
                INIT_C1;
                KERNEL_ELEMENT_C1(0);
                KERNEL_ELEMENT_C1(1);
                KERNEL_ELEMENT_C1(2);
                KERNEL_ELEMENT_C1(3);
                KERNEL_ELEMENT_C1(4);
                KERNEL_ELEMENT_C1(5);
                KERNEL_ELEMENT_C1(6);
                KERNEL_ELEMENT_C1(7);
                UPDATE_OUTPUT_C1;
            }

            INIT_C1;
            KERNEL_ELEMENT_C1(2);
            KERNEL_ELEMENT_C1(3);
            KERNEL_ELEMENT_C1(4);
            KERNEL_ELEMENT_C1(5);
            KERNEL_ELEMENT_C1(6);
            UPDATE_OUTPUT_C1;

            src += srcStep + 1 - size.width;
        }

        for( i = 0; i < size.width; i++, src++ )
        {
            INIT_C1;
            KERNEL_ELEMENT_C1(2);
            if( i > 0 )
            {
                KERNEL_ELEMENT_C1(3);
                KERNEL_ELEMENT_C1(4);
            }
            if( i < size.width - 1 )
            {
                KERNEL_ELEMENT_C1(1);
                KERNEL_ELEMENT_C1(0);
            }
            UPDATE_OUTPUT_C1;
        }
    }
    else
    {
        uchar* temp_color;

        if( channels != 3 )
            return CV_UNSUPPORTED_CHANNELS_ERR;

        for( i = 0; i < size.width; i++, src += 3 )
        {
            INIT_C3;
            KERNEL_ELEMENT_C3(6);
            if( i > 0 )
            {
                KERNEL_ELEMENT_C3(5);
                KERNEL_ELEMENT_C3(4);
            }
            if( i < size.width - 1 )
            {
                KERNEL_ELEMENT_C3(7);
                KERNEL_ELEMENT_C3(0);
            }
            UPDATE_OUTPUT_C3;
        }

        src += srcStep - size.width*3;
        dst += dstStep;

        for( j = 1; j < size.height - 1; j++, dst += dstStep )
        {
            i = 0;
            INIT_C3;
            KERNEL_ELEMENT_C3(0);
            KERNEL_ELEMENT_C3(1);
            KERNEL_ELEMENT_C3(2);
            KERNEL_ELEMENT_C3(6);
            KERNEL_ELEMENT_C3(7);
            UPDATE_OUTPUT_C3;

            for( i = 1, src += 3; i < size.width - 1; i++, src += 3 )
            {
                INIT_C3;
                KERNEL_ELEMENT_C3(0);
                KERNEL_ELEMENT_C3(1);
                KERNEL_ELEMENT_C3(2);
                KERNEL_ELEMENT_C3(3);
                KERNEL_ELEMENT_C3(4);
                KERNEL_ELEMENT_C3(5);
                KERNEL_ELEMENT_C3(6);
                KERNEL_ELEMENT_C3(7);
                UPDATE_OUTPUT_C3;
            }

            INIT_C3;
            KERNEL_ELEMENT_C3(2);
            KERNEL_ELEMENT_C3(3);
            KERNEL_ELEMENT_C3(4);
            KERNEL_ELEMENT_C3(5);
            KERNEL_ELEMENT_C3(6);
            UPDATE_OUTPUT_C3;

            src += srcStep + 3 - size.width*3;
        }

        for( i = 0; i < size.width; i++, src += 3 )
        {
            INIT_C3;
            KERNEL_ELEMENT_C3(2);
            if( i > 0 )
            {
                KERNEL_ELEMENT_C3(3);
                KERNEL_ELEMENT_C3(4);
            }
            if( i < size.width - 1 )
            {
                KERNEL_ELEMENT_C3(1);
                KERNEL_ELEMENT_C3(0);
            }
            UPDATE_OUTPUT_C3;
        }
    }

    return CV_OK;
#undef INIT_C1
#undef KERNEL_ELEMENT_C1
#undef UPDATE_OUTPUT_C1
#undef INIT_C3
#undef KERNEL_ELEMENT_C3
#undef UPDATE_OUTPUT_C3
#undef COLOR_DISTANCE_C3
}

//////////////////////////////// IPP smoothing functions /////////////////////////////////

icvFilterMedian_8u_C1R_t icvFilterMedian_8u_C1R_p = 0;
icvFilterMedian_8u_C3R_t icvFilterMedian_8u_C3R_p = 0;
icvFilterMedian_8u_C4R_t icvFilterMedian_8u_C4R_p = 0;

icvFilterBox_8u_C1R_t icvFilterBox_8u_C1R_p = 0;
icvFilterBox_8u_C3R_t icvFilterBox_8u_C3R_p = 0;
icvFilterBox_8u_C4R_t icvFilterBox_8u_C4R_p = 0;
icvFilterBox_32f_C1R_t icvFilterBox_32f_C1R_p = 0;
icvFilterBox_32f_C3R_t icvFilterBox_32f_C3R_p = 0;
icvFilterBox_32f_C4R_t icvFilterBox_32f_C4R_p = 0;

typedef CvStatus (CV_STDCALL * CvSmoothFixedIPPFunc)
( const void* src, int srcstep, void* dst, int dststep,
  CvSize size, CvSize ksize, CvPoint anchor );

//////////////////////////////////////////////////////////////////////////////////////////

CV_IMPL void
cvSmooth( const void* srcarr, void* dstarr, int smooth_type,
          int param1, int param2, double param3, double param4 )
{
    CvBoxFilter box_filter;
    CvSepFilter gaussian_filter;

    CvMat* temp = 0;

    CV_FUNCNAME( "cvSmooth" );

    __BEGIN__;

    int coi1 = 0, coi2 = 0;
    CvMat srcstub, *src = (CvMat*)srcarr;
    CvMat dststub, *dst = (CvMat*)dstarr;
    CvSize size;
    int src_type, dst_type, depth, cn;
    double sigma1 = 0, sigma2 = 0;
    bool have_ipp = icvFilterMedian_8u_C1R_p != 0;

    CV_CALL( src = cvGetMat( src, &srcstub, &coi1 ));
    CV_CALL( dst = cvGetMat( dst, &dststub, &coi2 ));

    if( coi1 != 0 || coi2 != 0 )
        CV_ERROR( CV_BadCOI, "" );

    src_type = CV_MAT_TYPE( src->type );
    dst_type = CV_MAT_TYPE( dst->type );
    depth = CV_MAT_DEPTH(src_type);
    cn = CV_MAT_CN(src_type);
    size = cvGetMatSize(src);

    if( !CV_ARE_SIZES_EQ( src, dst ))
        CV_ERROR( CV_StsUnmatchedSizes, "" );

    if( smooth_type != CV_BLUR_NO_SCALE && !CV_ARE_TYPES_EQ( src, dst ))
        CV_ERROR( CV_StsUnmatchedFormats,
        "The specified smoothing algorithm requires input and ouput arrays be of the same type" );

    if( smooth_type == CV_BLUR || smooth_type == CV_BLUR_NO_SCALE ||
        smooth_type == CV_GAUSSIAN || smooth_type == CV_MEDIAN )
    {
        // automatic detection of kernel size from sigma
        if( smooth_type == CV_GAUSSIAN )
        {
            sigma1 = param3;
            sigma2 = param4 ? param4 : param3;

            if( param1 == 0 && sigma1 > 0 )
                param1 = cvRound(sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
            if( param2 == 0 && sigma2 > 0 )
                param2 = cvRound(sigma2*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
        }

        if( param2 == 0 )
            param2 = size.height == 1 ? 1 : param1;
        if( param1 < 1 || (param1 & 1) == 0 || param2 < 1 || (param2 & 1) == 0 )
            CV_ERROR( CV_StsOutOfRange,
                "Both mask width and height must be >=1 and odd" );

        if( param1 == 1 && param2 == 1 )
        {
            cvConvert( src, dst );
            EXIT;
        }
    }

    if( have_ipp && (smooth_type == CV_BLUR || (smooth_type == CV_MEDIAN && param1 <= 15)) &&
        size.width >= param1 && size.height >= param2 && param1 > 1 && param2 > 1 )
    {
        CvSmoothFixedIPPFunc ipp_median_box_func = 0;

        if( smooth_type == CV_BLUR )
        {
            ipp_median_box_func =
                src_type == CV_8UC1 ? icvFilterBox_8u_C1R_p :
                src_type == CV_8UC3 ? icvFilterBox_8u_C3R_p :
                src_type == CV_8UC4 ? icvFilterBox_8u_C4R_p :
                src_type == CV_32FC1 ? icvFilterBox_32f_C1R_p :
                src_type == CV_32FC3 ? icvFilterBox_32f_C3R_p :
                src_type == CV_32FC4 ? icvFilterBox_32f_C4R_p : 0;
        }
        else if( smooth_type == CV_MEDIAN )
        {
            ipp_median_box_func =
                src_type == CV_8UC1 ? icvFilterMedian_8u_C1R_p :
                src_type == CV_8UC3 ? icvFilterMedian_8u_C3R_p :
                src_type == CV_8UC4 ? icvFilterMedian_8u_C4R_p : 0;
        }

        if( ipp_median_box_func )
        {
            CvSize el_size = { param1, param2 };
            CvPoint el_anchor = { param1/2, param2/2 };
            int stripe_size = 1 << 14; // the optimal value may depend on CPU cache,
                                       // overhead of the current IPP code etc.
            const uchar* shifted_ptr;
            int y, dy = 0;
            int temp_step, dst_step = dst->step;

            CV_CALL( temp = icvIPPFilterInit( src, stripe_size, el_size ));

            shifted_ptr = temp->data.ptr +
                el_anchor.y*temp->step + el_anchor.x*CV_ELEM_SIZE(src_type);
            temp_step = temp->step ? temp->step : CV_STUB_STEP;

            for( y = 0; y < src->rows; y += dy )
            {
                dy = icvIPPFilterNextStripe( src, temp, y, el_size, el_anchor );
                IPPI_CALL( ipp_median_box_func( shifted_ptr, temp_step,
                    dst->data.ptr + y*dst_step, dst_step, cvSize(src->cols, dy),
                    el_size, el_anchor ));
            }
            EXIT;
        }
    }

    if( smooth_type == CV_BLUR || smooth_type == CV_BLUR_NO_SCALE )
    {
        CV_CALL( box_filter.init( src->cols, src_type, dst_type,
            smooth_type == CV_BLUR, cvSize(param1, param2) ));
        CV_CALL( box_filter.process( src, dst ));
    }
    else if( smooth_type == CV_MEDIAN )
    {
        int img_size_mp = size.width*size.height;
        img_size_mp = (img_size_mp + (1<<19)) >> 20;
        
        if( depth != CV_8U || cn != 1 && cn != 3 && cn != 4 )
            CV_ERROR( CV_StsUnsupportedFormat,
            "Median filter only supports 8uC1, 8uC3 and 8uC4 images" );

        if( size.width < param1*2 || size.height < param1*2 ||
            param1 <= 3 + (img_size_mp < 1 ? 12 : img_size_mp < 4 ? 6 : 2)*(MEDIAN_HAVE_SIMD ? 1 : 3))
        {
            // Special case optimized for 3x3
            IPPI_CALL( icvMedianBlur_8u_CnR_Om( src->data.ptr, src->step,
                dst->data.ptr, dst->step, size, param1, cn ));
        }
        else
        {
            const int r = (param1 - 1) / 2;
            const int CACHE_SIZE = (int) ( 0.95 * 256 * 1024 / cn );  // assume a 256 kB cache size
            const int STRIPES = (int) cvCeil( (double) (size.width - 2*r) /
                    (CACHE_SIZE / sizeof(Histogram) - 2*r) );
            const int STRIPE_SIZE = (int) cvCeil(
                    (double) ( size.width + STRIPES*2*r - 2*r ) / STRIPES );

            for( int i = 0; i < size.width; i += STRIPE_SIZE - 2*r )
            {
                int stripe = STRIPE_SIZE;
                // Make sure that the filter kernel fits into one stripe.
                if( i + STRIPE_SIZE - 2*r >= size.width ||
                    size.width - (i + STRIPE_SIZE - 2*r) < 2*r+1 )
                    stripe = size.width - i;

                IPPI_CALL( icvMedianBlur_8u_CnR_O1( src->data.ptr + cn*i, src->step,
                    dst->data.ptr + cn*i, dst->step, cvSize(stripe, size.height),
                    param1, cn, i == 0, stripe == size.width - i ));

                if( stripe == size.width - i )
                    break;
            }
        }
    }
    else if( smooth_type == CV_GAUSSIAN )
    {
        CvSize ksize = { param1, param2 };
        float* kx = (float*)cvStackAlloc( ksize.width*sizeof(kx[0]) );
        float* ky = (float*)cvStackAlloc( ksize.height*sizeof(ky[0]) );
        CvMat KX = cvMat( 1, ksize.width, CV_32F, kx );
        CvMat KY = cvMat( 1, ksize.height, CV_32F, ky );
        
        CvSepFilter::init_gaussian_kernel( &KX, sigma1 );
        if( ksize.width != ksize.height || fabs(sigma1 - sigma2) > FLT_EPSILON )
            CvSepFilter::init_gaussian_kernel( &KY, sigma2 );
        else
            KY.data.fl = kx;
        
        if( have_ipp && size.width >= param1*3 &&
            size.height >= param2 && param1 > 1 && param2 > 1 )
        {
            int done;
            CV_CALL( done = icvIPPSepFilter( src, dst, &KX, &KY,
                        cvPoint(ksize.width/2,ksize.height/2)));
            if( done )
                EXIT;
        }

        CV_CALL( gaussian_filter.init( src->cols, src_type, dst_type, &KX, &KY ));
        CV_CALL( gaussian_filter.process( src, dst ));
    }
    else if( smooth_type == CV_BILATERAL )
    {
        if( param1 < 0 || param2 < 0 )
            CV_ERROR( CV_StsOutOfRange,
            "Thresholds in bilaral filtering should not bee negative" );
        param1 += param1 == 0;
        param2 += param2 == 0;

        if( depth != CV_8U || cn != 1 && cn != 3 )
            CV_ERROR( CV_StsUnsupportedFormat,
            "Bilateral filter only supports 8uC1 and 8uC3 images" );

        IPPI_CALL( icvBilateralFiltering_8u_CnR( src->data.ptr, src->step,
            dst->data.ptr, dst->step, size, param1, param2, cn ));
    }

    __END__;

    cvReleaseMat( &temp );
}

/* End of file. */
