English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 80): Clase de objeto "Fotograma de animación geométrica"

Gráficos en la biblioteca DoEasy (Parte 80): Clase de objeto "Fotograma de animación geométrica"

MetaTrader 5Ejemplos | 12 septiembre 2021, 15:38
537 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Seguimos trabajando con las clases para dibujar formularios en el lienzo. Ya hemos creado varias clases de fotogramas de animación que nos permiten dibujar un fotograma de animación en un área determinada del lienzo conservando el fondo sobre el que se superpone la imagen, para que luego podamos restaurar el fondo al eliminar o cambiar la imagen. A partir de estos fotogramas creados previamente, podremos componer secuencias de animación para realizar cambios rápidos de fotogramas en el futuro. No obstante, un solo fotograma en sí mismo también nos permite crear animaciones dentro de su espacio.

Hoy optimizaremos ligeramente los códigos de estas mismas clases que creamos antes, respetando la idea de que si existen secciones repetitivas de código, su lógica completa podrá (y deberá) organizarse en una función/método aparte para luego poder usar su llamada. Esto hará que el código resulte más legible, y también reducirá su volumen.

Además de esta optimización, crearemos una clase de objeto de fotograma de animación geométrica. ¿Qué significa esto?

Ya disponemos de métodos suficientes para construir varios polígonos, pero aún así, si necesitamos dibujar un polígono regular, resultará mucho más fácil usar la geometría que calcular manualmente las coordenadas de sus vértices (luego podremos añadir otras formas geométricas cuyas coordenadas de los vértices se pueden calcular mediante fórmulas, en lugar de configurarlas manualmente).

De la wikipedia:

Un polígono regular es un polígono convexo en el que todos los lados y todos los ángulos entre los lados adyacentes son iguales (por ejemplo):

Octágono regular.

Cualquier polígono regular se puede inscribir dentro de una circunferencia. Esta circunferencia se denomina circunscrita, es decir, la circunferencia que describe el polígono, mientras que todos los vértices del polígono se encuentran en la línea de la misma.

Circunferencia circunscrita.

Existe además el concepto de circunferencia inscrita, es decir, una circunferencia inscrita en un polígono donde todos los bordes del mismo se encuentran en la línea de la circunferencia.

Circunferencia inscrita.

No vamos a analizar estos polígonos, salvo el cuadrado, que inscribirá una circunferencia en la que, a su vez, dibujaremos el polígono regular.

Por consiguiente, tendremos un rectángulo (más precisamente, un cuadrado), en el que se inscribe una circunferencia, y un polígono regular que se inscribe en esta circunferencia. El cuadrado será un fotograma de animación: las coordenadas de su esquina superior izquierda y el tamaño (longitud) de sus lados. En la circunferencia, cuyo diámetro será igual a la longitud del lado del cuadrado del fotograma de animación, inscribiremos un polígono cuyos vértices se encontrarán en la línea de la circunferencia.
Es decir, para crear un polígono regular, no tendremos que crear por nosotros mismos las matrices de sus coordenadas: bastará con especificar el número deseado de vértices, las coordenadas del ángulo superior izquierdo y la longitud de los lados del cuadrado.


Mejorando las clases de la biblioteca

En el archivo \MQL5\Include\DoEasy\Data.mqh, añadimos el índice del nuevo mensaje:

//--- 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

y el texto del mensaje que se corresponde con el índice nuevamente añadido:

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

//--- CForm


Como la alineación (ángulo de anclaje) de los fotogramas de animación ahora está vinculada no solo a los fotogramas de texto de las animaciones, sino también a todos los demás (rectangulares, geométricos -que crearemos hoy- y todos los nuevos que seguirán), hemos decidido modificar levemente el nombre de la enumeración y sus constantes, para que estas no estén vinculadas al texto, es decir, a los fotogramas.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, en la enumeración de los ángulos de anclaje, sustituimos las entradas de texto "TEXT" por el texto "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
  };
//+------------------------------------------------------------------+

A continuación, añadimos a la enumeración de los fotogramas de animación el nuevo tipo, es decir, el fotograma de animaciones de figuras 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
  };
//+------------------------------------------------------------------+

y en la lista de los tipos de figuras dibujadas, añadimos el área coloreada que olvidamos crear en los artículos 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
   
  };
//+------------------------------------------------------------------+


En el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, cambiamos el nombre de la matriz que guarda la copia del recurso gráfico por otro más "elocuente" (simplemente porque el propio autor se ha confundido un poco con los nombres de las matrices, al intentar determinar cuál de ellas se ha pensado precisamente para guardar la copia del formulario incialmente creado), y eliminamos de la sección protegida de la clase el método que guarda el recurso gráfico en una matriz:

//+------------------------------------------------------------------+
//| 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:

Sustituimos en la lista de la clase (o mejor en todos los archivos de la biblioteca a la vez) las entradas de las líneas "TEXT_ANCHOR" por las líneas "FRAME_ANCHOR". Para encontrar todas las entradas en todos los archivos de la biblioteca, bastará con presionar la combinación de teclas Shift+Ctrl+H e introducir los siguientes criterios de búsqueda y reemplazo en la ventana que se abrirá:


Naturalmente, debemos especificar la ruta en el campo "Folder:" teniendo en cuenta la ubicación de nuestro editor.

En la sección pública de la clase, declaramos los métodos para guardar un recurso gráfico en una matriz y restaurar un recurso desde una matriz, y también escribimos los métodos para actualizar el lienzo y el método que retorna el tamaño de la matriz de copia de los recursos gráficos:

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:

El antiguo método ResourceCopy() ahora se llama 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 un recurso gráfico desde la 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;
  }
//+------------------------------------------------------------------+

La lógica del método se describe en los comentarios al código. Resumiendo: comprobamos el tamaño de la matriz de copia, y si no coincide con el original, informamos sobre el error en el log y salimos del método. A continuación, copiamos píxel a píxel en el lienzo todos los datos de la matriz de copia.

Como hemos modificado el nombre de la matriz de copia de recursos y el método que guarda el recurso gráfico en esta matriz, necesitaremos realizar correcciones en el archivo de la clase de objeto de sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.

Las correcciones se refieren solo al 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 a mejorar la clase de objeto de fotograma de animación en el archivo \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.

En la sección protegida de la clase, declaramos el método para registrar los valores de las coordenadas y el desplazamiento del rectángulo como los pasados, para su posterior uso, y luego escribimos el método virtual para guardar y restaurar el fondo debajo de la imagen:

//+------------------------------------------------------------------+
//| 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 estos métodos son el resultado de la optimización del código de los métodos para dibujar figuras en las clases que escribimos en los artículos anteriores.

El método virtual aquí simplemente retorna false, y debe implementarse en las clases herederas (si su implementación en todas las clases heredadas es la misma, haremos que este método no sea virtual, solo en esta clase). Veremos el método SetLastParams() un poco más tarde.

En la sección pública de la clase, escribimos el método para poner a cero la matriz de píxeles:

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:

El método simplemente establece en cero el tamaño de la matriz de píxeles. Esto es necesario para procesar correctamente el cambio de tamaño del rectángulo dibujado, ya que, en el método que guarda el fondo para su posterior restauración, primero se comprueba el tamaño de esta matriz, y si es igual a cero, luego se guarda el fondo; de lo contrario, se considerará que el fondo ha sido previamente guardado con los valores correctos de las coordenadas y los tamaños del área guardada. En consecuencia, si cambiamos la figura que se está dibujando, la matriz deberá restablecerse a cero; de lo contrario, el fondo debajo de la nueva figura no se guardará, y luego se restaurará un fondo completamente diferente desde un área completamente distinta (el guardado antes del cambio del tamaño, las coordenadas y la apariencia de la figura que se está dibujando).

En la sección protegida de la clase, declaramos el constructor para la clase de fotograma de animación para las figuras geométricas, que crearemos y probaremos hoy:

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);
  };
//+------------------------------------------------------------------+

Por analogía con las otras clases heredadas creadas anteriormente, transmitiremos al constructor de la clase el identificador del objeto, las coordenadas X e Y de la esquina superior izquierda del fotograma, la longitud de los lados del fotograma cuadrado y el puntero al elemento gráfico a partir del cual se crea el nuevo objeto.

Implementación del constructor del objeto de fotograma de animación 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;
  }
//+------------------------------------------------------------------+

En la lista de inicialización, transmitimos todos los parámetros necesarios al constructor de la clase padre, y en el cuerpo de la clase, escribimos el tipo de figura como ANIMATION_FRAME_TYPE_GEOMETRY, que añadiremos hoy a la lista de tipos de fotogramas de animación. El resto de los parámetros se inicializan de forma similar a los constructores previamente analizados de las clases de animación rectangular y de texto.

Método que registra las coordenadas y el desplazamiento del rectángulo dibujado como los anteriores:

//+------------------------------------------------------------------+
//| 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 se ha transferido un fragmento de código que se repite constantemente desde los métodos para dibujar las formas con guardado y restauración del fondo del formulario que hemos analizado en artículos anteriores.

Vamos a mejorar las clases herederas de la clase CFrame.

Abrimos el archivo de la clase de animación rectangular \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh y hacemos los cambios necesarios.

En la sección privada de la clase, declaramos dos variables para almacenar los desplazamientos de las coordenadas del rectángulo dibujado y declaramos un método virtual para guardar y restaurar el fondo debajo de la imagen:

//+------------------------------------------------------------------+
//| 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:

En la sección pública de la clase, completamos la implementación del constructor paramétrico; ahora inicializaremos en su cuerpo todas las variables de clase (antes estas no se inicializaban, lo cual no es correcto):

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 a ver cómo eran nnuestros métodos de dibujado con guardado/restauración del fondo usando como ejemplo el método de dibujado de un punto:

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

Ahora, podemos sustituir los fragmentos de código resaltados a color por los nuevos métodos que hemos escrito hoy. Este es el aspecto que tendrá ahora el método:

//+------------------------------------------------------------------+
//| 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 podemos ver, sustituyendo los fragmentos de código indicados por la llamada de los nuevos métodos hemos reducido significativamente el código, y también lo hemos hecho más legible. Hemos realizado cambios idénticos en todos los métodos de dibujado de figuras con guardado y restauración del fondo. Como hay muchos métodos y ya hemos realizado los mismos cambios en todos ellos, no analizaremos su lista completa; podemos familiarizarnos con ellas en los archivos adjuntos al artículo.

Vamos a echar un vistazo a los métodos para dibujar elipses. Como el lector recordará, en el último artículo no dibujamos elipses, ya que existe una división potencial por cero en CCanvas. Esto sucede si transmitimos al método las mismas coordenadas x1 y x2 o y1 e y2 del rectángulo en el que se dibuja la elipse. Por consiguiente, en este caso, necesitamos corregir los valores de las mismas coordenadas si estos son iguales:

//+------------------------------------------------------------------+
//| 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 que guarda y restaura el fondo debajo del dibujo:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Simplemente hemos trasladado al método el bloque de código que se repite constantemente en los métodos para dibujar figuras con guardado y restauración del fondo.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh, los cambios serán mínimos: solo tenemos que sustituir en dos lugares del código las líneas "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)
  {


Clase de objeto de fotograma de animación geométrica

En cuanto a su lógica, la clase de objeto de fotograma de animación geométrica no diferirá mucho de sus dos predecesoras: la clase de los objetos de fotograma de animación de texto y rectangular. Solo necesitamos crear un método que calcule las coordenadas de los vértices del polígono en la circunferencia, dependiendo del número de vértices en el polígono:

Fórmulas para calcular las coordenadas cartesianas de un polígono regular:

Supongamos que xc e yc son las coordenadas del centro, y que R es el radio de una circunferencia circunscrita alrededor de un polígono regular; si ϕ0 es la coordenada angular del primer vértice relativo al centro; entonces, las coordenadas cartesianas de los vértices de un pológono n regular serán determinadas por las fórmulas:


donde i adopta los valores de 0 a n−1

En la carpeta \MQL5\Include\DoEasy\Objects\Graph\Animations\, creamos el nuevo archivo FrameGeometry.mqh de la clase CFrameGeometry.
Debemos incluir en el archivo el archivo de la clase de objeto de fotograma de animación, y ya de este se heredará la clase:

//+------------------------------------------------------------------+
//|                                                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
  {
  }

Vamos a analizar la definición del cuerpo de la clase como un todo donde todas las variables de la clase y el método virtual para guardar y restaurar el fondo debajo del dibujo se declaran en la sección privada (hemos analizado anteriormente el método en el contexto de la clase de objeto de animación rectangular: se trata simplemente de transferir los bloques de código repetidos en los métodos para dibujar las figuras de los artículos anteriores). También en la sección privada, declaramos el método para calcular las coordenadas de un polígono regular.
En la sección pública de la clase se encuentran los constructores (predeterminados y paramétricos) y los métodos necesarios para dibujar polígonos regulares: simples, coloreados y con suavizado:

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

Vamos a echar un vistazo a algunos métodos de la clase.

Método que dibuja un rectangulo 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 podemos ver, lo único en lo que se diferencia este método de otros similares es en el dibujado de los polígonos de las clases anteriores (la clase de fotograma de animación rectangular), es decir, aquí no se transmiten las matrices de las coordenadas de los vértices del polígono preparadas de antemano, sino el número de vértices del polígono y las coordenadas de la esquina superior izquierda del fotograma cuadrado en el que se dibujará el polígono, y ya en el propio método, se llama al método que calcula las coordenadas de los vértices del polígono según el número de sus vértices, las coordenadas, el radio de la circunferencia y el ángulo de rotación, en el que se rellenan las matrices de las coordenadas de los vértices X e Y. Después, simplemente dibujamos el polígono que se corresponde con el método, usando para ello la clase CCanvas.

Para comparar, veamos el mismo método que dibuja un polígono coloreado:

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

La única diferencia respecto al primer método es la llamada del método de dibujado del polígono coloreado.

El resto de los métodos son casi idénticos a los dos anteriores, con la excepción de algunas peculiaridades de cálculo de las coordenadas del rectángulo delineado para dibujar un polígono con un grosor de línea determinado; ahí debemos tener en cuenta al calcular las coordenadas y las dimensiones del rectángulo de contorno el valor del grosor de la línea dibujada.

Aquí tenemos los otros métodos para dibujar 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 guarda y restaura el fondo debajo del dibujo:

//+------------------------------------------------------------------+
//| 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 es un bloque de código repetido transferido desde los métodos de dibujado de figuras del último artículo.

Método para calcular las coordenadas de un polígono regular inscrito en una circunferencia:

//+------------------------------------------------------------------+
//| 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));
     }
  }
//+------------------------------------------------------------------+

La lógica del método se describe con detalle en los comentarios al código. Fórmulas de cálculo de las coordenadas cartesianas de un polígono.


El lector podrá estudiar el método por su cuenta; no creemos que le plantee dudas. En cualquier caso, podrá escribir cualquier duda en los comentarios al artículo.

La clase de objeto de fotograma de animación geométrica ahora está completa.

Ahora, necesitaremos darle acceso desde un programa externo, y también la capacidad de crear rápidamente objetos de esta clase.

Todos los objetos de fotograma de animación recién creados se guardan en sus propias listas en la clase CAnimations.
Vamos a introducir las mejoras necesarias en el archivo de clase \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh.

Conectamos el archivo de la clase de objeto de fotograma de animación geométrica recién creada al archivo de la clase, y luego declaramos en la sección privada de la clase la lista que almacenará todos los objetos recién creados de esta clase:

//+------------------------------------------------------------------+
//|                                                   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:

En la sección pública de la clase, declaramos el método para crear un nuevo objeto de fotograma de animación geométrica y
escribimos el método que retorna el puntero a la lista de estos 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 continuación, declaramos los métodos para dibujar 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 las entradas de la línea "ENUM_TEXT_ANCHOR" en el listado de la clase deberán sustituirse por la línea "ENUM_FRAME_ANCHOR".

En el método que retorna un objeto de fotograma de animación según su tipo e identificador, añadimos el procesamiento del nuevo tipo de objeto de fotograma de animación:

//+------------------------------------------------------------------+
//| 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 crea un nuevo objeto de fotograma de animación 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;
  }
//+------------------------------------------------------------------+

La lógica del método se describe al completo en los comentarios al código.

En el método que retorna o crea un nuevo objeto de fotograma de animación, añadimos el procesamiento del nuevo tipo de fotograma de animación:

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

Aquí, de la misma manera, la lógica al completo se describe en los comentarios al código.

Al final del listado de la clase, añadimos la implementación de los métodos de dibujado de 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);
  }
//+------------------------------------------------------------------+

La lógica de todos estos métodos es absolutamente idéntica, así que analizaremos el ejemplo del último método.

Como podemos ver, todo aquí es simple: en primer lugar, o bien obtenemos de la lista el objeto de fotograma de animación geométrica ya creado, o bien lo creamos si no está en la lista, y después, en el caso de que la bandera de creación de un nuevo objeto esté establecida, y si el objeto no se ha podido obtener o crear, retornamos false.
De lo contrario, retornamos el resultado de la llamada al método homónimo de la clase de objeto de fotograma de animación geométrica obtenido de la lista o recién creado.


Ahora, vamos a modificar la clase del objeto de formulario en el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

Todas las entradas de la línea "ENUM_TEXT_ANCHOR" en la lista de clases deben reemplazarse por "ENUM_FRAME_ANCHOR".

En la sección privada de la clase, declaramos los métodos para poner a cero los tamaños de las matrices de píxeles de las tres clases de fotogramas de animación:

//+------------------------------------------------------------------+
//| 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

Necesitamos esto para que funcionen correctamente los métodos de guardado y restauración del fondo del formulario donde se dibujan las figuras (que hemos analizado anteriormente).

En la sección pública de la clase, escribimos un método para fijar el aspecto del formulario y declaramos un método virtual que restaura el recurso gráfico de una 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                             |
//+------------------------------------------------------------------+

¿Y para qué necesitamos el método que fija el aspecto del formulario?
Por ejemplo: supongamos que hemos creado un formulario y hemos dibujado todos los elementos inmutables necesarios en él. Ahora necesitamos copiar la apariencia creada del formulario en la matriz de copia del recurso gráfico, de forma que, si fuera necesario, podamos devolver la forma original del formulario. Y es que, al fin y al cabo, todo lo que dibujamos en el formulario, todos estos cambios, se muestran con precisión en el recurso gráfico: para que tengamos la posibilidad de no redibujar el formulario completo, solo necesitamos almacenar una copia del formulario creado originalmente en una matriz especial desde donde siempre podremos restaurar la apariencia original, cosa que en realidad hace el método Reset().

En la sección pública de la clase, escribimos los métodos para dibujar 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 los métodos son idénticos y retornan el resultado de la llamada de los métodos correspondientes de la instancia de la clase CAnimations que hemos analizado anteriormente.

Implementamos fuera del cuerpo de la clase los métodos declarados:

Estos son los tres métodos que resetean el tamaño de las matrices de los tres objetos de fotograma de animación:

//+------------------------------------------------------------------+
//| 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 los métodos son idénticos entre sí:

Si el objeto de la clase CAnimation no existe —salimos del método—, este objeto no tiene animaciones.
Obtenemos el puntero a la lista de fotogramas de animación que se corresponden con el método. En un ciclo por la lista resultante, obtenemos el puntero al siguiente objeto de fotograma de animación y ponemos su matriz de píxeles a cero.

Método que restaura el recurso de una matriz:

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

Primero, llamamos al método de la clase principal que restaura el recurso gráfico de la matriz de copia, y luego ponemos a cero las matrices de píxeles de todos los objetos de fotogramas de animación, de manera que después de restaurar la apariencia del formulario, podamos copiar de nuevo el fondo en las coordenadas deseadas y en el tamaño del área de fondo a guardar.

Bueno, ya estamos listos para poner a prueba el dibujado de polígonos regulares en el formulario.


Simulación

Para las pruebas, tomaremos el asesor de prueba del artículo anterior y
lo guardaremos en la carpeta \MQL5\Experts\TestDoEasy\Part80\ con el nuevo nombre TestDoEasyPart80.mq5.

¿Cómo vamos a realizar la prueba? Si el lector recuerda, en el último artículo dibujamos figuras en un objeto de formulario presionando las teclas del teclado. Hoy haremos lo mismo. Solo tendremos que reasignar los botones cuya pulsación activa el dibujado de los polígonos a los que hemos asignado dinámicamente las coordenadas y los tamaños. En esta ocasión, también cambiaremos dinámicamente las coordenadas del fotograma de animación en el eje X y el número de vértices del polígono dibujado (de 3 a 10).

  • Si presionamos la tecla "Y", se dibujará un polígono regular sin suavizar,
  • Si presionamos la tecla "U", se dibujará un polígono regular coloreado sin suavizar,
  • Si presionamos la tecla "I", se dibujará un polígono regular suavizado por el algoritmo AntiAlliasing (AA),
  • Si presionamos la tecla "O", se dibujará un polígono regular suavizado por el algoritmo de Xiaolin Wu,
  • Si presionamos la tecla "P", se dibujará un polígono regular del grosor indicado, suavizado por dos algoritmos suavizado (Smooth),
  • Si presionamos la tecla "A", se dibujará un polígono regular del grosor indicado, con uso de suavizado con filtrado preliminar (Thick),
  • A la tecla "." le asignaremos el dibujado del área coloreada; de hecho, rellenará todo el formulario con el color especificado.

Además, con cada clic en el formulario, la coordenada X del fotograma dibujado cambiará, y el número de vértices del polígono dibujado aumentará en 1.

Todas las entradas de la sublínea "TEXT_ANCHOR" serán reemplazadas por la sublínea "FRAME_ANCHOR".

En el manejador OnInit() del asesor, después de la creación de cada formulario, fijaremos su apariencia:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+


En el manejador OnChartEvent(), en el bloque de procesamiento de la pulsación de las teclas, añadiremos la llamada del método de restauración de la apariencia del formulario y el reseteo de las matrices de píxeles de los objetos de fotograma:

//--- 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;
        }
     }

En la función FigureType(), añadiremos el procesamiento de la 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;
     }
  }
//+------------------------------------------------------------------+

En la función FigureProcessing(), crearemos dinámicamente las matrices de coordenadas:

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

y allí donde sea necesario transmitir a los métodos de las clases las matrices de coordinadas, estableceremos el tamaño de dichas matrices:

   //--- "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


Allí donde tengamos los códigos para procesar la pulsación de las teclas para dibujar polígonos, escribiremos un nuevo manejador; ahora, en estos lugares, llamaremos a los métodos para dibujar 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

Aquí hemos comentado el código con bastante detalle, así que no debería plantear dudas. En cualquier caso, el lector podrá escribir cualquier duda en los comentarios al artículo.

Al final de la función, no debemos olvidarnos de añadir el procesamiento de la pulsación de la tecla "." para colorear el formulario:

   //--- "." = 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 el asesor y lo iniciamos en el gráfico del símbolo.

Después del inicio, presionamos las teclas a las que les hemos asignado el dibujado de polígonos regulares y al mismo tiempo que el rellenado a color del área:


Bueno, todo funciona según lo previsto. Un pequeño detalle: las cifras no son muy uniformes... En nuestra opinión, tienen mejor apariencia los polígonos con el algoritmo de suavizado de Wu. Al colorear, podemos ajustar el grado (umbral) de rellenado a color, indicando el valor deseado del parámetro threshould:

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


¿Qué es lo próximo?

En el próximo artículo, seguiremos trabajando con las animaciones y el objeto de formulario.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

Gráficos en la biblioteca DoEasy (Parte 73): Objeto de formulario del elemento gráfico
Gráficos en la biblioteca DoEasy (Parte 74): Elemento gráfico básico sobre la clase CCanvas
Gráficos en la biblioteca DoEasy (Parte 75): Métodos de trabajo con primitivas y texto en el elemento gráfico básico.
Gráficos en la biblioteca DoEasy (Parte 76): Objeto de formulario y temas de color predeterminados
Gráficos en la biblioteca DoEasy (Parte 77): Clase de objeto Sombra
Gráficos en la biblioteca DoEasy (Parte 78): Fundamentos de animación en la biblioteca. Cortando las imágenes
Gráficos en la biblioteca DoEasy (Parte 79): Clase de objeto "Fotograma de animación" y sus objetos herederos

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9689

Archivos adjuntos |
MQL5.zip (4083.03 KB)
Cómo ser un mejor programador (parte 03): 5 cosas que evitar para convertirse en un programador exitoso de MQL5 Cómo ser un mejor programador (parte 03): 5 cosas que evitar para convertirse en un programador exitoso de MQL5
Este es un artículo de lectura obligada para todo aquel que quiera mejorar su carrera como programador. Esta serie de artículos tiene como objetivo hacer de usted el mejor programador posible, sin importar la experiencia que tenga. Las ideas debatidas funcionan tanto para principiantes como para profesionales de la programación en MQL5.
Cómo ser un mejor programador (parte 02): 5 cosas que evitar para convertirse en un programador exitoso de MQL5 Cómo ser un mejor programador (parte 02): 5 cosas que evitar para convertirse en un programador exitoso de MQL5
Este es un artículo de lectura obligada para cualquiera que desee mejorar su carrera como programador. Esta serie de artículos tiene como objetivo convertirlo a usted en el mejor programador posible, sin importar la experiencia que tenga. Las ideas analizadas funcionan tanto para principiantes como para profesionales de la programación en MQL5.
Gráficos en la biblioteca DoEasy (Parte 81): Integrando gráficos en los objetos de la biblioteca Gráficos en la biblioteca DoEasy (Parte 81): Integrando gráficos en los objetos de la biblioteca
Vamos a comenzar a integrar los objetos gráficos ya creados en el resto de objetos de la biblioteca creados previamente, lo que finalmente dotará a cada objeto de biblioteca de su propio objeto gráfico, permitiendo al usuario interactuar con el programa.
Gráficos en la biblioteca DoEasy (Parte 79): Clase de objeto "Fotograma de animación" y sus objetos herederos Gráficos en la biblioteca DoEasy (Parte 79): Clase de objeto "Fotograma de animación" y sus objetos herederos
En el presente artículo, desarrollaremos la clase de fotograma de animación y sus clases herederas. La clase permitirá dibujar figuras, con el posterior almacenamiento y restauración del fondo según la figura dibujada.