Graphics in DoEasy library (Part 80): "Geometric animation frame" object class

Artyom Trishkin | 10 September, 2021

Contents


Concept

In this article, I will continue working on classes meant for drawing a shape on the canvas. I have already created the animation frame classes that allow drawing a single animation frame in a given area of the canvas, while preserving the background the image is superimposed on. This will allow us to restore the background when deleting or changing the image. These preliminarily created frames will enable us to compose animation sequences for quick frame changes. A single frame also allows making animations right inside its space.

Today I will slightly optimize the previously created codes of these classes. I will adhere to the concept that if there are repetitive sections of code, then all their logic can (and should) be formalized into a separate function/method, which is then called. This will make the code more readable and reduce its volume.

In addition, I will create the object class of the geometric animation frame. What does this mean?

We already have enough methods to construct various polygons. But if we need to draw a regular polygon, it is much easier to use geometry than to manually calculate the coordinates of its vertices. Later, I may add other geometric shapes, whose vertex coordinates can be calculated using equations rather than setting them manually.

According to Wikipedia:

A regular polygon is a polygon that is equiangular (all angles are equal in measure) and equilateral (all sides have the same length), for example:

Regular octagon

Any regular polygon can be inscribed inside a circle. Such a circle is called circumscribed. The circle passes through all the vertices of the polygon.

Circumscribed circle

There is also an inscribed circle. This is a circle inscribed in a polygon. In this case, all sides of the polygon touch the circle line.

Inscribed circle

I will not consider such polygons, with the exception of a square, into which a circle will be inscribed. A regular polygon will, in turn, be inscribed into the circle.

The square will represent the animation frame — its coordinates of the upper left corner and the size (length) of its sides. The circle, whose diameter is to be equal to the length of the animation frame square side, will feature an inscribed polygon with its vertices touching the circle line.
So, there is no need to create polygon coordinate arrays. Instead, we only need to specify the necessary number of vertices, the coordinates of the upper left corner and the length of the square sides.


Improving library classes

In \MQL5\Include\DoEasy\Data.mqh, add the new message index:

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

and the message text corresponding to the newly added index:

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

//--- CForm


The alignment (anchor angle) of animation frames now depends on all animation frames (text, rectangular, geometric and others). Therefore, I have decided to slightly change the names of the enumeration and its constants, so that they are tied to frames rather than to a text.

In \MQL5\Include\DoEasy\Defines.mqh, namely in the enumeration of anchor angles, replace "TEXT" with "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
  };
//+------------------------------------------------------------------+

In the enumeration of the animation frame types, add a new type — frame of geometric shape animations:

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

while in the list of drawn shape types, add a filled area I forgot to implement in the previous articles:

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


In \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, replace the name of the array for storing a copy of a graphical resource with a more illustrative one, since the array names are pretty confusing and make it difficult to define which one is to be used to store the copy of the initially created form. Also, remove the method saving the graphical resource to the array from the protected class section:

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

Replace "TEXT_ANCHOR" with "FRAME_ANCHOR" in the class listing (or, even better, in all the library files at once). To find all occurrences in all library files, just press Shift+Ctrl+H and set the following search and replace criteria in the new window:


The "Folder:" field should feature the path based on the location of your editor.

In the public section of the class, declare the methods for saving the graphical resource to the array and restoring the resource from it, as well as write the methods for updating the canvas and the method returning the size of the graphical resource copy array:

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:

The former ResourceCopy() method is now called ResourceStamp():

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

The method restoring the graphical resource from the array:

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

The method logic is described in the code comments. In short, we check the size of the resource copy array. If it is empty or the copy size does not match the original, report the error to the journal and exit the method. Next, copy all data to the canvas from the array copy pixel by pixel.

Since I have changed the name of the resource array copy and the method saving the graphical resource to the array, I need to make corrections in the \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh shadow object class file.

The corrections only concern the GaussianBlur() method:

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


Let's improve the animation frame object class in \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.

In the protected section of the class, declare the method for writing coordinate values and shifting the outlining rectangle like the previous ones for their subsequent use, and write the virtual method for saving and restoring the background under the image:

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

All these methods are the results of the optimization of the codes for shape-drawing methods in the classes I created in the previous articles.

The virtual method simply returns false here and should be implemented in the descendant classes. If its implementation in all inherited classes turns out to be the same, the method will be made non-virtual. Besides, it will be implemented in this class only. The SetLastParams() method will be considered a bit later.

In the public section of the class, write the method for resetting the pixel array:

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:

The method simply sets the pixel array size to zero. This enables correct handling of changes in the outlining rectangle size, since the method saving the background for its subsequent restoration first checks the array size. If it is zero, the background is saved. Otherwise, the background is considered to be saved previously with correct coordinates and saved area size. So, if we change the drawn shape, the array should be reset. Otherwise, the background under a new image is not saved and a completely different background from a different area is restored afterwards (the one saved earlier — before the size, the coordinates and the appearance of the drawn shape were changed).

In the protected class section, declare the class constructor — geometric shape animation frame I am going to create and test today:

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

Similarly to the previously created inherited classes, let's pass the object ID, X and Y coordinates of the upper left frame angle, the length of the square frame sides and the pointer to the graphical element, a new object is created from, to the class constructor.

Implementing the constructor of the geometric animation frame object:

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

In the initialization list, pass all the necessary parameters to the parent class constructor, while in the class body, set the shape type as ANIMATION_FRAME_TYPE_GEOMETRY I have added to the list of animation frame types in the current article. Other parameters are initialized similarly to previously considered constructors of the text and rectangular animation classes.

The method that sets the coordinates and the offset of the outlining rectangle as the previous ones:

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

The constantly repeated piece of code from the previously considered methods for drawing shapes, while saving and restoring the form background, has been moved to the method.

Let's improve the CFrame class descendant classes.

Open the \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh rectangular animation class file and make the necessary changes to it.

In the private section of the class, declare the two variables for storing the offsets of the outlining rectangle coordinates and declare the virtual method for saving and restoring the background under the image:

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

In the public section of the class, supplement the implementation of the parametric constructor. Now all the class variables are to be initialized in its body (previously, they were not initialized, which is incorrect):

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

Let's see the drawing methods with the background saving/restoration that we had using the point drawing method as an example:

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

We can now replace the highlighted code segments with the newly created methods. This is how the method looks now:

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

As we can see, replacing the specified code segments with calling the new methods has significantly shortened the code and made it more readable. Identical changes were made in all the methods of drawing shapes with saving and restoring the background. Since such methods are numerous and feature similar changes, there is no point in considering all of them here. You can find them in the files attached below.

I will only dwell on the ellipse drawing methods. As you might remember, I did not draw ellipses in the previous article since CCanvas features a potential division by zero. This happens if the method receives similar x1 and x2 or y1 and y2 coordinates of the rectangle, in which the ellipse is drawn. Therefore, in this case, we need to adjust the values of the same coordinates if they are equal:

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

The method saving and restoring the background under the image:

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

The constantly repeated code block from the methods for drawing shapes with saving and restoring the background has been simply moved to the method.

\MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh features minimal changes — simply replace the strings "ENUM_TEXT_ANCHOR" with "ENUM_FRAME_ANCHOR" in two code fragments:

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


Geometric animation frame object class

The logic behind the geometric animation frame object class is quite similar to its two predecessors — objects of text and rectangular animation. We only need to create the method calculating the coordinates of the polygon vertices on the circle depending on the number of polygon vertices:

The equations of the Cartesian coordinates of the regular polygon:

Let xc and yc be the center coordinates, R is a radius of a circle circumscribed around a regular polygon, while ϕ0 is the angular coordinate of the first vertex relative to the center. In this case, the Cartesian coordinates of the vertices of a regular n-gon are determined by the following equations:


where i takes values from 0 to n−1.

In \MQL5\Include\DoEasy\Objects\Graph\Animations\, create a new file FrameGeometry.mqh of the CFrameGeometry class.
The file should contain the animation frame object class file, and the class should be inherited from it:

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

Consider the definition of the class body in its entirety, where all the class variables and the virtual method for saving and restoring the background of the image are declared in the private section (I have considered the method above in the context of the rectangular animation object class, it simply transfers repeating code blocks from the shape drawing methods considered in the previous articles). The method for calculating the regular polygon coordinates is also declared in the private section.
The public section of the class features constructors (the default and parametric ones) and the methods of drawing regular polygons — simple, filled, as well as the ones with smoothing:

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

Let's have a look at the implementation of some class methods.

The method for drawing a regular polygon:

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

The only difference of the method from similar polygon drawing methods of the previous classes (rectangular animation frame class) is in the fact that the previously prepared arrays of polygon vertices coordinates are not passed here. Instead, the method receives the number of polygon vertices and the coordinates of the upper-left angle of the square frame the polygon is drawn into. The method calculating the polygon vertices coordinates by the number of its vertices, coordinates, circle radius and rotation angle, as well as filling in the X and Y vertices coordinates arrays, is called in the method. The polygon corresponding to the method is then simply drawn using the CCanvas class.

For comparison, let's have a look at the method drawing a filled polygon:

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

The difference from the first method lies only in calling the method for drawing a filled polygon.

The remaining methods are almost identical to the two considered above except for some peculiarities of calculating the coordinates of the outlining rectangle for drawing a polygon with a given line width. There we should consider the width of the drawn line when calculating the coordinates and size of the outlining rectangle.

The remaining drawing methods of regular polygons:

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


The virtual method saving and restoring the background under the image:

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

This is a moved repeated code block from the methods for drawing shapes from the previous articles.

The method calculating the coordinates of the regular polygon inscribed in a circle:

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

The method logic is described in detail in the code comments. The equations for calculating the polygon Cartesian coordinates:


I will leave the method for independent study. I believe, all is clear there. If you have any questions, feel free to ask them in the comments below.

The class of the geometric animation frame object is ready.

Now we need to give access to it from an external program and the ability to quickly create objects of this class.

All newly created animation frame objects are stored in their own lists in the CAnimations class.
Let's make the necessary improvements in the \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh class file.

Include the file of a newly created geometric animation frame object class to the class file and declare the list, which is to store all newly created class objects, in the private section of the class:

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

In the public section of the class, declare the method for creating a new object of the geometric animation frame and write the method returning the pointer to the list of these objects:

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

Next, declare the methods for drawing regular polygons:

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


All occurrences of the "ENUM_TEXT_ANCHOR" line in the class listing should be replaced with "ENUM_FRAME_ANCHOR".

Add handling a new type of the animation frame object in the method returning the animation frame object by type and ID:

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

The method creating a new geometric animation frame object:

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

The method logic is fully described in the code comments.

Add handling a new animation frame type in the method returning or creating a new animation frame object:

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

As usual, the entire logic here is described in the code comments.

At the very end of the class listing, implement the methods of drawing regular polygons:

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

The logic of all these methods is absolutely identical, so let's use the last method as an example.

As we can see, all is simple here: first, we either get the ready-made geometric animation frame object from the list, or create it if it is not in the list. If the new object creation flag is enabled and we fail to get or create the object, return false.
Otherwise, return the result of calling the same-name method of the geometric animation frame object class received from the list or created from scratch.


Now let's improve the form object class in \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.

All occurrences of the "ENUM_TEXT_ANCHOR" string in the class listing should be replaced with "ENUM_FRAME_ANCHOR".

In the private section of the class, declare the methods for resetting the size of the pixel arrays of the three animation frame classes:

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

This is necessary for the correct operation of the methods for saving and restoring the background of the form where shapes are drawn (this has been discussed above).

In the public section of the class, write the method for capturing the form appearance and declare the virtual method restoring the graphical resource from the array:

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

Why do we need the method for capturing the form appearance?
Suppose that we have created the form and drawn all the necessary unchangeable elements on it. Now we need to copy the newly created form appearance to the graphical resource copy array, so that we can return the original form appearance if necessary. All changes in the form are displayed exactly in the graphical resource. To avoid redrawing the form, we should simply store the copy of the originally created form in a special array, from which we can always restore the initial appearance. The Reset() method does exactly that.

Write the methods of drawing regular polygons in the public section of the class:

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

All methods are identical and return the result of calling the appropriate methods of the CAnimations class instance I have considered above.

Implement declared methods outside the class body.

Three methods resetting the sizes of the arrays of three animation frame objects:

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

All methods are identical to each other:

If the CAnimation class object does not exist, exit the method. The object has no animations.
Get the pointer to the list of animation frames corresponding to the method. In the loop by the obtained list, get the pointer to the next animation frame object and reset its pixel array to zero.

The method restoring the resource from the array:

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

First, call the method of the parent class restoring the graphical resource from the copy array. Then reset the pixel arrays of all animation frame objects, so that we are able to copy the background with the necessary coordinates and the size of the saved background area after restoring the form appearance.

We are now ready to test drawing regular polygons on the form.


Test

To perform the test, let's use the EA from the previous article and save it to \MQL5\Experts\TestDoEasy\Part80\ as TestDoEasyPart80.mq5.

In the previous article, we drew shapes on the form object by pressing keys. I suggest doing the same here. Simply reassign the keys that draw polygons whose coordinates and size are set dynamically. Here I will also dynamically change animation frame coordinates along the X axis and the number of vertices of the drawn polygon (from 3 to 10).

Next, each click on the form changes the X coordinate of the drawn frame and increases the number of drawn polygon vertices by one.

Replace all occurrences of the "TEXT_ANCHOR" substring with "FRAME_ANCHOR".

In the EA's OnInit() handler, capture the appearance of each created form:

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


In the block of handling keystrokes of the OnChartEvent() handler, implement calling the method of restoring the form appearance and resetting the arrays of frame object pixels to zero:

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

In the FigureType() function, add handling the "." key:

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

In the FigureProcessing() function, make the coordinate arrays dynamic:

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

and set the array size wherever it is necessary to pass the coordinate arrays to the class methods:

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


The codes of handling keystrokes for drawing polygons are replaced with calling the methods for drawing regular polygons:

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

The code has detailed comments. So everything should be clear here. If you have any questions, feel free to ask them in the comments.

Do not forget to add handling pressing the "." key for filling the form with color:

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

Compile the EA and launch it on a symbol chart.

After the launch, press the keys drawing regular polygons and filling the area with color:


Everything works as intended. However, the shapes turn out to be pretty uneven... In my opinion, the appearance of polygons applying the Wu smoothing algorithm is the best. While filling, we are able to adjust the degree (threshold) of color filling by specifying the necessary threshold parameter:

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


What's next?

In the next article, I will continue the development of animations and the form object.

All files of the current version of the library are attached below together with the test EA file for MQL5 for you to test and download.
Leave your questions and suggestions in the comments.

Back to contents

*Previous articles within the series:

Graphics in DoEasy library (Part 73): Form object of a graphical element
Graphics in DoEasy library (Part 74): Basic graphical element powered by the CCanvas class
Graphics in DoEasy library (Part 75): Methods of handling primitives and text in the basic graphical element
Graphics in DoEasy library (Part 76): Form object and predefined color themes
Graphics in DoEasy library (Part 77): Shadow object class
Graphics in DoEasy library (Part 78): Animation principles in the library. Image slicing
Graphics in DoEasy library (Part 79): "Animation frame" object class and its descendant objects