/*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"

/****************************************************************************************\
                                    Initialization
\****************************************************************************************/

CvFilterState* icvFilterInitAlloc(
             int roiWidth, CvDataType dataType, int channels,
             CvSize elSize, CvPoint elAnchor,
             const void* elData, int elementType )
{
    CvFilterState *state = 0;
    const int align = sizeof(size_t);
    int ker_size = 0;
    int buffer_step = 0;
    int buffer_size;
    int bt_pix, bt_pix_n;
    int aligned_hdr_size = cvAlign((int)sizeof(*state),align);
    int temp_lines = 2;
    int binaryElement = ICV_KERNEL_TYPE(elementType) == ICV_BINARY_KERNEL;
    int separableElement = ICV_KERNEL_TYPE(elementType) == ICV_SEPARABLE_KERNEL;
    char *ptr;

    CV_FUNCNAME( "icvFilterInitAlloc" );

    __BEGIN__;

    if( roiWidth <= 0 )
        CV_ERROR( CV_StsOutOfRange, "image width <= 0" );
    if( dataType != cv8u && dataType != cv32s && dataType != cv32f )
        CV_ERROR( CV_StsUnsupportedFormat, "dataType is not CV_8U, CV_32S or CV_32F" );
    if( channels < 1 || channels > 4 )
        CV_ERROR( CV_StsUnsupportedFormat, "number of channels is not within 1..4" );
    if( elSize.width <= 0 || elSize.height <= 0 )
        CV_ERROR( CV_StsBadSize, "aperture width <= 0 or aperture height <= 0" );
    if( (unsigned) elAnchor.x >= (unsigned) elSize.width ||
        (unsigned) elAnchor.y >= (unsigned) elSize.height )
        CV_ERROR( CV_StsOutOfRange, "anchor point is outside the aperture" );

    bt_pix = dataType == cv8u ? 1 : dataType == cv32s ? sizeof(int) : sizeof(float);
    bt_pix_n = bt_pix * channels;

    if( elData )
        ker_size = binaryElement ? (elSize.width + 1) * elSize.height * 2 :
                   separableElement ? (elSize.width + elSize.height)*bt_pix :
                                      elSize.width * elSize.height * bt_pix;

    buffer_step = cvAlign((roiWidth + elSize.width+1 + CV_MORPH_ALIGN*4)*bt_pix_n, align);

    buffer_size = (elSize.height + temp_lines) * (buffer_step + sizeof(void*)) +
                  ker_size + aligned_hdr_size + elSize.width * bt_pix_n + align*4;

    buffer_size = cvAlign(buffer_size + align, align);

    CV_CALL( state = (CvFilterState*)cvAlloc( buffer_size ));

    ptr = (char*)state;
    state->buffer_step = buffer_step;
    state->crows = 0;

    state->ker_x = elAnchor.x;
    state->ker_y = elAnchor.y;
    state->ker_height = elSize.height;
    state->ker_width = elSize.width;
    state->kerType = elementType;
    state->divisor = 1;

    state->max_width = roiWidth;
    state->dataType = dataType;
    state->channels = channels;
    state->origin = 0;
    ptr += aligned_hdr_size + align;

    state->buffer = ptr;
    ptr += buffer_step * elSize.height;

    state->tbuf = ptr;
    ptr += buffer_step * temp_lines;

    state->rows = (char**)ptr;
    ptr += sizeof(state->rows[0])*elSize.height;

    state->ker0 = state->ker1 = 0;

    if( elData )
    {
        state->ker0 = (uchar*)ptr;

        if( binaryElement )
        {
            int i, mask_size = elSize.width * elSize.height;
            state->ker1 = 0;

            for( i = 0; i < mask_size; i++ )
            {
                int t = ((int*)elData)[i] ? -1 : 0;
                state->ker0[i] = (uchar)t;
            }
        }
        else
        {
            memcpy( state->ker0, elData, ker_size );
            state->ker1 = (uchar*)(separableElement ? ptr + elSize.width*bt_pix : 0);
        }
    }

    __END__;

    if( cvGetErrStatus() < 0 )
        icvFilterFree( &state );

    return state;
}


void icvFilterFree( CvFilterState** filterState )
{
    cvFree( (void**)filterState );
}


#define SMALL_GAUSSIAN_SIZE  7

static void
icvCalcGaussianKernel( int n, double sigma, float* kernel )
{
    static const float small_gaussian_tab[][SMALL_GAUSSIAN_SIZE] =
    {
        {1.f},
        {0.25f, 0.5f, 0.25f},
        {0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
        {0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125}
    };

    if( n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 )
    {
        assert( n%2 == 1 );
        memcpy( kernel, small_gaussian_tab[n>>1], n*sizeof(kernel[0]));
    }
    else
    {
        double sigmaX = sigma > 0 ? sigma : (n/2 - 1)*0.3 + 0.8;
        double scale2X = -0.5/(sigmaX*sigmaX);
        double sum = 1.;
        int i;
        sum = kernel[n/2] = 1.f;

        for( i = 1; i <= n/2; i++ )
        {
            kernel[n/2+i] = kernel[n/2-i] = (float)exp(scale2X*i*i);
            sum += kernel[n/2+i]*2;
        }

        sum = 1./sum;
        for( i = 0; i <= n/2; i++ )
            kernel[n/2+i] = kernel[n/2-i] = (float)(kernel[n/2+i]*sum);
    }
}


static CvFilterState*
icvSmoothInitAlloc( int width, CvDataType /*dataType*/, int channels,
                    CvSize el_size, int smoothtype, double sigma )
{
    CvFilterState* filterState = 0;
    CvPoint el_anchor = { el_size.width/2, el_size.height/2 };
    float* kx = 0;

    if( smoothtype == CV_GAUSSIAN )
    {
        kx = (float*)cvStackAlloc( (el_size.width + el_size.height)*sizeof(kx[0]));
        float* ky = kx + el_size.width;

        icvCalcGaussianKernel( el_size.width, sigma, kx );
        if( el_size.width == el_size.height )
            memcpy( ky, kx, el_size.width*sizeof(kx[0]) );
        else
            icvCalcGaussianKernel( el_size.height, sigma, ky );
    }

    filterState = icvFilterInitAlloc( width, cv32f, channels, el_size, el_anchor,
                                      kx, ICV_SEPARABLE_KERNEL );

    if( filterState )
    {
        if( smoothtype == CV_BLUR )
            filterState->divisor = el_size.width * el_size.height;
        else if( smoothtype == CV_GAUSSIAN )
        {
            filterState->divisor = (double)(1 << el_size.width) * (1 << el_size.height);
            filterState->kerType = (filterState->kerType & ~255) |
                (sigma > 0 ? ICV_CUSTOM_GAUSSIAN_KERNEL : ICV_DEFAULT_GAUSSIAN_KERNEL);
        }
    }

    return filterState;
}


static void
icvSmoothFree( CvFilterState ** filterState )
{
    icvFilterFree( filterState );
}


/****************************************************************************************\
                                      Simple Blur
\****************************************************************************************/

static CvStatus CV_STDCALL
icvBlur_8u_CnR( uchar* src, int srcStep,
                uchar* dst, int dstStep, CvSize * roiSize,
                CvFilterState* state, int stage )
{
    int width = roiSize->width;
    int src_height = roiSize->height;
    int dst_height = src_height;
    int x, y = 0, i;

    int ker_x = state->ker_x;
    int ker_y = state->ker_y;
    int ker_width = state->ker_width;
    int ker_height = state->ker_height;
    int ker_right = ker_width - ker_x;

    int crows = state->crows;
    int **rows = (int**)(state->rows);
    uchar *tbuf = (uchar*)(state->tbuf);
    int   *sumbuf = (int*)((char*)state->tbuf + state->buffer_step);
    int *trow, *trow2;

    int channels = state->channels;
    int ker_x_n = ker_x * channels;
    int ker_width_n = ker_width * channels;
    int ker_right_n = ker_right * channels;
    int width_n = width * channels;

    int is_small_width = width <= ker_width;
    int starting_flag = 0;
    int width_rest = width_n & (CV_MORPH_ALIGN - 1);
    int divisor = cvRound(state->divisor);
    double inv_divisor = divisor ? 1./divisor : 0.;

    if( stage == CV_START + CV_END )
        stage = CV_WHOLE;

    /* initialize cyclic buffer when starting */
    if( stage == CV_WHOLE || stage == CV_START )
    {
        for( i = 0; i < ker_height; i++ )
            rows[i] = (int*)(state->buffer + state->buffer_step * i);

        crows = ker_y;
        if( stage != CV_WHOLE )
            dst_height -= ker_height - ker_y - 1;
        starting_flag = 1;
    }

    if( stage == CV_END )
        dst_height += ker_height - ker_y - 1;

    do
    {
        int need_copy = is_small_width | (y == 0);
        uchar *tsrc;
        int *tdst;
        uchar *tdst2;
        int *saved_row = rows[ker_y];

        /* fill cyclic buffer - horizontal filtering */
        for( ; crows < ker_height; crows++ )
        {
            tsrc = src - ker_x_n;
            tdst = rows[crows];

            if( src_height-- <= 0 )
            {
                if( stage != CV_WHOLE && stage != CV_END )
                    break;
                /* duplicate last row */
                trow = rows[crows - 1];
                CV_COPY( tdst, trow, width_n, x );
                continue;
            }

            need_copy |= src_height == 0;

            if( ker_width > 1 )
            {
                if( need_copy )
                {
                    tsrc = tbuf - ker_x_n;
                    CV_COPY( tbuf, src, width_n, x );
                }
                else
                {
                    CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                    CV_COPY( tbuf, src + width_n, ker_right_n, x );
                }

                /* make replication borders */
                for( i = ker_x_n - 1; i >= 0; i-- )
                    tsrc[i] = tsrc[i + channels];
                for( i = width_n + ker_x_n; i < width_n + ker_width_n; i++ )
                    tsrc[i] = tsrc[i - channels];

                if( channels == 1 )
                {
                    int t0 = 0;

                    for( i = 0; i < ker_width_n - 1; i++ )
                        t0 += tsrc[i];

                    /* horizontal blurring */
                    for( i = ker_width_n; i < width_n + ker_width_n; i++ )
                    {
                        t0 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        t0 -= tsrc[i - ker_width_n];
                    }
                }
                else if( channels == 3 )
                {
                    int t0 = 0, t1 = 0, t2 = 0;

                    for( i = 0; i < ker_width_n - 3; i += 3 )
                    {
                        t0 += tsrc[i];
                        t1 += tsrc[i+1];
                        t2 += tsrc[i+2];
                    }

                    /* horizontal blurring */
                    for( i = ker_width_n; i < width_n + ker_width_n; i += 3 )
                    {
                        t0 += tsrc[i-3];
                        t1 += tsrc[i-2];
                        t2 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        tdst[i-ker_width_n+1] = t1;
                        tdst[i-ker_width_n+2] = t2;
                        t0 -= tsrc[i - ker_width_n];
                        t1 -= tsrc[i - ker_width_n + 1];
                        t2 -= tsrc[i - ker_width_n + 2];
                    }
                }
                else            /* channels == 4 */
                {
                    int t0 = 0, t1 = 0, t2 = 0, t3 = 0;

                    for( i = 0; i < ker_width_n - 4; i += 4 )
                    {
                        t0 += tsrc[i];
                        t1 += tsrc[i+1];
                        t2 += tsrc[i+2];
                        t3 += tsrc[i+3];
                    }

                    /* horizontal blurring */
                    for( i = ker_width_n; i < width_n + ker_width_n; i += 4 )
                    {
                        t0 += tsrc[i-4];
                        t1 += tsrc[i-3];
                        t2 += tsrc[i-2];
                        t3 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        tdst[i-ker_width_n+1] = t1;
                        tdst[i-ker_width_n+2] = t2;
                        tdst[i-ker_width_n+3] = t3;
                        t0 -= tsrc[i - ker_width_n];
                        t1 -= tsrc[i - ker_width_n + 1];
                        t2 -= tsrc[i - ker_width_n + 2];
                        t3 -= tsrc[i - ker_width_n + 3];
                    }
                }

                if( !need_copy )
                {
                    /* restore borders */
                    CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                    CV_COPY( src + width_n, tbuf, ker_right_n, x );
                }
            }
            else
            {
                CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
            }

            if( crows < ker_height )
                src += srcStep;
        }

        if( starting_flag )
        {
            starting_flag = 0;

            for( x = 0; x < width_n; x++ )
            {
                int t = rows[ker_y][x];
                for( i = 0; i < ker_y; i++ ) // copy border
                    rows[i][x] = t;

                t *= ker_y;
                for( i = ker_y; i < ker_height - 1; i++ ) // accumulate initial sum
                    t += rows[i][x];
                sumbuf[x] = t;
            }
        }

        /* vertical convolution */
        if( crows != ker_height )
            break;

        tdst2 = dst;

        if( width_rest )
        {
            need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

            if( need_copy )
                tdst2 = tbuf;
            else
                CV_COPY( tbuf + width_n, dst + width_n, CV_MORPH_ALIGN, x );
        }

        trow = rows[0];
        trow2 = rows[ker_height-1];

        for( x = 0; x < width_n; x += 4 )
        {
            int val0, val1, t0, t1;

            val0 = sumbuf[x];
            val1 = sumbuf[x+1];

            val0 += trow2[x];
            val1 += trow2[x+1];

            t0 = cvRound(val0*inv_divisor);
            t1 = cvRound(val1*inv_divisor);

            tdst2[x] = (uchar)t0;
            tdst2[x+1] = (uchar)t1;

            sumbuf[x] = val0 - trow[x];
            sumbuf[x+1] = val1 - trow[x+1];

            val0 = sumbuf[x+2];
            val1 = sumbuf[x+3];

            val0 += trow2[x+2];
            val1 += trow2[x+3];

            t0 = cvRound(val0*inv_divisor);
            t1 = cvRound(val1*inv_divisor);

            tdst2[x+2] = (uchar)t0;
            tdst2[x+3] = (uchar)t1;

            sumbuf[x+2] = val0 - trow[x+2];
            sumbuf[x+3] = val1 - trow[x+3];
        }

        if( width_rest )
        {
            if( need_copy )
                CV_COPY( dst, tbuf, width_n, x );
            else
                CV_COPY( dst + width_n, tbuf + width_n, CV_MORPH_ALIGN, x );
        }

        rows[ker_y] = saved_row;

        /* rotate buffer */
        {
            int *t = rows[0];

            CV_COPY( rows, rows + 1, ker_height - 1, i );
            rows[i] = t;
            crows--;
            dst += dstStep;
        }
    }
    while( ++y < dst_height );

    roiSize->height = y;
    state->crows = crows;

    return CV_OK;
}


CvStatus CV_STDCALL
icvBlur_8u16s_C1R( const uchar* pSrc, int srcStep,
                   short* dst, int dstStep, CvSize * roiSize,
                   CvFilterState* state, int stage )
{
    uchar* src = (uchar*)pSrc;
    int width = roiSize->width;
    int src_height = roiSize->height;
    int dst_height = src_height;
    int x, y = 0, i;

    int ker_x = state->ker_x;
    int ker_y = state->ker_y;
    int ker_width = state->ker_width;
    int ker_height = state->ker_height;
    int ker_right = ker_width - ker_x;

    int crows = state->crows;
    int **rows = (int**)(state->rows);
    short *tbufw = (short*)(state->tbuf);
    int   *sumbuf = (int*)((char*)state->tbuf + state->buffer_step);
    int *trow, *trow2;

    int channels = state->channels;
    int ker_x_n = ker_x * channels;
    int ker_width_n = ker_width * channels;
    int ker_right_n = ker_right * channels;
    int width_n = width * channels;

    int is_small_width = width <= ker_width;
    int starting_flag = 0;
    int width_rest = width_n & (CV_MORPH_ALIGN - 1);

    /*if( stage == CV_START + CV_END )
        stage = CV_WHOLE;*/

    /* initialize cyclic buffer when starting */
    if( stage == CV_WHOLE || stage == CV_START )
    {
        for( i = 0; i < ker_height; i++ )
            rows[i] = (int*)(state->buffer + state->buffer_step * i);

        crows = ker_y;
        if( stage != CV_WHOLE )
            dst_height -= ker_height - ker_y - 1;
        starting_flag = 1;
    }

    if( stage == CV_END )
        dst_height += ker_height - ker_y - 1;

    dstStep /= sizeof(dst[0]);

    do
    {
        int need_copy = is_small_width | (y == 0);
        uchar *tsrc;
        int *tdst;
        short *tdst2;
        int *saved_row = rows[ker_y];

        /* fill cyclic buffer - horizontal filtering */
        for( ; crows < ker_height; crows++ )
        {
            tsrc = src - ker_x_n;
            tdst = rows[crows];

            if( src_height-- <= 0 )
            {
                if( stage != CV_WHOLE && stage != CV_END )
                    break;
                /* duplicate last row */
                trow = rows[crows - 1];
                CV_COPY( tdst, trow, width_n, x );
                continue;
            }

            need_copy |= src_height == 0;

            if( ker_width > 1 )
            {
                uchar* tbuf = (uchar*)tbufw;

                if( need_copy )
                {
                    tsrc = tbuf - ker_x_n;
                    CV_COPY( tbuf, src, width_n, x );
                }
                else
                {
                    CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                    CV_COPY( tbuf, src + width_n, ker_right_n, x );
                }

                {
                    /* make replication borders */
                    uchar pix = tsrc[ker_x];
                    int t0 = 0;

                    CV_SET( tsrc, pix, ker_x, x );

                    pix = tsrc[width + ker_x - 1];
                    CV_SET( tsrc + width + ker_x, pix, ker_right, x );

                    for( i = 0; i < ker_width_n - 1; i++ )
                        t0 += tsrc[i];

                    /* horizontal blurring */
                    for( i = ker_width_n; i < width_n + ker_width_n; i++ )
                    {
                        t0 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        t0 -= tsrc[i - ker_width_n];
                    }
                }

                if( !need_copy )
                {
                    /* restore borders */
                    CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                    CV_COPY( src + width_n, tbuf, ker_right_n, x );
                }
            }
            else
            {
                CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
            }

            if( crows < ker_height )
                src += srcStep;
        }

        if( starting_flag )
        {
            starting_flag = 0;

            for( x = 0; x < width_n; x++ )
            {
                int t = rows[ker_y][x];
                for( i = 0; i < ker_y; i++ ) // copy border
                    rows[i][x] = t;

                t *= ker_y;
                for( i = ker_y; i < ker_height - 1; i++ ) // accumulate initial sum
                    t += rows[i][x];
                sumbuf[x] = t;
            }
        }

        /* vertical convolution */
        if( crows != ker_height )
            break;

        tdst2 = dst;

        if( width_rest )
        {
            need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

            if( need_copy )
                tdst2 = tbufw;
            else
                CV_COPY( tbufw + width_n, dst + width_n, CV_MORPH_ALIGN, x );
        }

        trow = rows[0];
        trow2 = rows[ker_height-1];

        for( x = 0; x < width_n; x += 4 )
        {
            int val0, val1;

            val0 = sumbuf[x];
            val1 = sumbuf[x+1];

            val0 += trow2[x];
            val1 += trow2[x+1];

            tdst2[x] = CV_CAST_16S(val0);
            tdst2[x+1] = CV_CAST_16S(val1);

            sumbuf[x] = val0 - trow[x];
            sumbuf[x+1] = val1 - trow[x+1];

            val0 = sumbuf[x+2];
            val1 = sumbuf[x+3];

            val0 += trow2[x+2];
            val1 += trow2[x+3];

            tdst2[x+2] = CV_CAST_16S(val0);
            tdst2[x+3] = CV_CAST_16S(val1);

            sumbuf[x+2] = val0 - trow[x+2];
            sumbuf[x+3] = val1 - trow[x+3];
        }

        if( width_rest )
        {
            if( need_copy )
                CV_COPY( dst, tbufw, width_n, x );
            else
                CV_COPY( dst + width_n, tbufw + width_n, CV_MORPH_ALIGN, x );
        }

        rows[ker_y] = saved_row;

        /* rotate buffer */
        {
            int *t = rows[0];

            CV_COPY( rows, rows + 1, ker_height - 1, i );
            rows[i] = t;
            crows--;
            dst += dstStep;
        }
    }
    while( ++y < dst_height );

    roiSize->height = y;
    state->crows = crows;

    return CV_OK;
}


CvStatus CV_STDCALL
icvBlur_32f_CnR( const float* pSrc, int srcStep,
                 float* dst, int dstStep, CvSize * roiSize,
                 CvFilterState* state, int stage )
{
    float* src = (float*)pSrc;
    int width = roiSize->width;
    int src_height = roiSize->height;
    int dst_height = src_height;
    int x, y = 0, i;

    int ker_x = state->ker_x;
    int ker_y = state->ker_y;
    int ker_width = state->ker_width;
    int ker_height = state->ker_height;
    int ker_right = ker_width - ker_x;

    int crows = state->crows;
    float **rows = (float**)(state->rows);
    float *tbuf = (float*)(state->tbuf);
    float *sumbuf = (float*)((char*)state->tbuf + state->buffer_step);
    float *trow, *trow2;

    int channels = state->channels;
    int ker_x_n = ker_x * channels;
    int ker_width_n = ker_width * channels;
    int ker_right_n = ker_right * channels;
    int width_n = width * channels;

    int is_small_width = width <= ker_width;
    int starting_flag = 0;
    int width_rest = width_n & (CV_MORPH_ALIGN - 1);
    float scale = (float)(1./state->divisor);
    int no_scale = fabs(state->divisor - 1) < DBL_EPSILON;

    srcStep /= sizeof(float);
    dstStep /= sizeof(float);

    if( stage == CV_START + CV_END )
        stage = CV_WHOLE;

    /* initialize cyclic buffer when starting */
    if( stage == CV_WHOLE || stage == CV_START )
    {
        for( i = 0; i < ker_height; i++ )
        {
            rows[i] = (float*)(state->buffer + state->buffer_step * i);
            for( x = width_n; (x&3) != 0; x++ )
                rows[i][x] = 0.f;
        }
        for( x = width_n; (x&3) != 0; x++ )
             sumbuf[x] = 0.f;

        crows = ker_y;
        if( stage != CV_WHOLE )
            dst_height -= ker_height - ker_y - 1;
        starting_flag = 1;
    }

    if( stage == CV_END )
        dst_height += ker_height - ker_y - 1;

    do
    {
        int need_copy = is_small_width | (y == 0);
        float *tsrc;
        float *tdst;
        float *tdst2;
        float *saved_row = rows[ker_y];

        /* fill cyclic buffer - horizontal filtering */
        for( ; crows < ker_height; crows++ )
        {
            tsrc = src - ker_x_n;
            tdst = rows[crows];

            if( src_height-- <= 0 )
            {
                if( stage != CV_WHOLE && stage != CV_END )
                    break;
                /* duplicate last row */
                trow = rows[crows - 1];
                CV_COPY( tdst, trow, width_n, x );
                continue;
            }

            need_copy |= src_height == 0;

            if( ker_width > 1 )
            {
                if( need_copy )
                {
                    tsrc = tbuf - ker_x_n;
                    CV_COPY( tbuf, src, width_n, x );
                }
                else
                {
                    CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                    CV_COPY( tbuf, src + width_n, ker_right_n, x );
                }

                /* make replication borders */
                for( i = ker_x_n - 1; i >= 0; i-- )
                    tsrc[i] = tsrc[i + channels];
                for( i = width_n + ker_x_n; i < width_n + ker_width_n; i++ )
                    tsrc[i] = tsrc[i - channels];

                /* horizontal blurring */
                if( channels == 1 )
                {
                    float t0 = 0;
                    for( i = 0; i < ker_width_n - 1; i++ )
                        t0 += tsrc[i];

                    for( i = ker_width_n; i < width_n + ker_width_n; i++ )
                    {
                        t0 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        t0 -= tsrc[i - ker_width_n];
                    }
                }
                else if( channels == 3 )
                {
                    float t0 = 0, t1 = 0, t2 = 0;
                    for( i = 0; i < ker_width_n - 3; i += 3 )
                    {
                        t0 += tsrc[i];
                        t1 += tsrc[i+1];
                        t2 += tsrc[i+2];
                    }

                    for( i = ker_width_n; i < width_n + ker_width_n; i += 3 )
                    {
                        t0 += tsrc[i-3];
                        t1 += tsrc[i-2];
                        t2 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        tdst[i-ker_width_n+1] = t1;
                        tdst[i-ker_width_n+2] = t2;
                        t0 -= tsrc[i - ker_width_n];
                        t1 -= tsrc[i - ker_width_n + 1];
                        t2 -= tsrc[i - ker_width_n + 2];
                    }
                }
                else            /* channels == 4 */
                {
                    float t0 = 0, t1 = 0, t2 = 0, t3 = 0;
                    for( i = 0; i < ker_width_n - 4; i += 4 )
                    {
                        t0 += tsrc[i];
                        t1 += tsrc[i+1];
                        t2 += tsrc[i+2];
                        t3 += tsrc[i+3];
                    }

                    /* horizontal blurring */
                    for( i = ker_width_n; i < width_n + ker_width_n; i += 4 )
                    {
                        t0 += tsrc[i-4];
                        t1 += tsrc[i-3];
                        t2 += tsrc[i-2];
                        t3 += tsrc[i-1];
                        tdst[i-ker_width_n] = t0;
                        tdst[i-ker_width_n+1] = t1;
                        tdst[i-ker_width_n+2] = t2;
                        tdst[i-ker_width_n+3] = t3;
                        t0 -= tsrc[i - ker_width_n];
                        t1 -= tsrc[i - ker_width_n + 1];
                        t2 -= tsrc[i - ker_width_n + 2];
                        t3 -= tsrc[i - ker_width_n + 3];
                    }
                }

                if( !need_copy )
                {
                    /* restore borders */
                    CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                    CV_COPY( src + width_n, tbuf, ker_right_n, x );
                }
            }
            else
            {
                CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
            }

            if( crows < ker_height )
                src += srcStep;
        }

        if( starting_flag )
        {
            starting_flag = 0;

            for( x = 0; x < width_n; x++ )
            {
                float t = rows[ker_y][x];
                for( i = 0; i < ker_y; i++ ) // copy border
                    rows[i][x] = t;

                t *= ker_y;
                for( i = ker_y; i < ker_height - 1; i++ ) // accumulate initial sum
                    t += rows[i][x];
                sumbuf[x] = t;
            }
        }

        /* vertical convolution */
        if( crows != ker_height )
            break;

        tdst2 = dst;

        if( width_rest )
        {
            need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

            if( need_copy )
                tdst2 = tbuf;
            else
                CV_COPY( tbuf + width_n, dst + width_n, CV_MORPH_ALIGN, x );
        }

        trow = rows[0];
        trow2 = rows[ker_height-1];

        if( no_scale )
        {
            for( x = 0; x < width_n; x += 4 )
            {
                float val0, val1;

                val0 = sumbuf[x];
                val1 = sumbuf[x+1];

                val0 += trow2[x];
                val1 += trow2[x+1];

                tdst2[x] = (float)val0;
                tdst2[x+1] = (float)val1;

                sumbuf[x] = val0 - trow[x];
                sumbuf[x+1] = val1 - trow[x+1];

                val0 = sumbuf[x+2];
                val1 = sumbuf[x+3];

                val0 += trow2[x+2];
                val1 += trow2[x+3];

                tdst2[x+2] = (float)val0;
                tdst2[x+3] = (float)val1;

                sumbuf[x+2] = val0 - trow[x+2];
                sumbuf[x+3] = val1 - trow[x+3];
            }
        }
        else
        {
            for( x = 0; x < width_n; x += 4 )
            {
                float val0, val1, t0, t1;

                val0 = sumbuf[x];
                val1 = sumbuf[x+1];

                val0 += trow2[x];
                val1 += trow2[x+1];

                t0 = val0 * scale;
                t1 = val1 * scale;

                tdst2[x] = (float)t0;
                tdst2[x+1] = (float)t1;

                sumbuf[x] = val0 - trow[x];
                sumbuf[x+1] = val1 - trow[x+1];

                val0 = sumbuf[x+2];
                val1 = sumbuf[x+3];

                val0 += trow2[x+2];
                val1 += trow2[x+3];

                t0 = val0 * scale;
                t1 = val1 * scale;

                tdst2[x+2] = (float)t0;
                tdst2[x+3] = (float)t1;

                sumbuf[x+2] = val0 - trow[x+2];
                sumbuf[x+3] = val1 - trow[x+3];
            }
        }

        if( width_rest )
        {
            if( need_copy )
                CV_COPY( dst, tbuf, width_n, x );
            else
                CV_COPY( dst + width_n, tbuf + width_n, CV_MORPH_ALIGN, x );
        }

        rows[ker_y] = saved_row;

        /* rotate buffer */
        {
            float *t = rows[0];

            CV_COPY( rows, rows + 1, ker_height - 1, i );
            rows[i] = t;
            crows--;
            dst += dstStep;
        }
    }
    while( ++y < dst_height );

    roiSize->height = y;
    state->crows = crows;

    return CV_OK;
}


CvFilterState* icvBlurInitAlloc( int roiWidth, int depth, int channels, int size )
{
    return icvSmoothInitAlloc( roiWidth, (CvDataType)depth, channels,
                               cvSize(size,size), CV_BLUR_NO_SCALE, 0 );
}


/****************************************************************************************\
                                    Gaussian Blur
\****************************************************************************************/

static CvStatus CV_STDCALL
icvGaussianBlur_small_8u_CnR( uchar* src, int srcStep,
                              uchar* dst, int dstStep, CvSize * roiSize,
                              CvFilterState* state, int stage )
{
    int width = roiSize->width;
    int src_height = roiSize->height;
    int dst_height = src_height;
    int x, y = 0, i;

    int ker_width = state->ker_width;
    int ker_height = state->ker_height;
    int ker_x = ker_width/2;
    int ker_y = ker_height/2;
    int ker_right = ker_width - ker_x;

    int crows = state->crows;
    int **rows = (int**)(state->rows);
    uchar *tbuf = (uchar*)(state->tbuf);
    int *trow = 0;

    int channels = state->channels;
    int ker_x_n = ker_x * channels;
    int ker_right_n = ker_right * channels;
    int ker_width_n = ker_width * channels;
    int width_n = width * channels;

    int is_small_width = width <= ker_width;
    int starting_flag = 0;
    int width_rest = width_n & (CV_MORPH_ALIGN - 1);
    int rshift = ker_width + ker_height - 2;
    int delta = 1 << (rshift - 1);

    assert( ker_width <= SMALL_GAUSSIAN_SIZE &&
            ker_height <= SMALL_GAUSSIAN_SIZE );

    if( stage == CV_START + CV_END )
        stage = CV_WHOLE;

    /* initialize cyclic buffer when starting */
    if( stage == CV_WHOLE || stage == CV_START )
    {
        for( i = 0; i < ker_height; i++ )
            rows[i] = (int*)(state->buffer + state->buffer_step * i);

        crows = ker_y;
        if( stage != CV_WHOLE )
            dst_height -= ker_height - ker_y - 1;
        starting_flag = 1;
    }

    if( stage == CV_END )
        dst_height += ker_height - ker_y - 1;

    do
    {
        int need_copy = is_small_width | (y == 0);
        uchar *tsrc;
        int *tdst;
        uchar *tdst2;
        int *saved_row = rows[ker_y];

        /* fill cyclic buffer - horizontal filtering */
        for( ; crows < ker_height; crows++ )
        {
            tsrc = src - ker_x_n;
            tdst = rows[crows];

            if( src_height-- <= 0 )
            {
                if( stage != CV_WHOLE && stage != CV_END )
                    break;
                /* duplicate last row */
                trow = rows[crows - 1];
                CV_COPY( tdst, trow, width_n, x );
                continue;
            }

            need_copy |= src_height == 0;

            if( ker_width > 1 )
            {
                if( need_copy )
                {
                    tsrc = tbuf - ker_x_n;
                    CV_COPY( tbuf, src, width_n, x );
                }
                else
                {
                    CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                    CV_COPY( tbuf, src + width_n, ker_right_n, x );
                }

                /* make replication borders */
                for( i = ker_x_n - 1; i >= 0; i-- )
                    tsrc[i] = tsrc[i + channels];
                for( i = width_n + ker_x_n; i < width_n + ker_width_n; i++ )
                    tsrc[i] = tsrc[i - channels];

                if( channels == 1 )
                {
                    /* horizontal blurring */
                    if( ker_width == 3 )
                    {
                        for( i = 0; i < width_n; i++ )
                            tdst[i] = tsrc[i+1]*2 + tsrc[i] + tsrc[i + 2];
                    }
                    else if( ker_width == 5 )
                    {
                        for( i = 0; i < width_n; i++ )
                            tdst[i] = tsrc[i+2]*6 + (tsrc[i+1] + tsrc[i+3])*4 +
                                      tsrc[i] + tsrc[i+4];
                    }
                    else if( ker_width == 7 )
                    {
                        for( i = 0; i < width_n; i++ )
                            tdst[i] = tsrc[i+3]*18 + (tsrc[i+2] + tsrc[i+4])*14 +
                                (tsrc[i+1] + tsrc[i+5])*7 + (tsrc[i] + tsrc[i+6])*2;
                    }
                }
                else if( channels == 3 )
                {
                    /* horizontal blurring */
                    if( ker_width == 3 )
                    {
                        for( i = 0; i < width_n; i += 3 )
                        {
                            int t0 = tsrc[i + 3]*2 + tsrc[i] + tsrc[i+6];
                            int t1 = tsrc[i + 4]*2 + tsrc[i+1] + tsrc[i+7];
                            int t2 = tsrc[i + 5]*2 + tsrc[i+2] + tsrc[i+8];

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                        }
                    }
                    else if( ker_width == 5 )
                    {
                        for( i = 0; i < width_n; i += 3 )
                        {
                            int t0 = tsrc[i+6]*6 + (tsrc[i+3] + tsrc[i+9])*4 +
                                     tsrc[i]+tsrc[i+12];
                            int t1 = tsrc[i+7]*6 + (tsrc[i+4] + tsrc[i+10])*4 +
                                     tsrc[i+1]+tsrc[i+13];
                            int t2 = tsrc[i+8]*6 + (tsrc[i+5] + tsrc[i+11])*4 +
                                     tsrc[i+2]+tsrc[i+14];

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                        }
                    }
                    else
                    {
                        assert( ker_width == 7 );
                        for( i = 0; i < width_n; i += 3 )
                        {
                            int t0 = tsrc[i+9]*18 + (tsrc[i+6] + tsrc[i+12])*14 +
                                     (tsrc[i+3]+tsrc[i+15])*7 + (tsrc[i] + tsrc[i+18])*2;
                            int t1 = tsrc[i+10]*18 + (tsrc[i+7] + tsrc[i+13])*14 +
                                     (tsrc[i+4]+tsrc[i+16])*7 + (tsrc[i+1] + tsrc[i+19])*2;
                            int t2 = tsrc[i+11]*18 + (tsrc[i+8] + tsrc[i+14])*14 +
                                     (tsrc[i+5]+tsrc[i+17])*7 + (tsrc[i+2] + tsrc[i+20])*2;

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                        }
                    }
                }
                else   /* channels == 4 */
                {
                    /* horizontal blurring */
                    if( ker_width == 3 )
                    {
                        for( i = 0; i < width_n; i += 4 )
                        {
                            int t0 = tsrc[i + 4]*2 + tsrc[i] + tsrc[i+8];
                            int t1 = tsrc[i + 5]*2 + tsrc[i+1] + tsrc[i+9];
                            int t2 = tsrc[i + 6]*2 + tsrc[i+2] + tsrc[i+10];
                            int t3 = tsrc[i + 7]*2 + tsrc[i+3] + tsrc[i+11];

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                            tdst[i+3] = t3;
                        }
                    }
                    else if( ker_width == 5 )
                    {
                        for( i = 0; i < width_n; i += 4 )
                        {
                            int t0 = tsrc[i+8]*6 + (tsrc[i+4] + tsrc[i+12])*4 +
                                     tsrc[i]+tsrc[i+16];
                            int t1 = tsrc[i+9]*6 + (tsrc[i+5] + tsrc[i+13])*4 +
                                     tsrc[i+1]+tsrc[i+17];
                            int t2 = tsrc[i+10]*6 + (tsrc[i+6] + tsrc[i+14])*4 +
                                     tsrc[i+2]+tsrc[i+18];
                            int t3 = tsrc[i+11]*6 + (tsrc[i+7] + tsrc[i+15])*4 +
                                     tsrc[i+2]+tsrc[i+19];

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                            tdst[i+3] = t3;
                        }
                    }
                    else
                    {
                        assert( ker_width == 7 );
                        for( i = 0; i < width_n; i += 4 )
                        {
                            int t0 = tsrc[i+12]*18 + (tsrc[i+8] + tsrc[i+16])*14 +
                                     (tsrc[i+4]+tsrc[i+20])*7 + (tsrc[i] + tsrc[i+24])*2;
                            int t1 = tsrc[i+13]*18 + (tsrc[i+9] + tsrc[i+17])*14 +
                                     (tsrc[i+5]+tsrc[i+21])*7 + (tsrc[i+1] + tsrc[i+25])*2;
                            int t2 = tsrc[i+14]*18 + (tsrc[i+10] + tsrc[i+18])*14 +
                                     (tsrc[i+6]+tsrc[i+22])*7 + (tsrc[i+2] + tsrc[i+26])*2;
                            int t3 = tsrc[i+15]*18 + (tsrc[i+11] + tsrc[i+19])*14 +
                                     (tsrc[i+7]+tsrc[i+23])*7 + (tsrc[i+3] + tsrc[i+27])*2;

                            tdst[i] = t0;
                            tdst[i+1] = t1;
                            tdst[i+2] = t2;
                            tdst[i+3] = t3;
                        }
                    }
                }

                if( !need_copy )
                {
                    /* restore borders */
                    CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                    CV_COPY( src + width_n, tbuf, ker_right_n, x );
                }
            }
            else
            {
                CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
            }

            if( crows < ker_height )
                src += srcStep;
        }

        if( starting_flag )
        {
            starting_flag = 0;
            trow = rows[ker_y];

            for( i = 0; i < ker_y; i++ )
            {
                tdst = rows[i];
                CV_COPY( tdst, trow, width_n, x );
            }
        }

        /* vertical convolution */
        if( crows != ker_height )
            break;

        tdst2 = dst;

        if( width_rest )
        {
            need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

            if( need_copy )
                tdst2 = tbuf;
            else
                CV_COPY( tbuf + width_n, dst + width_n, CV_MORPH_ALIGN, x );
        }

        trow = rows[ker_y];

        if( ker_height == 1 )
        {
            for( x = 0; x < width_n; x += 4 )
            {
                int val0, val1, val2, val3;

                val0 = trow[x];
                val1 = trow[x + 1];
                val2 = trow[x + 2];
                val3 = trow[x + 3];

                tdst2[x + 0] = (uchar)((val0 + delta) >> rshift);
                tdst2[x + 1] = (uchar)((val1 + delta) >> rshift);
                tdst2[x + 2] = (uchar)((val2 + delta) >> rshift);
                tdst2[x + 3] = (uchar)((val3 + delta) >> rshift);
            }
        }
        else if( ker_height == 3 )
        {
            int *trow1 = rows[0], *trow2 = rows[2];

            for( x = 0; x < width_n; x += 4 )
            {
                int val0, val1;
                val0 = (trow[x]*2 + trow1[x] + trow2[x] + delta) >> rshift;
                val1 = (trow[x + 1]*2 + trow1[x+1] + trow2[x+1] + delta) >> rshift;

                tdst2[x + 0] = (uchar)val0;
                tdst2[x + 1] = (uchar)val1;

                val0 = (trow[x + 2]*2 + trow1[x+2] + trow2[x+2] + delta) >> rshift;
                val1 = (trow[x + 3]*2 + trow1[x+3] + trow2[x+3] + delta) >> rshift;

                tdst2[x + 2] = (uchar)val0;
                tdst2[x + 3] = (uchar)val1;
            }
        }
        else if( ker_height == 5 )
        {
            int *trow1 = rows[0], *trow2 = rows[1],
                *trow3 = rows[3], *trow4 = rows[4];

            for( x = 0; x < width_n; x++ )
            {
                int val0 = (trow[x]*6 + (trow2[x] + trow3[x])*4 +
                            trow1[x] + trow4[x] + delta) >> rshift;
                tdst2[x] = (uchar)val0;
            }
        }
        else
        {
            int *trow1 = rows[0], *trow2 = rows[1],
                *trow3 = rows[2], *trow4 = rows[4],
                *trow5 = rows[5], *trow6 = rows[6];

            for( x = 0; x < width_n; x++ )
            {
                int val0 = (trow[x]*18 + (trow3[x] + trow4[x])*14 +
                    (trow2[x] + trow5[x])*7 + (trow1[x] + trow6[x])*2 + delta) >> rshift;
                tdst2[x] = (uchar)val0;
            }
        }

        if( width_rest )
        {
            if( need_copy )
                CV_COPY( dst, tbuf, width_n, x );
            else
                CV_COPY( dst + width_n, tbuf + width_n, CV_MORPH_ALIGN, x );
        }

        rows[ker_y] = saved_row;

        /* rotate buffer */
        {
            int *t = rows[0];

            CV_COPY( rows, rows + 1, ker_height - 1, i );
            rows[i] = t;
            crows--;
            dst += dstStep;
        }
    }
    while( ++y < dst_height );

    roiSize->height = y;
    state->crows = crows;

    return CV_OK;
}


static CvStatus CV_STDCALL
icvGaussianBlur_8u_CnR( uchar* src, int srcStep,
                        uchar* dst, int dstStep, CvSize * roiSize,
                        CvFilterState* state, int stage )
{
    if( state->ker_width <= SMALL_GAUSSIAN_SIZE &&
        state->ker_height <= SMALL_GAUSSIAN_SIZE &&
        ICV_X_KERNEL_TYPE(state->kerType) == ICV_DEFAULT_GAUSSIAN_KERNEL )
    {
        return icvGaussianBlur_small_8u_CnR( src, srcStep, dst, dstStep,
                                             roiSize, state, stage );
    }
    else
    {
        int width = roiSize->width;
        int src_height = roiSize->height;
        int dst_height = src_height;
        int x, y = 0, i;

        int ker_width = state->ker_width;
        int ker_height = state->ker_height;
        int ker_x = ker_width/2;
        int ker_y = ker_height/2;
        int ker_right = ker_width - ker_x;

        int crows = state->crows;
        float **rows = (float**)(state->rows);
        uchar *tbuf = (uchar*)(state->tbuf);
        float *trow = 0;

        int channels = state->channels;
        int ker_x_n = ker_x * channels;
        int ker_right_n = ker_right * channels;
        int width_n = width * channels;
        int ker_width_n = ker_width * channels;

        float* fmaskX = (float*)(state->ker0) + ker_x;
        float* fmaskY = (float*)(state->ker1) + ker_y;
        double fmX0 = fmaskX[0], fmY0 = fmaskY[0];

        int is_small_width = width <= ker_width;
        int starting_flag = 0;
        int width_rest = width_n & (CV_MORPH_ALIGN - 1);

        if( stage == CV_START + CV_END )
            stage = CV_WHOLE;

        /* initialize cyclic buffer when starting */
        if( stage == CV_WHOLE || stage == CV_START )
        {
            for( i = 0; i < ker_height; i++ )
            {
                rows[i] = (float*)(state->buffer + state->buffer_step * i);
                for( x = width_n; (x&3) != 0; x++ )
                    rows[i][x] = 0.f;
            }

            crows = ker_y;
            if( stage != CV_WHOLE )
                dst_height -= ker_height - ker_y - 1;
            starting_flag = 1;
        }

        if( stage == CV_END )
            dst_height += ker_height - ker_y - 1;

        do
        {
            int need_copy = is_small_width | (y == 0);
            uchar *tsrc;
            float *tdst;
            uchar *tdst2;
            float *saved_row = rows[ker_y];

            /* fill cyclic buffer - horizontal filtering */
            for( ; crows < ker_height; crows++ )
            {
                tsrc = src - ker_x_n;
                tdst = rows[crows];

                if( src_height-- <= 0 )
                {
                    if( stage != CV_WHOLE && stage != CV_END )
                        break;
                    /* duplicate last row */
                    trow = rows[crows - 1];
                    CV_COPY( tdst, trow, width_n, x );
                    continue;
                }

                need_copy |= src_height == 0;

                if( ker_width > 1 )
                {
                    if( need_copy )
                    {
                        tsrc = tbuf - ker_x_n;
                        CV_COPY( tbuf, src, width_n, x );
                    }
                    else
                    {
                        CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                        CV_COPY( tbuf, src + width_n, ker_right_n, x );
                    }

                    /* make replication borders */
                    for( i = ker_x_n - 1; i >= 0; i-- )
                        tsrc[i] = tsrc[i + channels];
                    for( i = width_n + ker_x_n; i < width_n + ker_width_n; i++ )
                        tsrc[i] = tsrc[i - channels];

                    /* horizontal blurring */
                    if( channels == 1 )
                    {
                        for( i = 0; i < width_n; i++ )
                        {
                            int j;
                            double t0 = CV_8TO32F(tsrc[i + ker_x_n])*fmX0;

                            for( j = 1; j <= ker_x; j++ )
                                t0 += (CV_8TO32F(tsrc[i+ker_x_n+j]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j]))*(double)fmaskX[j];

                            ((float*)tdst)[i] = (float)t0;
                        }
                    }
                    else if( channels == 3 )
                    {
                        for( i = 0; i < width_n; i += 3 )
                        {
                            int j;
                            double t0 = CV_8TO32F(tsrc[i + ker_x_n])*fmX0;
                            double t1 = CV_8TO32F(tsrc[i + ker_x_n + 1])*fmX0;
                            double t2 = CV_8TO32F(tsrc[i + ker_x_n + 2])*fmX0;

                            for( j = 1; j <= ker_x; j++ )
                            {
                                int j3 = j*3;
                                double m = fmaskX[j];
                                t0 += (CV_8TO32F(tsrc[i+ker_x_n+j3]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j3]))*m;
                                t1 += (CV_8TO32F(tsrc[i+ker_x_n+j3+1]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j3+1]))*m;
                                t2 += (CV_8TO32F(tsrc[i+ker_x_n+j3+2]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j3+2]))*m;
                            }

                            tdst[i] = (float)t0;
                            tdst[i+1] = (float)t1;
                            tdst[i+2] = (float)t2;
                        }
                    }
                    else    /* channels == 4 */
                    {
                        for( i = 0; i < width_n; i += 4 )
                        {
                            int j;
                            double t0 = CV_8TO32F(tsrc[i + ker_x_n])*fmX0;
                            double t1 = CV_8TO32F(tsrc[i + ker_x_n + 1])*fmX0;
                            double t2 = CV_8TO32F(tsrc[i + ker_x_n + 2])*fmX0;
                            double t3 = CV_8TO32F(tsrc[i + ker_x_n + 3])*fmX0;

                            for( j = 1; j <= ker_x; j++ )
                            {
                                int j4 = j*4;
                                double m = fmaskX[j];
                                t0 += (CV_8TO32F(tsrc[i+ker_x_n+j4]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j4]))*m;
                                t1 += (CV_8TO32F(tsrc[i+ker_x_n+j4+1]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j4+1]))*m;
                                t2 += (CV_8TO32F(tsrc[i+ker_x_n+j4+2]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j4+2]))*m;
                                t3 += (CV_8TO32F(tsrc[i+ker_x_n+j4+3]) +
                                       CV_8TO32F(tsrc[i+ker_x_n-j4+3]))*m;
                            }

                            tdst[i] = (float)t0;
                            tdst[i+1] = (float)t1;
                            tdst[i+2] = (float)t2;
                            tdst[i+3] = (float)t3;
                        }
                    }

                    if( !need_copy )
                    {
                        /* restore borders */
                        CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                        CV_COPY( src + width_n, tbuf, ker_right_n, x );
                    }
                }
                else
                {
                    CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
                }

                if( crows < ker_height )
                    src += srcStep;
            }

            if( starting_flag )
            {
                starting_flag = 0;
                trow = rows[ker_y];

                for( i = 0; i < ker_y; i++ )
                {
                    tdst = rows[i];
                    CV_COPY( tdst, trow, width_n, x );
                }
            }

            /* vertical convolution */
            if( crows != ker_height )
                break;

            tdst2 = dst;

            if( width_rest )
            {
                need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

                if( need_copy )
                    tdst2 = tbuf;
                else
                    CV_COPY( tbuf + width_n, dst + width_n, CV_MORPH_ALIGN, x );
            }

            trow = rows[ker_y];

            for( x = 0; x < width_n; x += 4 )
            {
                double val0, val1, val2, val3;

                val0 = trow[x]*fmY0;
                val1 = trow[x + 1]*fmY0;
                val2 = trow[x + 2]*fmY0;
                val3 = trow[x + 3]*fmY0;

                for( i = 1; i <= ker_y; i++ )
                {
                    float *trow1, *trow2;
                    double m = fmaskY[i];
                    trow1 = rows[ker_y - i];
                    trow2 = rows[ker_y + i];
                    val0 += (trow1[x] + trow2[x])*m;
                    val1 += (trow1[x+1] + trow2[x+1])*m;
                    val2 += (trow1[x+2] + trow2[x+2])*m;
                    val3 += (trow1[x+3] + trow2[x+3])*m;
                }

                tdst2[x + 0] = (uchar)cvRound(val0);
                tdst2[x + 1] = (uchar)cvRound(val1);
                tdst2[x + 2] = (uchar)cvRound(val2);
                tdst2[x + 3] = (uchar)cvRound(val3);
            }

            if( width_rest )
            {
                if( need_copy )
                    CV_COPY( dst, tbuf, width_n, x );
                else
                    CV_COPY( dst + width_n, tbuf + width_n, CV_MORPH_ALIGN, x );
            }

            rows[ker_y] = saved_row;

            /* rotate buffer */
            {
                float *t = rows[0];

                CV_COPY( rows, rows + 1, ker_height - 1, i );
                rows[i] = t;
                crows--;
                dst += dstStep;
            }
        }
        while( ++y < dst_height );

        roiSize->height = y;
        state->crows = crows;

        return CV_OK;
    }
}


static CvStatus CV_STDCALL
icvGaussianBlur_32f_CnR( float* src, int srcStep,
                         float* dst, int dstStep, CvSize * roiSize,
                         CvFilterState* state, int stage )
{
    int width = roiSize->width;
    int src_height = roiSize->height;
    int dst_height = src_height;
    int x, y = 0, i;

    int ker_width = state->ker_width;
    int ker_height = state->ker_height;
    int ker_x = ker_width/2;
    int ker_y = ker_height/2;
    int ker_right = ker_width - ker_x;

    int crows = state->crows;
    float **rows = (float**)(state->rows);
    float *tbuf = (float*)(state->tbuf);
    float *trow = 0;

    int channels = state->channels;
    int ker_x_n = ker_x * channels;
    int ker_right_n = ker_right * channels;
    int width_n = width * channels;
    int ker_width_n = ker_width * channels;

    float* fmaskX = (float*)(state->ker0) + ker_x;
    float* fmaskY = (float*)(state->ker1) + ker_y;
    double fmX0 = fmaskX[0], fmY0 = fmaskY[0];

    int is_small_width = width <= ker_width;
    int starting_flag = 0;
    int width_rest = width_n & (CV_MORPH_ALIGN - 1);

    srcStep /= sizeof(src[0]);
    dstStep /= sizeof(dst[0]);

    if( stage == CV_START + CV_END )
        stage = CV_WHOLE;

    /* initialize cyclic buffer when starting */
    if( stage == CV_WHOLE || stage == CV_START )
    {
        for( i = 0; i < ker_height; i++ )
        {
            rows[i] = (float*)(state->buffer + state->buffer_step * i);
            for( x = width_n; (x&3) != 0; x++ )
                rows[i][x] = 0.f;
        }

        crows = ker_y;
        if( stage != CV_WHOLE )
            dst_height -= ker_height - ker_y - 1;
        starting_flag = 1;
    }

    if( stage == CV_END )
        dst_height += ker_height - ker_y - 1;

    do
    {
        int need_copy = is_small_width | (y == 0);
        float *tsrc;
        float *tdst;
        float *tdst2;
        float *saved_row = rows[ker_y];

        /* fill cyclic buffer - horizontal filtering */
        for( ; crows < ker_height; crows++ )
        {
            tsrc = src - ker_x_n;
            tdst = rows[crows];

            if( src_height-- <= 0 )
            {
                if( stage != CV_WHOLE && stage != CV_END )
                    break;
                /* duplicate last row */
                trow = rows[crows - 1];
                CV_COPY( tdst, trow, width_n, x );
                continue;
            }

            need_copy |= src_height == 0;

            if( ker_width > 1 )
            {
                if( need_copy )
                {
                    tsrc = tbuf - ker_x_n;
                    CV_COPY( tbuf, src, width_n, x );
                }
                else
                {
                    CV_COPY( tbuf - ker_x_n, src - ker_x_n, ker_x_n, x );
                    CV_COPY( tbuf, src + width_n, ker_right_n, x );
                }

                /* make replication borders */
                for( i = ker_x_n - 1; i >= 0; i-- )
                    tsrc[i] = tsrc[i + channels];
                for( i = width_n + ker_x_n; i < width_n + ker_width_n; i++ )
                    tsrc[i] = tsrc[i - channels];

                /* horizontal blurring */
                if( channels == 1 )
                {
                    for( i = 0; i < width_n; i++ )
                    {
                        int j;
                        double t0 = (tsrc[i + ker_x_n])*fmX0;

                        for( j = 1; j <= ker_x; j++ )
                            t0 += ((tsrc[i+ker_x_n+j]) +
                                   (tsrc[i+ker_x_n-j]))*(double)fmaskX[j];

                        ((float*)tdst)[i] = (float)t0;
                    }
                }
                else if( channels == 3 )
                {
                    for( i = 0; i < width_n; i += 3 )
                    {
                        int j;
                        double t0 = (tsrc[i + ker_x_n])*fmX0;
                        double t1 = (tsrc[i + ker_x_n + 1])*fmX0;
                        double t2 = (tsrc[i + ker_x_n + 2])*fmX0;

                        for( j = 1; j <= ker_x; j++ )
                        {
                            int j3 = j*3;
                            double m = fmaskX[j];
                            t0 += ((tsrc[i+ker_x_n+j3]) +
                                   (tsrc[i+ker_x_n-j3]))*m;
                            t1 += ((tsrc[i+ker_x_n+j3+1]) +
                                   (tsrc[i+ker_x_n-j3+1]))*m;
                            t2 += ((tsrc[i+ker_x_n+j3+2]) +
                                   (tsrc[i+ker_x_n-j3+2]))*m;
                        }

                        tdst[i] = (float)t0;
                        tdst[i+1] = (float)t1;
                        tdst[i+2] = (float)t2;
                    }
                }
                else    /* channels == 4 */
                {
                    for( i = 0; i < width_n; i += 4 )
                    {
                        int j;
                        double t0 = (tsrc[i + ker_x_n])*fmX0;
                        double t1 = (tsrc[i + ker_x_n + 1])*fmX0;
                        double t2 = (tsrc[i + ker_x_n + 2])*fmX0;
                        double t3 = (tsrc[i + ker_x_n + 3])*fmX0;

                        for( j = 1; j <= ker_x; j++ )
                        {
                            int j4 = j*4;
                            double m = fmaskX[j];
                            t0 += ((tsrc[i+ker_x_n+j4]) +
                                   (tsrc[i+ker_x_n-j4]))*m;
                            t1 += ((tsrc[i+ker_x_n+j4+1]) +
                                   (tsrc[i+ker_x_n-j4+1]))*m;
                            t2 += ((tsrc[i+ker_x_n+j4+2]) +
                                   (tsrc[i+ker_x_n-j4+2]))*m;
                            t3 += ((tsrc[i+ker_x_n+j4+3]) +
                                   (tsrc[i+ker_x_n-j4+3]))*m;
                        }

                        tdst[i] = (float)t0;
                        tdst[i+1] = (float)t1;
                        tdst[i+2] = (float)t2;
                        tdst[i+3] = (float)t3;
                    }
                }

                if( !need_copy )
                {
                    /* restore borders */
                    CV_COPY( src - ker_x_n, tbuf - ker_x_n, ker_x_n, x );
                    CV_COPY( src + width_n, tbuf, ker_right_n, x );
                }
            }
            else
            {
                CV_COPY( tdst, tsrc + ker_x_n, width_n, x );
            }

            if( crows < ker_height )
                src += srcStep;
        }

        if( starting_flag )
        {
            starting_flag = 0;
            trow = rows[ker_y];

            for( i = 0; i < ker_y; i++ )
            {
                tdst = rows[i];
                CV_COPY( tdst, trow, width_n, x );
            }
        }

        /* vertical convolution */
        if( crows != ker_height )
            break;

        tdst2 = dst;

        if( width_rest )
        {
            need_copy = width_n < CV_MORPH_ALIGN || y == dst_height - 1;

            if( need_copy )
                tdst2 = tbuf;
            else
                CV_COPY( tbuf + width_n, dst + width_n, CV_MORPH_ALIGN, x );
        }

        trow = rows[ker_y];

        for( x = 0; x < width_n; x += 4 )
        {
            double val0, val1, val2, val3;

            val0 = trow[x]*fmY0;
            val1 = trow[x + 1]*fmY0;
            val2 = trow[x + 2]*fmY0;
            val3 = trow[x + 3]*fmY0;

            for( i = 1; i <= ker_y; i++ )
            {
                float *trow1, *trow2;
                double m = fmaskY[i];
                trow1 = rows[ker_y - i];
                trow2 = rows[ker_y + i];
                val0 += (trow1[x] + trow2[x])*m;
                val1 += (trow1[x+1] + trow2[x+1])*m;
                val2 += (trow1[x+2] + trow2[x+2])*m;
                val3 += (trow1[x+3] + trow2[x+3])*m;
            }

            tdst2[x + 0] = (float)val0;
            tdst2[x + 1] = (float)val1;
            tdst2[x + 2] = (float)val2;
            tdst2[x + 3] = (float)val3;
        }

        if( width_rest )
        {
            if( need_copy )
                CV_COPY( dst, tbuf, width_n, x );
            else
                CV_COPY( dst + width_n, tbuf + width_n, CV_MORPH_ALIGN, x );
        }

        rows[ker_y] = saved_row;

        /* rotate buffer */
        {
            float *t = rows[0];

            CV_COPY( rows, rows + 1, ker_height - 1, i );
            rows[i] = t;
            crows--;
            dst += dstStep;
        }
    }
    while( ++y < dst_height );

    roiSize->height = y;
    state->crows = crows;

    return CV_OK;
}


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

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

static CvStatus CV_STDCALL
icvMedianBlur_8u_CnR( uchar* src, int src_step, uchar* dst, int dst_step,
                      CvSize* roiSize, int* param, int /*stub */ )
{
    #define N  16
    int     zone0[4][N];
    int     zone1[4][N*N];
    int     x, y;
    int     cn = param[0];
    int     m = param[1], n2 = m*m/2;
    int     nx = (m + 1)/2 - 1;
    CvSize  size = *roiSize;
    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;

        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 = -m/2; y < m/2; y++ )
        {
            for( c = 0; c < cn; c++ )
            {
                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, ++ );
            }

            if( (unsigned)y < (unsigned)(size.height-1) )
                src_bottom += src_step;
        }

        for( y = 0; y < size.height; y++, dst_cur += dst_step )
        {
            if( cn == 1 )
            {
                for( k = 0; k < nx; k++ )
                    UPDATE_ACC01( src_bottom[k], 0, ++ );
            }
            else if( cn == 3 )
            {
                for( k = 0; k < nx*3; k += 3 )
                {
                    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_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_bottom[x0+c], c, += (m - nx) );
            }

            if( src_bottom + src_step < src_max )
                src_bottom += src_step;

            // 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( cn == 1 )
            {
                for( k = 0; k < nx; k++ )
                    UPDATE_ACC01( src_top[k], 0, -- );
            }
            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, -- );
                }
            }
            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, -- );
                }
            }

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

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

        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* roiSize, int* param, int /*stub*/ )
{
    CvSize size = *roiSize;

    int channels = param[0];
    double sigma_color = param[1];
    double sigma_space = param[2];

    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
}


static void icvInitSmoothTab( CvFuncTable* blur_no_scale_tab,
                              CvFuncTable* blur_tab, CvFuncTable* gaussian_tab,
                              CvFuncTable* median_tab, CvFuncTable* bilateral_tab )
{
    blur_no_scale_tab->fn_2d[CV_8U] = (void*)icvBlur_8u16s_C1R;
    blur_no_scale_tab->fn_2d[CV_32F] = (void*)icvBlur_32f_CnR;

    blur_tab->fn_2d[CV_8U] = (void*)icvBlur_8u_CnR;
    blur_tab->fn_2d[CV_32F] = (void*)icvBlur_32f_CnR;

    gaussian_tab->fn_2d[CV_8U] = (void*)icvGaussianBlur_8u_CnR;
    gaussian_tab->fn_2d[CV_32F] = (void*)icvGaussianBlur_32f_CnR;

    median_tab->fn_2d[CV_8U] = (void*)icvMedianBlur_8u_CnR;

    bilateral_tab->fn_2d[CV_8U] = (void*)icvBilateralFiltering_8u_CnR;
}


//////////////////////////////// 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 smoothtype,
          int param1, int param2, double param3 )
{
    static CvFuncTable smooth_tab[5];
    static int inittab = 0;
    CvFilterState *state = 0;
    CvMat* temp = 0;

    CV_FUNCNAME( "cvSmooth" );

    __BEGIN__;

    CvFilterFunc func = 0;
    int coi1 = 0, coi2 = 0;
    CvMat srcstub, *src = (CvMat*)srcarr;
    CvMat dststub, *dst = (CvMat*)dstarr;
    CvSize size;
    int type, depth, dsttype;
    int src_step, dst_step;
    int nonlin_param[] = { 0, param1, param2 };
    void* ptr = nonlin_param;
    double sigma = 0;

    if( !inittab )
    {
        icvInitSmoothTab( smooth_tab + CV_BLUR_NO_SCALE, smooth_tab + CV_BLUR,
                          smooth_tab + CV_GAUSSIAN, smooth_tab + CV_MEDIAN,
                          smooth_tab + CV_BILATERAL );
        inittab = 1;
    }

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

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

    type = CV_MAT_TYPE( src->type );
    dsttype = CV_MAT_TYPE( dst->type );

    if( smoothtype > 0 && !CV_ARE_TYPES_EQ( src, dst ) ||
        smoothtype == 0 &&
        !(type == CV_8UC1 && dsttype == CV_16SC1 ||
          type == CV_32FC1 && dsttype == CV_32FC1 ))
        CV_ERROR( CV_StsUnmatchedFormats, "" );

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

    size = cvGetMatSize( src );

    depth = CV_MAT_DEPTH(type);
    nonlin_param[0] = CV_MAT_CN(type);

    if( (unsigned)smoothtype > CV_BILATERAL )
        CV_ERROR( CV_StsBadArg, "Unsupported smoothing type" );

    if( (smoothtype == CV_MEDIAN || smoothtype == CV_BILATERAL) &&
        src->data.ptr == dst->data.ptr )
        CV_ERROR( CV_StsBadArg,
        "Inplace operation is not supported for that type of smoothing" );

    if( smoothtype == 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;
    }
    else /* simple blurring, gaussian, median */
    {
        sigma = param3;

        // automatic detection of kernel size from sigma
        if( smoothtype == CV_GAUSSIAN && param1 == 0 && sigma > 0 )
            param1 = cvRound(sigma*(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,
                "One of aperture dimensions is incorrect (should be >=1 and odd)" );

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

    src_step = src->step;
    dst_step = dst->step;
    if( size.height == 1 )
        src_step = dst_step = CV_STUB_STEP;

    if( CV_MAT_CN(type) == 2 )
        CV_ERROR( CV_BadNumChannels, "Unsupported number of channels" );

    if( (smoothtype == CV_BLUR || smoothtype == CV_MEDIAN) && icvFilterBox_8u_C1R_p &&
        size.width >= param1 && size.height >= param2 && param1 > 1 && param2 > 1 )
    {
        CvSmoothFixedIPPFunc ipp_median_box_func = 0;

        if( smoothtype == CV_BLUR )
        {
            ipp_median_box_func =
                type == CV_8UC1 ? icvFilterBox_8u_C1R_p :
                type == CV_8UC3 ? icvFilterBox_8u_C3R_p :
                type == CV_8UC4 ? icvFilterBox_8u_C4R_p :
                type == CV_32FC1 ? icvFilterBox_32f_C1R_p :
                type == CV_32FC3 ? icvFilterBox_32f_C3R_p :
                type == CV_32FC4 ? icvFilterBox_32f_C4R_p : 0;
        }
        else if( smoothtype == CV_MEDIAN )
        {
            ipp_median_box_func =
                type == CV_8UC1 ? icvFilterMedian_8u_C1R_p :
                type == CV_8UC3 ? icvFilterMedian_8u_C3R_p :
                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 current IPP code etc.
            const uchar* shifted_ptr;
            int y, dy = 0;
            int temp_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(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;
        }
    }
    else if( smoothtype == CV_GAUSSIAN &&
             icvFilterBox_8u_C1R_p /* this is to check that IPP is loaded */ &&
             size.width >= param1*3 && size.height >= param2 && param1 > 1 && param2 > 1 )
    {
        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 );
        int done = 0;

        icvCalcGaussianKernel( ksize.width, sigma, kx );
        if( ksize.width != ksize.height )
            icvCalcGaussianKernel( ksize.height, sigma, ky );
        else
            KY.data.fl = kx;

        CV_CALL( done = icvIPPSepFilter( src, dst, &KX, &KY,
                        cvPoint(ksize.width/2,ksize.height/2)));
        if( done )
            EXIT;
    }

    if( smoothtype <= CV_GAUSSIAN )
    {
        CV_CALL( state = icvSmoothInitAlloc( src->width, depth < CV_32F ? cv32s : cv32f,
            CV_MAT_CN(type), cvSize(param1, smoothtype <= CV_GAUSSIAN ? param2 : param1),
            smoothtype, sigma ));
        ptr = state;
    }

    func = (CvFilterFunc)(smooth_tab[smoothtype].fn_2d[depth]);

    if( !func )
        CV_ERROR( CV_StsUnsupportedFormat, "" );

    IPPI_CALL( func( src->data.ptr, src_step, dst->data.ptr,
                     dst_step, &size, (CvFilterState*)ptr, 0 ));

    __END__;

    cvReleaseMat( &temp );
    icvSmoothFree( &state );
}

/* End of file. */
