English 中文 Español Deutsch 日本語 Português
Графика в библиотеке DoEasy (Часть 80): Класс объекта "Кадр геометрической анимации"

Графика в библиотеке DoEasy (Часть 80): Класс объекта "Кадр геометрической анимации"

MetaTrader 5Примеры | 29 июля 2021, 16:47
3 617 2
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

Продолжаем работать над классами, предназначенными для рисования на холсте формы. Мы уже создали классы кадров анимаций, которые позволяют нарисовать один анимационный кадр в заданной области холста, при этом предварительно сохранив фон, на который накладывается рисунок, — чтобы впоследствии можно было восстановить фон при удалении или изменении рисунка. Из этих кадров, заранее созданных, можно будет в дальнейшем составить анимационные последовательности для быстрой смены кадров. Впрочем, и сам отдельный кадр тоже позволяет делать анимации прямо внутри его пространства.

Сегодня мы немного оптимизируем ранее созданные коды этих классов — будем придерживаться концепции, что если есть повторяющиеся участки кода, то всю их логику можно (да и нужно) оформить в отдельную функцию/метод и использовать его вызов. Тем самым код станет более читаемым и уменьшится его объём.

Помимо этой оптимизации, мы создадим класс объекта кадра геометрической анимации. Что это значит?

Для построения различных многоугольников у нас есть уже достаточно методов, но всё же, если нам необходимо нарисовать правильный многоугольник, то куда проще воспользоваться геометрией, чем вручную высчитывать координаты его вершин (а далее мы, может быть, добавим и другие геометрические фигуры, координаты вершин которых можно рассчитать при помощи формул, а не задавать их вручную).

Из Википедии:

Правильный многоугольник — выпуклый многоугольник, у которого равны все стороны и все углы между смежными сторонами (например):

Правильный восьмиугольник

Любой правильный многоугольник можно вписать внутрь окружности. Такая окружность называется описанной — окружность, описывающая многоугольник, при этом все вершины многоугольника лежат на линии окружности.

Описанная окружность

Есть ещё понятие вписанной окружности — это окружность, вписанная в многоугольник, где все грани многоугольника лежат на линии окружности

Вписанная окружность

Такие многоугольники мы рассматривать не будем за исключением квадрата, в который будет вписана окружность, в которой в свою очередь мы будем рисовать правильный многоугольник.

Таким образом, у нас будет прямоугольник (точнее — квадрат), в который вписана окружность, а в эту окружность вписан правильный многоугольник. Квадрат будет представлять собой кадр анимации — его координаты верхнего левого угла и размер (длина) его сторон. В окружность же, диаметр которой будет равен длине стороны квадрата кадра анимации, мы будем вписывать многоугольник, вершины которого будут лежать на линии окружности.
То есть нам для создания правильного многоугольника не нужно будет самостоятельно создавать массивы его координат, а достаточно будет указать нужное количество вершин, координаты левого верхнего угла и длину сторон квадрата.


Доработка классов библиотеки

В файл \MQL5\Include\DoEasy\Data.mqh впишем индекс нового сообщения:

//--- CGCnvElement
   MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,                  // Ошибка! Пустой массив
   MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH,             // Ошибка! Массив-копия ресурса не совпадает с оригиналом
   
//--- CForm

и текст сообщения, соответствующий вновь добавленному индексу:

//--- CGCnvElement
   {"Ошибка! Пустой массив","Error! Empty array"},
   {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"},

//--- CForm


Так как выравнивание (угол привязки) кадров анимации теперь у нас завязаны не только на текстовые кадры анимаций, но и на все остальные — прямоугольные, геометрические, которые будем сегодня создавать, и все новые последующие, то решено было немного изменить название перечисления и его констант — чтобы они не были привязаны к тексту, а именно к кадрам.

В файле \MQL5\Include\DoEasy\Defines.mqh в перечислении углов привязки заменим вхождение текста "TEXT" на текст "FRAME":

//+------------------------------------------------------------------+
//| Данные для работы с графическими элементами                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список способов привязки                                         |
//| (выравнивание текста по горизонтали и вертикали)                 |
//+------------------------------------------------------------------+
enum ENUM_FRAME_ANCHOR
  {
   FRAME_ANCHOR_LEFT_TOP       =  0,                  // Точка привязки кадра в левом верхнем углу ограничивающего прямоугольника
   FRAME_ANCHOR_CENTER_TOP     =  1,                  // Точка привязки кадра по центру верхней стороны ограничивающего прямоугольника
   FRAME_ANCHOR_RIGHT_TOP      =  2,                  // Точка привязки кадра в правом верхнем углу ограничивающего прямоугольника
   FRAME_ANCHOR_LEFT_CENTER    =  4,                  // Точка привязки кадра в по центру левой стороны ограничивающего прямоугольника
   FRAME_ANCHOR_CENTER         =  5,                  // Точка привязки кадра по центру ограничивающего прямоугольника
   FRAME_ANCHOR_RIGHT_CENTER   =  6,                  // Точка привязки кадра в по центру правой стороны ограничивающего прямоугольника
   FRAME_ANCHOR_LEFT_BOTTOM    =  8,                  // Точка привязки кадра в в левом нижнем углу ограничивающего прямоугольника
   FRAME_ANCHOR_CENTER_BOTTOM  =  9,                  // Точка привязки кадра по центру нижней стороны ограничивающего прямоугольника
   FRAME_ANCHOR_RIGHT_BOTTOM   =  10,                 // Точка привязки кадра в правом нижнем углу ограничивающего прямоугольника
  };
//+------------------------------------------------------------------+

В перечисление типов кадров анимаций добавим новый тип — кадр анимаций геометрических фигур:

//+------------------------------------------------------------------+
//| Данные для работы с анимацией графических элементов              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Список  типов кадров анимации                                    |
//+------------------------------------------------------------------+
enum ENUM_ANIMATION_FRAME_TYPE
  {
   ANIMATION_FRAME_TYPE_TEXT,                         // Текстовый кадр анимации
   ANIMATION_FRAME_TYPE_QUAD,                         // Прямоугольный кадр анимации
   ANIMATION_FRAME_TYPE_GEOMETRY,                     // Квадратный кадр анимации геометрических фигур
  };
//+------------------------------------------------------------------+

а в список типов рисуемых фигур добавим закрашенную область, которую мы забыли сделать в прошлых статьях:

//+------------------------------------------------------------------+
//| Список типов рисуемых фигур                                      |
//+------------------------------------------------------------------+
enum ENUM_FIGURE_TYPE
  {
   FIGURE_TYPE_PIXEL,                                 // Точка
   FIGURE_TYPE_PIXEL_AA,                              // Точка со сглаживанием AntiAlliasing
   
   FIGURE_TYPE_LINE_VERTICAL,                         // Вертикальная линия
   FIGURE_TYPE_LINE_VERTICAL_THICK,                   // Вертикальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
   
   FIGURE_TYPE_LINE_HORIZONTAL,                       // Горизонтальная линия
   FIGURE_TYPE_LINE_HORIZONTAL_THICK,                 // Горизонтальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
   
   FIGURE_TYPE_LINE,                                  // Произвольная линия
   FIGURE_TYPE_LINE_AA,                               // Линия со сглаживанием AntiAlliasing
   FIGURE_TYPE_LINE_WU,                               // Линия со сглаживанием WU
   FIGURE_TYPE_LINE_THICK,                            // Отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
   
   FIGURE_TYPE_POLYLINE,                              // Ломаная линия
   FIGURE_TYPE_POLYLINE_AA,                           // Ломаная линия со сглаживанием AntiAlliasing
   FIGURE_TYPE_POLYLINE_WU,                           // Ломаная линия со сглаживанием WU
   FIGURE_TYPE_POLYLINE_SMOOTH,                       // Ломаная линия заданной толщины с использованием двух алгоритмов сглаживания
   FIGURE_TYPE_POLYLINE_THICK,                        // Ломаная линия заданной толщины с использованием алгоритма сглаживания    
   
   FIGURE_TYPE_POLYGON,                               // Многоугольник
   FIGURE_TYPE_POLYGON_FILL,                          // Закрашенный многоугольник
   FIGURE_TYPE_POLYGON_AA,                            // Многоугольник со сглаживанием AntiAlliasing
   FIGURE_TYPE_POLYGON_WU,                            // Многоугольник со сглаживанием WU
   FIGURE_TYPE_POLYGON_SMOOTH,                        // Многоугольник заданной толщины с использованием двух алгоритмов сглаживания
   FIGURE_TYPE_POLYGON_THICK,                         // Многоугольник заданной толщины с использованием алгоритма сглаживания
   
   FIGURE_TYPE_RECTANGLE,                             // Прямоугольник
   FIGURE_TYPE_RECTANGLE_FILL,                        // Закрашенный прямоугольник
   
   FIGURE_TYPE_CIRCLE,                                // Окружность
   FIGURE_TYPE_CIRCLE_FILL,                           // Закрашенный круг
   FIGURE_TYPE_CIRCLE_AA,                             // Окружность со сглаживанием AntiAlliasing
   FIGURE_TYPE_CIRCLE_WU,                             // Окружность со сглаживанием WU
   
   FIGURE_TYPE_TRIANGLE,                              // Треугольник
   FIGURE_TYPE_TRIANGLE_FILL,                         // Закрашенный треугольник
   FIGURE_TYPE_TRIANGLE_AA,                           // Треугольник со сглаживанием AntiAlliasing
   FIGURE_TYPE_TRIANGLE_WU,                           // Треугольник со сглаживанием WU
   
   FIGURE_TYPE_ELLIPSE,                               // Эллипс
   FIGURE_TYPE_ELLIPSE_FILL,                          // Закрашенный эллипс
   FIGURE_TYPE_ELLIPSE_AA,                            // Эллипс со сглаживанием AntiAlliasing
   FIGURE_TYPE_ELLIPSE_WU,                            // Эллипс со сглаживанием WU
   
   FIGURE_TYPE_ARC,                                   // Дуга эллипса
   FIGURE_TYPE_PIE,                                   // Сектор эллипса
   
   FIGURE_TYPE_FILL,                                  // Закрашенная область
   
  };
//+------------------------------------------------------------------+


В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh поменяем название массива для хранения копии графического ресурса на более "говорящее" — просто по причине, что я сам путался в названиях массивов, пытаясь определить, какой же из них предназначен именно для хранения копии изначально созданной формы, и удалим из защищённой секции класса метод, сохраняющий графический ресурс в массив:

//+------------------------------------------------------------------+
//| Класс объекта графического элемента                              |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // Объект класса CCanvas
   CPause            m_pause;                                  // Объект класса "Пауза"
   bool              m_shadow;                                 // Наличие тени
   color             m_chart_color_bg;                         // Цвет фона графика
   uint              m_duplicate_res[];                        // Массив для хранения копии данных ресурса

//--- Возвращает положение курсора относительно (1) всего элемента, (2) активной зоны элемента
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Создаёт (1) структуру объекта, (2) объект из структуры
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
//--- Сохраняет графический ресурс в массив
   bool              ResourceCopy(const string source);

private:

Заменим в листинге класса (а лучше сразу во всех файлах библиотеки) вхождения строк "TEXT_ANCHOR" на строки "FRAME_ANCHOR". Чтобы найти все вхождения во всех файлах библиотеки, достаточно нажать комбинацию клавиш Shift+Ctrl+H и в открывшемся окне ввести такие критерии поиска и замены:


Естественно, в поле "Folder:" путь должен быть указан с учётом расположения вашего редактора.

В публичной секции класса объявим методы для сохранения графического ресурса в массив и восстановления ресурса из массива, и напишем методы для обновления канваса и метод, возвращающий размер массива-копии графического ресурса:

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                   }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value; }
   void              SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value; }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство объекта
   long              GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)        const { return this.m_long_prop[property];                  }
   double            GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];}
   string            GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];}

//--- Возвращает флаг поддержания объектом данного свойства
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property)          { return true;    }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)           { return false;   }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)           { return true;    }

//--- Возвращает себя
   CGCnvElement     *GetObject(void)                                                   { return &this;   }

//--- Сравнивает объекты CGCnvElement между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CGCnvElement между собой по всем свойствам (для поиска равных объектов)
   bool              IsEqual(CGCnvElement* compared_obj) const;

//--- (1) Сохраняет объект в файл, (2), загружает объект из файла
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);

//--- (1) Сохраняет графический ресурс в массив, (2) восстанавливает ресурс из массива
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Создаёт элемент
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const color colour,
                            const uchar opacity,
                            const bool redraw=false);
                                
//--- Возвращает указатель на объект-канвас
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                     }
//--- Устанавливает частоту обновления канваса
   void              SetFrequency(const ulong value)                                   { this.m_pause.SetWaitingMSC(value);         }
//--- Обновляет канвас
   void              CanvasUpdate(const bool redraw=false)                             { this.m_canvas.Update(redraw);              }
//--- Возвращает размер массива-копии графического ресурса
   uint              DuplicateResArraySize(void)                                       { return ::ArraySize(this.m_duplicate_res);  }
   
//--- Обновляет координаты (сдвигает канвас)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Сохраняет изображение в массив
   bool              ImageCopy(const string source,uint &array[]);
   
//--- Изменяет светлоту (1) ARGB, (2) COLOR цвета на указанную величину
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const double change_value);
//--- Изменяет насыщенность (1) ARGB, (2) COLOR цвета на указанную величину
   uint              ChangeColorSaturation(const uint clr,const double change_value);
   color             ChangeColorSaturation(const color colour,const double change_value);
   
protected:

Бывший метод ResourceCopy() теперь называется ResourceStamp():

//+------------------------------------------------------------------+
//| Сохраняет графический ресурс в массив                            |
//+------------------------------------------------------------------+
bool CGCnvElement::ResourceStamp(const string source)
  {
   return this.ImageCopy(DFUN,this.m_duplicate_res);
  }
//+------------------------------------------------------------------+

Метод, восстанавливающий графический ресурс из массива:

//+------------------------------------------------------------------+
//| Восстанавливает графический ресурс из массива                    |
//+------------------------------------------------------------------+
bool CGCnvElement::Reset(void)
  {
//--- Получаем размер массива-копии графического ресурса
   int size=::ArraySize(this.m_duplicate_res);
//--- Если массив пустой - сообщаем об этом и возвращаем false
   if(size==0)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return false;
     }
//--- Если размер массива-копии графического ресурса не совпадает с размером графического ресурса -
//--- сообщаем об этом в журнал и возвращаем false
   if(this.m_canvas.Width()*this.m_canvas.Height()!=size)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH);
      return false;
     }
//--- Устанавливаем индекс массива для установки пикселя изображения
   int n=0;
//--- В цикле по высоте ресурса
   for(int y=0;y<this.m_canvas.Height();y++)
     {
      //--- в цикле по ширине ресурса
      for(int x=0;x<this.m_canvas.Width();x++)
        {
         //--- Восстанавливаем очередной пиксель изображения из массива и увеличиваем индекс массива
         this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]);
         n++;
        }
     }
//--- Обновляем данные на канвасе и возвращаем true
   this.m_canvas.Update(false);
   return true;
  }
//+------------------------------------------------------------------+

Логика метода описана в комментариях к коду. Вкратце — проверяем размер массива-копии ресурса, и если он пустой, или размер копии не совпадает с оригиналом, — сообщаем об ошибке в журнал и уходим из метода. Далее попиксельно копируем из массива-копии все данные на канвас.

Так как мы поменяли название массива-копии ресурса и метода, сохраняющего графический ресурс в этот массив, то нам необходимо внести исправления в файл класса объекта-тени \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.

Исправления касаются только метода GaussianBlur():

//+------------------------------------------------------------------+
//| Размытие по-Гауссу                                               |
//| https://www.mql5.com/ru/articles/1612#chapter4                   |
//+------------------------------------------------------------------+
bool CShadowObj::GaussianBlur(const uint radius)
  {
//---
   int n_nodes=(int)radius*2+1;
//--- Читаем данные графического ресурса, и если не получилось - возвращаем false
   if(!CGCnvElement::ResourceStamp(DFUN))
      return false;
   
//--- Проверяем величину размытия, и если радиус размытия больше половины высоты или ширины - возвращаем false
   if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2)
     {
      ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE));
      return false;
     }
     
//--- Раскладываем данные изображения из ресурса на компоненты цвета a, r, g, b
   int  size=::ArraySize(this.m_duplicate_res);
//--- массивы для хранения компонент цвета A, R, G и B
//--- для горизонтального и вертикального размытия
   uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[];
   uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[];
   
//--- Изменяем размеры массивов компонент под размер массива данных графического ресурса
   if(::ArrayResize(a_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_h_data\"");
      return false;
     }
   if(::ArrayResize(r_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_h_data\"");
      return false;
     }
   if(::ArrayResize(g_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_h_data\"");
      return false;
     }
   if(ArrayResize(b_h_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_h_data\"");
      return false;
     }
   if(::ArrayResize(a_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"a_v_data\"");
      return false;
     }
   if(::ArrayResize(r_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"r_v_data\"");
      return false;
     }
   if(::ArrayResize(g_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"g_v_data\"");
      return false;
     }
   if(::ArrayResize(b_v_data,size)==-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE);
      ::Print(DFUN_ERR_LINE,": \"b_v_data\"");
      return false;
     }
//--- Объявляем массив для хранения весовых коэффициентов размытия и
//--- если не удалось получить массив весовых коэффициентов - возвращаем false
   double weights[];
   if(!this.GetQuadratureWeights(1,n_nodes,weights))
      return false;
      
//--- В массивы компонент цвета записываем компоненты каждого пикселя изображения
   for(int i=0;i<size;i++)
     {
      a_h_data[i]=GETRGBA(this.m_duplicate_res[i]);
      r_h_data[i]=GETRGBR(this.m_duplicate_res[i]);
      g_h_data[i]=GETRGBG(this.m_duplicate_res[i]);
      b_h_data[i]=GETRGBB(this.m_duplicate_res[i]);
     }

//--- Размываем изображение горизонтально (по оси X)
   uint XY; // Координата пикселя в массиве
   double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0;
   int coef=0;
   int j=(int)radius;
   //--- Цикл по ширине изображения
   for(int Y=0;Y<this.Height();Y++)
     {
      //--- Цикл по высоте изображения
      for(uint X=radius;X<this.Width()-radius;X++)
        {
         XY=Y*this.Width()+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         //--- Каждую компоненту цвета умножаем на весовой коэффициент, соответствующий текущему пикселю изображения
         for(int i=-1*j;i<j+1;i=i+1)
           {
            a_temp+=a_h_data[XY+i]*weights[coef];
            r_temp+=r_h_data[XY+i]*weights[coef];
            g_temp+=g_h_data[XY+i]*weights[coef];
            b_temp+=b_h_data[XY+i]*weights[coef];
            coef++;
           }
         //--- Сохраняем в массивы компонент округлённые, рассчитанные по коэффициентам, каждую компоненту цвета
         a_h_data[XY]=(uchar)::round(a_temp);
         r_h_data[XY]=(uchar)::round(r_temp);
         g_h_data[XY]=(uchar)::round(g_temp);
         b_h_data[XY]=(uchar)::round(b_temp);
        }
      //--- Удаляем артефакты размытия слева методом копирования соседних пикселей
      for(uint x=0;x<radius;x++)
        {
         XY=Y*this.Width()+x;
         a_h_data[XY]=a_h_data[Y*this.Width()+radius];
         r_h_data[XY]=r_h_data[Y*this.Width()+radius];
         g_h_data[XY]=g_h_data[Y*this.Width()+radius];
         b_h_data[XY]=b_h_data[Y*this.Width()+radius];
        }
      //--- Удаляем артефакты размытия справа методом копирования соседних пикселей
      for(int x=int(this.Width()-radius);x<this.Width();x++)
        {
         XY=Y*this.Width()+x;
         a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1];
         r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1];
         g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1];
         b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1];
        }
     }

//--- Размываем вертикально (по оси Y) уже размытое горизонтально изображение
   int dxdy=0;
   //--- Цикл по высоте изображения
   for(int X=0;X<this.Width();X++)
     {
      //--- Цикл по ширине изображения
      for(uint Y=radius;Y<this.Height()-radius;Y++)
        {
         XY=Y*this.Width()+X;
         a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0;
         coef=0;
         //--- Каждую компоненту цвета умножаем на весовой коэффициент, соответствующий текущему пикселю изображения
         for(int i=-1*j;i<j+1;i=i+1)
           {
            dxdy=i*(int)this.Width();
            a_temp+=a_h_data[XY+dxdy]*weights[coef];
            r_temp+=r_h_data[XY+dxdy]*weights[coef];
            g_temp+=g_h_data[XY+dxdy]*weights[coef];
            b_temp+=b_h_data[XY+dxdy]*weights[coef];
            coef++;
           }
         //--- Сохраняем в массивы компонент округлённые, рассчитанные по коэффициентам, каждую компоненту цвета
         a_v_data[XY]=(uchar)::round(a_temp);
         r_v_data[XY]=(uchar)::round(r_temp);
         g_v_data[XY]=(uchar)::round(g_temp);
         b_v_data[XY]=(uchar)::round(b_temp);
        }
      //--- Удаляем артефакты размытия сверху методом копирования соседних пикселей
      for(uint y=0;y<radius;y++)
        {
         XY=y*this.Width()+X;
         a_v_data[XY]=a_v_data[X+radius*this.Width()];
         r_v_data[XY]=r_v_data[X+radius*this.Width()];
         g_v_data[XY]=g_v_data[X+radius*this.Width()];
         b_v_data[XY]=b_v_data[X+radius*this.Width()];
        }
      //--- Удаляем артефакты размытия снизу методом копирования соседних пикселей
      for(int y=int(this.Height()-radius);y<this.Height();y++)
        {
         XY=y*this.Width()+X;
         a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()];
         r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()];
         g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()];
         b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()];
        }
     }
     
//--- Записываем в массив данных графического ресурса дважды размытые (горизонтально и вертикально) пиксели изображения
   for(int i=0;i<size;i++)
      this.m_duplicate_res[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]);
//--- Выводим пиксели изображения на канвас в цикле по высоте и ширине изображения из массива данных графического ресурса
   for(int X=0;X<this.Width();X++)
     {
      for(uint Y=radius;Y<this.Height()-radius;Y++)
        {
         XY=Y*this.Width()+X;
         this.m_canvas.PixelSet(X,Y,this.m_duplicate_res[XY]);
        }
     }
//--- Готово
   return true;
  }
//+------------------------------------------------------------------+


Доработаем класс объекта-кадра анимации в файле \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.

В защищённой секции класса объявим метод для записи значений координат и смещения очерчивающего прямоугольника как прошлые — для последующего их использования, и напишем виртуальный метод для сохранения и восстановления фона под изображением:

//+------------------------------------------------------------------+
//| Класс одного кадра анимации                                      |
//+------------------------------------------------------------------+
class CFrame : public CPixelCopier
  {
protected:
   ENUM_ANIMATION_FRAME_TYPE m_frame_figure_type;           // Тип фигуры, рисуемой кадром
   ENUM_FRAME_ANCHOR m_anchor_last;                         // Точка привязки последнего кадра
   double            m_x_last;                              // Координата X верхнего левого угла последнего кадра
   double            m_y_last;                              // Координата Y верхнего левого угла последнего кадра
   int               m_shift_x_prev;                        // Смещение координаты X верхнего левого угла последнего кадра
   int               m_shift_y_prev;                        // Смещение координаты Y верхнего левого угла последнего кадра
//--- Записывает координаты и смещение очерчивающего прямоугольника как прошлые
   void              SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP);
//--- Сохраняет-восстанавливает фон под рисунком
   virtual bool      SaveRestoreBG(void)                    { return false;                     }
public:

Все эти методы — результат оптимизации кода методов рисования фигур в классах, написанных нами в предыдущих статьях.

Виртуальный метод здесь просто возвращает false и должен быть реализован в классах-наследниках (если его реализация во всех наследуемых классах окажется одинаковой, то сделаем этот метод не виртуальным, и только в этом классе). Метод SetLastParams() рассмотрим чуть позже.

В публичной секции класса напишем метод для обнуления массива пикселей:

public:
//--- Обнуляет массив пикселей
   void              ResetArray(void)                       { ::ArrayResize(this.m_array,0);    }
   
//--- Возвращает последнюю (1) точку привязки, координату (2) X, (3) Y,
//--- предыдущее смещение по (4) X и (5) Y, (6) тип фигуры, рисуемой кадром
   ENUM_FRAME_ANCHOR LastAnchor(void)                 const { return this.m_anchor_last;        }
   double            LastX(void)                      const { return this.m_x_last;             }
   double            LastY(void)                      const { return this.m_y_last;             }
   int               LastShiftX(void)                 const { return this.m_shift_x_prev;       }
   int               LastShiftY(void)                 const { return this.m_shift_y_prev;       }
   ENUM_ANIMATION_FRAME_TYPE FrameFigureType(void)    const { return this.m_frame_figure_type;  }
   
//--- Конструктор по умолчанию
                     CFrame();
protected:

Метод просто устанавливает размер массива пикселей в нулевое значение. Это необходимо для того, чтобы корректно обрабатывать изменение размеров очерчивающего прямоугольника, так как в методе, сохраняющем фон для последующего его восстановления сначала проверяется размер этого массива, и если он нулевой, то фон сохраняется, в противном случае считается, что фон ранее сохранён при корректных значениях координат и размеров сохраняемой области. Соответственно, если мы меняем рисуемую фигуру, то массив необходимо сбросить в ноль — иначе фон под новым рисунком сохранён не будет, а потом ещё и восстановится совсем другой фон из совсем другой области (сохранённый ранее — до изменения размеров, координат и вида рисуемой фигуры).

В защищённой секции класса объявим конструктор для класса — кадра анимации геометрических фигур, который сегодня будем создавать и тестировать:

protected:
//--- Конструктор текстового кадра
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const string text,
                            CGCnvElement *element);
//--- Конструктор прямоугольного кадра
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            CGCnvElement *element);
//--- Конструктор геометрического кадра
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const int len,
                            CGCnvElement *element);
  };
//+------------------------------------------------------------------+

В конструктор класса, по аналогии с ранее созданными другими наследуемыми классами, будем передавать идентификатор объекта, координаты X и Y верхнего левого угла кадра, длину сторон квадратного кадра и указатель на графический элемент, из которого производится создание нового объекта.

Реализация конструктора объекта-кадра геометрической анимации:

//+------------------------------------------------------------------+
//| Конструктор геометрических кадров                                |
//+------------------------------------------------------------------+
CFrame::CFrame(const int id,const int x,const int y,const int len,CGCnvElement *element) : CPixelCopier(id,x,y,len,len,element)
  {
   this.m_frame_figure_type=ANIMATION_FRAME_TYPE_GEOMETRY;
   this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP;
   this.m_x_last=x;
   this.m_y_last=y;
   this.m_shift_x_prev=0;
   this.m_shift_y_prev=0;
  }
//+------------------------------------------------------------------+

В списке инициализации передаём все необходимые параметры в конструктор родительского класса, а в теле класса записываем тип фигуры как ANIMATION_FRAME_TYPE_GEOMETRY, добавленный нами сегодня в список типов кадров анимации. Остальные параметры инициализируются аналогично ранее рассмотренным конструкторам классов текстовой и прямоугольной анимации.

Метод, записывающий координаты и смещение очерчивающего прямоугольника как прошлые:

//+------------------------------------------------------------------+
//| Записывает координаты и смещение                                 |
//| очерчивающего прямоугольника как прошлые                         |
//+------------------------------------------------------------------+
void CFrame::SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP)
  {
   this.m_anchor_last=anchor;
   this.m_x_last=quad_x;
   this.m_y_last=quad_y;
   this.m_shift_x_prev=shift_x;
   this.m_shift_y_prev=shift_y;
  }
//+------------------------------------------------------------------+

В этот метод перенесён постоянно повторяющийся участок кода из методов рисования фигур с сохранением и восстановлением фона формы, рассмотренных нами в предыдущих статьях.

Доработаем классы-наследники класса CFrame.

Откроем файл класса прямоугольной анимации \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh и внесём в него необходимые изменения.

В приватной секции класса объявим две переменные для хранения смещений координат очерчивающего прямоугольника и объявим виртуальный метод для сохранения и восстановления фона под рисунком:

//+------------------------------------------------------------------+
//| Класс одного кадра прямоугольной анимации                        |
//+------------------------------------------------------------------+
class CFrameQuad : public CFrame
  {
private:
   double            m_quad_x;                                 // X-координата охватывающего фигуру прямоугольника
   double            m_quad_y;                                 // Y-координата охватывающего фигуру прямоугольника
   uint              m_quad_width;                             // Ширина охватывающего фигуру прямоугольника
   uint              m_quad_height;                            // Высота охватывающего фигуру прямоугольника
   int               m_shift_x;                                // Смещение координаты X охватывающего фигуру прямоугольника
   int               m_shift_y;                                // Смещение координаты Y охватывающего фигуру прямоугольника
//--- Сохраняет-восстанавливает фон под рисунком
   virtual bool      SaveRestoreBG(void);
public:

В публичной секции класса дополним реализацию параметрического конструктора — теперь в его теле будут инициализироваться все переменные класса (ранее они не были инициализированы, что неверно):

public:
//--- Конструкторы
                     CFrameQuad() {;}
                     CFrameQuad(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element)
                       {
                        this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP;
                        this.m_quad_x=0;
                        this.m_quad_y=0;
                        this.m_quad_width=0;
                        this.m_quad_height=0;
                        this.m_shift_x=0;
                        this.m_shift_y=0;
                       }

Посмотрим, какими у нас были методы рисования с сохранением/восстановлением фона на примере метода рисования точки:

//+------------------------------------------------------------------+
//| Устанавливает цвет точки с указанными координатами               |
//+------------------------------------------------------------------+
bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false)
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_quad_x=x;
   this.m_quad_y=y;
//--- Установим ширину и высоту очерчивающего прямоугольника рисунка (это и будет размером сохраняемой области)
   this.m_quad_width=1;
   this.m_quad_height=1;
   
//--- Рассчитаем смещения координат для сохраняемой области в зависимости от точки привязки
   int shift_x=0,shift_y=0;
   this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,TEXT_ANCHOR_LEFT_TOP,shift_x,shift_y);
//--- Если массив пикселей не пустой - значит уже сохраняли фон под рисунком -
//--- восстановим фон, который ранее был сохранён (по прошлым координатам и смещениям)
   if(::ArraySize(this.m_array)>0)
     {
      if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev)))
         return false;
     }
//--- Если область фона с рассчитанными координатами и размерами под под будущим рисунком успешно сохранена
   if(!CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+shift_x),int(this.m_quad_y+shift_y),this.m_quad_width,this.m_quad_height))
      return false;

//--- Нарисуем фигуру и обновим элемент
   this.m_element.SetPixel(x,y,clr,opacity);
   this.m_element.Update(redraw);
   this.m_anchor_last=TEXT_ANCHOR_LEFT_TOP;
   this.m_x_last=this.m_quad_x;
   this.m_y_last=this.m_quad_y;
   this.m_shift_x_prev=shift_x;
   this.m_shift_y_prev=shift_y;
   return true;
  }
//+------------------------------------------------------------------+

Выделенные цветом участки кода мы теперь можем заменить новыми, написанными сегодня, методами. Так теперь будет выглядеть этот метод:

//+------------------------------------------------------------------+
//| Устанавливает цвет точки с указанными координатами               |
//+------------------------------------------------------------------+
bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false)
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_quad_x=x;
   this.m_quad_y=y;
//--- Установим ширину и высоту очерчивающего прямоугольника рисунка (это и будет размером сохраняемой области)
   this.m_quad_width=1;
   this.m_quad_height=1;
   
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем фигуру и обновим элемент
   this.m_element.SetPixel(x,y,clr,opacity);
   this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Как видим, замена указанных участков кода на вызов новых методов значительно укоротила код и сделала его более читаемым. Идентичные изменения проведены во всех методах рисования фигур с сохранением и восстановлением фона. Так как методов много, и во всех них сделаны одинаковые изменения, то не будем рассматривать их полный список — с ними можно ознакомиться в прикреплённых к статье файлах.

Остановимся лишь на методах рисования эллипсов. Как вы помните, мы в прошлой статье не рисовали эллипсы, так как в CCanvas есть место потенициального деления на ноль. Это происходит в случае, если в метод переданы одинаковые координаты x1 и x2 или y1 и y2 прямоугольника, в котором рисуется эллипс. Поэтому в таком случае нам необходимо скорректировать значения одинаковых координат в случае, если они равны:

//+------------------------------------------------------------------+
//| Рисует эллипс по двум точкам с использованием                    |
//| алгоритма сглаживания AntiAliasing                               |
//+------------------------------------------------------------------+
bool CFrameQuad::DrawEllipseAAOnBG(const double x1,               // Координата X первой точки, определяющей эллипс
                                   const double y1,               // Координата Y первой точки, определяющей эллипс
                                   const double x2,               // Координата X второй точки, определяющей эллипс
                                   const double y2,               // Координата Y второй точки, определяющей эллипс
                                   const color  clr,              // Цвет
                                   const uchar  opacity=255,      // Непрозрачность
                                   const bool   redraw=false,     // Флаг перерисовки чарта
                                   const uint   style=UINT_MAX)   // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
//--- Получим минимальную и максимальную координаты
   double xn1=::fmin(x1,x2);
   double xn2=::fmax(x1,x2);
   double yn1=::fmin(y1,y2);
   double yn2=::fmax(y1,y2);
   if(xn2==xn1)
      xn2=xn1+0.1;
   if(yn2==yn1)
      yn2=yn1+0.1;
//--- Установим координаты очерчивающего прямоугольника
   this.m_quad_x=xn1-1;
   this.m_quad_y=yn1-1;
//--- Установим ширину и высоту очерчивающего прямоугольника рисунка (это и будет размером сохраняемой области)
   this.m_quad_width=int(::ceil((xn2-xn1)+1))+2;
   this.m_quad_height=int(::ceil((yn2-yn1)+1))+2;
//--- Скорректируем ширину и высоту очерчивающего прямоугольника
   if(this.m_quad_width<3)
      this.m_quad_width=3;
   if(this.m_quad_height<3)
      this.m_quad_height=3;
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем фигуру и обновим элемент
   this.m_element.DrawEllipseAA(xn1,yn1,xn2,yn2,clr,opacity,style);
   this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Рисует эллипс по двум точкам с использованием                    |
//| алгоритма сглаживания Wu                                         |
//+------------------------------------------------------------------+
bool CFrameQuad::DrawEllipseWuOnBG(const int   x1,             // Координата X первой точки, определяющей эллипс
                                   const int   y1,             // Координата Y первой точки, определяющей эллипс
                                   const int   x2,             // Координата X второй точки, определяющей эллипс
                                   const int   y2,             // Координата Y второй точки, определяющей эллипс
                                   const color clr,            // Цвет
                                   const uchar opacity=255,    // Непрозрачность
                                   const bool  redraw=false,   // Флаг перерисовки чарта
                                   const uint  style=UINT_MAX) // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
//--- Получим минимальную и максимальную координаты
   double xn1=::fmin(x1,x2);
   double xn2=::fmax(x1,x2);
   double yn1=::fmin(y1,y2);
   double yn2=::fmax(y1,y2);
   if(xn2==xn1)
      xn2=xn1+0.1;
   if(yn2==yn1)
      yn2=yn1+0.1;
//--- Установим координаты очерчивающего прямоугольника
   this.m_quad_x=xn1-1;
   this.m_quad_y=yn1-1;
//--- Установим ширину и высоту очерчивающего прямоугольника рисунка (это и будет размером сохраняемой области)
   this.m_quad_width=int(::ceil((xn2-xn1)+1))+2;
   this.m_quad_height=int(::ceil((yn2-yn1)+1))+2;
//--- Скорректируем ширину и высоту очерчивающего прямоугольника
   if(this.m_quad_width<3)
      this.m_quad_width=3;

   if(this.m_quad_height<3)
      this.m_quad_height=3;
  
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем фигуру и обновим элемент
   this.m_element.DrawEllipseWu((int)xn1,(int)yn1,(int)xn2,(int)yn2,clr,opacity,style);
   this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Метод, сохраняющий и восстанавливающий фон под рисунком:

//+------------------------------------------------------------------+
//| Сохраняет-восстанавливает фон под рисунком                       |
//+------------------------------------------------------------------+
bool CFrameQuad::SaveRestoreBG(void)
  {
//--- Рассчитаем смещения координат для сохраняемой области в зависимости от точки привязки
   this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y);

//--- Если массив пикселей не пустой - значит уже сохраняли фон под рисунком -
//--- восстановим фон, который ранее был сохранён (по прошлым координатам и смещениям)
   if(::ArraySize(this.m_array)>0)
     {
      if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev)))
         return false;
     }
//--- Возвращаем результат сохранения области фона с рассчитанными координатами и размерами под будущим рисунком
   return CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+this.m_shift_x),int(this.m_quad_y+this.m_shift_y),this.m_quad_width,this.m_quad_height);
  }
//+------------------------------------------------------------------+

В метод просто перенесён постоянно повторяющийся блок кода из методов рисования фигур с сохранением и восстановлением фона.

В файле \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh у нас минимальные изменения — просто заменим в двух местах кода строки "ENUM_TEXT_ANCHOR" на "ENUM_FRAME_ANCHOR":

//+------------------------------------------------------------------+
//| Класс одного кадра текстовой анимации                            |
//+------------------------------------------------------------------+
class CFrameText : public CFrame
  {
private:

public:
//--- Выводит текст на фон с сохранением и восстановлением фона
   bool              TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false);

//--- Конструкторы
                     CFrameText() {;}
                     CFrameText(const int id,CGCnvElement *element) : CFrame(id,0,0,"",element) {}
  };
//+------------------------------------------------------------------+
//| Выводит текст на фон с сохранением и восстановлением фона        |
//+------------------------------------------------------------------+
bool CFrameText::TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false)
  {


Класс объекта кадра геометрической анимации

Класс объекта кадра геометрической анимации по своей логике не будет особо отличаться от двух своих предшественников — объектов кадров текстовой и прямоугольной анимации. Нам лишь необходимо будет создать метод, рассчитывающий координаты вершин многоугольника на окружности в зависимости от количества вершин многоугольника:

Формулы расчёта декартовых координат правильного многоугольника:

Пусть xc и yc — координаты центра, а R — радиус описанной вокруг правильного многоугольника окружности, ϕ0 — угловая координата первой вершины относительно центра, тогда декартовы координаты вершин правильного n-угольника определяются формулами:


где i принимает значения от 0 до n−1

В папке \MQL5\Include\DoEasy\Objects\Graph\Animations\ создадим новый файл FrameGeometry.mqh класса CFrameGeometry.
К файлу должен быть подключен файл класса объекта-кадра анимации, и от него же класс должен быть унаследован:

//+------------------------------------------------------------------+
//|                                                FrameGeometry.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Frame.mqh"
//+------------------------------------------------------------------+
//| Класс одного кадра прямоугольной анимации                        |
//+------------------------------------------------------------------+
class CFrameGeometry : public CFrame
  {
  }

Рассмотрим определение тела класса целиком, где в приватной секции объявлены все переменные класса и виртуальный метод для сохранения и восстановления фона под рисунком (метод рассматривался нами выше в контексте класса объекта прямоугольной анимации, и является просто переносом повторяющихся блоков кода из методов рисования фигур из предыдущих статей). Также в приватной секции объявлен метод для расчёта координат правильного многоугольника.
В публичной секции класса расположены конструкторы (по умолчанию и параметрический) и методы рисования правильных многоугольников — простых, закрашенных и со сглаживанием:

//+------------------------------------------------------------------+
//| Класс одного кадра прямоугольной анимации                        |
//+------------------------------------------------------------------+
class CFrameGeometry : public CFrame
  {
private:
   double            m_square_x;                               // X-координата охватывающего фигуру квадрата
   double            m_square_y;                               // Y-координата охватывающего фигуру квадрата
   uint              m_square_length;                          // Длина сторон охватывающего фигуру квадрата
   int               m_shift_x;                                // Смещение координаты X охватывающего фигуру квадрата
   int               m_shift_y;                                // Смещение координаты Y охватывающего фигуру квадрата
   int               m_array_x[];                              // Массив X-координат фигуры
   int               m_array_y[];                              // Массив Y-координат фигуры
//--- Сохраняет-восстанавливает фон под рисунком
   virtual bool      SaveRestoreBG(void);
   
//--- Рассчитывает координаты правильного многоугольника, строящегося в описанной окружности, вписанной в квадрат
   void              CoordsNgon(const int N,                   // Число вершин многоугольника
                                const int coord_x,             // Координата X левого-верхнего угла квадрата, в который будет вписана окружность
                                const int coord_y,             // Координата Y левого-верхнего угла квадрата, во вписанной окружности которого будет строиться многоугольник
                                const int len,                 // Длина сторон квадрата
                                const double angle);           // Угол, на который нужно повернуть многоугольник, который строится от точки 0, находящейся справа от центра окружности
public:
//--- Конструкторы
                     CFrameGeometry() {;}
                     CFrameGeometry(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element)
                       {
                        ::ArrayResize(this.m_array_x,0);
                        ::ArrayResize(this.m_array_y,0);
                        this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP;
                        this.m_square_x=0;
                        this.m_square_y=0;
                        this.m_square_length=0;
                        this.m_shift_x=0;
                        this.m_shift_y=0;
                       }
//--- Деструктор
                    ~CFrameGeometry()                             { ::ArrayFree(this.m_array_x); ::ArrayFree(this.m_array_y); }
                    
//+------------------------------------------------------------------+
//| Методы рисования правильных многоугольников                      |
//+------------------------------------------------------------------+
//--- Рисует правильный многоугольник без сглаживания
   bool              DrawNgonOnBG(const int    N,                          // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const bool   redraw=false);              // Флаг перерисовки чарта
                                  
//--- Рисует правильный закрашенный многоугольник
   bool              DrawNgonFillOnBG(const    int N,                      // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const bool   redraw=false);              // Флаг перерисовки чарта
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания AntiAliasing
   bool              DrawNgonAAOnBG(const int  N,                          // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const bool   redraw=false,               // Флаг перерисовки чарта
                                  const uint   style=UINT_MAX);            // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания Wu
   bool              DrawNgonWuOnBG(const int  N,                          // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const bool   redraw=false,               // Флаг перерисовки чарта
                                  const uint   style=UINT_MAX);            // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       
//--- Рисует  правильный многоугольник заданной толщины с использованием двух алгоритмов сглаживания последовательно.
//--- Сначала на основе кривых Безье сглаживаются отдельные отрезки.
//--- Затем для повышения качества отрисовки к построенному из этих отрезков многоугольнику применяется растровый алгоритм сглаживания. 
   bool              DrawNgonSmoothOnBG(const  int N,                      // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const int    size,                       // Толщина линии
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const double tension=0.5,                // Значение параметра сглаживания
                                  const double step=10,                    // Шаг аппроксимации
                                  const bool   redraw=false,               // Флаг перерисовки чарта
                                  const ENUM_LINE_STYLE style=STYLE_SOLID, // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                  const ENUM_LINE_END end_style=LINE_END_ROUND);// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       
//--- Рисует правильный многоугольник заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   bool              DrawNgonThickOnBG(const   int N,                      // Число вершин многоугольника
                                  const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                  const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                  const int    len,                        // Длина сторон кадра
                                  const double angle,                      // Угол поворота многоугольника
                                  const int    size,                       // толщина линии
                                  const color  clr,                        // Цвет
                                  const uchar  opacity=255,                // Непрозрачность
                                  const bool   redraw=false,               // Флаг перерисовки чарта
                                  const uint   style=STYLE_SOLID,          // стиль линии
                                  ENUM_LINE_END end_style=LINE_END_ROUND); // стиль концов линии
                                  
  };
//+------------------------------------------------------------------+

Рассмотрим реализацию некоторых методов класса.

Метод, рисующий правильный многоугольник:

//+------------------------------------------------------------------+
//| Рисует правильный многоугольник                                  |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonOnBG(const int N,
                                  const int coord_x,
                                  const int coord_y,
                                  const int len,
                                  const double angle,
                                  const color clr,
                                  const uchar opacity=255,
                                  const bool redraw=false)
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygon(this.m_array_x,this.m_array_y,clr,opacity);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Как видим, всё, чем отличается этот метод от похожих методов рисования многоугольников из предыдущих классов (класс прямоугольного кадра анимации), — это то, что сюда не передаются заранее подготовленные массивы координат вершин многоугольника, а передаются количество вершин многоугольника и координаты верхнего левого угла квадратного кадра, в котором будет рисоваться многоугольник, и уже в самом методе вызывается метод, рассчитывающий координаты вершин многоугольника по количеству его вершин, координат, радиуса окружности и угла вращения, в котором заполняются массивы координат вершин X и Y. И далее просто рисуется соответствующий методу многоугольник при помощи класса CCanvas.

Для сравнения — такой же метод, рисующий закрашенный многоугольник:

//+------------------------------------------------------------------+
//| Рисует правильный закрашенный многоугольник                      |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonFillOnBG(const    int N,                   // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const bool   redraw=false)               // Флаг перерисовки чарта
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+

Отличие от первого метода — лишь вызов метода рисования закрашенного многоугольника.

Остальные методы практически идентичны двум вышерассмотренным, за исключением некоторых особенностей расчёта координат очерчивающего прямоугольника для рисования многоугольника с заданной толщиной линии — там нужно учитывать при расчёте координат и размеров очерчивающего прямоугольника значение толщины рисуемой линии.

Остальные методы рисования правильных многоугольников:

//+------------------------------------------------------------------+
//| Рисует правильный закрашенный многоугольник                      |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonFillOnBG(const    int N,                   // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const bool   redraw=false)               // Флаг перерисовки чарта
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник с использованием                 |
//| алгоритма сглаживания AntiAliasing                               |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonAAOnBG(const int  N,                          // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const bool   redraw=false,               // Флаг перерисовки чарта
                                       const uint   style=UINT_MAX)             // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonAA(this.m_array_x,this.m_array_y,clr,opacity,style);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник с использованием                 |
//| алгоритма сглаживания Wu                                         |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonWuOnBG(const int  N,                          // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const bool   redraw=false,               // Флаг перерисовки чарта
                                       const uint   style=UINT_MAX)             // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonWu(this.m_array_x,this.m_array_y,clr,opacity,style);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Рисует  правильный многоугольник заданной толщины                |
//| с использованием двух алгоритмов сглаживания последовательно.    |
//| Сначала на основе кривых Безье сглаживаются отдельные отрезки.   |
//| Затем для повышения качества отрисовки к построенному            |
//| из этих отрезков многоугольнику применяется                      |
//| растровый алгоритм сглаживания.                                  |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonSmoothOnBG(const  int N,                      // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const int    size,                       // Толщина линии
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const double tension=0.5,                // Значение параметра сглаживания
                                       const double step=10,                    // Шаг аппроксимации
                                       const bool   redraw=false,               // Флаг перерисовки чарта
                                       const ENUM_LINE_STYLE style=STYLE_SOLID, // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                                       const ENUM_LINE_END end_style=LINE_END_ROUND)// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
  {
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonSmooth(this.m_array_x,this.m_array_y,size,clr,opacity,tension,step,style,end_style);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник заданной толщины с использованием|
//| алгоритма сглаживания с предварительной фильтрацией              |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonThickOnBG(const   int N,                      // Число вершин многоугольника
                                       const int    coord_x,                    // Координата X левого-верхнего угла кадра
                                       const int    coord_y,                    // Координата Y левого-верхнего угла кадра
                                       const int    len,                        // Длина сторон кадра
                                       const double angle,                      // Угол поворота многоугольника
                                       const int    size,                       // толщина линии
                                       const color  clr,                        // Цвет
                                       const uchar  opacity=255,                // Непрозрачность
                                       const bool   redraw=false,               // Флаг перерисовки чарта
                                       const uint   style=STYLE_SOLID,          // стиль линии
                                       ENUM_LINE_END end_style=LINE_END_ROUND) // стиль концов линии
  {
//--- Рассчитаем корректировку координат очерчивающего прямоугольника в зависимости от размера линии
   int correct=int(::ceil((double)size/2.0))+1;
//--- Установим координаты очерчивающего прямоугольника
   this.m_square_x=coord_x-correct;
   this.m_square_y=coord_y-correct;
//--- Установим ширину и высоту квадратного кадра (это и будет размером сохраняемой области)
   this.m_square_length=len+correct*2;
//--- Рассчитаем координаты многоугольника на окружности
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Восстановим ранее сохранённый фон и сохраним новый
   if(!this.SaveRestoreBG())
      return false;
      
//--- Нарисуем многоугольник, вписанный в окружность, и обновим элемент
   this.m_element.DrawPolygonThick(this.m_array_x,this.m_array_y,size,clr,opacity,style,end_style);
   this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y);
   this.m_element.Update(redraw);
   return true;
  }
//+------------------------------------------------------------------+


Виртуальный метод, сохраняющий и восстанавливающий фон под рисунком:

//+------------------------------------------------------------------+
//| Сохраняет-восстанавливает фон под рисунком                       |
//+------------------------------------------------------------------+
bool CFrameGeometry::SaveRestoreBG(void)
  {
//--- Рассчитаем смещения координат для сохраняемой области в зависимости от точки привязки
   this.m_element.GetShiftXYbySize(this.m_square_length,this.m_square_length,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y);
//--- Если массив пикселей не пустой - значит уже сохраняли фон под рисунком -
//--- восстановим фон, который ранее был сохранён (по прошлым координатам и смещениям)
   if(::ArraySize(this.m_array)>0)
     {
      if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev)))
         return false;
     }
//--- Возвращаем результат сохранения области фона с рассчитанными координатами и размерами под будущим рисунком
   return CPixelCopier::CopyImgDataToArray(int(this.m_square_x+this.m_shift_x),int(this.m_square_y+this.m_shift_y),this.m_square_length,this.m_square_length);
  }
//+------------------------------------------------------------------+

Это перенесённый повторяющийся блок кода из методов рисования фигур из прошлой статьи.

Метод, рассчитывающий координаты правильного многоугольника, вписанного в окружность:

//+------------------------------------------------------------------+
//| Рассчитывает координаты правильного многоугольника               |
//+------------------------------------------------------------------+
void CFrameGeometry::CoordsNgon(const int N,          // Число вершин многоугольника
                                const int coord_x,    // Координата X левого-верхнего угла квадрата, в который будет вписана окружность
                                const int coord_y,    // Координата Y левого-верхнего угла квадрата, во вписанной окружности которого будет строиться многоугольник
                                const int len,        // Длина сторон квадрата, в который будет вписан многоугольник
                                const double angle)   // Угол, на который нужно повернуть многоугольник, который строится от точки 0, находящейся справа от центра окружности
  {
//--- Если сторон меньше трёх - будет три
   int n=(N<3 ? 3 : N);
//--- Устанавливаем размеры массивов координат в соответствии с количеством вершин
   ::ArrayResize(this.m_array_x,n);
   ::ArrayResize(this.m_array_y,n);
//--- Рассчитаем радиус описанной окружности
   double R=(double)len/2.0;
//--- Координаты X и Y центра окружности
   double xc=coord_x+R;
   double yc=coord_y+R;
//--- Рассчитаем угол наклона многоугольника в градусах
   double grad=angle*M_PI/180.0;
//--- В цикле по количеству вершин рассчитаем координаты каждой очередной вершины многоугольника
   for(int i=0; i<n; i++)
     {
      //--- Угол текущей вершины многоугольника с поворотом в градусах
      double a=2.0*M_PI*i/n+grad;
      //--- Координаты X и Y текущей вершины многоугольника
      double xi=xc+R*::cos(a);
      double yi=yc+R*::sin(a);
      //--- Запишем текущие координаты в массивы
      this.m_array_x[i]=int(::floor(xi));
      this.m_array_y[i]=int(::floor(yi));
     }
  }
//+------------------------------------------------------------------+

Логика метода подробно расписана в комментариях к коду, формулы расчёта декартовых координат многоугольника:


Оставим метод для самостоятельного изучения — думаю, вопросов возникнуть не должно. В любом случае, их всегда можно задать в обсуждении статьи.

Класс объекта-кадра геометрической анимации готов.

Теперь нам необходимо дать к нему доступ из внешней программы и возожность оперативно создавать объекты этого класса.

Все вновь создаваемые объекты кадров анимаций у нас хранятся в собственных списках в классе CAnimations.
Внесём в файл класса \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh необходимые доработки.

Подключим к файлу класса файл только что созданного класса объекта кадра геометрической анимации и в приватной секции класса объявим список, в котором будут храниться все вновь созданные объекты этого класса:

//+------------------------------------------------------------------+
//|                                                   Animations.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "FrameText.mqh"
#include "FrameQuad.mqh"
#include "FrameGeometry.mqh"
//+------------------------------------------------------------------+
//| Класс копировщика пикселей                                       |
//+------------------------------------------------------------------+
class CAnimations : public CObject
  {
private:
   CGCnvElement     *m_element;                             // Указатель на графический элемент
   CArrayObj         m_list_frames_text;                    // Список текстовых кадров анимаций
   CArrayObj         m_list_frames_quad;                    // Список прямоугольных кадров анимаций
   CArrayObj         m_list_frames_geom;                    // Список кадров анимаций геометрических фигур

//--- Возвращает флаг присутствия в списке объекта-кадра с указанным идентификатором
   bool              IsPresentFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id);
//--- Возвращает или создаёт новый объект-кадр анимации
   CFrame           *GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new);

public:

В публичной секции класса объявим метод для создания нового объекта кадра геометрической анимации и напишем метод, возвращающий указатель на список этих объектов:

public:
                     CAnimations(CGCnvElement *element);
                     CAnimations(){;}

//--- Создаёт новый (1) прямоугольный, (2) текстовый, (3) геометрический объект-кадр анимации
   CFrame           *CreateNewFrameText(const int id);
   CFrame           *CreateNewFrameQuad(const int id);
   CFrame           *CreateNewFrameGeometry(const int id);
//--- Возвращает объект-кадр анимации по идентификатору
   CFrame           *GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id);
//--- Возвращает список (1) текстовых, (2) прямоугольных кадров анимаций, (3) кадров анимаций геометрических фигур
   CArrayObj        *GetListFramesText(void)                { return &this.m_list_frames_text;  }
   CArrayObj        *GetListFramesQuad(void)                { return &this.m_list_frames_quad;  }
   CArrayObj        *GetListFramesGeometry(void)            { return &this.m_list_frames_geom;  }

Далее — объявим методы для рисования правильных многоугольников:

//+------------------------------------------------------------------+
//| Методы рисования правильных многоугольников                      |
//+------------------------------------------------------------------+
//--- Рисует правильный многоугольник без сглаживания
   bool              DrawNgonOnBG(const int id,                         // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false);               // Флаг перерисовки чарта
                                  
//--- Рисует правильный закрашенный многоугольник
   bool              DrawNgonFillOnBG(const int id,                     // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false);               // Флаг перерисовки чарта
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания AntiAliasing
   bool              DrawNgonAAOnBG(const int id,                       // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=UINT_MAX);             // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания Wu
   bool              DrawNgonWuOnBG(const int id,                       // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=UINT_MAX);             // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       
//--- Рисует  правильный многоугольник заданной толщины с использованием двух алгоритмов сглаживания последовательно.
//--- Сначала на основе кривых Безье сглаживаются отдельные отрезки.
//--- Затем для повышения качества отрисовки к построенному из этих отрезков многоугольнику применяется растровый алгоритм сглаживания. 
   bool              DrawNgonSmoothOnBG(const int id,                   // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const int    size,                        // Толщина линии
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const double tension=0.5,                 // Значение параметра сглаживания
                              const double step=10,                     // Шаг аппроксимации
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const ENUM_LINE_STYLE style=STYLE_SOLID,  // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                              const ENUM_LINE_END end_style=LINE_END_ROUND);// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       
//--- Рисует правильный многоугольник заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   bool              DrawNgonThickOnBG(const int id,                    // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const int    size,                        // толщина линии
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=STYLE_SOLID,           // стиль линии
                              ENUM_LINE_END end_style=LINE_END_ROUND);  // стиль концов линии
  
  };
//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+


Все вхождения строки "ENUM_TEXT_ANCHOR" в листинге класса должны быть заменены на строку "ENUM_FRAME_ANCHOR".

В методе, возвращающем объект-кадр анимации по типу и идентификатору, впишем обработку нового типа объекта-кадра анимации:

//+------------------------------------------------------------------+
//| Возвращает объект-кадр анимации по типу и идентификатору         |
//+------------------------------------------------------------------+
CFrame *CAnimations::GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id)
  {
//--- Объявляем указатель на объект-кадр анимации
   CFrame *frame=NULL;
//--- В зависимости от типа нужного объекта получаем их количество в соответствующем списке
   int total=
     (
      frame_type==ANIMATION_FRAME_TYPE_TEXT     ?  this.m_list_frames_text.Total()  : 
      frame_type==ANIMATION_FRAME_TYPE_QUAD     ?  this.m_list_frames_quad.Total()  :
      frame_type==ANIMATION_FRAME_TYPE_GEOMETRY ?  this.m_list_frames_geom.Total()  :  0
     );
//--- В цикле ...
   for(int i=0;i<total;i++)
     {
      //--- ... по списку, соответствующему типу кадра анимации получаем очередной объект
      switch(frame_type)
        {
         case ANIMATION_FRAME_TYPE_TEXT      :  frame=this.m_list_frames_text.At(i);   break;
         case ANIMATION_FRAME_TYPE_QUAD      :  frame=this.m_list_frames_quad.At(i);   break;
         case ANIMATION_FRAME_TYPE_GEOMETRY  :  frame=this.m_list_frames_geom.At(i);   break;
         default: break;
        }
      //--- если указатель получить не удалось - идём к следующему
      if(frame==NULL)
         continue;
      //--- Если идентификатор объекта соответствует искомому -
      //--- возвращаем указатель на найденный объект
      if(frame.ID()==id)
         return frame;
     }
//--- Ничего не нашли - возвращаем NULL
   return NULL;
  }
//+------------------------------------------------------------------+

Метод, создающий новый объект-кадр геометрической анимации:

//+------------------------------------------------------------------+
//| Создаёт новый объект-кадр геометрической анимации                |
//+------------------------------------------------------------------+
CFrame *CAnimations::CreateNewFrameGeometry(const int id)
  {
//--- Если объект с таким идентификатором уже есть - сообщаем об этом в журнал и возвращаем NULL
   if(this.IsPresentFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id))
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_FRAME_ALREADY_IN_LIST),(string)id);
      return NULL;
     }
//--- Создаём новый геометрический объект-кадр анимации с указанным идентификатором
   CFrame *frame=new CFrameGeometry(id,this.m_element);
//--- Если объект создать не удалось - сообщаем об этом и возвращаем NULL
   if(frame==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME));
      return NULL;
     }
//--- Если успешно созданный объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL
   if(!this.m_list_frames_geom.Add(frame))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id);
      delete frame;
      return NULL;
     }
//--- Возвращаем указатель на вновь созданный объект
   return frame;
  }
//+------------------------------------------------------------------+

Логика метода полностью расписана в комментариях к коду.

В методе, возвращающем или создающем новый объект-кадр анимации, впишем обработку нового типа кадра анимации:

//+------------------------------------------------------------------+
//| Возвращает или создаёт новый объект-кадр анимации                |
//+------------------------------------------------------------------+
CFrame *CAnimations::GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new)
  {
   //--- Объявляем нулевые указатели на объекты
   CFrameQuad     *frame_q=NULL;
   CFrameText     *frame_t=NULL;
   CFrameGeometry *frame_g=NULL;
   //--- В зависимости от типа требуемого объекта
   switch(frame_type)
     {
      //--- Если это текстовый анимационный кадр
      case ANIMATION_FRAME_TYPE_TEXT :
        //--- получим указатель на объект с указанным идентификатором
        frame_t=this.GetFrame(ANIMATION_FRAME_TYPE_TEXT,id);
        //--- Если указатель получен - вернём его
        if(frame_t!=NULL)
           return frame_t;
        //--- Если не установлен флаг создания нового объекта - сообщим об ошибке и вернём NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Вернём результат создания нового объекта-кадра текстовой анимации (указатель на созданный объект)
        return this.CreateNewFrameText(id);
      
      //--- Если это прямоугольный анимационный кадр
      case ANIMATION_FRAME_TYPE_QUAD :
        //--- получим указатель на объект с указанным идентификатором
        frame_q=this.GetFrame(ANIMATION_FRAME_TYPE_QUAD,id);
        //--- Если указатель получен - вернём его
        if(frame_q!=NULL)
           return frame_q;
        //--- Если не установлен флаг создания нового объекта - сообщим об ошибке и вернём NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Вернём результат создания нового объекта-прямоугольного кадра анимации (указатель на созданный объект)
        return this.CreateNewFrameQuad(id);
      
      //--- Если это геометрический анимационный кадр
      case ANIMATION_FRAME_TYPE_GEOMETRY :
        //--- получим указатель на объект с указанным идентификатором
        frame_g=this.GetFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id);
        //--- Если указатель получен - вернём его
        if(frame_g!=NULL)
           return frame_g;
        //--- Если не установлен флаг создания нового объекта - сообщим об ошибке и вернём NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Вернём результат создания нового объекта-геометрического кадра анимации (указатель на созданный объект)
        return this.CreateNewFrameGeometry(id);
      //--- Остальные случаи - возвращаем NULL
      default:
        return NULL;
     }
  }
//+------------------------------------------------------------------+

Здесь точно так же вся логика полностью расписана в комментариях к коду.

В самом конце листинга класса впишем реализацию методов рисования правильных многоугольников:

//+------------------------------------------------------------------+
//| Рисует правильный многоугольник без сглаживания                  |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonOnBG(const int id,                         // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false)                // Флаг перерисовки чарта
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw);
  }
//+------------------------------------------------------------------+
//| Рисует правильный закрашенный многоугольник                      |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonFillOnBG(const int id,                     // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false)                // Флаг перерисовки чарта
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonFillOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw);
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник с использованием                 |
//| алгоритма сглаживания AntiAliasing                               |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonAAOnBG(const int id,                       // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false,                // Флаг перерисовки чарта
                           const uint   style=UINT_MAX)              // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonAAOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style);
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник с использованием                 |
//| алгоритма сглаживания Wu                                         |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonWuOnBG(const int id,                       // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false,                // Флаг перерисовки чарта
                           const uint   style=UINT_MAX)              // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonWuOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style);
  }
//+------------------------------------------------------------------+
//| Рисует  правильный многоугольник заданной толщины                |
//| с использованием двух алгоритмов сглаживания последовательно.    |
//| Сначала на основе кривых Безье сглаживаются отдельные отрезки.   |
//| Затем для повышения качества отрисовки к построенному из этих    |
//| отрезков многоугольнику применяется                              |
//| растровый алгоритм сглаживания.                                  |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonSmoothOnBG(const int id,                   // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const int    size,                        // Толщина линии
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const double tension=0.5,                 // Значение параметра сглаживания
                           const double step=10,                     // Шаг аппроксимации
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false,                // Флаг перерисовки чарта
                           const ENUM_LINE_STYLE style=STYLE_SOLID,  // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                           const ENUM_LINE_END end_style=LINE_END_ROUND)// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonSmoothOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,redraw,style,end_style);
  }
//+------------------------------------------------------------------+
//| Рисует правильный многоугольник заданной толщины с использованием|
//| алгоритма сглаживания с предварительной фильтрацией              |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonThickOnBG(const int id,                    // Идентификатор кадра
                           const int    N,                           // Число вершин многоугольника
                           const int    coord_x,                     // Координата X левого-верхнего угла кадра
                           const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                           const int    len,                         // Длина сторон кадра
                           const double angle,                       // Угол поворота многоугольника
                           const int    size,                        // толщина линии
                           const color  clr,                         // Цвет
                           const uchar  opacity=255,                 // Непрозрачность
                           const bool   create_new=true,             // Флаг создания нового объекта
                           const bool   redraw=false,                // Флаг перерисовки чарта
                           const uint   style=STYLE_SOLID,           // стиль линии
                           ENUM_LINE_END end_style=LINE_END_ROUND)   // стиль концов линии
  {
   CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new);
   if(frame==NULL)
      return false;
   return frame.DrawNgonThickOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,redraw,style,end_style);
  }
//+------------------------------------------------------------------+

Логика всех этих методов абсолютно идентична, поэтому рассмотрим на примере последнего метода.

Как видим, здесь всё просто: сначала либо получаем из списка уже созданный объект-кадр геометрической анимации, либо создаём его при его отсутствии в списке и в случае, если флаг создания нового объекта установлен, если объект получить или создать не удалось — возвращаем false.
Иначе — возвращаем результат вызова одноимённого метода класса объекта-кадра геометрической анимации, полученного из списка или вновь созданного.


Теперь доработаем класс объекта-формы в файле \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Все вхождения строки "ENUM_TEXT_ANCHOR" в листинге класса должны быть заменены на "ENUM_FRAME_ANCHOR".

В приватной секции класса объявим методы для сброса в ноль размеров массивов пикселей трёх классов кадров анимаций:

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // Список присоединённых элементов
   CAnimations      *m_animations;                             // Указатель на объект анимаций
   CShadowObj       *m_shadow_obj;                             // Указатель на объект тени
   color             m_color_frame;                            // Цвет рамки формы
   int               m_frame_width_left;                       // Ширина рамки формы слева
   int               m_frame_width_right;                      // Ширина рамки формы справа
   int               m_frame_width_top;                        // Ширина рамки формы сверху
   int               m_frame_width_bottom;                     // Ширина рамки формы снизу

//--- Инициализирует переменные
   void              Initialize(void);
//--- Сбрасывает размер массива (1) текстовых, (2) прямоугольных, (3) геометрических кадров анимаций
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Возвращает имя зависимого объекта

Это нам необходимо для корректной работы методов сохранения и восстановления фона формы, где рисуются фигуры (обсуждали выше).

В публичной секции класса напишем метод для фиксации внешнего вида формы и объявим виртуальный метод, восстанавливающий графический ресурс из массива:

//--- Рисует рельефное (вдавленное) поле
   void              DrawFieldStamp(const int x,                              // Координата X относительно формы
                                    const int y,                              // Координата Y относительно формы
                                    const int width,                          // Ширина поля
                                    const int height,                         // Высота поля
                                    const color colour,                       // Цвет поля
                                    const uchar opacity);                     // Непрозрачность поля

//--- Фиксирует внешний вид созданной формы
   void              Done(void)     { CGCnvElement::CanvasUpdate(false); CGCnvElement::ResourceStamp(DFUN);                   }
//--- Восстанавливает ресурс из массива
   virtual bool      Reset(void);

//+------------------------------------------------------------------+
//| Методы работы с пикселями изображения                            |
//+------------------------------------------------------------------+

Для чего нам нужен метод фиксации внешнего вида формы?
Например: мы создали форму, нарисовали на ней все необходимые неизменяемые элементы. Теперь нам нужно скопировать созданный внешний вид формы в массив-копию графического ресурса — чтобы при необходимости можно было вернуть изначальный вид формы. Ведь всё, что мы рисуем на форме, — все эти изменения отображаются именно в графическом ресурсе, и чтобы можно было не перерисовывать всю форму заново, нам нужно просто хранить копию изначально созданной формы в специальном массиве, откуда мы всегда сможем восстановить изначальный внешний вид, что собственно и делает метод Reset().

В публичной секции класса напишем методы рисования правильных многоугольников:

//--- Рисует правильный многоугольник без сглаживания
   bool              DrawNgonOnBG(const int id,                         // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false)                // Флаг перерисовки чарта
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false);  }
                       
//--- Рисует правильный закрашенный многоугольник
   bool              DrawNgonFillOnBG(const int id,                     // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false)                // Флаг перерисовки чарта
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonFillOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false);  }
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания AntiAliasing
   bool              DrawNgonAAOnBG(const int id,                       // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=UINT_MAX)              // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonAAOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false);  }
                       
//--- Рисует правильный многоугольник с использованием алгоритма сглаживания Wu
   bool              DrawNgonWuOnBG(const int id,                       // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=UINT_MAX)              // Стиль линии - одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonWuOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false);  }
                       
//--- Рисует  правильный многоугольник заданной толщины с использованием двух алгоритмов сглаживания последовательно.
//--- Сначала на основе кривых Безье сглаживаются отдельные отрезки.
//--- Затем для повышения качества отрисовки к построенному из этих отрезков многоугольнику применяется растровый алгоритм сглаживания. 
   bool              DrawNgonSmoothOnBG(const int id,                   // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const int    size,                        // Толщина линии
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const double tension=0.5,                 // Значение параметра сглаживания
                              const double step=10,                     // Шаг аппроксимации
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const ENUM_LINE_STYLE style=STYLE_SOLID,  // Стиль линии — одно из значений перечисления ENUM_LINE_STYLE или пользовательское значение
                              const ENUM_LINE_END end_style=LINE_END_ROUND)// Стиль концов линии — одно из значений перечисления ENUM_LINE_END
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonSmoothOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,create_new,redraw,style,end_style) : false);  }
                       
//--- Рисует правильный многоугольник заданной толщины с использованием алгоритма сглаживания с предварительной фильтрацией
   bool              DrawNgonThickOnBG(const int id,                    // Идентификатор кадра
                              const int    N,                           // Число вершин многоугольника
                              const int    coord_x,                     // Координата X левого-верхнего угла кадра
                              const int    coord_y,                     // Координата Y левого-верхнего угла кадра
                              const int    len,                         // Длина сторон кадра
                              const double angle,                       // Угол поворота многоугольника
                              const int    size,                        // толщина линии
                              const color  clr,                         // Цвет
                              const uchar  opacity=255,                 // Непрозрачность
                              const bool   create_new=true,             // Флаг создания нового объекта
                              const bool   redraw=false,                // Флаг перерисовки чарта
                              const uint   style=STYLE_SOLID,           // стиль линии
                              ENUM_LINE_END end_style=LINE_END_ROUND)   // стиль концов линии
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonThickOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,create_new,redraw,style,end_style) : false);  }

Все методы идентичны и возвращают результат вызова соответствующих методов экземпляра класса CAnimations, рассмотренных нами выше.

За пределами тела класса напишем реализацию объявленных методов.

Три метода, сбрасывающих размеры массивов трёх объектов-кадров анимаций:

//+------------------------------------------------------------------+
//| Сбрасывает размер массива текстовых кадров анимаций              |
//+------------------------------------------------------------------+
void CForm::ResetArrayFrameT(void)
  {
   if(this.m_animations==NULL)
      return;
   CArrayObj *list=this.m_animations.GetListFramesText();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CFrameText *frame=list.At(i);
      if(frame==NULL)
         continue;
      frame.ResetArray();
     }
  }
//+------------------------------------------------------------------+
//| Сбрасывает размер массива прямоугольных кадров анимаций          |
//+------------------------------------------------------------------+
void CForm::ResetArrayFrameQ(void)
  {
   if(this.m_animations==NULL)
      return;
   CArrayObj *list=this.m_animations.GetListFramesQuad();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CFrameQuad *frame=list.At(i);
      if(frame==NULL)
         continue;
      frame.ResetArray();
     }
  }
//+------------------------------------------------------------------+
//| Сбрасывает размер массива геометрических кадров анимаций         |
//+------------------------------------------------------------------+
void CForm::ResetArrayFrameG(void)
  {
   if(this.m_animations==NULL)
      return;
   CArrayObj *list=this.m_animations.GetListFramesGeometry();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CFrameGeometry *frame=list.At(i);
      if(frame==NULL)
         continue;
      frame.ResetArray();
     }
  }
//+------------------------------------------------------------------+

Все методы идентичны друг другу:

Если объекта класса CAnimation не существует — уходим из метода — у данного объекта нет никаких анимаций.
Получаем указатель на список соответствующих методу кадров анимаций. В цикле по полученному списку получаем указатель на очередной объект-кадр анимации и сбрасываем его массив пикселей в ноль.

Метод, восстанавливающий ресурс из массива:

//+------------------------------------------------------------------+
//| Восстанавливает ресурс из массива                                |
//+------------------------------------------------------------------+
bool CForm::Reset(void)
  {
   CGCnvElement::Reset();
   this.ResetArrayFrameQ();
   this.ResetArrayFrameT();
   this.ResetArrayFrameG();
   return true;
  }
//+------------------------------------------------------------------+

Сначала вызываем метод родительского класса, восстанавливающий графический ресурс из массива-копии, а затем обнуляем массивы пикселей всех объектов-кадров анимаций — чтобы после восстановления внешнего вида формы, мы смогли заново скопировать фон в нужных координатах и размере сохраняемой области фона.

Итак, у нас всё готово для тестирования рисования правильных многоугольников на форме.


Тестирование

Для тестирования возьмём тестовый советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part80\ под новым именем TestDoEasyPart80.mq5.

Как будем тестировать? Если помните, то в прошлой статье мы рисовали фигуры на объекте-форме при нажатии клавиш клавиатуры. Сегодня сделаем точно так же. Просто переназначим кнопки, при нажатии на которые рисовались многоугольники, которым динамически задавались координаты и размеры. Сегодня мы также будем динамически менять координаты кадра анимации по оси X и количество вершин рисуемого многоугольника (от 3 до 10).

  • При нажатии на клавишу "Y" будет рисоваться несглаженный правильный многоугольник,
  • При нажатии на клавишу "U" будет рисоваться несглаженный закрашенный многоугольник,
  • При нажатии на клавишу "I" будет рисоваться правильный многоугольник, сглаженный алгоритмом AntiAlliasing (AA),
  • При нажатии на клавишу "O" будет рисоваться правильный многоугольник, сглаженный алгоритмом У Сяолиня (Xiaolin Wu),
  • При нажатии на клавишу "P" будет рисоваться правильный многоугольник заданной толщины, сглаженный двумя алгоритмами сглаживания (Smooth),
  • При нажатии на клавишу "A" будет рисоваться правильный многоугольник заданной толщины с использованием сглаживания с предварительной фильтрацией (Thick),
  • На клавишу "." назначим рисование закрашенной области — по сути это будет заливка всей формы заданным цветом.

Далее — при каждом щелчке по форме будет изменяться координата X рисуемого кадра и увеличиваться на 1 количество вершин рисуемого многоугольника.

Все вхождения подстроки "TEXT_ANCHOR" заменим на подстроку "FRAME_ANCHOR".

В обработчике OnInit() советника после создания каждой формы зафиксируем её внешний вид:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка разрешений на отсылку событий перемещения курсора и прокрутки колёсика мышки
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);
   array_clr[0]=C'26,100,128';      // Исходный ≈Тёмно-лазурный цвет
   array_clr[1]=C'35,133,169';      // Осветлённый исходный цвет
//--- Создадим заданное количество объектов-форм
   list_forms.Clear();
   int total=FORMS_TOTAL;
   for(int i=0;i<total;i++)
     {
      int y=40;
      if(i>0)
        {
         CForm *form_prev=list_forms.At(i-1);
         if(form_prev==NULL)
            continue;
         y=form_prev.BottomEdge()+10;
        }
      //--- При создании объекта передаём в него все требуемые параметры
      CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30));
      if(form==NULL)
         continue;
      //--- Установим форме флаги активности, и перемещаемости
      form.SetActive(true);
      form.SetMovable(false);
      //--- Установим форме её идентификатор, равный индексу цикла и номер в списке объектов
      form.SetID(i);
      form.SetNumber(0);   // (0 - означает главный объект-форма) К главному объекту могут прикрепляться второстепенные, которыми он будет управлять
      //--- Установим частичную непрозрачность для средней формы и полную - для остальных
      uchar opacity=(i==1 ? 250 : 255);
      //--- Указываем стиль формы и её цветовую тему в зависимости от индекса цикла
      if(i<2)
        {
         ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i;
         ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i;
         //--- Устанавливаем форме её стиль и тему
         form.SetFormStyle(style,theme,opacity,true,false);
        }
      //--- Если это первая (верхняя) форма
      if(i==0)
        {
         //--- Нарисуем на ней вдавленное поле, немного смещённое от центра формы книзу
         form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity());
         form.Done();
        }
      //--- Если это вторая форма
      if(i==1)
        {
         //--- Нарисуем на ней вдавленное полупрозрачное поле по центру в виде "затемнённого стекла"
         form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200);
         form.Done();
        }
      //--- Если это третья форма
      if(i==2)
        {
         //--- Установим непрозрачность 200
         form.SetOpacity(200);
         //--- Цвет фона формы зададим как первый цвет из массива цветов
         form.SetColorBackground(array_clr[0]);
         //--- Цвет очерчивающей рамки формы
         form.SetColorFrame(clrDarkBlue);
         //--- Установим флаг рисования тени
         form.SetShadow(true);
         //--- Рассчитаем цвет тени как цвет фона графика, преобразованный в монохромный
         color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
         //--- Если в настройках задано использовать цвет фона графика, то затемним монохромный цвет на 20 единиц
         //--- Иначе - будем использовать для рисования тени заданный в настройках цвет
         color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
         //--- Нарисуем тень формы со смещением от формы вправо-вниз на три пикселя по всем осям
         //--- Непрозрачность тени при этом установим равной 200, а радиус размытия равный 4
         form.DrawShadow(3,3,clr,200,4);
         //--- Зальём фон формы вертикальным градиентом
         form.Erase(array_clr,form.Opacity());
         //--- Нарисуем очерчивающий прямоугольник по краям формы
         form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
         form.Done();
         
         //--- Выведем текст с описанием типа градиента и обновим форму
         //--- Параметры текста: координаты текста в центре формы и точка привязки - тоже по центру
         //--- Создаём новый кадр текстовой анимации с идентификатором 0 и выводим текст на форму
         form.TextOnBG(0,TextByLanguage("V-Градиент","V-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
        }
      //--- Если это четвёртая (нижняя) форма
      if(i==3)
        {
         //--- Установим непрозрачность 200
         form.SetOpacity(200);
         //--- Цвет фона формы зададим как первый цвет из массива цветов
         form.SetColorBackground(array_clr[0]);
         //--- Цвет очерчивающей рамки формы
         form.SetColorFrame(clrDarkBlue);
         //--- Установим флаг рисования тени
         form.SetShadow(true);
         //--- Рассчитаем цвет тени как цвет фона графика, преобразованный в монохромный
         color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
         //--- Если в настройках задано использовать цвет фона графика, то заемним монохромный цвет на 20 единиц
         //--- Иначе - будем использовать для рисования тени заданный в настройках цвет
         color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
         //--- Нарисуем тень формы со смещением от формы вправо-вниз на три пикселя по всем осям
         //--- Непрозрачность тени при этом установим равной 200, а радиус размытия равный 4
         form.DrawShadow(3,3,clr,200,4);
         //--- Зальём фон формы горизонтальным градиентом
         form.Erase(array_clr,form.Opacity(),false);
         //--- Нарисуем очерчивающий прямоугольник по краям формы
         form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
         form.Done();
         
         //--- Выведем текст с описанием типа градиента и обновим форму
         //--- Параметры текста: координаты текста в центре формы и точка привязки - тоже по центру
         //--- Создаём новый кадр текстовой анимации с идентификатором 0 и выводим текст на форму
         form.TextOnBG(0,TextByLanguage("H-Градиент","H-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
        }
      //--- Добавим объекты в список
      if(!list_forms.Add(form))
        {
         delete form;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


В обработчике OnChartEvent() в блоке обработки нажатий клавиш впишем вызов метода восстановления внешнего вида формы и сброса массивов пикселей объектов-кадров в ноль:

//--- Если нажатие на клавишу клавиатуры
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- В зависимости от нажатой клавиши получим тип рисуемой фигуры
      figure_type=FigureType(lparam);
      //--- Если тип фигуры сменился
      if(figure_type!=figure_type_prev)
        {
         //--- Получим текст описания типа рисуемой фигуры
         figure=FigureTypeDescription(figure_type);
         //--- В цикле по всем формам 
         for(int i=0;i<list_forms.Total();i++)
           {
            //--- получим указатель на очередной объект-форму
            CForm *form=list_forms.At(i);
            if(form==NULL)
               continue;
            //--- Если идентификатор формы 2
            if(form.ID()==2)
              {
               //--- Сбросим все смещения координат в ноль, восстановим фон формы и выведем текст с описанием типа рисуемой фигуры
               nx1=ny1=nx2=ny2=nx3=ny3=nx4=ny4=nx5=ny5=0;
               form.Reset();
               form.TextOnBG(0,figure,form.TextLastX(),form.TextLastY(),form.TextAnchor(),C'211,233,149',255,false,true);
              }
           }
         //--- Запишем новый тип фигуры
         figure_type_prev=figure_type;
        }
     }

В функцию FigureType() впишем обработку клавиши "." :

//+------------------------------------------------------------------+
//| Возвращает фигуру в зависимости от нажатой кнопки клавиатуры     |
//+------------------------------------------------------------------+
ENUM_FIGURE_TYPE FigureType(const long key_code)
  {
   switch((int)key_code)
     {
      //--- Клавиша "1" = Точка
      case 49  :  return FIGURE_TYPE_PIXEL;
      
      //--- Клавиша "2" = Точка со сглаживанием AntiAlliasing
      case 50  :  return FIGURE_TYPE_PIXEL_AA;
      
      //--- Клавиша "3" = Вертикальная линия
      case 51  :  return FIGURE_TYPE_LINE_VERTICAL;
      
      //--- Клавиша "4" = Вертикальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
      case 52  :  return FIGURE_TYPE_LINE_VERTICAL_THICK;
      
      //--- Клавиша "5" = Горизонтальная линия
      case 53  :  return FIGURE_TYPE_LINE_HORIZONTAL;
      
      //--- Клавиша "6" = Горизонтальный отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
      case 54  :  return FIGURE_TYPE_LINE_HORIZONTAL_THICK;
      
      //--- Клавиша "7" = Произвольная линия
      case 55  :  return FIGURE_TYPE_LINE;
      
      //--- Клавиша "8" = Линия со сглаживанием AntiAlliasing
      case 56  :  return FIGURE_TYPE_LINE_AA;
      
      //--- Клавиша "9" = Линия со сглаживанием WU
      case 57  :  return FIGURE_TYPE_LINE_WU;
      
      //--- Клавиша "0" = Отрезок произвольной линии заданной толщины с использованием алгоритма сглаживания
      case 48  :  return FIGURE_TYPE_LINE_THICK;
      
      //--- Клавиша "q" = Ломаная линия
      case 81  :  return FIGURE_TYPE_POLYLINE;
      
      //--- Клавиша "w" = Ломаная линия со сглаживанием AntiAlliasing
      case 87  :  return FIGURE_TYPE_POLYLINE_AA;
      
      //--- Клавиша "e" = Ломаная линия со сглаживанием WU
      case 69  :  return FIGURE_TYPE_POLYLINE_WU;
      
      //--- Клавиша "r" = Ломаная линия заданной толщины с использованием двух алгоритмов сглаживания
      case 82  :  return FIGURE_TYPE_POLYLINE_SMOOTH;
      
      //--- Клавиша "t" = Ломаная линия заданной толщины с использованием алгоритма сглаживания
      case 84  :  return FIGURE_TYPE_POLYLINE_THICK;
      
      //--- Клавиша "y" = Многоугольник
      case 89  :  return FIGURE_TYPE_POLYGON;
      
      //--- Клавиша "u" = Закрашенный многоугольник
      case 85  :  return FIGURE_TYPE_POLYGON_FILL;
      
      //--- Клавиша "i" = Многоугольник со сглаживанием AntiAlliasing
      case 73  :  return FIGURE_TYPE_POLYGON_AA;
      
      //--- Клавиша "o" = Многоугольник со сглаживанием WU
      case 79  :  return FIGURE_TYPE_POLYGON_WU;
      
      //--- Клавиша "p" = Многоугольник заданной толщины с использованием двух алгоритмов сглаживания
      case 80  :  return FIGURE_TYPE_POLYGON_SMOOTH;
      
      //--- Клавиша "a" = Многоугольник заданной толщины с использованием алгоритма сглаживания
      case 65  :  return FIGURE_TYPE_POLYGON_THICK;
      
      //--- Клавиша "s" = Прямоугольник
      case 83  :  return FIGURE_TYPE_RECTANGLE;
      
      //--- Клавиша "d" = Закрашенный прямоугольник
      case 68  :  return FIGURE_TYPE_RECTANGLE_FILL;
      
      //--- Клавиша "f" = Окружность
      case 70  :  return FIGURE_TYPE_CIRCLE;
      
      //--- Клавиша "g" = Закрашенный круг
      case 71  :  return FIGURE_TYPE_CIRCLE_FILL;
      
      //--- Клавиша "h" = Окружность со сглаживанием AntiAlliasing
      case 72  :  return FIGURE_TYPE_CIRCLE_AA;
      
      //--- Клавиша "j" = Окружность со сглаживанием WU
      case 74  :  return FIGURE_TYPE_CIRCLE_WU;
      
      //--- Клавиша "k" = Треугольник
      case 75  :  return FIGURE_TYPE_TRIANGLE;
      
      //--- Клавиша "l" = Закрашенный треугольник
      case 76  :  return FIGURE_TYPE_TRIANGLE_FILL;
      
      //--- Клавиша "z" = Треугольник со сглаживанием AntiAlliasing
      case 90  :  return FIGURE_TYPE_TRIANGLE_AA;
      
      //--- Клавиша "x" = Треугольник со сглаживанием WU
      case 88  :  return FIGURE_TYPE_TRIANGLE_WU;
      
      //--- Клавиша "c" = Эллипс
      case 67  :  return FIGURE_TYPE_ELLIPSE;
      
      //--- Клавиша "v" = Закрашенный эллипс
      case 86  :  return FIGURE_TYPE_ELLIPSE_FILL;
      
      //--- Клавиша "b" = Эллипс со сглаживанием AntiAlliasing
      case 66  :  return FIGURE_TYPE_ELLIPSE_AA;
      
      //--- Клавиша "n" = Эллипс со сглаживанием WU
      case 78  :  return FIGURE_TYPE_ELLIPSE_WU;
      
      //--- Клавиша "m" = Дуга эллипса
      case 77  :  return FIGURE_TYPE_ARC;
      
      //--- Клавиша "," = Сектор эллипса
      case 188 :  return FIGURE_TYPE_PIE;
      
      //--- Клавиша "." = Закрашенная область
      case 190 :  return FIGURE_TYPE_FILL;
      
      //--- По умолчанию = Точка
      default  :  return FIGURE_TYPE_PIXEL;
     }
  }
//+------------------------------------------------------------------+

В функции FigureProcessing() сделаем массивы координат динамическими:

//+------------------------------------------------------------------+
//| Обрабатывает выбранную фигуру                                    |
//+------------------------------------------------------------------+
void FigureProcessing(CForm *form,const ENUM_FIGURE_TYPE figure_type)
  {
   int array_x[];
   int array_y[];
   
   switch(figure_type)
     {

и там, где необходимо в методы классов передавать массивы координат, будем устанавливать размеры этих массивов:

   //--- Клавиша "q" = Ломаная линия
      case FIGURE_TYPE_POLYLINE  :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         coordX2=coordX1+nx2*8;
         coordY2=coordY1;
         coordX3=coordX2;
         coordY3=coordY2+ny3*2;
         coordX4=coordX1;
         coordY4=coordY3;
         coordX5=coordX1;
         coordY5=coordY1;
         //--- Заполняем массивы значениями координат
         ArrayResize(array_x,5);
         ArrayResize(array_y,5);
         array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5;
         array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5;
         //--- проверяем координаты x1, y1 на выход за пределы формы

...

   //--- Клавиша "w" = Ломаная линия со сглаживанием AntiAlliasing
      case FIGURE_TYPE_POLYLINE_AA  :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         coordX2=coordX1+nx2*8;
         coordY2=coordY1;
         coordX3=coordX2;
         coordY3=coordY2+ny3*2;
         coordX4=coordX1;
         coordY4=coordY3;
         coordX5=coordX1;
         coordY5=coordY1;
         //--- Заполняем массивы значениями координат
         ArrayResize(array_x,5);
         ArrayResize(array_y,5);
         array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5;
         array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5;
         //--- проверяем координаты x1, y1 на выход за пределы формы

...

   //--- Клавиша "e" = Ломаная линия со сглаживанием WU
      case FIGURE_TYPE_POLYLINE_WU  :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         coordX2=coordX1+nx2*8;
         coordY2=coordY1;
         coordX3=coordX2;
         coordY3=coordY2+ny3*2;
         coordX4=coordX1;
         coordY4=coordY3;
         coordX5=coordX1;
         coordY5=coordY1;
         //--- Заполняем массивы значениями координат
         ArrayResize(array_x,5);
         ArrayResize(array_y,5);
         array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5;
         array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5;
         //--- проверяем координаты x1, y1 на выход за пределы формы

...

   //--- Клавиша "r" = Ломаная линия заданной толщины с использованием двух алгоритмов сглаживания
      case FIGURE_TYPE_POLYLINE_SMOOTH  :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         coordX2=coordX1+nx2*8;
         coordY2=coordY1;
         coordX3=coordX2;
         coordY3=coordY2+ny3*2;
         coordX4=coordX1;
         coordY4=coordY3;
         coordX5=coordX1;
         coordY5=coordY1;
         //--- Заполняем массивы значениями координат
         ArrayResize(array_x,5);
         ArrayResize(array_y,5);
         array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5;
         array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5;
         //--- проверяем координаты x1, y1 на выход за пределы формы

...

   //--- Клавиша "t" = Ломаная линия заданной толщины с использованием алгоритма сглаживания
      case FIGURE_TYPE_POLYLINE_THICK  :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         coordX2=coordX1+nx2*8;
         coordY2=coordY1;
         coordX3=coordX2;
         coordY3=coordY2+ny3*2;
         coordX4=coordX1;
         coordY4=coordY3;
         coordX5=coordX1;
         coordY5=coordY1;
         //--- Заполняем массивы значениями координат
         ArrayResize(array_x,5);
         ArrayResize(array_y,5);
         array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5;
         array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5;
         //--- проверяем координаты x1, y1 на выход за пределы формы


Там, где у нас были коды обработки нажатий клавиш для рисования многоугольников, напишем новую обработку — теперь в этих местах будем вызывать методы рисования правильных многоугольников:

   //--- Клавиша "y" = Многоугольник
      case FIGURE_TYPE_POLYGON  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrAliceBlue);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "u" = Закрашенный многоугольник
      case FIGURE_TYPE_POLYGON_FILL  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonFillOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCoral);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "i" = Многоугольник со сглаживанием AntiAlliasing
      case FIGURE_TYPE_POLYGON_AA  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonAAOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCyan);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "o" = Многоугольник со сглаживанием WU
      case FIGURE_TYPE_POLYGON_WU  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonWuOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightGoldenrod);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "p" = Многоугольник заданной толщины с использованием двух алгоритмов сглаживания
      case FIGURE_TYPE_POLYGON_SMOOTH  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonSmoothOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,3,clrLightGreen,255,0.5,10.0,true,false,STYLE_SOLID,LINE_END_BUTT);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "a" = Многоугольник заданной толщины с использованием алгоритма сглаживания
      case FIGURE_TYPE_POLYGON_THICK  :
         coordX1=START_X+nx1; // Координата X
         coordY1=START_Y;     // Координата Y
         coordX2=3+nx2*4;     // Длина сторон квадрата
         coordY2=3+ny2;       // Количество граней
         coordX3=0;           // Угол поворота
         //--- проверяем длину сторон квадрата на превышение двойной высоты формы
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- проверяем координату x1 на выход за пределы формы
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- проверяем количество граней на превышение 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- проверяем угол поворота на превышение 360 градусов
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Рисуем фигуру
         form.DrawNgonThickOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,5,clrLightSalmon,255,true,false,STYLE_SOLID,LINE_END_BUTT);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- Клавиша "s" = Прямоугольник

Тут код прокомментирован, и должно быть всё достаточно понятно. В любом случае можно задать вопрос в обсуждении к статье.

Не забудем в самом конце функции добавить обработку нажатия клавиши "." для заливки формы цветом:

   //--- Клавиша "." = Закрашенная область
      case FIGURE_TYPE_FILL :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);
         break;
      
   //--- По умолчанию = Ничего
      default  :
         
         break;
     }
  }
//+------------------------------------------------------------------+

Скомпилируем советник и запустим его на графике символа.

После запуска понажимаем на клавиши, на которые назначили рисование правильных многоугольников и заодно — заливку области цветом:


Что ж, всё работает как и задумывалось. Один нюанс: фигуры получаются не очень-то ровными ... Самый удачный, на мой взгляд, вид получается у многоугольников с алгоритмом сглаживания Wu. При заливке мы можем регулировать степень (порог) заливки цветом, указывая нужное значение параметра threshould:

form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);


Что дальше

В следующей статье продолжим работу над анимациями и с объектом-формой.

Ниже прикреплены все файлы текущей версии библиотеки и файл тестового советника для MQL5. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Графика в библиотеке DoEasy (Часть 73): Объект-форма графического элемента
Графика в библиотеке DoEasy (Часть 74): Базовый графический элемент на основе класса CCanvas
Графика в библиотеке DoEasy (Часть 75): Методы работы с примитивами и текстом в базовом графическом элементе
Графика в библиотеке DoEasy (Часть 76): Объект Форма и предопределённые цветовые темы
Графика в библиотеке DoEasy (Часть 77): Класс объекта Тень
Графика в библиотеке DoEasy (Часть 78): Принципы анимации в библиотеке. Нарезка изображений
Графика в библиотеке DoEasy (Часть 79): Класс объекта "Кадр анимации" и его объекты-наследники

Прикрепленные файлы |
MQL5.zip (4136.1 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Oleg Kartashov
Oleg Kartashov | 12 авг. 2021 в 15:52

Добрались до 80-й части.

Сдаётся мне, что библиотеку пора переименовывать из DoEasy в DoHardly.

Artyom Trishkin
Artyom Trishkin | 12 авг. 2021 в 16:00
Oleg Kartashov:

Добрались до 80-й части.

Сдаётся мне, что библиотеку пора переименовывать из DoEasy в DoHardly.

Сарказм засчитан.

Никто не говорил о том, что библиотеку сделать быстро. А про "easy" - это не о простоте создания самой библиотеки, а о простоте использования и простоте написания с ней программ - она делает за пользователя то, что ему бы пришлось делать самостоятельно.

Плюс - это-таки учебный материал, направленный на аудиторию начинающих.

Впрочем, у вас всегда есть возможность показать как сделать в трёх строчках то, что делается здесь. Буду благодарен за примеры и науку.

Комбинаторика и теория вероятностей для трейдинга (Часть III): Первая математическая модель Комбинаторика и теория вероятностей для трейдинга (Часть III): Первая математическая модель
Закономерным продолжением темы стала потребность разработки многофункциональных математических моделей для задач трейдинга. В связи с этим в данной статье я буду описывать весь процесс разработки первой математической модели для описания фракталов с нуля. Данная модель должна стать важным кирпичиком и быть многофункциональной и универсальной, в том числе для того, чтобы нарастить теоретическую базу для дальнейшего развития ветки.
Графика в библиотеке DoEasy (Часть 79): Класс объекта "Кадр анимации" и его объекты-наследники Графика в библиотеке DoEasy (Часть 79): Класс объекта "Кадр анимации" и его объекты-наследники
В статье разработаем класс одного кадра анимации и его наследников. Класс будет позволять рисовать фигуры с сохранением и последующим восстановлением фона под нарисованной фигурой.
Графика в библиотеке DoEasy (Часть 81): Интегрируем графику в объекты библиотеки Графика в библиотеке DoEasy (Часть 81): Интегрируем графику в объекты библиотеки
Начинаем интегрирование уже созданных графических объектов в остальные — ранее созданные объекты библиотеки, что в итоге наделит каждый объект библиотеки своим графическим объектом, позволяющим интерактивно взаимодействовать пользователю с программой.
Кластерный анализ (Часть I): Использование наклона индикаторных линий Кластерный анализ (Часть I): Использование наклона индикаторных линий
Кластерный анализ — один из важнейших элементов искусственного интеллекта. В этой статье я пытаюсь применить кластерный анализ наклона индикатора, чтобы получить пороговые значения для определения флэтового или трендового характера рынка.