English Русский 中文 Español Deutsch 日本語
Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica

Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica

MetaTrader 5Exemplos | 13 setembro 2021, 14:59
665 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Continuamos a trabalhar com as classes para desenhar formas na tela. Já criamos classes que permitem desenhar um quadro de animação numa determinada área da tela e, ao mesmo tempo, manter o fundo no qual a imagem é colocada, para restaurar mais tarde o fundo ao excluir ou alterar a imagem. A partir dos quadros criados anteriormente, será possível compor sequências de animação para alterar quadros rapidamente. Além disso, um quadro em si também permite fazer animações dentro de seu espaço.

Hoje vamos otimizar um pouco os códigos criados anteriormente para essas classes. Para fazer isso, vamos aderir à ideia de que, se houver seções de código repetitivas, toda a sua lógica pode (e deve) ser plasmada numa função/método individual e usar sua chamada. Isso tornará o código mais legível e reduzirá seu tamanho.

Além dessa otimização, criaremos uma classe de objeto de quadro de animação geométrica. O que isso significa?

Já temos métodos suficientes para construir polígonos diferentes, assim, se precisarmos desenhar um polígono regular, será muito mais fácil usar geometria do que calcular manualmente as coordenadas dos vértices (depois, podemos adicionar outras formas geométricas, as coordenadas dos vértices que podem ser calculados por meio de fórmulas, em vez de defini-las manualmente).

Parafraseando a Wikipedia:

Polígono regular: um polígono é regular se tiver todos os seus lados e ângulos iguais (equilátero e equiângulo, respectivamente), sejam eles internos ou externos.

Octógono regular

Todo polígono regular pode ser inscrito numa circunferência. Tal circunferência é chamada de circunscrita, porque tangencia todos os vértices do polígono, ficando externa a ele.

Circunferência circunscrita

Também existe outra circunferência chamada de inscrita, pois tangencia todas as arestas do polígono, ficando interna a ele.

Circunferência inscrita

Não consideraremos estes polígonos, com exceção de um quadrado que conterá um círculo no qual, por sua vez, desenharemos um polígono regular.

Assim, teremos um retângulo (mais precisamente, um quadrado) com um círculo dentro que, por sua vez, conterá um polígono regular. O quadrado será o quadro da animação - as coordenadas do canto superior esquerdo e o tamanho (comprimento) de seus lados. Na circunferência - cujo diâmetro circunferência será igual ao comprimento do lado do quadrado do quadro da animação - traçaremos um polígono cujos vértices estarão sobre a linha do círculo.
Ou seja, para criar um polígono regular, não precisaremos criar matrizes de coordenadas de forma independente, bastará indicar o número necessário de vértices, as coordenadas do canto superior esquerdo e o comprimento dos lados do quadrado .


Aprimorando as classes da biblioteca

No arquivo \MQL5\Include\DoEasy\Data.mqh incluímos o índice da nova mensagem:

//--- CGCnvElement
   MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,                  // Error! Empty array
   MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH,             // Error! Array-copy of the resource does not match the original
   
//--- CForm

e o texto da mensagem correspondente ao índice recém-adicionado:

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

//--- CForm


Como o alinhamento (ângulo de âncora) dos quadros de animação agora está vinculado não apenas aos quadros de texto das animações, mas também a todos os outros - retangulares, geométricos - que criaremos hoje e todos os outros novos que faremos no futuro, decidimos alterar ligeiramente o nome da enumeração e suas constantes, de modo que não fiquem vinculadas ao texto, senão aos quadros.

No arquivo \MQL5\Include\DoEasy\Defines.mqh na enumeração de ângulos de âncora substituímos a entrada do texto "TEXT" por "FRAME":

//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of anchoring methods                                        |
//| (horizontal and vertical text alignment)                         |
//+------------------------------------------------------------------+
enum ENUM_FRAME_ANCHOR
  {
   FRAME_ANCHOR_LEFT_TOP       =  0,                  // Frame anchor point at the upper left corner of the bounding rectangle
   FRAME_ANCHOR_CENTER_TOP     =  1,                  // Frame anchor point at the top center side of the bounding rectangle
   FRAME_ANCHOR_RIGHT_TOP      =  2,                  // Frame anchor point at the upper right corner of the bounding rectangle
   FRAME_ANCHOR_LEFT_CENTER    =  4,                  // Frame anchor point at the center of the left side of the bounding rectangle
   FRAME_ANCHOR_CENTER         =  5,                  // Frame anchor point at the center of the bounding rectangle
   FRAME_ANCHOR_RIGHT_CENTER   =  6,                  // Frame anchor point at the center of the right side of the bounding rectangle
   FRAME_ANCHOR_LEFT_BOTTOM    =  8,                  // Frame anchor point at the lower left corner of the bounding rectangle
   FRAME_ANCHOR_CENTER_BOTTOM  =  9,                  // Frame anchor point at the bottom center side of the bounding rectangle
   FRAME_ANCHOR_RIGHT_BOTTOM   =  10,                 // Frame anchor point at the lower right corner of the bounding rectangle
  };
//+------------------------------------------------------------------+

À enumerando de tipos de quadros de animação adicionamos um novo tipo, o quadro de animações de formas geométricas:

//+------------------------------------------------------------------+
//| Data for working with graphical element animation                |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of animation frame types                                    |
//+------------------------------------------------------------------+
enum ENUM_ANIMATION_FRAME_TYPE
  {
   ANIMATION_FRAME_TYPE_TEXT,                         // Text animation frame
   ANIMATION_FRAME_TYPE_QUAD,                         // Rectangular animation frame
   ANIMATION_FRAME_TYPE_GEOMETRY,                     // Square animation frame of geometric shapes
  };
//+------------------------------------------------------------------+

e na lista de tipos de formas a serem desenhadas adicionamos uma área sombreada que esquecemos de fazer em artigos anteriores:

//+------------------------------------------------------------------+
//| List of drawn shape types                                        |
//+------------------------------------------------------------------+
enum ENUM_FIGURE_TYPE
  {
   FIGURE_TYPE_PIXEL,                                 // Pixel
   FIGURE_TYPE_PIXEL_AA,                              // Pixel with antialiasing
   
   FIGURE_TYPE_LINE_VERTICAL,                         // Vertical line
   FIGURE_TYPE_LINE_VERTICAL_THICK,                   // a Vertical segment of a freehand line having a specified width using antialiasing algorithm
   
   FIGURE_TYPE_LINE_HORIZONTAL,                       // Horizontal line
   FIGURE_TYPE_LINE_HORIZONTAL_THICK,                 // Horizontal segment of a freehand line having a specified width using antialiasing algorithm
   
   FIGURE_TYPE_LINE,                                  // Arbitrary line
   FIGURE_TYPE_LINE_AA,                               // Line with antialiasing
   FIGURE_TYPE_LINE_WU,                               // Line with WU smoothing
   FIGURE_TYPE_LINE_THICK,                            // Segment of a freehand line having a specified width using antialiasing algorithm
   
   FIGURE_TYPE_POLYLINE,                              // Polyline
   FIGURE_TYPE_POLYLINE_AA,                           // Polyline with antialiasing
   FIGURE_TYPE_POLYLINE_WU,                           // Polyline with WU smoothing
   FIGURE_TYPE_POLYLINE_SMOOTH,                       // Polyline with a specified width using two smoothing algorithms
   FIGURE_TYPE_POLYLINE_THICK,                        // Polyline with a specified width using a smoothing algorithm    
   
   FIGURE_TYPE_POLYGON,                               // Polygon
   FIGURE_TYPE_POLYGON_FILL,                          // Filled polygon
   FIGURE_TYPE_POLYGON_AA,                            // Polygon with antialiasing
   FIGURE_TYPE_POLYGON_WU,                            // Polygon with WU smoothing
   FIGURE_TYPE_POLYGON_SMOOTH,                        // Polygon with a specified width using two smoothing algorithms
   FIGURE_TYPE_POLYGON_THICK,                         // Polygon with a specified width using a smoothing algorithm
   
   FIGURE_TYPE_RECTANGLE,                             // Rectangle
   FIGURE_TYPE_RECTANGLE_FILL,                        // Filled rectangle
   
   FIGURE_TYPE_CIRCLE,                                // Circle
   FIGURE_TYPE_CIRCLE_FILL,                           // Filled circle
   FIGURE_TYPE_CIRCLE_AA,                             // Circle with antialiasing
   FIGURE_TYPE_CIRCLE_WU,                             // Circle with WU smoothing
   
   FIGURE_TYPE_TRIANGLE,                              // Triangle
   FIGURE_TYPE_TRIANGLE_FILL,                         // Filled triangle
   FIGURE_TYPE_TRIANGLE_AA,                           // Triangle with antialiasing
   FIGURE_TYPE_TRIANGLE_WU,                           // Triangle with WU smoothing
   
   FIGURE_TYPE_ELLIPSE,                               // Ellipse
   FIGURE_TYPE_ELLIPSE_FILL,                          // Filled ellipse
   FIGURE_TYPE_ELLIPSE_AA,                            // Ellipse with antialiasing
   FIGURE_TYPE_ELLIPSE_WU,                            // Ellipse with WU smoothing
   
   FIGURE_TYPE_ARC,                                   // Ellipse arc
   FIGURE_TYPE_PIE,                                   // Ellipse sector
   
   FIGURE_TYPE_FILL,                                  // Filled area
   
  };
//+------------------------------------------------------------------+


No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh substituímos o nome da matriz de armazenamento de cópia do recurso gráfico por outro mais "significativo" - eu mesmo estava confuso com os nomes, ao tentar definir qual matriz era destinada para armazenar uma cópia da forma inicial -, e removemos da seção privada a classe do método que salva numa matriz o recurso gráfico:

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
   uint              m_duplicate_res[];                        // Array for storing resource data copy

//--- Return the cursor position relative to the (1) entire element and (2) the element's active area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
//--- Save the graphical resource to the array
   bool              ResourceCopy(const string source);

private:

Vamos substituir na listagem da classe (ou melhor, em todos os arquivos da biblioteca) as ocorrências das linhas "TEXT_ANCHOR" pelas linhas "FRAME_ANCHOR". Para encontrar todas as ocorrências em todos os arquivos da biblioteca, basta pressionar a combinação de teclas Shift+Ctrl+H e, na janela que se abre, inserir os seguintes critérios de pesquisa e substituição:


Naturalmente, no campo "Folder:" o caminho deve ser especificado com base na localização do seu editor.

Na seção pública da classe, declaramos os métodos para salvar numa matriz um recurso gráfico e restaurá-lo a partir dela, e escrevemos métodos para atualizar a tela e um método que retorna o tamanho da matriz de cópia de recurso gráfico:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   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; }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   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)];}

//--- Return the flag of the object supporting this 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;    }

//--- Return itself
   CGCnvElement     *GetObject(void)                                                   { return &this;   }

//--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CGCnvElement objects with each other by all properties (to search equal objects)
   bool              IsEqual(CGCnvElement* compared_obj) const;

//--- (1) Save the object to file and (2) upload the object from the file
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Create the element
   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);
                                
//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                     }
//--- Set the canvas update frequency
   void              SetFrequency(const ulong value)                                   { this.m_pause.SetWaitingMSC(value);         }
//--- Update the canvas
   void              CanvasUpdate(const bool redraw=false)                             { this.m_canvas.Update(redraw);              }
//--- Return the size of the graphical resource copy array
   uint              DuplicateResArraySize(void)                                       { return ::ArraySize(this.m_duplicate_res);  }
   
//--- Update the coordinates (shift the canvas)
   bool              Move(const int x,const int y,const bool redraw=false);

//--- Save an image to the array
   bool              ImageCopy(const string source,uint &array[]);
   
//--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorLightness(const uint clr,const double change_value);
   color             ChangeColorLightness(const color colour,const double change_value);
//--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount
   uint              ChangeColorSaturation(const uint clr,const double change_value);
   color             ChangeColorSaturation(const color colour,const double change_value);
   
protected:

O antigo método ResourceCopy() agora é chamado ResourceStamp():

//+------------------------------------------------------------------+
//| Save the graphical resource to the array                         |
//+------------------------------------------------------------------+
bool CGCnvElement::ResourceStamp(const string source)
  {
   return this.ImageCopy(DFUN,this.m_duplicate_res);
  }
//+------------------------------------------------------------------+

Método que restaura um recurso gráfico a partir de uma matriz:

//+------------------------------------------------------------------+
//| Restore the graphical resource from the array                    |
//+------------------------------------------------------------------+
bool CGCnvElement::Reset(void)
  {
//--- Get the size of the graphical resource copy array
   int size=::ArraySize(this.m_duplicate_res);
//--- If the array is empty, inform of that and return 'false'
   if(size==0)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return false;
     }
//--- If the size of the graphical resource copy array does not match the size of the graphical resource,
//--- inform of that in the journal and return 'false'
   if(this.m_canvas.Width()*this.m_canvas.Height()!=size)
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH);
      return false;
     }
//--- Set the index of the array for setting the image pixel
   int n=0;
//--- In the loop by the resource height,
   for(int y=0;y<this.m_canvas.Height();y++)
     {
      //--- in the loop by the resource width
      for(int x=0;x<this.m_canvas.Width();x++)
        {
         //--- Restore the next image pixel from the array and increase the array index
         this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]);
         n++;
        }
     }
//--- Update the data on the canvas and return 'true'
   this.m_canvas.Update(false);
   return true;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários ao código. Resumindo, verificamos o tamanho da matriz de cópia do recurso e, se estiver vazio ou se o tamanho da cópia não corresponder ao original, imprimiremos o erro no log e saímos do método. Em seguida, copiamos para a tela todos os dados da matriz de cópia pixel por pixel.

Como alteramos o nome da matriz de cópia de recursos e o método que salva o recurso gráfico nela, precisamos fazer correções no arquivo de classe de objeto sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.

Correções de bugs só são para o método GaussianBlur():

//+------------------------------------------------------------------+
//| Gaussian blur                                                    |
//| https://www.mql5.com/en/articles/1612#chapter4                   |
//+------------------------------------------------------------------+
bool CShadowObj::GaussianBlur(const uint radius)
  {
//---
   int n_nodes=(int)radius*2+1;
//--- Read graphical resource data. If failed, return false
   if(!CGCnvElement::ResourceStamp(DFUN))
      return false;
   
//--- Check the blur amount. If the blur radius exceeds half of the width or height, return '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;
     }
     
//--- Decompose image data from the resource into a, r, g, b color components
   int  size=::ArraySize(this.m_duplicate_res);
//--- arrays for storing A, R, G and B color components
//--- for horizontal and vertical blur
   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[];
   
//--- Change the size of component arrays according to the array size of the graphical resource 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;
     }
//--- Declare the array for storing blur weight ratios and,
//--- if failed to get the array of weight ratios, return 'false'
   double weights[];
   if(!this.GetQuadratureWeights(1,n_nodes,weights))
      return false;
      
//--- Set components of each image pixel to the color component arrays
   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]);
     }

//--- Blur the image horizontally (along the X axis)
   uint XY; // Pixel coordinate in the array
   double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0;
   int coef=0;
   int j=(int)radius;
   //--- Loop by the image width
   for(int Y=0;Y<this.Height();Y++)
     {
      //--- Loop by the image height
      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;
         //--- Multiply each color component by the weight ratio corresponding to the current image pixel
         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++;
           }
         //--- Save each rounded color component calculated according to the ratios to the component arrays
         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);
        }
      //--- Remove blur artifacts to the left by copying adjacent pixels
      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];
        }
      //--- Remove blur artifacts to the right by copying adjacent pixels
      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];
        }
     }

//--- Blur vertically (along the Y axis) the image already blurred horizontally
   int dxdy=0;
   //--- Loop by the image height
   for(int X=0;X<this.Width();X++)
     {
      //--- Loop by the image width
      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;
         //--- Multiply each color component by the weight ratio corresponding to the current image pixel
         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++;
           }
         //--- Save each rounded color component calculated according to the ratios to the component arrays
         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);
        }
      //--- Remove blur artifacts at the top by copying adjacent pixels
      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()];
        }
      //--- Remove blur artifacts at the bottom by copying adjacent pixels
      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()];
        }
     }
     
//--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array
   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]);
//--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array
   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]);
        }
     }
//--- Done
   return true;
  }
//+------------------------------------------------------------------+


Vamos modificar a classe do objeto-quadro de animação no arquivo \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.

Na seção protegida da classe, declaramos um método para escrever os valores das coordenadas e do deslocamento do retângulo de contorno como passados, para seu posterior uso, e escrevemos o método virtual para salvamento e restauração do fundo sob a imagem:

//+------------------------------------------------------------------+
//| Single animation frame class                                     |
//+------------------------------------------------------------------+
class CFrame : public CPixelCopier
  {
protected:
   ENUM_ANIMATION_FRAME_TYPE m_frame_figure_type;           // Type of the figure drawn by the frame
   ENUM_FRAME_ANCHOR m_anchor_last;                         // Last frame anchor point
   double            m_x_last;                              // X coordinate of the upper left corner of the last frame
   double            m_y_last;                              // Y coordinate of the upper left corner of the last frame
   int               m_shift_x_prev;                        // Offset of the X coordinate of the last frame upper left corner
   int               m_shift_y_prev;                        // Offset of the Y coordinate of the last frame upper left corner
//--- Set the coordinates and offset of the outlining rectangle as the previous ones
   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);
//--- Save and restore the background under the image
   virtual bool      SaveRestoreBG(void)                    { return false;                     }
public:

Todos esses métodos são o resultado da otimização do código dos métodos para desenhar formas nas classes que escrevemos nos artigos anteriores.

Aqui o método virtual simplesmente retorna false e deve ser implementado nas classes herdeiros (se sua implementação em todas as classes herdeiras for a mesma, faremos esse método não virtual e apenas nesta classe). Veremos o método SetLastParams() um pouco mais tarde.

Na seção pública da classe, escrevemos o método para zerar uma matriz de pixels:

public:
//--- Reset the pixel array
   void              ResetArray(void)                       { ::ArrayResize(this.m_array,0);    }
   
//--- Return the last (1) anchor point, (2) X and (3) Y coordinate,
//--- previous offset by (4) X and (5) Y, (6) type of the figure drawn by the frame
   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;  }
   
//--- Default constructor
                     CFrame();
protected:

O método simplesmente define o tamanho da matriz de pixels como zero. Isso é necessário para processar corretamente o redimensionamento do retângulo de contorno, pois, no método que salva o fundo para sua posterior restauração, o tamanho é verificado - se for zero, o fundo será salvo, caso contrário considera-se-á que o fundo foi previamente salvo com os valores corretos das coordenadas e tamanhos da área salva. Assim, se alterarmos a figura que está sendo desenhada, a matriz deve ser redefinida para zero. Caso contrário, o fundo sob a nova figura não será salvo e, logo, um fundo completamente diferente será restaurado de uma área completamente diferente (salva anteriormente - antes da alteração do tamanho, das coordenadas e da aparência da figura que está sendo desenhada).

Na seção protegida da classe, declaramos o construtor para uma classe - quadro de animação de formas geométricas, que criaremos e testaremos hoje:

protected:
//--- Text frame constructor
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const string text,
                            CGCnvElement *element);
//--- Rectangular frame constructor
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            CGCnvElement *element);
//--- Geometric frame constructor
                     CFrame(const int id,
                            const int x,
                            const int y,
                            const int len,
                            CGCnvElement *element);
  };
//+------------------------------------------------------------------+

Ao construtor da classe, por analogia com as outras classes herdadas criadas anteriormente, passaremos o identificador do objeto, as coordenadas X e Y do canto superior esquerdo do quadro, o comprimento das laterais do quadro quadrado e o ponteiro para o elemento gráfico a partir do qual é criado o novo objeto.

Implementação do construtor do objeto-quadro de animação geométrica:

//+------------------------------------------------------------------+
//| Geometric frame constructor                                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Na lista de inicialização, ao construtor da classe pai passamos todos os parâmetros necessários, e no corpo da classe escrevemos o tipo de forma como ANIMATION_FRAME_TYPE_GEOMETRY, tipo esse adicionado por nós hoje à lista de tipos de quadro de animação. O restante dos parâmetros são inicializados de forma semelhante aos construtores anteriormente considerados das classes de texto e animação retangular.

Método que registra as coordenadas e deslocamento do retângulo de contorno como passados:

//+------------------------------------------------------------------+
//| Set the coordinates and the offset                               |
//| of the outlining rectangle as the previous ones                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

A este método foi passado um trecho de código recorrente a partir dos métodos de desenho de formas com salvamento e restauração do fundo da forma, que consideramos em artigos anteriores.

Modificamos as classes herdeiros da classe CFrame.

Abrimos o arquivo da classe da animação retangular \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh e fazemos as alterações necessárias.

Na seção privada da classe declaramos duas variáveis para armazenar os deslocamentos das coordenadas do retângulo de contorno e declaramos um método virtual para salvar e restaurar o fundo sob a imagem:

//+------------------------------------------------------------------+
//| Class of a single rectangular animation frame                    |
//+------------------------------------------------------------------+
class CFrameQuad : public CFrame
  {
private:
   double            m_quad_x;                                 // X coordinate of the rectangle enclosing the shape
   double            m_quad_y;                                 // Y coordinate of the rectangle enclosing the shape
   uint              m_quad_width;                             // Width of the rectangle enclosing the shape
   uint              m_quad_height;                            // Height of the rectangle enclosing the shape
   int               m_shift_x;                                // Offset of the X coordinate of the rectangle enclosing the shape
   int               m_shift_y;                                // Offset of the Y coordinate of the rectangle enclosing the shape
//--- Save and restore the background under the image
   virtual bool      SaveRestoreBG(void);
public:

Na seção pública da classe, iremos complementar a implementação do construtor paramétrico - agora em seu corpo serão inicializadas todas as variáveis de classe (anteriormente não eram inicializadas, o que não é correto):

public:
//--- Constructors
                     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;
                       }

Vamos ver quais foram nossos métodos de desenho com salvamento/restauração do plano de fundo usando o exemplo do método de desenho de ponto:

//+------------------------------------------------------------------+
//| Set the color of the dot with the specified coordinates          |
//+------------------------------------------------------------------+
bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false)
  {
//--- Set the coordinates of the outlining rectangle
   this.m_quad_x=x;
   this.m_quad_y=y;
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
   this.m_quad_width=1;
   this.m_quad_height=1;
   
//--- Calculate coordinate offsets for the saved area depending on the anchor point
   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 the pixel array is not empty, the background under the image has already been saved -
//--- restore the previously saved background (by the previous coordinates and offsets)
   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 a background area with calculated coordinates and size under the future image is successfully saved
   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;

//--- Draw the shape and update the element
   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;
  }
//+------------------------------------------------------------------+

Agora podemos substituir as seções de código destacadas pelos novos métodos escritos hoje. Assim fica este método agora:

//+------------------------------------------------------------------+
//| Set the color of the dot with the specified coordinates          |
//+------------------------------------------------------------------+
bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false)
  {
//--- Set the coordinates of the outlining rectangle
   this.m_quad_x=x;
   this.m_quad_y=y;
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
   this.m_quad_width=1;
   this.m_quad_height=1;
   
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw the shape and update the element
   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;
  }
//+------------------------------------------------------------------+

Como se pode ver, a substituição das seções indicadas do código pela chamada de novos métodos reduziu significativamente o código e o tornou mais legível. Mudanças idênticas foram feitas em todos os métodos de desenho de formas com salvamento e restauração do plano de fundo. Como existem muitos métodos e fizemos as mesmas alterações em todos eles, não analisaremos suas listas inteiras - você pode se familiarizar com elas nos arquivos anexados ao artigo.

Vamos apenas nos deter nos métodos para desenhar elipses. Como você lembra, no último artigo não desenhamos elipses, pois podia acontecer uma potencial divisão por zero no CCanvas. Isso acontece se as mesmas coordenadas x1 e x2 ou y1 e y2 do retângulo no qual a elipse é desenhada forem passadas para o método. Por isso, neste caso, precisamos corrigir os valores das mesmas coordenadas se forem iguais:

//+------------------------------------------------------------------+
//| Draw an ellipse using two points while applying                  |
//| AntiAliasing algorithm                                           |
//+------------------------------------------------------------------+
bool CFrameQuad::DrawEllipseAAOnBG(const double x1,               // X coordinate of the first point defining the ellipse
                                   const double y1,               // Y coordinate of the first point defining the ellipse
                                   const double x2,               // X coordinate of the second point defining the ellipse
                                   const double y2,               // Y coordinate of the second point defining the ellipse
                                   const color  clr,              // Color
                                   const uchar  opacity=255,      // Opacity
                                   const bool   redraw=false,     // Chart redraw flag
                                   const uint   style=UINT_MAX)   // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
//--- Get the minimum and maximum coordinates
   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;
//--- Set the coordinates of the outlining rectangle
   this.m_quad_x=xn1-1;
   this.m_quad_y=yn1-1;
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
   this.m_quad_width=int(::ceil((xn2-xn1)+1))+2;
   this.m_quad_height=int(::ceil((yn2-yn1)+1))+2;
//--- Adjust the width and height of the outlining rectangle
   if(this.m_quad_width<3)
      this.m_quad_width=3;
   if(this.m_quad_height<3)
      this.m_quad_height=3;
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw the shape and update the element
   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;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Draw an ellipse using two points while applying                  |
//| Wu algorithm                                                     |
//+------------------------------------------------------------------+
bool CFrameQuad::DrawEllipseWuOnBG(const int   x1,             // X coordinate of the first point defining the ellipse
                                   const int   y1,             // Y coordinate of the first point defining the ellipse
                                   const int   x2,             // X coordinate of the second point defining the ellipse
                                   const int   y2,             // Y coordinate of the second point defining the ellipse
                                   const color clr,            // Color
                                   const uchar opacity=255,    // Opacity
                                   const bool  redraw=false,   // Chart redraw flag
                                   const uint  style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
//--- Get the minimum and maximum coordinates
   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;
//--- Set the coordinates of the outlining rectangle
   this.m_quad_x=xn1-1;
   this.m_quad_y=yn1-1;
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
   this.m_quad_width=int(::ceil((xn2-xn1)+1))+2;
   this.m_quad_height=int(::ceil((yn2-yn1)+1))+2;
//--- Adjust the width and height of the outlining rectangle
   if(this.m_quad_width<3)
      this.m_quad_width=3;

   if(this.m_quad_height<3)
      this.m_quad_height=3;
  
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw the shape and update the element
   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;
  }
//+------------------------------------------------------------------+

Método para preservar e restaurar o fundo sob a imagem:

//+------------------------------------------------------------------+
//| Save and restore the background under the image                  |
//+------------------------------------------------------------------+
bool CFrameQuad::SaveRestoreBG(void)
  {
//--- Calculate coordinate offsets for the saved area depending on the anchor point
   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 the pixel array is not empty, the background under the image has already been saved -
//--- restore the previously saved background (by the previous coordinates and offsets)
   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 the result of saving the background area with the calculated coordinates and size under the future image
   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);
  }
//+------------------------------------------------------------------+

Um bloco de código foi transferido ao método recorrente desde métodos para desenhar formas com salvamento e restauração do plano de fundo.

No arquivo \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh temos mudanças mínimas - simplesmente em dois lugares do código substituímos a linha "ENUM_TEXT_ANCHOR" por "ENUM_FRAME_ANCHOR":

//+------------------------------------------------------------------+
//| Single text animation frame class                                |
//+------------------------------------------------------------------+
class CFrameText : public CFrame
  {
private:

public:
//--- Display the text on the background while saving and restoring the background
   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);

//--- Constructors
                     CFrameText() {;}
                     CFrameText(const int id,CGCnvElement *element) : CFrame(id,0,0,"",element) {}
  };
//+--------------------------------------------------------------------------------------+
//| Display the text on the background, while saving and restoring the background        |
//+--------------------------------------------------------------------------------------+
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)
  {


Classe para o objeto de quadro de animação geométrica

A classe destinada ao objeto de quadro de animação geométrica, por sua lógica, não será muito diferente de seus dois predecessoras - objetos de quadros de animação retangulares e de texto. Precisaremos apenas criar um método que calcule as coordenadas dos vértices do polígono na circunferência, dependendo do seu número de vértices:

Fórmulas para calcular as coordenadas cartesianas de um polígono regular:

Sejam xc e yc as coordenadas do centro; R, o raio de um círculo circunscrito em torno de um polígono regular; ϕ0, a coordenada angular do primeiro vértice em relação ao centro. Depois as coordenadas cartesianas dos vértices de um polígono regular são determinados pelas fórmulas:


onde i toma valores de 0 a n−1

Na pasta \MQL5\Include\DoEasy\Objects\Graph\Animations\ criamos o novo arquivo FrameGeometry.mqh da classe CFrameGeometry.
No arquivo deve ser integrado o arquivo da classe do objeto-quadro de animação, e a classe deve ser herdada deste último:

//+------------------------------------------------------------------+
//|                                                FrameGeometry.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Frame.mqh"
//+------------------------------------------------------------------+
//| Class of a single rectangular animation frame                    |
//+------------------------------------------------------------------+
class CFrameGeometry : public CFrame
  {
  }

Vejamos a definição geral do corpo da classe: na seção privada são declarados todas as variáveis da classe e o método virtual para salvar e restaurar o fundo sob a imagem (nós vimos o método acima no contexto da classe de objeto de animação retangular, ele simplesmente é uma transferência de blocos de código recorrentes desde métodos de desenho de figuras estudados em artigos anteriores). Além disso, nessa mesma seção é declarado um método para calcular as coordenadas de um polígono regular.
Já na seção pública da classe estão localizados os construtores (padrão e paramétricos) e métodos para desenhar polígonos regulares - simples, preenchidos e com suavização:

//+------------------------------------------------------------------+
//| Class of a single rectangular animation frame                    |
//+------------------------------------------------------------------+
class CFrameGeometry : public CFrame
  {
private:
   double            m_square_x;                               // X coordinate of the square enclosing the shape
   double            m_square_y;                               // Y coordinate of the square enclosing the shape
   uint              m_square_length;                          // Length of the sides of the square enclosing the shape
   int               m_shift_x;                                // Offset of the X coordinate of the square enclosing the shape
   int               m_shift_y;                                // Offset of the Y coordinate of the square enclosing the shape
   int               m_array_x[];                              // Array of shape X coordinates
   int               m_array_y[];                              // Array of shape Y coordinates
//--- Save and restore the background under the image
   virtual bool      SaveRestoreBG(void);
   
//--- Calculate coordinates of the regular polygon built in a circumscribed circle inscribed in a square
   void              CoordsNgon(const int N,                   // Number of polygon vertices
                                const int coord_x,             // X coordinate of the upper-left square angle the circle will be inscribed into
                                const int coord_y,             // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon
                                const int len,                 // Square sides length
                                const double angle);           // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center)
public:
//--- Constructors
                     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;
                       }
//--- Destructor
                    ~CFrameGeometry()                             { ::ArrayFree(this.m_array_x); ::ArrayFree(this.m_array_y); }
                    
//+------------------------------------------------------------------+
//| Methods of drawing regular polygons                              |
//+------------------------------------------------------------------+
//--- Draw a regular polygon without smoothing
   bool              DrawNgonOnBG(const int    N,                          // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const bool   redraw=false);              // Chart redraw flag
                                  
//--- Draw a regular filled polygon
   bool              DrawNgonFillOnBG(const    int N,                      // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const bool   redraw=false);              // Chart redraw flag
                       
//--- Draw a regular polygon using AntiAliasing algorithm
   bool              DrawNgonAAOnBG(const int  N,                          // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const bool   redraw=false,               // Chart redraw flag
                                  const uint   style=UINT_MAX);            // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       
//--- Draw a regular polygon using Wu algorithm
   bool              DrawNgonWuOnBG(const int  N,                          // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const bool   redraw=false,               // Chart redraw flag
                                  const uint   style=UINT_MAX);            // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       
//--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms.
//--- First, individual segments are smoothed based on Bezier curves.
//--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. 
   bool              DrawNgonSmoothOnBG(const  int N,                      // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const int    size,                       // Line width
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const double tension=0.5,                // Smoothing parameter value
                                  const double step=10,                    // Approximation step
                                  const bool   redraw=false,               // Chart redraw flag
                                  const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                                  const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values
                       
//--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration
   bool              DrawNgonThickOnBG(const   int N,                      // Number of polygon vertices
                                  const int    coord_x,                    // X coordinate of the upper-left frame angle
                                  const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                  const int    len,                        // Frame sides length
                                  const double angle,                      // Polygon rotation angle
                                  const int    size,                       // line width
                                  const color  clr,                        // Color
                                  const uchar  opacity=255,                // Opacity
                                  const bool   redraw=false,               // Chart redraw flag
                                  const uint   style=STYLE_SOLID,          // line style
                                  ENUM_LINE_END end_style=LINE_END_ROUND); // line ends style
                                  
  };
//+------------------------------------------------------------------+

Vejamos a implementação de alguns dos métodos de classe.

Método para desenhar um polígono regular:

//+------------------------------------------------------------------+
//| Draw a regular polygon                                           |
//+------------------------------------------------------------------+
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)
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+

Como se pode ver, este método difere de outros semelhantes das classes anteriores (classe de quadro de animação retangular) no seguinte: neste caso, não são transferidas as matrizes - previamente preparadas - das coordenadas dos vértices do polígono, mas, sim, o número de vértices do polígono e de coordenadas do canto superior esquerdo do quadrado do quadro em que o polígono será desenhado, e já no próprio método é chamado um método que calcula as coordenadas dos vértices do polígono pelo número de vértices, coordenadas, o raio da circunferência e o ângulo de rotação, e neste último método são preenchidas as matrizes das coordenadas dos vértices X e Y. Em seguida, o polígono correspondente ao método é desenhado usando a classe CCanvas.

Vejamos uma comparação, este mesmo método desenha um polígono preenchido:

//+------------------------------------------------------------------+
//| Draw a regular filled polygon                                    |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonFillOnBG(const    int N,                   // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const bool   redraw=false)               // Chart redraw flag
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+

A diferença em relação ao primeiro método está apenas na chamada do método de desenho de polígono preenchido.

Os outros métodos são quase idênticos aos dois acima, com exceção de algumas peculiaridades quanto ao cálculo de coordenadas do retângulo de contorno para desenho de um polígono com uma determinada espessura de linha, uma vez que no cálculo das coordenadas e dimensões do retângulo de contorno deve ser considerado o valor da espessura da linha desenhada.

Outros métodos para desenhar polígonos regulares:

//+------------------------------------------------------------------+
//| Draw a regular filled polygon                                    |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonFillOnBG(const    int N,                   // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const bool   redraw=false)               // Chart redraw flag
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon using                                     |
//| AntiAliasing algorithm                                           |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonAAOnBG(const int  N,                          // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const bool   redraw=false,               // Chart redraw flag
                                       const uint   style=UINT_MAX)             // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon using                                     |
//| Wu algorithm                                                     |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonWuOnBG(const int  N,                          // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const bool   redraw=false,               // Chart redraw flag
                                       const uint   style=UINT_MAX)             // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon of a specified width                      |
//| using two smoothing algorithms in series.                        |
//| First, individual segments are smoothed based on Bezier curves.  |
//| Then, to improve the rendering quality,                          |
//| a raster smoothing algorithm is applied                          |
//| made of these segments.                                          |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonSmoothOnBG(const  int N,                      // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const int    size,                       // Line width
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const double tension=0.5,                // Smoothing parameter value
                                       const double step=10,                    // Approximation step
                                       const bool   redraw=false,               // Chart redraw flag
                                       const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                                       const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values
  {
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-1;
   this.m_square_y=coord_y-1;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon with a specified width using              |
//| a smoothing algorithm with the preliminary sorting               |
//+------------------------------------------------------------------+
bool CFrameGeometry::DrawNgonThickOnBG(const   int N,                      // Number of polygon vertices
                                       const int    coord_x,                    // X coordinate of the upper-left frame angle
                                       const int    coord_y,                    // Y coordinate of the upper-left frame angle
                                       const int    len,                        // Frame sides length
                                       const double angle,                      // Polygon rotation angle
                                       const int    size,                       // line width
                                       const color  clr,                        // Color
                                       const uchar  opacity=255,                // Opacity
                                       const bool   redraw=false,               // Chart redraw flag
                                       const uint   style=STYLE_SOLID,          // line style
                                       ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style
  {
//--- Calculate the adjustment of the outlining rectangle coordinates depending on the line size
   int correct=int(::ceil((double)size/2.0))+1;
//--- Set the coordinates of the outlining rectangle
   this.m_square_x=coord_x-correct;
   this.m_square_y=coord_y-correct;
//--- Set the width and height of a square frame (to be used as the size of the saved area)
   this.m_square_length=len+correct*2;
//--- Calculate the polygon coordinates on the circle
   this.CoordsNgon(N,coord_x,coord_y,len,angle);
      
//--- Restore the previously saved background and save the new one
   if(!this.SaveRestoreBG())
      return false;
      
//--- Draw a polygon inscribed in a circle and update the element
   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;
  }
//+------------------------------------------------------------------+


Método virtual que salva e restaura o fundo sob a imagem:

//+------------------------------------------------------------------+
//| Save and restore the background under the image                  |
//+------------------------------------------------------------------+
bool CFrameGeometry::SaveRestoreBG(void)
  {
//--- Calculate coordinate offsets for the saved area depending on the anchor point
   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 the pixel array is not empty, the background under the image has already been saved -
//--- restore the previously saved background (by the previous coordinates and offsets)
   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 the result of saving the background area with the calculated coordinates and size under the future image
   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);
  }
//+------------------------------------------------------------------+

Este é um bloco de código recorrente transferido a partir dos métodos de desenho de forma vistos no último artigo.

Método para calcular as coordenadas de um polígono regular circunscrito:

//+------------------------------------------------------------------+
//| Calculate the coordinates of the regular polygon                 |
//+------------------------------------------------------------------+
void CFrameGeometry::CoordsNgon(const int N,          // Number of polygon vertices
                                const int coord_x,    // X coordinate of the upper-left square angle the circle will be inscribed into
                                const int coord_y,    // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon
                                const int len,        // Length of the sides of the square a polygon is to be inscribed into
                                const double angle)   // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center)
  {
//--- If there are less than three sides, there will be three
   int n=(N<3 ? 3 : N);
//--- Set the size of coordinate arrays according to the number of vertices
   ::ArrayResize(this.m_array_x,n);
   ::ArrayResize(this.m_array_y,n);
//--- Calculate the radius of the circumscribed circle
   double R=(double)len/2.0;
//--- X and Y coordinates of the circle center
   double xc=coord_x+R;
   double yc=coord_y+R;
//--- Calculate the polygon inclination angle in degrees
   double grad=angle*M_PI/180.0;
//--- In the loop by the number of vertices, calculate the coordinates of each next polygon vertex
   for(int i=0; i<n; i++)
     {
      //--- Angle of the current polygon vertex with the rotation in degrees
      double a=2.0*M_PI*i/n+grad;
      //--- X and Y coordinates of the current polygon vertex
      double xi=xc+R*::cos(a);
      double yi=yc+R*::sin(a);
      //--- Set the current coordinates to the arrays
      this.m_array_x[i]=int(::floor(xi));
      this.m_array_y[i]=int(::floor(yi));
     }
  }
//+------------------------------------------------------------------+

A lógica do método é descrita em detalhes nos comentários ao código, eis as fórmulas de cálculo das coordenadas cartesianas do polígono:


Vamos deixar o método para que cada um de nós analise individualmente. Acho que não deveria suscitar perguntas. Em qualquer caso, você pode colocar qualquer questão na discussão do artigo.

A classe de objeto-quadro de animação geométrica agora está completa.

Agora precisamos dar acesso a ela a partir de um programa externo e gerar a capacidade de criar objetos rapidamente.

Todos os objetos recém-criados de quadros de animação são armazenados em listas próprias na classe CAnimations.
Realizamos as alterações necessárias no arquivo da classe \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh.

Ao arquivo da classe anexamos o arquivo destinado à recém-criada classe do objeto de quadro de animação geométrica e na seção privada da classe declaramos a lista, em que todos os objetos recém-criados desta classe serão armazenados:

//+------------------------------------------------------------------+
//|                                                   Animations.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "FrameText.mqh"
#include "FrameQuad.mqh"
#include "FrameGeometry.mqh"
//+------------------------------------------------------------------+
//| Pixel copier class                                               |
//+------------------------------------------------------------------+
class CAnimations : public CObject
  {
private:
   CGCnvElement     *m_element;                             // Pointer to the graphical element
   CArrayObj         m_list_frames_text;                    // List of text animation frames
   CArrayObj         m_list_frames_quad;                    // List of rectangular animation frames
   CArrayObj         m_list_frames_geom;                    // List of geometric shape animations frames

//--- Return the flag indicating the presence of the frame object with the specified ID in the list
   bool              IsPresentFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id);
//--- Return or create a new animation frame object
   CFrame           *GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new);

public:

Na seção pública da classe declaramos um método para criar um novo objeto de quadro de animação geométrica e escrevemos um método que retorna um ponteiro para a lista desses objetos:

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

//--- Create a new (1) rectangular, (2) text and geometric animation frame object
   CFrame           *CreateNewFrameText(const int id);
   CFrame           *CreateNewFrameQuad(const int id);
   CFrame           *CreateNewFrameGeometry(const int id);
//--- Return the animation frame objects by ID
   CFrame           *GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id);
//--- Return the list of (1) text, (2) rectangular and (3) geometric shape animation frames
   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;  }

A seguir, declaramos métodos para desenhar polígonos regulares:

//+------------------------------------------------------------------+
//| Methods of drawing regular polygons                              |
//+------------------------------------------------------------------+
//--- Draw a regular polygon without smoothing
   bool              DrawNgonOnBG(const int id,                         // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false);               // Chart redraw flag
                                  
//--- Draw a regular filled polygon
   bool              DrawNgonFillOnBG(const int id,                     // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false);               // Chart redraw flag
                       
//--- Draw a regular polygon using AntiAliasing algorithm
   bool              DrawNgonAAOnBG(const int id,                       // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=UINT_MAX);             // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       
//--- Draw a regular polygon using Wu algorithm
   bool              DrawNgonWuOnBG(const int id,                       // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=UINT_MAX);             // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       
//--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms.
//--- First, individual segments are smoothed based on Bezier curves.
//--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. 
   bool              DrawNgonSmoothOnBG(const int id,                   // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const int    size,                        // Line width
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const double tension=0.5,                 // Smoothing parameter value
                              const double step=10,                     // Approximation step
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const ENUM_LINE_STYLE style=STYLE_SOLID,  // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                              const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values
                       
//--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration
   bool              DrawNgonThickOnBG(const int id,                    // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const int    size,                        // line width
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=STYLE_SOLID,           // line style
                              ENUM_LINE_END end_style=LINE_END_ROUND);  // line ends style
  
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+


Todas as ocorrências da string "ENUM_TEXT_ANCHOR" na listagem de classes devem ser substituídas pela string "ENUM_FRAME_ANCHOR".

No método que retorna um objeto-quadro de animação por tipo e identificador, incluiremos o processamento de novo tipo de objeto-quadro de animação:

//+------------------------------------------------------------------+
//| Return the animation frame objects by type and ID                |
//+------------------------------------------------------------------+
CFrame *CAnimations::GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id)
  {
//--- Declare the pointer to the animation frame object
   CFrame *frame=NULL;
//--- Depending on the necessary object type, receive their number in the appropriate list
   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
     );
//--- Get the next object in the loop ...
   for(int i=0;i<total;i++)
     {
      //--- ... by the list corresponding to the animation frame type
      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 failed to get the pointer, move on to the next one
      if(frame==NULL)
         continue;
      //--- If the object ID correspond to the required one,
      //--- return the pointer to the detected object
      if(frame.ID()==id)
         return frame;
     }
//--- Nothing is found - return NULL
   return NULL;
  }
//+------------------------------------------------------------------+

Método que cria um novo objeto-quadro de animação geométrica:

//+------------------------------------------------------------------+
//| Create a new geometric animation frame object                    |
//+------------------------------------------------------------------+
CFrame *CAnimations::CreateNewFrameGeometry(const int id)
  {
//--- If the object with such an ID is already present, inform of that in the journal and return NULL
   if(this.IsPresentFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id))
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_FRAME_ALREADY_IN_LIST),(string)id);
      return NULL;
     }
//--- Create a new geometric animation frame object with the specified ID
   CFrame *frame=new CFrameGeometry(id,this.m_element);
//--- If failed to create an object, inform of that and return NULL
   if(frame==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME));
      return NULL;
     }
//--- If failed to add the created object to the list, inform of that, remove the object and return 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 the pointer to a newly created object
   return frame;
  }
//+------------------------------------------------------------------+

A lógica do método é totalmente descrita nos comentários do código.

No método que retorna ou cria um novo objeto-quadro de animação, incluímos o processamento de novo tipo de quadro de animação:

//+------------------------------------------------------------------+
//| Return or create a new animation frame object                    |
//+------------------------------------------------------------------+
CFrame *CAnimations::GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new)
  {
   //--- Declare null pointers to objects
   CFrameQuad     *frame_q=NULL;
   CFrameText     *frame_t=NULL;
   CFrameGeometry *frame_g=NULL;
   //--- Depending on the required object type
   switch(frame_type)
     {
      //--- If this is a text animation frame,
      case ANIMATION_FRAME_TYPE_TEXT :
        //--- get the pointer to an object with a specified ID
        frame_t=this.GetFrame(ANIMATION_FRAME_TYPE_TEXT,id);
        //--- If the pointer is obtained, return it
        if(frame_t!=NULL)
           return frame_t;
        //--- If the flag of creating a new object is not set, report an error and return NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Return the result of creating a new text animation frame object (pointer to the created object)
        return this.CreateNewFrameText(id);
      
      //--- If this is a rectangular animation frame
      case ANIMATION_FRAME_TYPE_QUAD :
        //--- get the pointer to an object with a specified ID
        frame_q=this.GetFrame(ANIMATION_FRAME_TYPE_QUAD,id);
        //--- If the pointer is obtained, return it
        if(frame_q!=NULL)
           return frame_q;
        //--- If the flag of creating a new object is not set, report an error and return NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Return the result of creating a new rectangular animation frame object (pointer to the created object)
        return this.CreateNewFrameQuad(id);
      
      //--- If this is a geometric animation frame
      case ANIMATION_FRAME_TYPE_GEOMETRY :
        //--- get the pointer to an object with a specified ID
        frame_g=this.GetFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id);
        //--- If the pointer is obtained, return it
        if(frame_g!=NULL)
           return frame_g;
        //--- If the flag of creating a new object is not set, report an error and return NULL
        if(!create_new)
          {
           ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id);
           return NULL;
          }
        //--- Return the result of creating a new geometric animation frame object (pointer to the created object)
        return this.CreateNewFrameGeometry(id);
      //--- In the remaining cases, return NULL
      default:
        return NULL;
     }
  }
//+------------------------------------------------------------------+

Aqui toda a lógica é descrita nos comentários ao código.

No final da listagem da classes, incluímos a implementação de métodos para desenhar polígonos regulares:

//+------------------------------------------------------------------+
//| Draw a regular polygon without smoothing                         |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonOnBG(const int id,                         // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false)                // Chart redraw flag
  {
   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);
  }
//+------------------------------------------------------------------+
//| Draw a regular filled polygon                                    |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonFillOnBG(const int id,                     // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false)                // Chart redraw flag
  {
   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);
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon using                                     |
//| AntiAliasing algorithm                                           |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonAAOnBG(const int id,                       // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false,                // Chart redraw flag
                           const uint   style=UINT_MAX)              // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
   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);
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon using                                     |
//| Wu algorithm                                                     |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonWuOnBG(const int id,                       // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false,                // Chart redraw flag
                           const uint   style=UINT_MAX)              // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
  {
   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);
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon of a specified width                      |
//| using two smoothing algorithms in series.                        |
//| First, individual segments are smoothed based on Bezier curves.  |
//| Then, to improve the rendering quality,                          |
//| a raster smoothing algorithm is applied                          |
//| to the polygon made of these segments.                           |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonSmoothOnBG(const int id,                   // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const int    size,                        // Line width
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const double tension=0.5,                 // Smoothing parameter value
                           const double step=10,                     // Approximation step
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false,                // Chart redraw flag
                           const ENUM_LINE_STYLE style=STYLE_SOLID,  // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                           const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values
  {
   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);
  }
//+------------------------------------------------------------------+
//| Draw a regular polygon with a specified width using              |
//| a smoothing algorithm with the preliminary sorting               |
//+------------------------------------------------------------------+
bool CAnimations::DrawNgonThickOnBG(const int id,                    // Frame ID
                           const int    N,                           // Number of polygon vertices
                           const int    coord_x,                     // X coordinate of the upper-left frame angle
                           const int    coord_y,                     // Y coordinate of the upper-left frame angle
                           const int    len,                         // Frame sides length
                           const double angle,                       // Polygon rotation angle
                           const int    size,                        // line width
                           const color  clr,                         // Color
                           const uchar  opacity=255,                 // Opacity
                           const bool   create_new=true,             // New object creation flag
                           const bool   redraw=false,                // Chart redraw flag
                           const uint   style=STYLE_SOLID,           // line style
                           ENUM_LINE_END end_style=LINE_END_ROUND)   // line ends style
  {
   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);
  }
//+------------------------------------------------------------------+

Visto que a lógica de todos esses métodos é absolutamente idêntica, daremos uma olhada no exemplo do último método.

Como se pode ver, basta obtermos desde a lista um objeto-quadro de animação geométrica já criado ou, se não houver nenhum nela e se definido o sinalizador de criação de novo objeto, basta criamos um, já se o objeto não puder ser obtido ou criado, retornamos false.
De outra forma, retornamos o resultado da chamada do método da classe - que tem o mesmo nome - do objeto-quadro da animação geométrica recuperado da lista ou recém-criado.


Agora modificaremos a classe do objeto-forma no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Todas as ocorrências da linha "ENUM_TEXT_ANCHOR" na listagem da classe devem ser substituídas por "ENUM_FRAME_ANCHOR".

Na seção privada da classe declararemos os métodos que permitirão redefinir para zero os tamanhos das matrizes de pixels de três classes de quadros de animação:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_elements;                          // List of attached elements
   CAnimations      *m_animations;                             // Pointer to the animation object
   CShadowObj       *m_shadow_obj;                             // Pointer to the shadow object
   color             m_color_frame;                            // Form frame color
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom

//--- Initialize the variables
   void              Initialize(void);
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- Return the name of the dependent object

Precisamos disso para o correto funcionamento dos métodos de salvamento/restauração do fundo da forma onde as figuras são desenhadas (vimos isso acima).

Na seção pública da classe escrevemos um método para definir a aparência da forma e declaramos um método virtual que restaura um recurso gráfico desde uma matriz:

//--- Draw an embossed (concave) field
   void              DrawFieldStamp(const int x,                              // X coordinate relative to the form
                                    const int y,                              // Y coordinate relative to the form
                                    const int width,                          // Field width
                                    const int height,                         // Field height
                                    const color colour,                       // Field color
                                    const uchar opacity);                     // Field opacity

//--- Capture the appearance of the created form
   void              Done(void)     { CGCnvElement::CanvasUpdate(false); CGCnvElement::ResourceStamp(DFUN);                   }
//--- Restore the resource from the array
   virtual bool      Reset(void);

//+------------------------------------------------------------------+
//| Methods of working with image pixels                             |
//+------------------------------------------------------------------+

Por que precisamos de um método para definir a aparência da forma?
Vejamos que se, por exemplo, criássemos uma forma e desenhássemos nela todos os elementos inalteráveis necessários, agora precisaríamos copiar a nova aparência da forma na matriz de cópia do recurso gráfico, para que, se necessário, pudéssemos retornar a aparência original da forma. Afinal, tudo o que desenhamos na forma e todas as mudanças são exibidos no recurso gráfico, portanto para que não tenhamos que redesenhar toda a forma, basta armazenarmos numa matriz especial uma cópia da forma inicial e restaurarmos a aparência original a partir desta matriz - método Reset().

Na seção pública da classe, escreveremos métodos para desenhar polígonos regulares:

//--- Draw a regular polygon without smoothing
   bool              DrawNgonOnBG(const int id,                         // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false)                // Chart redraw flag
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false);  }
                       
//--- Draw a regular filled polygon
   bool              DrawNgonFillOnBG(const int id,                     // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false)                // Chart redraw flag
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonFillOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false);  }
                       
//--- Draw a regular polygon using AntiAliasing algorithm
   bool              DrawNgonAAOnBG(const int id,                       // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=UINT_MAX)              // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonAAOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false);  }
                       
//--- Draw a regular polygon using Wu algorithm
   bool              DrawNgonWuOnBG(const int id,                       // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=UINT_MAX)              // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                       { return(this.m_animations!=NULL ? this.m_animations.DrawNgonWuOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false);  }
                       
//--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms.
//--- First, individual segments are smoothed based on Bezier curves.
//--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. 
   bool              DrawNgonSmoothOnBG(const int id,                   // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const int    size,                        // Line width
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const double tension=0.5,                 // Smoothing parameter value
                              const double step=10,                     // Approximation step
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const ENUM_LINE_STYLE style=STYLE_SOLID,  // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value
                              const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values
                       { 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);  }
                       
//--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration
   bool              DrawNgonThickOnBG(const int id,                    // Frame ID
                              const int    N,                           // Number of polygon vertices
                              const int    coord_x,                     // X coordinate of the upper-left frame angle
                              const int    coord_y,                     // Y coordinate of the upper-left frame angle
                              const int    len,                         // Frame sides length
                              const double angle,                       // Polygon rotation angle
                              const int    size,                        // line width
                              const color  clr,                         // Color
                              const uchar  opacity=255,                 // Opacity
                              const bool   create_new=true,             // New object creation flag
                              const bool   redraw=false,                // Chart redraw flag
                              const uint   style=STYLE_SOLID,           // line style
                              ENUM_LINE_END end_style=LINE_END_ROUND)   // line ends style
                       { 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);  }

Todos os métodos são idênticos e retornam o resultado da chamada dos métodos em questão da instância da classe CAnimations discutidos acima.

Fora do corpo da classe, vamos escrever a implementação dos métodos declarados.

Três métodos para redefinir os tamanhos de matrizes de três objetos-quadros de animação:

//+------------------------------------------------------------------+
//| Reset the array size of the text animation frames                |
//+------------------------------------------------------------------+
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();
     }
  }
//+------------------------------------------------------------------+
//| Reset the size of the rectangular animation frame array          |
//+------------------------------------------------------------------+
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();
     }
  }
//+------------------------------------------------------------------+
//| Reset the size of the geometric animation frame array            |
//+------------------------------------------------------------------+
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();
     }
  }
//+------------------------------------------------------------------+

Todos os métodos são idênticos:

Se o objeto da classe CAnimation não existir, saímos do método, uma vez que este não possuirá animações.
Obtemos um ponteiro para a lista de quadros de animação correspondentes ao método. Num loop ao longo da lista resultante obtemos um ponteiro para o próximo objeto-quadro de animação e redefinimos sua matriz de pixels para zero.

Método que restaura um recurso desde uma matriz:

//+------------------------------------------------------------------+
//| Restore the resource from the array                              |
//+------------------------------------------------------------------+
bool CForm::Reset(void)
  {
   CGCnvElement::Reset();
   this.ResetArrayFrameQ();
   this.ResetArrayFrameT();
   this.ResetArrayFrameG();
   return true;
  }
//+------------------------------------------------------------------+

Inicialmente, chamamos o método da classe pai que restaura o recurso gráfico desde a matriz de cópia e, em seguida, zeramos as matrizes de pixels de todos os objetos-quadros de animação, para que depois de restaurar a aparência da forma, possamos copiar novamente o fundo com as coordenadas desejadas e o tamanho da área de fundo salva.

Então, estamos prontos para testar o desenho de polígonos regulares numa forma.


Teste

Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part80\ com o novo nome TestDoEasyPart80.mq5.

Como vamos testar? Se você se lembra, no último artigo desenhamos figuras num objeto-forma usando o teclado. Faremos o mesmo hoje. Só que desta vez remapearemos os botões que, quando eram pressionados, desenhavam polígonos com atribuição dinâmica de coordenadas e tamanhos. Hoje também alteraremos dinamicamente as coordenadas do quadro de animação ao longo do eixo X e o número de vértices do polígono desenhado (de 3 a 10).

  • Ao pressionar a tecla "Y" será desenhado um polígono regular não suavizado,
  • Ao pressionar a tecla "U" será desenhado um polígono preenchido não suavizado,
  • Ao pressionar a tecla "I" será desenhado um polígono regular suavizado pelo algoritmo AntiAlliasing (AA),
  • Ao pressionar a tecla "O" será desenhado um polígono regular suavizado pelo algoritmo de Xiaolin Wu,
  • Ao pressionar a tecla "P" será desenhado um polígono regular de uma determinada espessura suavizado por dois algoritmos de suavização (Smooth),
  • Ao pressionar a tecla "A" será desenhado um polígono regular de uma determinada espessura usando suavização com pré-filtrado (Thick),
  • À tecla "." atribuiremos o desenho da área preenchida - será preenchida toda a forma com a cor especificada.

Além disso, com cada clique na forma, a coordenada X do quadro desenhado mudará e o número de vértices do polígono desenhado aumentará em 1.

Alteramos todas as ocorrências da substring "TEXT_ANCHOR" por "FRAME_ANCHOR".

No manipulador OnInit() do Expert Advisor, após a criação de cada forma, fixamos sua aparência:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   ArrayResize(array_clr,2);
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the specified number of form objects
   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;
        }
      //--- When creating an object, pass all the required parameters to it
      CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30));
      if(form==NULL)
         continue;
      //--- Set activity and moveability flags for the form
      form.SetActive(true);
      form.SetMovable(false);
      //--- Set the form ID equal to the loop index and the index in the list of objects
      form.SetID(i);
      form.SetNumber(0);   // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them
      //--- Set the partial opacity for the middle form and the full one for the rest
      uchar opacity=(i==1 ? 250 : 255);
      //--- Set the form style and its color theme depending on the loop index
      if(i<2)
        {
         ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i;
         ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i;
         //--- Set the form style and theme
         form.SetFormStyle(style,theme,opacity,true,false);
        }
      //--- If this is the first (top) form
      if(i==0)
        {
         //--- Draw a concave field slightly shifted from the center of the form downwards
         form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity());
         form.Done();
        }
      //--- If this is the second form
      if(i==1)
        {
         //--- Draw a concave semi-transparent "tainted glass" field in the center
         form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200);
         form.Done();
        }
      //--- If this is the third form
      if(i==2)
        {
         //--- Set the opacity of 200
         form.SetOpacity(200);
         //--- The form background color is set as the first color from the color array
         form.SetColorBackground(array_clr[0]);
         //--- Form outlining frame color
         form.SetColorFrame(clrDarkBlue);
         //--- Draw the shadow drawing flag
         form.SetShadow(true);
         //--- Calculate the shadow color as the chart background color converted to the monochrome one
         color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
         //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
         //--- Otherwise, use the color specified in the settings for drawing the shadow
         color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
         //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
         //--- Set the shadow opacity to 200, while the blur radius is equal to 4
         form.DrawShadow(3,3,clr,200,4);
         //--- Fill the form background with a vertical gradient
         form.Erase(array_clr,form.Opacity());
         //--- Draw an outlining rectangle at the edges of the form
         form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
         form.Done();
         
         //--- Display the text describing the gradient type and update the form
         //--- Text parameters: the text coordinates and the anchor point in the form center
         //--- Create a new text animation frame with the ID of 0 and display the text on the form
         form.TextOnBG(0,TextByLanguage("V-Градиент","V-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
        }
      //--- If this is the fourth (bottom) form
      if(i==3)
        {
         //--- Set the opacity of 200
         form.SetOpacity(200);
         //--- The form background color is set as the first color from the color array
         form.SetColorBackground(array_clr[0]);
         //--- Form outlining frame color
         form.SetColorFrame(clrDarkBlue);
         //--- Draw the shadow drawing flag
         form.SetShadow(true);
         //--- Calculate the shadow color as the chart background color converted to the monochrome one
         color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100);
         //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units
         //--- Otherwise, use the color specified in the settings for drawing the shadow
         color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3);
         //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
         //--- Set the shadow opacity to 200, while the blur radius is equal to 4
         form.DrawShadow(3,3,clr,200,4);
         //--- Fill the form background with a horizontal gradient
         form.Erase(array_clr,form.Opacity(),false);
         //--- Draw an outlining rectangle at the edges of the form
         form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity());
         form.Done();
         
         //--- Display the text describing the gradient type and update the form
         //--- Text parameters: the text coordinates and the anchor point in the form center
         //--- Create a new text animation frame with the ID of 0 and display the text on the form
         form.TextOnBG(0,TextByLanguage("H-Градиент","H-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true);
        }
      //--- Add objects to the list
      if(!list_forms.Add(form))
        {
         delete form;
         continue;
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


No bloco de processamento de teclas do manipulador OnChartEvent() incluímos a chamada do método de restauração de aparência da forma e redefinição - para zero - das matrizes de pixels dos objetos-quadros:

//--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Get a drawn shape type depending on a pressed key
      figure_type=FigureType(lparam);
      //--- If the shape type has changed
      if(figure_type!=figure_type_prev)
        {
         //--- Get the text of the drawn shape type description
         figure=FigureTypeDescription(figure_type);
         //--- In the loop by all forms, 
         for(int i=0;i<list_forms.Total();i++)
           {
            //--- get the pointer to the next form object
            CForm *form=list_forms.At(i);
            if(form==NULL)
               continue;
            //--- If the form ID is 2,
            if(form.ID()==2)
              {
               //--- Reset all coordinate shifts to zero, restore the form background and display the text describing the drawn shape type
               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);
              }
           }
         //--- Write the new shape type
         figure_type_prev=figure_type;
        }
     }

Na função FigureType() incluímos o processamento da tecla "." :

//+------------------------------------------------------------------+
//| Return the shape depending on the pressed key                    |
//+------------------------------------------------------------------+
ENUM_FIGURE_TYPE FigureType(const long key_code)
  {
   switch((int)key_code)
     {
      //--- "1" = Dot
      case 49  :  return FIGURE_TYPE_PIXEL;
      
      //--- "2" = Dot with AntiAlliasing
      case 50  :  return FIGURE_TYPE_PIXEL_AA;
      
      //--- "3" = Vertical line
      case 51  :  return FIGURE_TYPE_LINE_VERTICAL;
      
      //--- "4" = Vertical segment of a freehand line having a specified width using a smoothing algorithm
      case 52  :  return FIGURE_TYPE_LINE_VERTICAL_THICK;
      
      //--- "5" = Horizontal line
      case 53  :  return FIGURE_TYPE_LINE_HORIZONTAL;
      
      //--- "6" = Horizontal segment of a freehand line having a specified width using a smoothing algorithm
      case 54  :  return FIGURE_TYPE_LINE_HORIZONTAL_THICK;
      
      //--- "7" = Freehand line
      case 55  :  return FIGURE_TYPE_LINE;
      
      //--- "8" = Line with AntiAlliasing
      case 56  :  return FIGURE_TYPE_LINE_AA;
      
      //--- "9" = Line with WU
      case 57  :  return FIGURE_TYPE_LINE_WU;
      
      //--- "0" = Segment of a freehand line having a specified width using a smoothing algorithm
      case 48  :  return FIGURE_TYPE_LINE_THICK;
      
      //--- "q" = Polyline
      case 81  :  return FIGURE_TYPE_POLYLINE;
      
      //--- "w" = Polyline with AntiAlliasing
      case 87  :  return FIGURE_TYPE_POLYLINE_AA;
      
      //--- "e" = Polyline with WU
      case 69  :  return FIGURE_TYPE_POLYLINE_WU;
      
      //--- "r" = Polyline with a specified width using two smoothing algorithms
      case 82  :  return FIGURE_TYPE_POLYLINE_SMOOTH;
      
      //--- "t" = Polyline with a specified width using a smoothing algorithm
      case 84  :  return FIGURE_TYPE_POLYLINE_THICK;
      
      //--- "y" = Polygon
      case 89  :  return FIGURE_TYPE_POLYGON;
      
      //--- "u" = Filled polygon
      case 85  :  return FIGURE_TYPE_POLYGON_FILL;
      
      //--- "i" = Polygon with AntiAlliasing
      case 73  :  return FIGURE_TYPE_POLYGON_AA;
      
      //--- "o" = Polygon with WU
      case 79  :  return FIGURE_TYPE_POLYGON_WU;
      
      //--- "p" = Polygon with a specified width using two smoothing algorithms
      case 80  :  return FIGURE_TYPE_POLYGON_SMOOTH;
      
      //--- "a" = Polygon with a specified width using a smoothing algorithm
      case 65  :  return FIGURE_TYPE_POLYGON_THICK;
      
      //--- "s" = Rectangle
      case 83  :  return FIGURE_TYPE_RECTANGLE;
      
      //--- "d" = Filled rectangle
      case 68  :  return FIGURE_TYPE_RECTANGLE_FILL;
      
      //--- "f" = Circle
      case 70  :  return FIGURE_TYPE_CIRCLE;
      
      //--- "g" = Filled circle
      case 71  :  return FIGURE_TYPE_CIRCLE_FILL;
      
      //--- "h" = Circle with AntiAlliasing
      case 72  :  return FIGURE_TYPE_CIRCLE_AA;
      
      //--- "j" = Circle with WU
      case 74  :  return FIGURE_TYPE_CIRCLE_WU;
      
      //--- "k" = Triangle
      case 75  :  return FIGURE_TYPE_TRIANGLE;
      
      //--- "l" = Filled triangle
      case 76  :  return FIGURE_TYPE_TRIANGLE_FILL;
      
      //--- "z" = Triangle with AntiAlliasing
      case 90  :  return FIGURE_TYPE_TRIANGLE_AA;
      
      //--- "x" = Triangle with WU
      case 88  :  return FIGURE_TYPE_TRIANGLE_WU;
      
      //--- "c" = Ellipse
      case 67  :  return FIGURE_TYPE_ELLIPSE;
      
      //--- "v" = Filled ellipse
      case 86  :  return FIGURE_TYPE_ELLIPSE_FILL;
      
      //--- "b" = Ellipse with AntiAlliasing
      case 66  :  return FIGURE_TYPE_ELLIPSE_AA;
      
      //--- "n" = Ellipse with WU
      case 78  :  return FIGURE_TYPE_ELLIPSE_WU;
      
      //--- "m" = Ellipse arc
      case 77  :  return FIGURE_TYPE_ARC;
      
      //--- "," = Ellipse sector
      case 188 :  return FIGURE_TYPE_PIE;
      
      //--- "." = Filled area
      case 190 :  return FIGURE_TYPE_FILL;
      
      //--- Default = Dot
      default  :  return FIGURE_TYPE_PIXEL;
     }
  }
//+------------------------------------------------------------------+

Na função FigureProcessing() tornamos as matrizes de coordenadas dinâmicas:

//+------------------------------------------------------------------+
//| Handle the selected shape                                        |
//+------------------------------------------------------------------+
void FigureProcessing(CForm *form,const ENUM_FIGURE_TYPE figure_type)
  {
   int array_x[];
   int array_y[];
   
   switch(figure_type)
     {

e, onde necessário transferir aos métodos de classe as matrizes de coordenadas, definiremos os tamanhos dessas matrizes:

   //--- "q" = Polyline
      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;
         //--- Fill in the arrays with coordinate values
         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;
         //--- check x1 and y1 coordinates for being outside the form

...

   //--- "w" = Polyline with 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;
         //--- Fill in the arrays with coordinate values
         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;
         //--- check x1 and y1 coordinates for being outside the form

...

   //--- "e" = Polyline with 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;
         //--- Fill in the arrays with coordinate values
         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;
         //--- check x1 and y1 coordinates for being outside the form

...

   //--- "r" = Polyline with a specified width using two smoothing algorithms
      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;
         //--- Fill in the arrays with coordinate values
         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;
         //--- check x1 and y1 coordinates for being outside the form

...

   //--- "t" = Polyline with a specified width using a smoothing algorithm
      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;
         //--- Fill in the arrays with coordinate values
         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;
         //--- check x1 and y1 coordinates for being outside the form


Agora nos lugares onde tínhamos códigos para processamento das teclas para desenho de polígonos chamaremos métodos para desenhar polígonos regulares:

   //--- "y" = Polygon
      case FIGURE_TYPE_POLYGON  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         form.DrawNgonOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrAliceBlue);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- "u" = Filled polygon
      case FIGURE_TYPE_POLYGON_FILL  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         form.DrawNgonFillOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCoral);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- "i" = Polygon with AntiAlliasing
      case FIGURE_TYPE_POLYGON_AA  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         form.DrawNgonAAOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCyan);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- "o" = Polygon with WU
      case FIGURE_TYPE_POLYGON_WU  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         form.DrawNgonWuOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightGoldenrod);
         nx1++;
         ny1++;
         nx2++;
         ny2++;
         nx3++;
         break;
      
   //--- "p" = Polygon with a specified width using two smoothing algorithms
      case FIGURE_TYPE_POLYGON_SMOOTH  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         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" = Polygon with a specified width using a smoothing algorithm
      case FIGURE_TYPE_POLYGON_THICK  :
         coordX1=START_X+nx1; // X coordinate
         coordY1=START_Y;     // Y coordinate
         coordX2=3+nx2*4;     // Length of square sides
         coordY2=3+ny2;       // Number of faces
         coordX3=0;           // Rotation angle
         //--- check the square side length for exceeding the double form height
         if(coordX2>form.Height()*2)
           {
            nx2=0;
            coordX2=3;
           }
         //--- check the x1 coordinate for exceeding the form boundaries
         if(coordX1>form.Width()-1)
           {
            nx1=0;
            coordX1=-coordX2;
           }
         //--- check the number of faces for exceeding 10
         if(coordY2>16)
           {
            ny2=0;
            coordY2=3;
           }
         //--- check the rotation angle for exceeding 360 degrees
         if(coordX3>360)
           {
            nx3=0;
            coordX3=0;
           }
         //--- Draw a shape
         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" = Rectangle

Todo o código é comentado e deve estar claro o suficiente. Em qualquer caso, você pode colocar qualquer questão na discussão do artigo.

Não vamos esquecer no final da função adicionar o processamento da tecla "." para preencher a forma com a cor:

   //--- "." = Filled area
      case FIGURE_TYPE_FILL :
         coordX1=START_X+nx1;
         coordY1=START_Y+ny1;
         form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);
         break;
      
   //--- Default = Nothing
      default  :
         
         break;
     }
  }
//+------------------------------------------------------------------+

Compilamos o EA e o executamos na gráfico do símbolo.

Depois de iniciar, pressionamos as teclas às quais atribuímos o desenho de polígonos regulares e, ao mesmo tempo, preenchemos a área com cor:


Bem, tudo funciona como planejado. Uma ressalva é que as figuras não resultaram muito uniformes. A mais bem-sucedida, em minha opinião, é o polígono com utilização do algoritmo de suavização Wu. Ao preencher, podemos ajustar o grau (limiar) de preenchimento com cor, indicando o valor desejado para o parâmetro threshould:

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


O que vem agora?

No próximo artigo, continuaremos a trabalhar com animações e com o objeto-forma.

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo por conta própria.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

*Artigos desta série:

Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico
Gráficos na biblioteca DoEasy (Parte 74): elemento gráfico básico baseado na classe CCanvas
Gráficos na biblioteca DoEasy (Parte 75): métodos para trabalhar com primitivas e texto num elemento gráfico básico
Gráficos na biblioteca DoEasy (Parte 76): objeto Forma e temas de cores predefinidos
Gráficos na biblioteca DoEasy (Parte 77): classe do objeto Sombra
Gráficos na Biblioteca DoEasy (Parte 78): princípios de animação dentro da biblioteca. Corte de imagens
Gráficos na biblioteca DoEasy (Parte 79): classe para o objeto quadro-de-animação e seus objetos herdeiros

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/9689

Arquivos anexados |
MQL5.zip (4083.03 KB)
Como se tornar um bom programador (Parte 1): cinco hábitos que devem ser abandonados para programar melhor em MQL5 Como se tornar um bom programador (Parte 1): cinco hábitos que devem ser abandonados para programar melhor em MQL5
Tanto iniciantes quanto programadores avançados têm alguns hábitos ruins que os impedem de melhorar. Neste artigo, vamos discuti-los e ver o que podemos fazer com eles. O artigo é destinado a todos que desejam se tornar um programador MQL5 de sucesso.
Gráficos na biblioteca DoEasy (Parte 79): classe para o objeto quadro-de-animação e seus objetos herdeiros Gráficos na biblioteca DoEasy (Parte 79): classe para o objeto quadro-de-animação e seus objetos herdeiros
Neste artigo, desenvolveremos uma classe para um quadro de animação e seus herdeiros. A classe permitirá desenhar formas, bem como salvar e restaurar o plano de fundo.
Gráficos na biblioteca DoEasy (Parte 81): integrando gráficos nos objetos da biblioteca Gráficos na biblioteca DoEasy (Parte 81): integrando gráficos nos objetos da biblioteca
Hoje começaremos a integrar os objetos gráficos já criados nos restantes, o que, em última análise, dotará cada objeto da biblioteca com seu próprio objeto gráfico, permitindo ao usuário interagir com o programa.
Gráficos na Biblioteca DoEasy (Parte 78): princípios de animação dentro da biblioteca Corte de imagens Gráficos na Biblioteca DoEasy (Parte 78): princípios de animação dentro da biblioteca Corte de imagens
Neste artigo, definiremos os princípios de animação que usaremos em algumas partes da biblioteca, desenvolveremos uma classe para copiar uma parte de uma imagem e colá-la no local especificado do objeto-forma, preservando e restaurando aquela parte do fundo da forma sobre a qual a imagem será colocada.