/// <summary>
        /// Process the filter on the specified image.
        /// </summary>
        ///
        /// <param name="image">Source image data.</param>
        ///
        protected override unsafe void ProcessFilter(UnmanagedImage image)
        {
            UnmanagedImage bgImage      = null;
            BitmapData     bgLockedData = null;

            // get image size
            int width  = image.Width;
            int height = image.Height;
            int offset = image.Stride - ((image.PixelFormat == PixelFormat.Format8bppIndexed) ? width : width * 3);

            // check if we have provided background
            if ((backgroundImage == null) && (unmanagedBackgroundImage == null))
            {
                // resize image to 1/3 of its original size to make bluring faster
                ResizeBicubic  resizeFilter = new ResizeBicubic((int)width / 3, (int)height / 3);
                UnmanagedImage tempImage    = resizeFilter.Apply(image);

                // create background image from the input image blurring it with Gaussian 5 times
                GaussianBlur blur = new GaussianBlur(5, 21);

                blur.ApplyInPlace(tempImage);
                blur.ApplyInPlace(tempImage);
                blur.ApplyInPlace(tempImage);
                blur.ApplyInPlace(tempImage);
                blur.ApplyInPlace(tempImage);

                // resize the blurred image back to original size
                resizeFilter.NewWidth  = width;
                resizeFilter.NewHeight = height;
                bgImage = resizeFilter.Apply(tempImage);

                tempImage.Dispose( );
            }
            else
            {
                if (backgroundImage != null)
                {
                    // check background image
                    if ((width != backgroundImage.Width) || (height != backgroundImage.Height) || (image.PixelFormat != backgroundImage.PixelFormat))
                    {
                        throw new InvalidImagePropertiesException("Source image and background images must have the same size and pixel format");
                    }

                    // lock background image
                    bgLockedData = backgroundImage.LockBits(
                        new Rectangle(0, 0, width, height),
                        ImageLockMode.ReadOnly, backgroundImage.PixelFormat);

                    bgImage = new UnmanagedImage(bgLockedData);
                }
                else
                {
                    bgImage = unmanagedBackgroundImage;
                }
            }

            // get background image's statistics (mean value is used as correction factor)
            ImageStatistics bgStatistics = new ImageStatistics(bgImage);

            byte *src = (byte *)image.ImageData.ToPointer( );
            byte *bg  = (byte *)bgImage.ImageData.ToPointer( );

            // do the job
            if (image.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                // grayscale image
                double mean = bgStatistics.Gray.Mean;

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src++, bg++)
                    {
                        if (*bg != 0)
                        {
                            *src = (byte)Math.Min(mean * *src / *bg, 255);
                        }
                    }
                    src += offset;
                    bg  += offset;
                }
            }
            else
            {
                // color image
                double meanR = bgStatistics.Red.Mean;
                double meanG = bgStatistics.Green.Mean;
                double meanB = bgStatistics.Blue.Mean;

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src += 3, bg += 3)
                    {
                        // red
                        if (bg[RGB.R] != 0)
                        {
                            src[RGB.R] = (byte)Math.Min(meanR * src[RGB.R] / bg[RGB.R], 255);
                        }
                        // green
                        if (bg[RGB.G] != 0)
                        {
                            src[RGB.G] = (byte)Math.Min(meanG * src[RGB.G] / bg[RGB.G], 255);
                        }
                        // blue
                        if (bg[RGB.B] != 0)
                        {
                            src[RGB.B] = (byte)Math.Min(meanB * src[RGB.B] / bg[RGB.B], 255);
                        }
                    }
                    src += offset;
                    bg  += offset;
                }
            }

            if (backgroundImage != null)
            {
                backgroundImage.UnlockBits(bgLockedData);
            }

            // dispose background image if it was not set manually
            if ((backgroundImage == null) && (unmanagedBackgroundImage == null))
            {
                bgImage.Dispose( );
            }
        }
        /// <summary>
        /// Process the filter on the specified image.
        /// </summary>
        /// 
        /// <param name="image">Source image data.</param>
        ///
        protected override unsafe void ProcessFilter( UnmanagedImage image )
        {
            UnmanagedImage bgImage = null;
            BitmapData bgLockedData = null;

            // get image size
            int width  = image.Width;
            int height = image.Height;
            int offset = image.Stride - ( ( image.PixelFormat == PixelFormat.Format8bppIndexed ) ? width : width * 3 );
            
            // check if we have provided background
            if ( ( backgroundImage == null ) && ( unmanagedBackgroundImage == null ) )
            {
                // resize image to 1/3 of its original size to make bluring faster
                ResizeBicubic resizeFilter = new ResizeBicubic( (int) width / 3, (int) height / 3 );
                UnmanagedImage tempImage = resizeFilter.Apply( image );

                // create background image from the input image blurring it with Gaussian 5 times
                GaussianBlur blur = new GaussianBlur( 5, 21 );

                blur.ApplyInPlace( tempImage );
                blur.ApplyInPlace( tempImage );
                blur.ApplyInPlace( tempImage );
                blur.ApplyInPlace( tempImage );
                blur.ApplyInPlace( tempImage );

                // resize the blurred image back to original size
                resizeFilter.NewWidth  = width;
                resizeFilter.NewHeight = height;
                bgImage = resizeFilter.Apply( tempImage );

                tempImage.Dispose( );
            }
            else
            {
                if ( backgroundImage != null )
                {
                    // check background image
                    if ( ( width != backgroundImage.Width ) || ( height != backgroundImage.Height ) || ( image.PixelFormat != backgroundImage.PixelFormat ) )
                    {
                        throw new InvalidImagePropertiesException( "Source image and background images must have the same size and pixel format" );
                    }

                    // lock background image
                    bgLockedData = backgroundImage.LockBits(
                        new Rectangle( 0, 0, width, height ),
                        ImageLockMode.ReadOnly, backgroundImage.PixelFormat );

                    bgImage = new UnmanagedImage( bgLockedData );
                }
                else
                {
                    bgImage = unmanagedBackgroundImage;
                }
            }

            // get background image's statistics (mean value is used as correction factor)
            ImageStatistics bgStatistics = new ImageStatistics( bgImage );

            byte* src = (byte*) image.ImageData.ToPointer( );
            byte* bg  = (byte*) bgImage.ImageData.ToPointer( );

            // do the job
            if ( image.PixelFormat == PixelFormat.Format8bppIndexed )
            {
                // grayscale image
                double mean = bgStatistics.Gray.Mean;

                for ( int y = 0; y < height; y++ )
                {
                    for ( int x = 0; x < width; x++, src++, bg++ )
                    {
                        if ( *bg != 0 )
                        {
                            *src = (byte) Math.Min( mean * *src / *bg, 255 );
                        }
                    }
                    src += offset;
                    bg  += offset;
                }
            }
            else
            {
                // color image
                double meanR = bgStatistics.Red.Mean;
                double meanG = bgStatistics.Green.Mean;
                double meanB = bgStatistics.Blue.Mean;

                for ( int y = 0; y < height; y++ )
                {
                    for ( int x = 0; x < width; x++, src += 3, bg += 3 )
                    {
                        // red
                        if ( bg[RGB.R] != 0 )
                        {
                            src[RGB.R] = (byte) Math.Min( meanR * src[RGB.R] / bg[RGB.R], 255 );
                        }
                        // green
                        if ( bg[RGB.G] != 0 )
                        {
                            src[RGB.G] = (byte) Math.Min( meanG * src[RGB.G] / bg[RGB.G], 255 );
                        }
                        // blue
                        if ( bg[RGB.B] != 0 )
                        {
                            src[RGB.B] = (byte) Math.Min( meanB * src[RGB.B] / bg[RGB.B], 255 );
                        }
                    }
                    src += offset;
                    bg  += offset;
                }
            }

            if ( backgroundImage != null )
            {
                backgroundImage.UnlockBits( bgLockedData );
            }

            // dispose background image if it was not set manually
            if ( ( backgroundImage == null ) && ( unmanagedBackgroundImage == null ) )
            {
                bgImage.Dispose( );
            }
        }