Do you like the article?
Share it with others -
Use new possibilities of MetaTrader 5

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

10 September 2021, 17:17
0
5 114

### 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_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
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);

//--- (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                   |
//+------------------------------------------------------------------+
{
//---
//--- 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'
{
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[];
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;
//--- Loop by the image width
for(int Y=0;Y<this.Height();Y++)
{
//--- Loop by the image height
{
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
{
XY=Y*this.Width()+x;
}
//--- Remove blur artifacts to the right by copying adjacent pixels
{
XY=Y*this.Width()+x;
}
}

//--- 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
{
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
{
XY=y*this.Width()+X;
}
//--- Remove blur artifacts at the bottom by copying adjacent pixels
{
XY=y*this.Width()+X;
}
}

//--- 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++)
{
{
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_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                    |
//+------------------------------------------------------------------+
{
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(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element)
{
this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP;
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
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)

//--- Calculate coordinate offsets for the saved area depending on the anchor point
int shift_x=0,shift_y=0;
//--- 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
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_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
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)

//--- 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.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
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
//--- Adjust the width and height of the outlining rectangle

//--- 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.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
//--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area)
//--- Adjust the width and height of the outlining rectangle

//--- 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.m_element.Update(redraw);
return true;
}
//+------------------------------------------------------------------+

```

The method saving and restoring the background under the image:

```//+------------------------------------------------------------------+
//| Save and restore the background under the image                  |
//+------------------------------------------------------------------+
{
//--- Calculate coordinate offsets for the saved area depending on the anchor point

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

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 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
//--- 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
//--- 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 version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "FrameText.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           *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        *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_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_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))
{
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
{
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
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
//--- get the pointer to an object with a specified 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)

//--- 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
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;
if(list==NULL)
return;
for(int i=0;i<list.Total();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).

• Y – unsmoothed regular polygon,
• U – unsmoothed filled polygon,
• I – regular polygon with AntiAlliasing (AA),
• O – regular polygon with Wu,
• P – regular polygon of a specified width applying two smoothing algorithms (Smooth),
• A – regular polygon of a specified width applying smoothing with preliminary sorting (Thick),
• . – draw a filled area. In fact, this means filling the entire form with a specified color.

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
//--- 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
//--- 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
}
//--- 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
//--- 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
//--- 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
}
//--- Add objects to the list
{
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.

Back to contents

*Previous articles within the series:

Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/9689

Attached files |
MQL5.zip (4083.03 KB)

#### Other articles by this author

Better programmer (Part 05): How to become a faster developer
Every developer wants to be able to write code faster, and being able to code faster and effective is not some kind of special ability that only a few people are born with. It's a skill that can be learned, that is what I'm trying to teach in this article.
Exploring options for creating multicolored candlesticks