using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media.Imaging;

namespace FindIT
{
    /// <summary>
    /// Описывает функции, применяемые к изображениям
    /// </summary>
    public static class Functions
    {
        public static int ImageWidth { set; get; }
        public static int ImageHeight { set; get; }

        private static byte[,] computed_colors_hog;
        //private static int[, ,] computed_cells;
        private static WriteableBitmap computed_image;
        public static Random random;

        private static double[] EMPTY_ARRAY;

        public static bool CHANNELS_ALL = true;

        static Functions()
        {
            EMPTY_ARRAY = new double[0];

            random = new Random();
            ImageWidth = 176;
            ImageHeight = 144;
        }

        /// <summary>
        /// Генерация HoG
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        public static double[,] HoG(this WriteableBitmap b)
        {
            HoG(CHANNELS_ALL ? b.Pixels : b.Gray().Pixels, b);
            return GetHistogram(b, 0, 0, b.PixelWidth, b.PixelHeight);
        }

        /// <summary>
        /// Генерация HoG для заданного окна
        /// </summary>
        /// <param name="b"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <returns></returns>
        public static double[] HoG(this WriteableBitmap b, int x, int y, int w, int h, int ch = 0)
        {
            if (b != computed_image)
            {
                computed_image = b;
                computed_image.HoG();
            }
            if (x == w && y == h) return EMPTY_ARRAY;
            return GetHistogram(b, x, y, w, h, ch);
        }

        /// <summary>
        /// Вычисление значений computed_cells и computed_colors
        /// </summary>
        /// <param name="pix"></param>
        /// <returns></returns>
        private static void HoG(int[] pix, WriteableBitmap b)
        {
            int w = b.PixelWidth;
            int h = b.PixelHeight;

            int count_pixels_in_cell = 8;
            int count_bins = 8;

            // Вычисляем направления (углы) градиента изображения и записываем в массив гистограмму по ячейке
            int count_cells_x = w / count_pixels_in_cell; // количество ячеек по x
            int count_cells_y = h / count_pixels_in_cell; // количество ячеек по y

            byte[,] pix2 = new byte[CHANNELS_ALL ? 4 : 1, pix.Length];

            for (int k = 0; k < 4; k++)
            {
                if (!CHANNELS_ALL && k > 0) break;
                for (int j = 0; j < h; j++)
                {
                    int sess_index_y = j * w;
                    int next_index_y = (j + 1 >= h ? j : j + 1) * w;
                    int prev_index_y = (j - 1 <= 0 ? 0 : j - 1) * w;
                    for (int i = 0; i < w; i++)
                    {
                        int i2 = i + 1;
                        if (i2 >= w) i2 = w - 1;
                        int i1 = i - 1;
                        if (i1 < 0) i1 = 0;
                        int Dx = ((pix[i2 + sess_index_y] >> (k == 0 ? 0 : (k - 1) * 8)) & 0xff) - ((pix[i1 + sess_index_y] >> (k == 0 ? 0 : (k - 1) * 8)) & 0xff); // градиент по x
                        int Dy = ((pix[i + next_index_y] >> (k == 0 ? 0 : (k - 1) * 8)) & 0xff) - ((pix[i + prev_index_y] >> (k == 0 ? 0 : (k - 1) * 8)) & 0xff); // градиент по y

                        Dx *= Dx;
                        Dy *= Dy;

                        int bin = (int)(((double)(Math.Atan2(Dy, Dx) + Math.PI) / (2d * Math.PI)) * count_bins);
                        bin--;

                        int px = (i + sess_index_y);
                        pix2[k, px] = (byte)bin;
                    }
                }
            }
            computed_colors_hog = pix2;
        }

        private static double[] GetHistogram(WriteableBitmap b, int x2, int y2, int w2, int h2, int k = 0)
        {
            int w = b.PixelWidth;
            int h = b.PixelHeight;

            float count_pixels_in_cell_x = (float)w2 / 22f;//8;
            float count_pixels_in_cell_y = (float)h2 / 18f;//8;
            int count_cells_in_block = 4;
            int step_block = 2;
            int count_bins = 8;

            // Вычисляем направления (углы) градиента изображения и записываем в массив гистограмму по ячейке
            int count_cells_x = 22;//w / count_pixels_in_cell_x; // количество ячеек по x
            int count_cells_y = 18;// h / count_pixels_in_cell_y; // количество ячеек по y

            int[,] cells = new int[count_bins, count_cells_x * count_cells_y]; // массив, в котором будут храниться гистограммы для каждой ячейки

            for (int j = 0; j < h2; j++)
            {
                int j2 = j + y2;
                int sess_index_y = j2 * w;
                int next_index_y = (j2 + 1 >= h ? j2 : j2 + 1) * w;
                int prev_index_y = (j2 - 1 <= 0 ? 0 : j2 - 1) * w;
                for (int i = 0; i < w2; i++)
                {
                    int i2 = i + x2;
                    int px = (i2 + sess_index_y);
                    float x_cell = (float)i / count_pixels_in_cell_x;
                    float y_cell = (float)j / count_pixels_in_cell_y;
                    int i_x = (int)x_cell;
                    int i_y = (int)y_cell;
                    float nx = x_cell - i_x;
                    float ny = y_cell - i_y;
                    cells[computed_colors_hog[k, px], i_x + i_y * count_cells_x]++;
                }
            }

            // Построение гистограммы

            // Всего ячеек в ширину count_cells_x. Каждый блок состоит из count_cells_in_block ячеек
            // Блоки идут с шагом step_block. Чтобы узнать количество блоков по каждой оси, нужно найти максимальную позицию, которую может занимать блок
            // (т.к. он имеет заданную ширину и он не может выходить за пределы изображения)
            int count_blocks_x = (count_cells_x - count_cells_in_block + step_block) / step_block; // количество блоков по x
            int count_blocks_y = (count_cells_y - count_cells_in_block + step_block) / step_block; // количество блоков по y

            int veclen = count_blocks_x * count_blocks_y; // Всего блоков
            int sz = count_bins * veclen;
            double[] vec = new double[sz]; // Вектор бинов; double потому что будем нормализовать; На каждый блок по count_bins значений - бины гистограммы

            // Перебираем все блоки и формируем итоговый массив
            for (int i = 0; i < veclen; i++)
            {
                int x_block = (i % count_blocks_x) * step_block; // координата ячейки начала блока x
                int y_block = (i / count_blocks_x) * step_block; // координата ячейки начала блока y
                int ind_i = i * count_bins;
                // Перебираем все ячейки из блока и суммируем данные гистограмм каждой ячейки
                for (int y = 0; y < count_cells_in_block; y++)
                {
                    int ind_y = (y + y_block) * count_cells_x;
                    for (int x = 0; x < count_cells_in_block; x++)
                    {
                        int ind_x = x + x_block;
                        for (int k2 = 0; k2 < count_bins; k2++)
                        {
                            vec[k2 + ind_i] += cells[k2, ind_x + ind_y];
                        }
                    }
                }
                // Нормируем вектор
                double norm = 0.001;
                for (int k1 = 0; k1 < count_bins; k1++)
                {
                    norm += Math.Abs(vec[k1 + ind_i]);// *vec[k + ind_i];
                }
                for (int k1 = 0; k1 < count_bins; k1++)
                {
                    vec[k1 + ind_i] /= norm;
                    vec[k1 + ind_i] = Math.Sqrt(vec[k1 + ind_i]);
                }
            }

            return vec;
        }

        private static double[,] GetHistogram(WriteableBitmap b, int x2, int y2, int w2, int h2)
        {
            int w = b.PixelWidth;
            int h = b.PixelHeight;

            float count_pixels_in_cell_x = (float)w2 / 22f;//8;
            float count_pixels_in_cell_y = (float)h2 / 18f;//8;
            int count_cells_in_block = 4;
            int step_block = 2;
            int count_bins = 8;

            // Вычисляем направления (углы) градиента изображения и записываем в массив гистограмму по ячейке
            int count_cells_x = 22;//w / count_pixels_in_cell_x; // количество ячеек по x
            int count_cells_y = 18;// h / count_pixels_in_cell_y; // количество ячеек по y

            int[, ,] cells = new int[4, count_bins, count_cells_x * count_cells_y]; // массив, в котором будут храниться гистограммы для каждой ячейки

            for (int k = 0; k < 4; k++)
            {
                if (!CHANNELS_ALL && k > 0) break;
                for (int j = 0; j < h2; j++)
                {
                    int j2 = j + y2;
                    int sess_index_y = j2 * w;
                    int next_index_y = (j2 + 1 >= h ? j2 : j2 + 1) * w;
                    int prev_index_y = (j2 - 1 <= 0 ? 0 : j2 - 1) * w;
                    for (int i = 0; i < w2; i++)
                    {
                        int i2 = i + x2;
                        int px = (i2 + sess_index_y);
                        float x_cell = (float)i / count_pixels_in_cell_x;
                        float y_cell = (float)j / count_pixels_in_cell_y;
                        int i_x = (int)x_cell;
                        int i_y = (int)y_cell;
                        float nx = x_cell - i_x;
                        float ny = y_cell - i_y;
                        cells[k, computed_colors_hog[k, px], i_x + i_y * count_cells_x]++;
                    }
                }
            }

            // Построение гистограммы

            // Всего ячеек в ширину count_cells_x. Каждый блок состоит из count_cells_in_block ячеек
            // Блоки идут с шагом step_block. Чтобы узнать количество блоков по каждой оси, нужно найти максимальную позицию, которую может занимать блок
            // (т.к. он имеет заданную ширину и он не может выходить за пределы изображения)
            int count_blocks_x = (count_cells_x - count_cells_in_block + step_block) / step_block; // количество блоков по x
            int count_blocks_y = (count_cells_y - count_cells_in_block + step_block) / step_block; // количество блоков по y

            int veclen = count_blocks_x * count_blocks_y; // Всего блоков
            int sz = count_bins * veclen;
            double[,] vec = new double[4, sz]; // Вектор бинов; double потому что будем нормализовать; На каждый блок по count_bins значений - бины гистограммы

            // Перебираем все блоки и формируем итоговый массив
            for (int md = 0; md < 4; md++)
            {
                for (int i = 0; i < veclen; i++)
                {
                    int x_block = (i % count_blocks_x) * step_block; // координата ячейки начала блока x
                    int y_block = (i / count_blocks_x) * step_block; // координата ячейки начала блока y
                    int ind_i = i * count_bins;
                    // Перебираем все ячейки из блока и суммируем данные гистограмм каждой ячейки
                    for (int y = 0; y < count_cells_in_block; y++)
                    {
                        int ind_y = (y + y_block) * count_cells_x;
                        for (int x = 0; x < count_cells_in_block; x++)
                        {
                            int ind_x = x + x_block;
                            for (int k = 0; k < count_bins; k++)
                            {
                                vec[md, k + ind_i] += cells[md, k, ind_x + ind_y];
                            }
                        }
                    }
                    // Нормируем вектор
                    double norm = 0.001;
                    for (int k = 0; k < count_bins; k++)
                    {
                        norm += Math.Abs(vec[md, k + ind_i]);// *vec[k + ind_i];
                    }
                    for (int k = 0; k < count_bins; k++)
                    {
                        vec[md, k + ind_i] /= norm;
                        vec[md, k + ind_i] = Math.Sqrt(vec[md, k + ind_i]);
                    }
                }
            }

            return vec;
        }

        public const byte NO_COMPRESSION = 0, HUFFMAN = 1;//, LZ77 = 2, LZ78 = 3, LZW = 4, LZSS = 5;

        private static int[] digit_powers;

        /// <summary>
        /// Посчитать степени десяти
        /// </summary>
        /// <param name="m"></param>
        private static void BuildDigitPowers(int m)
        {
            digit_powers = new int[m];
            digit_powers[0] = 1;
            for (int i = 1; i < m; i++)
            {
                digit_powers[i] = 10 * digit_powers[i - 1];
            }
        }

        /// <summary>
        /// Поразрядная сортировка. (За O(nk))
        /// </summary>
        /// <param name="A"></param>
        public static void RadixSort<T>(ref T[] A) where T : ISortable
        {
            int min = int.MaxValue;
            for (int i = 0; i < A.Length; i++)
            {
                if (min > A[i].Key) min = A[i].Key;
            }
            for (int i = 0; i < A.Length; i++)
            {
                A[i].Key -= min;
            }

            int m = 0;
            for (int i = 0; i < A.Length; i++)
            {
                if (Math.Abs(A[i].Key) > m) m = A[i].Key;
            }
            m = Math.Abs(m);
            m = (m + "").Length;
            BuildDigitPowers(m);
            RadixSort(ref A, 0, A.Length, 0, 10, m);

            for (int i = 0; i < A.Length; i++)
            {
                A[i].Key += min;
            }
        }

        /// <summary>
        /// Поразрядная сортировка (За O(nk))
        /// </summary>
        /// <param name="A">Массив</param>
        /// <param name="l">Левая граница сортируемой области в массиве</param>
        /// <param name="r">Правая граница</param>
        /// <param name="d">Индекс разряда</param>
        /// <param name="k">Основание системы счисления</param>
        /// <param name="m">Макс. количество цифр</param>
        public static void RadixSort<T>(ref T[] A, int l, int r, int d, int k, int m) where T : ISortable
        {
            if (digit_powers == null || digit_powers != null && digit_powers.Length < m) BuildDigitPowers(m);
            if (d >= m || l >= r) { return; }
            int[] pos = new int[k];
            T[] B = new T[A.Length];
            for (int i = l; i < r; i++)
            {
                pos[Digit(A[i].Key, d)]++;
            }
            for (int i = pos.Length - 1; i > 0; i--)
            {
                pos[i] = pos[i - 1];
            }
            pos[0] = 0;
            for (int i = 1; i < pos.Length; i++)
            {
                pos[i] += pos[i - 1];
            }
            for (int i = l; i < r; i++)
            {
                B[l + pos[Digit(A[i].Key, d)]++] = A[i];
            }
            for (int i = l; i < r; i++)
            {
                A[i] = B[i];
            }
            for (int i = 0; i < pos.Length; i++)
            {
                int left = (i == 0 ? 0 : pos[i - 1]) + l, right = l + pos[i];
                RadixSort(ref A, left, right, d + 1, k, m);
            }
        }

        /// <summary>
        /// Возвращает значение k-го разряда числа
        /// </summary>
        /// <param name="number"></param>
        /// <param name="k"></param>
        /// <returns></returns>
        private static int Digit(int number, int k)
        {
            return (number / digit_powers[digit_powers.Length - k - 1]) % 10;
        }

        /// <summary>
        /// Сортировка подсчётом (За O(n+k))
        /// </summary>
        /// <param name="src"></param>
        public static void CountingSort(ref int[] src)
        {
            int k = int.MaxValue, k2 = int.MinValue;
            for (int i = 0; i < src.Length; i++)
            {
                if (src[i] > k2) k2 = src[i];
                if (src[i] < k) k = src[i];
            }
            CountingSort(ref src, k, k2);
        }

        /// <summary>
        /// Сортировка подсчётом (За O(n+k))
        /// </summary>
        /// <param name="src"></param>
        public static void CountingSort(ref int[] src, int min, int max)
        {
            int[] d = new int[max - min + 1];
            for (int i = 0; i < src.Length; i++)
            {
                d[src[i] - min]++;
            }
            int j = 0;
            for (int i = 0; i < d.Length; i++)
            {
                int z = i + min;
                for (int k = 0; k < d[i]; k++)
                {
                    src[j++] = z;
                }
            }
        }

        /// <summary>
        /// Записать целое число (4 байта) в поток
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="data"></param>
        public static int ReadInt(this Stream stream)
        {
            return (stream.ReadByte() << 24) | (stream.ReadByte() << 16) | (stream.ReadByte() << 8) | stream.ReadByte();
        }

        /// <summary>
        /// Записать целое число (8 байт) в поток
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="data"></param>
        public static long ReadLong(this Stream stream)
        {
            return (stream.ReadInt() * 4294967296L) + (stream.ReadInt());
        }

        /// <summary>
        /// Записать число с плавающей запятой в поток
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="data"></param>
        public static double ReadDouble(this Stream stream)
        {
            return BitConverter.Int64BitsToDouble(stream.ReadLong());
        }

        /// <summary>
        /// Расстояние Хэмминга
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int HammingDistance(ulong a, ulong b)
        {
            int d = 0;
            ulong st = 1;
            for (int i = 0; i < 64; i++, st *= 2)
            {
                if ((a & st) != (b & st)) d++;
            }
            return d;
        }

        /// <summary>
        /// Производная по X
        /// </summary>
        /// <param name="img"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static int DerivativeX(int[] a, int x, int y, int w, int h)
        {
            int x1 = Math.Abs(x - 1);
            int x2 = Math.Abs(x + 1);
            if (x2 >= w) x2 = w - 1;
            return ((a[x2 + y * w]) & 0xff) - ((a[x1 + y * w]) & 0xff);// -2 * a[x + y * w];
        }

        /// <summary>
        /// Производная по Y
        /// </summary>
        /// <param name="img"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static int DerivativeY(int[] a, int x, int y, int w, int h)
        {
            int y1 = Math.Abs(y - 1);
            int y2 = Math.Abs(y + 1);
            if (y2 >= h) y2 = h - 1;
            return ((a[x + y2 * w]) & 0xff) - ((a[x + y1 * w]) & 0xff);// -2 * a[x + y * w];
        }

        /// <summary>
        /// Производная по X
        /// </summary>
        /// <param name="img"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static int DerivativeX(this WriteableBitmap img, int[] a, int x, int y)
        {
            int x1 = Math.Abs(x - 1);
            int x2 = Math.Abs(x + 1);
            if (x2 >= img.PixelWidth) x2 = img.PixelWidth - 1;
            return (a[(x2 + y * img.PixelWidth)] & 0xff) + (a[(x1 + y * img.PixelWidth)] & 0xff) - 2 * (a[(x + y * img.PixelWidth)] & 0xff);
        }

        /// <summary>
        /// Производная по Y
        /// </summary>
        /// <param name="img"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static int DerivativeY(this WriteableBitmap img, int[] a, int x, int y)
        {
            int y1 = Math.Abs(y - 1);
            int y2 = Math.Abs(y + 1);
            if (y2 >= img.PixelHeight) y2 = img.PixelHeight - 1;
            return (a[(x + y2 * img.PixelWidth)] & 0xff) + (a[(x + y1 * img.PixelWidth)] & 0xff) - 2 * (a[(x + y * img.PixelWidth)] & 0xff);
        }

        /// <summary>
        /// Производная по XY
        /// </summary>
        /// <param name="img"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static int DerivativeXY(this WriteableBitmap img, int[] a, int x, int y)
        {
            int x1 = Math.Abs(x - 1);
            int y1 = Math.Abs(y - 1);
            int x2 = Math.Abs(x + 1);
            if (x2 >= img.PixelWidth) x2 = img.PixelWidth - 1;
            int y2 = Math.Abs(y + 1);
            if (y2 >= img.PixelHeight) y2 = img.PixelHeight - 1;
            return (a[(x2 + y2 * img.PixelWidth)] & 0xff) + (a[(x1 + y1 * img.PixelWidth)] & 0xff) - 2 * (a[(x + y * img.PixelWidth)] & 0xff);
        }

        /// <summary>
        /// Детектор Харриса
        /// </summary>
        /// <param name="img"></param>
        /// <param name="pix"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static double Harris(this WriteableBitmap img, int[] pix, int x, int y)
        {
            int Ix = img.DerivativeX(pix, x, y);
            int Iy = img.DerivativeY(pix, x, y);
            int Ixy = img.DerivativeXY(pix, x, y);
            Ix *= Ix;
            Iy *= Iy;
            return Ix * Iy - Ixy * Ixy - 0.06 * (Ix + Iy) * (Ix + Iy);
        }

        /// <summary>
        /// Детектор Харриса
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static List<CPoint> Harris(this WriteableBitmap img, int[] pix)
        {
            List<CPoint> l = new List<CPoint>();
            // int[] pix = img.Gray().Pixels;
            //CPoint[,] dast = new CPoint[ImageWidth / 15, ImageHeight / 15];
            //for (int i = 0; i < dast.GetLength(0); i++)
            //{
            //    for (int j = 0; j < dast.GetLength(1); j++)
            //    {
            //        dast[i, j] = new CPoint(0, 0, 0);
            //    }
            //}
            for (int x = 0; x < img.PixelWidth; x++)
            {
                for (int y = 0; y < img.PixelHeight; y++)
                {
                    int Ix = img.DerivativeX(pix, x, y);
                    int Iy = img.DerivativeY(pix, x, y);
                    int Ixy = img.DerivativeXY(pix, x, y);
                    Ix *= Ix;
                    Iy *= Iy;
                    double Mc = Ix * Iy - Ixy * Ixy - 0.06 * (Ix + Iy) * (Ix + Iy);
                    if (Mc > 150)
                    {
                        //int xI = x / 15;
                        //int yI = y / 15;
                        //if (xI >= dast.GetLength(0)) xI = dast.GetLength(0) - 1;
                        //if (yI >= dast.GetLength(1)) yI = dast.GetLength(1) - 1;
                        //if (dast[xI, yI].Mc < Mc)
                        //{
                        //    dast[xI, yI].X = x;
                        //    dast[xI, yI].Y = y;
                        //    dast[xI, yI].Mc = Mc;
                        //}
                          CPoint c = null;
                          if ((c = l.Find(a => Math.Abs(x - a.X) < 14 && Math.Abs(y - a.Y) < 14)) == null)
                              l.Add(new CPoint(x, y, Mc));
                          else if (c != null && c.Mc < Mc)
                          {
                              c.X += x;
                              c.X /= 2;
                              c.Y += y;
                              c.Y /= 2;
                              c.Mc += Mc;
                              c.Mc /= 2;
                              // l.Add(new CPoint(x, y, Mc));
                          }
                    }
                }
            }
            //foreach (CPoint cp in dast)
            //{
            //    if (cp.Mc > 10)
            //    {
            //        l.Add(cp);
            //    }
            //}
            return l;
        }

        /// <summary>
        /// Вернуть коэффициенты масштаба (для вписывания всего повёрнутого изображения)
        /// </summary>
        /// <param name="w">Ширина изображения</param>
        /// <param name="h">Высота изображения</param>
        /// <param name="scale">Масштаб</param>
        /// <param name="sx">Результат по X</param>
        /// <param name="sy">Результат по Y</param>
        private static void getScale(int w, int h, double scale, out double sx, out double sy)
        {
            // считаем координаты всех углов, находим разницу между максимальными и минимальными значениями, отношения записываем в sx и sy
            double x1, y1; // левый верхний угол
            double x2, y2; // правый верхний угол
            double x3, y3; // левый нижний угол
            double x4, y4; // правый нижний угол

            int x = w / 2, y = h / 2; // центр поворота

            RotatePoint(x, y, 0, 0, scale, out x1, out y1);
            RotatePoint(x, y, w - 1, 0, scale, out x2, out  y2);
            RotatePoint(x, y, 0, h - 1, scale, out  x3, out y3);
            RotatePoint(x, y, w - 1, h - 1, scale, out x4, out y4);

            double x_min = Math.Min(Math.Min(x1, x3), Math.Min(x2, x4));
            double x_max = Math.Max(Math.Max(x1, x3), Math.Max(x2, x4));
            double y_min = Math.Min(Math.Min(y1, y3), Math.Min(y2, y4));
            double y_max = Math.Max(Math.Max(y1, y3), Math.Max(y2, y4));

            sx = (x_max - x_min) / (double)w;
            sy = (y_max - y_min) / (double)h;
        }

        /// <summary>
        /// Повернуть точку
        /// </summary>
        /// <param name="x">X центра</param>
        /// <param name="y">Y центра</param>
        /// <param name="x_">X поворачиваемой точки</param>
        /// <param name="y_">Y поворачиваемой точки</param>
        /// <param name="par">Угол в радианах</param>
        /// <param name="nx">X результат</param>
        /// <param name="ny">Y результат</param>
        private static void RotatePoint(int x, int y, int x_, int y_, double par, out double nx, out double ny)
        {
            double r_ = Math.Sqrt((x_ - x) * (x_ - x) + (y_ - y) * (y_ - y));
            double angle = Math.Atan2(y_ - y, x_ - x); // исходный угол

            // координаты пикселя на новом изображении
            nx = x + r_ * Math.Cos(angle + par);
            ny = y + r_ * Math.Sin(angle + par);
        }

        /// <summary>
        /// Получить время в мс
        /// </summary>
        /// <returns></returns>
        public static double CurrentTimeMillis()
        {
            return (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
        }
    }

    public struct BindedData : ISortable
    {
        public object Data;
        public int Key { get; set; }

        public BindedData(int k, object data)
            : this()
        {
            Key = k;
            Data = data;
        }
    }

    public interface ISortable
    {
        int Key { get; set; }
    }
}
