/// <summary>
 /// One dimensional Fast Fourier Transform.
 /// </summary>
 /// <param name="data">Data to transform.</param>
 /// <param name="direction">Transformation direction.</param>
 public unsafe static void FFT(ComplexF[] data, Direction direction)
 {
     fixed (ComplexF* dataPtr = data)
     {
         FFT(dataPtr, data.Length, direction);
     }
 }
        /// <summary>
        /// One dimensional Fast Fourier Transform.
        /// </summary>
        /// 
        /// <param name="data">Data to transform.</param>
        /// <param name="length">Array length.</param>
        /// <param name="direction">Transformation direction.</param>
        /// 
        /// <remarks><para><note>The method accepts <paramref name="data"/> array of 2<sup>n</sup> size
        /// only, where <b>n</b> may vary in the [1, 14] range.</note></para></remarks>
        /// 
        /// <exception cref="ArgumentException">Incorrect data length.</exception>
        /// 
        public unsafe static void FFT(ComplexF* data, int length, Direction direction)
        {
            int n = length;
            int m = AForge.Math.Tools.Log2(n);

            // reorder data first
            ReorderData(data, length);

            // compute FFT
            int tn = 1, tm;

            for (int k = 1; k <= m; k++)
            {
                ComplexF[] rotation = FourierTransform.GetComplexRotation(k,  direction);

                tm = tn;
                tn <<= 1;

                for (int i = 0; i < tm; i++)
                {
                    ComplexF t = rotation[i];

                    for (int even = i; even < n; even += tn)
                    {
                        int odd = even + tm;
                        ComplexF* ce = &data[even];
                        ComplexF* co = &data[odd];
                        
                        float tr = co->Re * t.Re - co->Im * t.Im;
                        float ti = co->Re * t.Im + co->Im * t.Re;

                        co->Re = ce->Re - tr;
                        co->Im = ce->Im - ti;

                        ce->Re += tr;
                        ce->Im += ti;
                    }
                }
            }

            if (direction == Direction.Backward)
            {
                for (int i = 0; i < n; i++)
                {
                    data[i].Re /= n;
                    data[i].Im /= n;
                }
            }
        }
        // Get rotation of complex number
        private static ComplexF[] GetComplexRotation(int numberOfBits, Direction direction)
        {
            int directionIndex = (direction == Direction.Forward) ? 1 : 0;

            // check if the array is already calculated
            if (complexRotation[numberOfBits - 1, directionIndex] == null)
            {
                int n = 1 << (numberOfBits - 1);
                float uR = 1.0f;
                float uI = 0.0f;
                double angle = System.Math.PI / n * (-(int)(direction));
                float wR = (float)System.Math.Cos(angle);
                float wI = (float)System.Math.Sin(angle);
                float t;
                ComplexF[] rotation = new ComplexF[n];

                for (int i = 0; i < n; i++)
                {
                    rotation[i] = new ComplexF { Re = uR, Im = uI};
                    t = uR * wI + uI * wR;
                    uR = uR * wR - uI * wI;
                    uI = t;
                }

                complexRotation[numberOfBits - 1, directionIndex] = rotation;
            }
            return complexRotation[numberOfBits - 1, directionIndex];
        }
        // Reorder data for FFT using
        private unsafe static void ReorderData(ComplexF* data, int length)
        {
            int len = length;

            // check data length
            if ((len < minLength) || (len > maxLength) || (!AForge.Math.Tools.IsPowerOf2(len)))
                throw new ArgumentException("Incorrect data length.");

            int[] rBits = GetReversedBits(AForge.Math.Tools.Log2(len));

            for (int i = 0; i < len; i++)
            {
                int s = rBits[i];

                if (s > i)
                {
                    ComplexF t = data[i];
                    data[i] = data[s];
                    data[s] = t;
                }
            }
        }
        /// <summary>
        /// Two dimensional Fast Fourier Transform.
        /// </summary>
        /// <param name="data">Data to transform.</param>
        /// <param name="direction">Transformation direction.</param>
        public unsafe static void FFT2(ComplexF[,] data, Direction direction)
        {
            int width = data.GetLength(1);
            int height = data.GetLength(0);

            fixed (ComplexF* dataPtr = data)
            {
                FFT2(dataPtr, width, height, width, direction);
            }
        }
        /// <summary>
        /// Two dimensional Fast Fourier Transform.
        /// </summary>
        /// <param name="width">Image width.</param> 
        /// <param name="height">Image height.</param>
        /// <param name="stride">Image stride.</param>
        /// <param name="data">Data to transform.</param>
        /// <param name="direction">Transformation direction.</param>
        /// 
        /// <remarks><para><note>The method accepts <paramref name="data"/> array of 2<sup>n</sup> size
        /// only in each dimension, where <b>n</b> may vary in the [1, 14] range. For example, 16x16 array
        /// is valid, but 15x15 is not.</note></para></remarks>
        /// 
        /// <exception cref="ArgumentException">Incorrect data length.</exception>
        /// 
        public unsafe static void FFT2(ComplexF* data, int width, int height, int stride, Direction direction)
        {
            int k = height;
            int n = width;

            // check data size
            if (
                    (!AForge.Math.Tools.IsPowerOf2(k)) ||
                    (!AForge.Math.Tools.IsPowerOf2(n)) ||
                    (k < minLength) || (k > maxLength) ||
                    (n < minLength) || (n > maxLength)
                    )
            {
                throw new ArgumentException("Incorrect data length.");
            }

            // process rows
            ComplexF* dataPtr = data; //get row

            for (int i = 0; i < height; i++)
            {
                // transform it
                FourierTransform.FFT(dataPtr, n, direction);

                dataPtr += stride / sizeof(ComplexF);
            }

            // process columns
            dataPtr = data; //get column 

            fixed (ComplexF* _col = new ComplexF[k])
            {
                ComplexF* col = _col;

                for (int j = 0; j < height; j++)
                {
                    // copy column
                    ComplexF* dataColPtr = &dataPtr[j];
                    for (int i = 0; i < k; i++)
                    {
                        col[i] = *dataColPtr;
                        dataColPtr += stride / sizeof(ComplexF);
                    }

                    // transform it
                    FourierTransform.FFT(col, k, direction);

                    // copy back
                    dataColPtr = &dataPtr[j];
                    for (int i = 0; i < k; i++)
                    {
                        *dataColPtr = col[i];
                        dataColPtr += stride / sizeof(ComplexF);
                    }
                }
            }     
        }
 private static void mulComplex(ref ComplexF sourceA, ref ComplexF sourceB, ref ComplexF destination)
 {
     destination.Re = sourceA.Re * sourceB.Re;
     destination.Im = sourceA.Im * sourceB.Im;
 }
 /// <summary>
 /// Multiplies two complex images element-wise.
 /// </summary>
 /// <param name="imageA">First image.</param>
 /// <param name="imageB">Second image.</param>
 /// <param name="inPlace">
 /// True to save the result in the first image, false otherwise.
 /// <para>If true the returned image is the source image.</para>
 /// </param>
 /// <returns>Multiplied image.</returns>
 public static ComplexF[,] MulComplex(this ComplexF[,] imageA, ComplexF[,] imageB, bool inPlace = false)
 {
     return imageA.Calculate(imageB, mulComplex, inPlace);
 }
 private static void convertGrayToComplex(Gray<float> source, ref ComplexF destination)
 {
     destination.Re = source.Intensity;
 }
 private static void convertComplexToMagnitude(ComplexF source, ref Gray<float> destination)
 {
     destination.Intensity = source.Magnitude();
 }
        /// <summary>
        /// Two dimensional Fast Fourier Transform.
        /// </summary>
        /// <param name="width">Image width.</param> 
        /// <param name="height">Image height.</param>
        /// <param name="stride">Image stride.</param>
        /// <param name="data">Data to transform.</param>
        /// <param name="direction">Transformation direction.</param>
        /// 
        /// <remarks><para><note>The method accepts <paramref name="data"/> array of 2<sup>n</sup> size
        /// only in each dimension, where <b>n</b> may vary in the [1, 14] range. For example, 16x16 array
        /// is valid, but 15x15 is not.</note></para></remarks>
        /// 
        /// <exception cref="ArgumentException">Incorrect data length.</exception>
        /// 
        public static unsafe void FFT2(ComplexF* data, int width, int height, int stride, Direction direction)
        {
            const int MIN_PATCH_SIZE = 32; //how much rows/columns should one thread process

            int k = height;
            int n = width;

            // check data size
            if (
                    (!AForge.Math.Tools.IsPowerOf2(k)) ||
                    (!AForge.Math.Tools.IsPowerOf2(n)) ||
                    (k < minLength) || (k > maxLength) ||
                    (n < minLength) || (n > maxLength)
                    )
            {
                throw new ArgumentException("Incorrect data length.");
            }

            // process rows
            var procRow = new ParallelProcessor<bool, bool>(new Size(1 /*does not matter*/, height),
                                                            () => true,
                                                            (_, __, area) =>
                                                            {
                                                                ComplexF* dataPatchPtr = data + area.Y * stride / sizeof(ComplexF); //get row

                                                                for (int i = 0; i < area.Height; i++)
                                                                {
                                                                    // transform it
                                                                    FourierTransform.FFT(dataPatchPtr, n, direction);

                                                                    dataPatchPtr += stride / sizeof(ComplexF);
                                                                }
                                                            },
                                                            new ParallelOptions2D { ParallelTrigger = (size) => size.Height >= MIN_PATCH_SIZE
                                                                                  /*,ForceSequential = true*/}
                                                              );

            // process columns
            //(y and x are swaped => proc thinks it is diving horizontal pacthes but instead we are using them as vertical ones)
            var procCol = new ParallelProcessor<bool, bool>(new Size(1 /*does not matter*/, width),
                                                            () => true,
                                                            (_, __, area) =>
                                                            {
                                                                ComplexF* dataPatchPtr = &data[area.Y]; //get column

                                                                fixed (ComplexF* _col = new ComplexF[k])
                                                                {
                                                                    ComplexF* col = _col;

                                                                    for (int j = 0; j < area.Height; j++)
                                                                    {
                                                                        // copy column
                                                                        ComplexF* dataColPtr = &dataPatchPtr[j];
                                                                        for (int i = 0; i < k; i++)
                                                                        {
                                                                            col[i] = *dataColPtr;
                                                                            dataColPtr += stride / sizeof(ComplexF);
                                                                        }

                                                                        // transform it
                                                                        FourierTransform.FFT(col, k, direction);

                                                                        // copy back
                                                                        dataColPtr = &dataPatchPtr[j];
                                                                        for (int i = 0; i < k; i++)
                                                                        {
                                                                            *dataColPtr = col[i];
                                                                            dataColPtr += stride / sizeof(ComplexF);
                                                                        }
                                                                    }
                                                                }
                                                            },
                                                            new ParallelOptions2D { ParallelTrigger = (size) => size.Height >= MIN_PATCH_SIZE
                                                                                 /*,ForceSequential = true */}
                                                            );

               procRow.Process(true);
               procCol.Process(true);
        }