
DoEasy 函数库中的图形(第八十部分):“几何动画框”对象类
内容
概述
在本文中,我将继续研究在画布上绘制造型的类。 我已创建了动画框类,允许在画布的给定区域绘制单个动画框,同时保留叠加的图像背景。 这可令我们在删除或更改图像时恢复背景。 这些初步创建的框令我们能够组合动画序列,从而能快速切换画框。 单个框也允许在其空间内制作动画。
今天我会略微优化之前创建的这些类的代码。 我坚持这样一个概念:如果有重复的代码部分,那么它们的所有逻辑都可以(并且应该)被归纳为一个单独的函数/方法,其可被调用。 如此,将令代码更具可读性,并缩减其体积。
另外,我将创建几何动画框对象类。 这是何意?
我们已有足够的方法来构造各种多边形。 但如果我们需要绘制一个正多边形,利用几何比手动计算其顶点坐标要容易得多。 稍后,我可能还会添加其它几何造型,其顶点坐标可用方程进行计算,非手动设置。
根据维基百科:
正多边形是等角(所有角的度数相等)和等边(所有边的长度相同)的多边形,例如:
正八边形
任何正多边形都可以内接在圆内。 这样的圆称为外接。 圆形穿过多边形的所有顶点。
外接圆
还有一个内接圆形。 这是一个在多边形中内接的圆。 在这种情况下,多边形的所有边都与圆周线接触。
内接圆
我不会考虑这样的多边形,即除了一个正方形,其中内接一个圆形。 反过来,正多边形将被内接到圆中。
正方形将代表动画框 — 它的左上角坐标和边的大小(长度)。 该圆的直径等于动画框正方形的边长,它还有一个内接多边形,其顶点接触圆周线。
因此,无需创建多边形坐标数组。 取而代之,我们只需要指定必要的顶点数、左上角的坐标和正方形的边长。
改进库类
在 \MQL5\Include\DoEasy\Data.mqh 里,添加新的消息索引:
//--- 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
以及与新添加索引对应的消息文本:
//--- CGCnvElement {"Ошибка! Пустой массив","Error! Empty array"}, {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"}, //--- CForm
动画框的对齐(锚点角度)现在取决于所有动画框(文本、矩形、几何等)。 因此,我决定稍微修改枚举及其常量的名称,以便将它们绑定到画框,而非文本。
在 \MQL5\Include\DoEasy\Defines.mqh 里,即在锚点角度的枚举中,将 “TEXT” 替换为 “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 }; //+------------------------------------------------------------------+
在动画框类型的枚举中,添加一个新类型 — 几何形状动画框:
//+------------------------------------------------------------------+ //| Data for working with graphical element animation | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of animation frame types | //+------------------------------------------------------------------+ enum ENUM_ANIMATION_FRAME_TYPE { ANIMATION_FRAME_TYPE_TEXT, // Text animation frame ANIMATION_FRAME_TYPE_QUAD, // Rectangular animation frame ANIMATION_FRAME_TYPE_GEOMETRY, // Square animation frame of geometric shapes }; //+------------------------------------------------------------------+
而在所绘制造型的类型列表中,添加一个填充区域,我在之前的文章中忘了实现它:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 里,将存储图形资源副本的数组名称替换为更具说明性的名称,由于数组名称非常混乱,故很难定义最初创建的交互窗副本存储在哪个数组里了。 另外,从类的受保护部分中删除将图形资源保存到数组的方法:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Save the graphical resource to the array bool ResourceCopy(const string source); private:
将类清单中的 “TEXT_ANCHOR” 替换为 “FRAME_ANCHOR”(或最好是同时在所有函数库文件中替换)。 为了查找所有函数库文件中的所有匹配项,只需按 Shift+Ctrl+H,并在新窗口中设置以下搜索和替换条件:
“文件夹:” 字段应根据您的编辑器的位置提供路径。
在类的公开部分,声明把图形资源保存到数组,及恢复资源的方法,并编写更新画布的方法,和返回图形资源副本数组大小的方法:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)];} string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)];} //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return false; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return itself CGCnvElement *GetObject(void) { return &this; } //--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CGCnvElement objects with each other by all properties (to search equal objects) bool IsEqual(CGCnvElement* compared_obj) const; //--- (1) Save the object to file and (2) upload the object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- (1) Save the graphical resource to the array and (2) restore the resource from the array bool ResourceStamp(const string source); virtual bool Reset(void); //--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; } //--- Set the canvas update frequency void SetFrequency(const ulong value) { this.m_pause.SetWaitingMSC(value); } //--- Update the canvas void CanvasUpdate(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Return the size of the graphical resource copy array uint DuplicateResArraySize(void) { return ::ArraySize(this.m_duplicate_res); } //--- Update the coordinates (shift the canvas) bool Move(const int x,const int y,const bool redraw=false); //--- Save an image to the array bool ImageCopy(const string source,uint &array[]); //--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorLightness(const uint clr,const double change_value); color ChangeColorLightness(const color colour,const double change_value); //--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorSaturation(const uint clr,const double change_value); color ChangeColorSaturation(const color colour,const double change_value); protected:
以前的 ResourceCopy() 方法现在改称为 ResourceStamp():
//+------------------------------------------------------------------+ //| Save the graphical resource to the array | //+------------------------------------------------------------------+ bool CGCnvElement::ResourceStamp(const string source) { return this.ImageCopy(DFUN,this.m_duplicate_res); } //+------------------------------------------------------------------+
从数组中恢复图形资源的方法:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
方法逻辑已在代码注释中描述。 简而言之,我们检查资源副本数组的大小。 如果为空或者副本大小与原件不匹配,则在日志报告错误,并退出该方法。 接下来,将所有像素数据从数组逐个复制到画布。
由于我已修改了资源数组副本的名称,以及把图形资源保存到数组的方法,因此需要在 \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh 中调整阴影对象类文件。
调整只涉及 GaussianBlur() 方法:
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/en/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CShadowObj::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceStamp(DFUN)) return false; //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2) { ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE)); return false; } //--- Decompose image data from the resource into a, r, g, b color components int size=::ArraySize(this.m_duplicate_res); //--- arrays for storing A, R, G and B color components //--- for horizontal and vertical blur uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[]; uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[]; //--- Change the size of component arrays according to the array size of the graphical resource data if(::ArrayResize(a_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_h_data\""); return false; } if(::ArrayResize(r_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_h_data\""); return false; } if(::ArrayResize(g_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_h_data\""); return false; } if(ArrayResize(b_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_h_data\""); return false; } if(::ArrayResize(a_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_v_data\""); return false; } if(::ArrayResize(r_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_v_data\""); return false; } if(::ArrayResize(g_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_v_data\""); return false; } if(::ArrayResize(b_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_v_data\""); return false; } //--- Declare the array for storing blur weight ratios and, //--- if failed to get the array of weight ratios, return 'false' double weights[]; if(!this.GetQuadratureWeights(1,n_nodes,weights)) return false; //--- Set components of each image pixel to the color component arrays for(int i=0;i<size;i++) { a_h_data[i]=GETRGBA(this.m_duplicate_res[i]); r_h_data[i]=GETRGBR(this.m_duplicate_res[i]); g_h_data[i]=GETRGBG(this.m_duplicate_res[i]); b_h_data[i]=GETRGBB(this.m_duplicate_res[i]); } //--- Blur the image horizontally (along the X axis) uint XY; // Pixel coordinate in the array double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0; int coef=0; int j=(int)radius; //--- Loop by the image width for(int Y=0;Y<this.Height();Y++) { //--- Loop by the image height for(uint X=radius;X<this.Width()-radius;X++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { a_temp+=a_h_data[XY+i]*weights[coef]; r_temp+=r_h_data[XY+i]*weights[coef]; g_temp+=g_h_data[XY+i]*weights[coef]; b_temp+=b_h_data[XY+i]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_h_data[XY]=(uchar)::round(a_temp); r_h_data[XY]=(uchar)::round(r_temp); g_h_data[XY]=(uchar)::round(g_temp); b_h_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts to the left by copying adjacent pixels for(uint x=0;x<radius;x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[Y*this.Width()+radius]; r_h_data[XY]=r_h_data[Y*this.Width()+radius]; g_h_data[XY]=g_h_data[Y*this.Width()+radius]; b_h_data[XY]=b_h_data[Y*this.Width()+radius]; } //--- Remove blur artifacts to the right by copying adjacent pixels for(int x=int(this.Width()-radius);x<this.Width();x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1]; r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1]; g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1]; b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1]; } } //--- Blur vertically (along the Y axis) the image already blurred horizontally int dxdy=0; //--- Loop by the image height for(int X=0;X<this.Width();X++) { //--- Loop by the image width for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { dxdy=i*(int)this.Width(); a_temp+=a_h_data[XY+dxdy]*weights[coef]; r_temp+=r_h_data[XY+dxdy]*weights[coef]; g_temp+=g_h_data[XY+dxdy]*weights[coef]; b_temp+=b_h_data[XY+dxdy]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_v_data[XY]=(uchar)::round(a_temp); r_v_data[XY]=(uchar)::round(r_temp); g_v_data[XY]=(uchar)::round(g_temp); b_v_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts at the top by copying adjacent pixels for(uint y=0;y<radius;y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+radius*this.Width()]; r_v_data[XY]=r_v_data[X+radius*this.Width()]; g_v_data[XY]=g_v_data[X+radius*this.Width()]; b_v_data[XY]=b_v_data[X+radius*this.Width()]; } //--- Remove blur artifacts at the bottom by copying adjacent pixels for(int y=int(this.Height()-radius);y<this.Height();y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()]; r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()]; g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()]; b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()]; } } //--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array for(int i=0;i<size;i++) this.m_duplicate_res[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]); //--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array for(int X=0;X<this.Width();X++) { for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; this.m_canvas.PixelSet(X,Y,this.m_duplicate_res[XY]); } } //--- Done return true; } //+------------------------------------------------------------------+
我们来改进 \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh 中的动画框对象类。
在类的受保护部分,声明同前一样的记录轮廓矩形坐标值和偏移的方法,供以后使用,以及保存和恢复被图像覆盖的背景的虚方法:
//+------------------------------------------------------------------+ //| 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:
所有这些方法都是我在之前文章中所创建类中绘制造型方法的代码优化的结果。
虚方法在这里简单地返回 false,且应在衍生后代类中实现。 如果其在所有继承的类中的实现结果均相同,则该方法将变为非虚拟的。 此外,它只会在这个类中实现。 SetLastParams() 方法稍后会研究。
在类的公开部分,编写重置像素数组的方法:
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:
该方法简单地将像素数组大小设置为零。 这能够针对轮廓矩形大小的变化进行正确处理,因后续恢复背景前,保存背景的方法首先检查数组大小。 如果为零,才会保存背景。 否则,则其是按照正确的坐标和区域大小保存的先前背景。 所以,如果我们改变绘制的造型,数组应该被重置。 否则,新图像之下的背景不会被保存,之后会恢复时也许是来自不同区域的完全不同的背景(之前保存的背景 — 在所绘制造型的大小、坐标和外观发生变化之前)。
在类的受保护部分,声明类构造函数 — 几何造型动画框,我今天要创建和测试:
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); }; //+------------------------------------------------------------------+
与之前创建的继承类相似,我们将对象 ID、画框左上角的 X 和 Y 坐标、正方形边框的长度、和指向图形元素的指针、欲创建的新对象来处,都传递给类构造函数。
实现几何动画帧对象的构造函数:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
在初始化清单中,将所有必要的参数传递给父类的构造函数,而在类主体中,将造型类型设置为 ANIMATION_FRAME_TYPE_GEOMETRY,这些枚举我已添加到当前文章的动画框类型列表当中。 其它参数的初始化类似于之前研究过的文本和矩形动画框类的构造函数。
设置轮廓矩形的坐标和偏移量如之前的方法:
//+------------------------------------------------------------------+ //| Set the coordinates and the offset | //| of the outlining rectangle as the previous ones | //+------------------------------------------------------------------+ void CFrame::SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP) { this.m_anchor_last=anchor; this.m_x_last=quad_x; this.m_y_last=quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; } //+------------------------------------------------------------------+
之前研究过的绘制造型的方法中有许多重复的代码段,其作用是保存和恢复交互窗背景,这些均已移至该方法中。
我们来改进 CFrame 类的衍生后代类。
打开 \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh 矩形动画框类文件,并对其进行必要的修改。
在类的私密部分,声明两个存储轮廓矩形坐标偏移量的变量,声明保存和恢复图片之下被覆盖背景的虚方法:
//+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameQuad : public CFrame { private: double m_quad_x; // X coordinate of the rectangle enclosing the shape double m_quad_y; // Y coordinate of the rectangle enclosing the shape uint m_quad_width; // Width of the rectangle enclosing the shape uint m_quad_height; // Height of the rectangle enclosing the shape int m_shift_x; // Offset of the X coordinate of the rectangle enclosing the shape int m_shift_y; // Offset of the Y coordinate of the rectangle enclosing the shape //--- Save and restore the background under the image virtual bool SaveRestoreBG(void); public:
在类的公开部分,补全参数型构造函数的实现。 现在,所有的类变量都会在其主体中初始化(以前,它们并未被初始化,这是不正确的):
public: //--- Constructors CFrameQuad() {;} CFrameQuad(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element) { this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_quad_x=0; this.m_quad_y=0; this.m_quad_width=0; this.m_quad_height=0; this.m_shift_x=0; this.m_shift_y=0; }
我们以绘制像素点的方法为例来看看保存/恢复背景的绘制方法:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Calculate coordinate offsets for the saved area depending on the anchor point int shift_x=0,shift_y=0; this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,TEXT_ANCHOR_LEFT_TOP,shift_x,shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- If a background area with calculated coordinates and size under the future image is successfully saved if(!CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+shift_x),int(this.m_quad_y+shift_y),this.m_quad_width,this.m_quad_height)) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.m_element.Update(redraw); this.m_anchor_last=TEXT_ANCHOR_LEFT_TOP; this.m_x_last=this.m_quad_x; this.m_y_last=this.m_quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; return true; } //+------------------------------------------------------------------+
我们现在可以用新创建的方法来替换高亮显示的代码段。 这是该方法现在的模样:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
如我们所见,用新方法调用替换指定的代码段之后,代码长度显著缩短,且更具可读性。 在保存和恢复背景的所有绘制造型的方法中都进行了相同的修改。 由于这样的方法众多,且修改相似,故在此并无细研所有这些方法的必要。 您可在文后所附的文件中找到它们。
我只研讨椭圆绘制方法。 或许您还记得,我在上一篇文章中没有绘制椭圆,因为 Canvas 有一个潜在的除零漏洞。 如果该方法收到的矩形坐标类似于 x1 和 x2 或 y1 和 y2(在其中绘制椭圆),就会发生这种情况。 因此,在这种情况下,如果坐标值相同,我们需要预先进行调整:
//+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseAAOnBG(const double x1, // X coordinate of the first point defining the ellipse const double y1, // Y coordinate of the first point defining the ellipse const double x2, // X coordinate of the second point defining the ellipse const double y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseAA(xn1,yn1,xn2,yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| Wu algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseWuOnBG(const int x1, // X coordinate of the first point defining the ellipse const int y1, // Y coordinate of the first point defining the ellipse const int x2, // X coordinate of the second point defining the ellipse const int y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseWu((int)xn1,(int)yn1,(int)xn2,(int)yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
保存和恢复图片下被覆盖背景的方法:
//+------------------------------------------------------------------+ //| Save and restore the background under the image | //+------------------------------------------------------------------+ bool CFrameQuad::SaveRestoreBG(void) { //--- Calculate coordinate offsets for the saved area depending on the anchor point this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- Return the result of saving the background area with the calculated coordinates and size under the future image return CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+this.m_shift_x),int(this.m_quad_y+this.m_shift_y),this.m_quad_width,this.m_quad_height); } //+------------------------------------------------------------------+
绘制造型并保存和恢复背景的方法中,有许多重复的代码模块,均已移至该方法中。
\MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh 具有最小的变化 — 在两段代码模块里,简单地把文本串 "ENUM_TEXT_ANCHOR" 替换为 "ENUM_FRAME_ANCHOR":
//+------------------------------------------------------------------+ //| Single text animation frame class | //+------------------------------------------------------------------+ class CFrameText : public CFrame { private: public: //--- Display the text on the background while saving and restoring the background bool TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false); //--- Constructors CFrameText() {;} CFrameText(const int id,CGCnvElement *element) : CFrame(id,0,0,"",element) {} }; //+------------------------------------------------------------------+ //| Display the text on the background, while saving and restoring the background | //+------------------------------------------------------------------+ bool CFrameText::TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false) {
几何动画框对象类
几何动画框对象类背后的逻辑与其两个前辈 — 文本对象和矩形动画框对象非常相似。 我们仅需要创建一个方法,根据多边形顶点数计算圆周线上多边形顶点的坐标:
正多边形的笛卡尔坐标方程:
设 xc 和 yc 是中心坐标,R 是正多边形外接圆的半径,而 ϕ0 是第一个顶点相对于中心的角坐标。 在这种情况下,正 n 边形顶点的笛卡尔坐标由以下等式确定:
其中 i 值取自 0 至 n−1。
在 \MQL5\Include\DoEasy\Objects\Graph\Animations\ 里,创建 CFrameGeometry 类的新文件 FrameGeometry.mqh。
该文件应包含动画框对象类文件,而该类应继承自它:
//+------------------------------------------------------------------+ //| FrameGeometry.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Frame.mqh" //+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameGeometry : public CFrame { }
研究整个类主体中的定义,其中所有的类变量,及保存和恢复图像背景的虚方法都在私密部分中声明(我已在矩形动画框对象的关联文章中研讨了上述类方法,它只简单转换之前文章里研讨过的造型绘制方法中的重复代码模块)。 计算正多边形坐标的方法也在私密部分中声明。
类的公开部分含有构造函数(默认和参数型),及绘制正多边形的方法 — 简单的、填充的、以及平滑的:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
我们来看看一些类方法的实现。
该方法绘制正多边形:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
该方法与之前类中相似多边形绘制方法(矩形动画框类)的唯一区别在于,此处没有传递之前准备好的多边形顶点坐标数组。 取而代之,该方法接收多边形顶点的数量,和在方形框上绘制多边形的左上角坐标。 在该方法中还会调用:依据顶点数、坐标、圆半径和旋转角度计算多边形顶点坐标的方法,以及填充 X 和 Y 顶点坐标数组的方法。 然后使用 CCanvas 类来简单地绘制与该方法对应的多边形。
为了进行比较,我们来看看绘制填充多边形的方法:
//+------------------------------------------------------------------+ //| 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 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; } //+------------------------------------------------------------------+
保存和恢复图片下被覆盖背景的虚方法:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
这是从之前文章里绘制造型的方法中移值来的重复代码模块。
该方法计算圆内接正多边形的坐标:
//+------------------------------------------------------------------+ //| Calculate the coordinates of the regular polygon | //+------------------------------------------------------------------+ void CFrameGeometry::CoordsNgon(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left square angle the circle will be inscribed into const int coord_y, // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon const int len, // Length of the sides of the square a polygon is to be inscribed into const double angle) // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center) { //--- If there are less than three sides, there will be three int n=(N<3 ? 3 : N); //--- Set the size of coordinate arrays according to the number of vertices ::ArrayResize(this.m_array_x,n); ::ArrayResize(this.m_array_y,n); //--- Calculate the radius of the circumscribed circle double R=(double)len/2.0; //--- X and Y coordinates of the circle center double xc=coord_x+R; double yc=coord_y+R; //--- Calculate the polygon inclination angle in degrees double grad=angle*M_PI/180.0; //--- In the loop by the number of vertices, calculate the coordinates of each next polygon vertex for(int i=0; i<n; i++) { //--- Angle of the current polygon vertex with the rotation in degrees double a=2.0*M_PI*i/n+grad; //--- X and Y coordinates of the current polygon vertex double xi=xc+R*::cos(a); double yi=yc+R*::sin(a); //--- Set the current coordinates to the arrays this.m_array_x[i]=int(::floor(xi)); this.m_array_y[i]=int(::floor(yi)); } } //+------------------------------------------------------------------+
该方法的逻辑在代码注释中已有详述。 计算多边形笛卡尔坐标的方程:
我把这些方法留给大家独立研究。 我相信,一切都很清晰明了。 如果您有任何疑问,请随时在下面的评论中提问。
几何动画框对象类已准备就绪。
现在我们需要从外部程序访问它,并能够快速创建这个类的对象。
所有新创建动画框对象都存储在 CAnimations 类中它们自己的列表之中。
我们在 \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh 类文件中进行必要的改进。
将新创建的几何动画框对象类的文件包含到类文件当中,并在私密部分声明列表,该列表用于存放所有新创建的类对象:
//+------------------------------------------------------------------+ //| Animations.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "FrameText.mqh" #include "FrameQuad.mqh" #include "FrameGeometry.mqh" //+------------------------------------------------------------------+ //| Pixel copier class | //+------------------------------------------------------------------+ class CAnimations : public CObject { private: CGCnvElement *m_element; // Pointer to the graphical element CArrayObj m_list_frames_text; // List of text animation frames CArrayObj m_list_frames_quad; // List of rectangular animation frames CArrayObj m_list_frames_geom; // List of geometric shape animations frames //--- Return the flag indicating the presence of the frame object with the specified ID in the list bool IsPresentFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return or create a new animation frame object CFrame *GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new); public:
在类的公开部分,声明创建几何动画框新对象的方法,并编写返回指向列表中这些对象指针的方法:
public: CAnimations(CGCnvElement *element); CAnimations(){;} //--- Create a new (1) rectangular, (2) text and geometric animation frame object CFrame *CreateNewFrameText(const int id); CFrame *CreateNewFrameQuad(const int id); CFrame *CreateNewFrameGeometry(const int id); //--- Return the animation frame objects by ID CFrame *GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return the list of (1) text, (2) rectangular and (3) geometric shape animation frames CArrayObj *GetListFramesText(void) { return &this.m_list_frames_text; } CArrayObj *GetListFramesQuad(void) { return &this.m_list_frames_quad; } CArrayObj *GetListFramesGeometry(void) { return &this.m_list_frames_geom; }
接下来,声明绘制正多边形的方法:
//+------------------------------------------------------------------+ //| 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 | //+------------------------------------------------------------------+
类清单中所有出现的 “ENUM_TEXT_ANCHOR” 行都应替换为 “ENUM_FRAME_ANCHOR”。
在按类型和 ID 返回动画框对象的方法中添加处理动画框新类型对象:
//+------------------------------------------------------------------+ //| Return the animation frame objects by type and ID | //+------------------------------------------------------------------+ CFrame *CAnimations::GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id) { //--- Declare the pointer to the animation frame object CFrame *frame=NULL; //--- Depending on the necessary object type, receive their number in the appropriate list int total= ( frame_type==ANIMATION_FRAME_TYPE_TEXT ? this.m_list_frames_text.Total() : frame_type==ANIMATION_FRAME_TYPE_QUAD ? this.m_list_frames_quad.Total() : frame_type==ANIMATION_FRAME_TYPE_GEOMETRY ? this.m_list_frames_geom.Total() : 0 ); //--- Get the next object in the loop ... for(int i=0;i<total;i++) { //--- ... by the list corresponding to the animation frame type switch(frame_type) { case ANIMATION_FRAME_TYPE_TEXT : frame=this.m_list_frames_text.At(i); break; case ANIMATION_FRAME_TYPE_QUAD : frame=this.m_list_frames_quad.At(i); break; case ANIMATION_FRAME_TYPE_GEOMETRY : frame=this.m_list_frames_geom.At(i); break; default: break; } //--- if failed to get the pointer, move on to the next one if(frame==NULL) continue; //--- If the object ID correspond to the required one, //--- return the pointer to the detected object if(frame.ID()==id) return frame; } //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
该方法创建一个新的几何动画框对象:
//+------------------------------------------------------------------+ //| Create a new geometric animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::CreateNewFrameGeometry(const int id) { //--- If the object with such an ID is already present, inform of that in the journal and return NULL if(this.IsPresentFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id)) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_FRAME_ALREADY_IN_LIST),(string)id); return NULL; } //--- Create a new geometric animation frame object with the specified ID CFrame *frame=new CFrameGeometry(id,this.m_element); //--- If failed to create an object, inform of that and return NULL if(frame==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME)); return NULL; } //--- If failed to add the created object to the list, inform of that, remove the object and return NULL if(!this.m_list_frames_geom.Add(frame)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id); delete frame; return NULL; } //--- Return the pointer to a newly created object return frame; } //+------------------------------------------------------------------+
代码注释中完整描述了方法逻辑。
在返回或创建新动画框对象的方法中添加处理新的动画框类型:
//+------------------------------------------------------------------+ //| Return or create a new animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new) { //--- Declare null pointers to objects CFrameQuad *frame_q=NULL; CFrameText *frame_t=NULL; CFrameGeometry *frame_g=NULL; //--- Depending on the required object type switch(frame_type) { //--- If this is a text animation frame, case ANIMATION_FRAME_TYPE_TEXT : //--- get the pointer to an object with a specified ID frame_t=this.GetFrame(ANIMATION_FRAME_TYPE_TEXT,id); //--- If the pointer is obtained, return it if(frame_t!=NULL) return frame_t; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new text animation frame object (pointer to the created object) return this.CreateNewFrameText(id); //--- If this is a rectangular animation frame case ANIMATION_FRAME_TYPE_QUAD : //--- get the pointer to an object with a specified ID frame_q=this.GetFrame(ANIMATION_FRAME_TYPE_QUAD,id); //--- If the pointer is obtained, return it if(frame_q!=NULL) return frame_q; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new rectangular animation frame object (pointer to the created object) return this.CreateNewFrameQuad(id); //--- If this is a geometric animation frame case ANIMATION_FRAME_TYPE_GEOMETRY : //--- get the pointer to an object with a specified ID frame_g=this.GetFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id); //--- If the pointer is obtained, return it if(frame_g!=NULL) return frame_g; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new geometric animation frame object (pointer to the created object) return this.CreateNewFrameGeometry(id); //--- In the remaining cases, return NULL default: return NULL; } } //+------------------------------------------------------------------+
如往常一样,此处的整个逻辑在代码注释中均有讲述。
在类清单的末尾, 实现了绘制正多边形的方法:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
所有这些方法的逻辑完全相同,所以我们仅以最后一个方法为例。
正如我们所见,此处的一切都很简单:首先,我们既能 从列表中获取现成的几何动画框对象,亦或在列表中没有的情况下创建它。 如果新对象创建标志已启用,且当我们无法获取或创建对象时,则返回 false。
否则,返回调用几何动画框对象类同名方法,从列表中获取或从头创建的结果。
现在,我们来改进 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 中的交互窗对象类。
类清单中所有出现的 “ENUM_TEXT_ANCHOR” 字符串都应替换为 “ENUM_FRAME_ANCHOR”。
在类的私密部分,声明重置三个动画框类像素数组大小的方法:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object
这对于该方法正确地保存和恢复绘制造型后被覆盖的交互窗背景的动作是必要的(这已经在上面讨论过了)。
在类的公开部分,编写捕获交互窗外观的方法,并声明从数组中恢复图形资源的虚方法:
//--- 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 | //+------------------------------------------------------------------+
为什么我们需要捕获交互窗外观的方法?
假设我们已经创建了交互窗,并在其上绘制了所有必要的不可更改元素。 现在我们需要将新创建的交互窗外观复制到图形资源副本数组之中,以便我们可以在必要时恢复原始交互窗外观。 交互窗中的所有变化都精准地地体现在图形资源当中。 为了避免交互窗重绘,我们应该简单地将最初创建的交互窗副本存储在一个特殊的数组当中,我们总能从中恢复初始外观。 Reset() 方法就是做这个的。
在类的公开部分编写绘制正多边形的方法:
//--- 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); }
所有方法都雷同,并返回调用 CAnimations 类实例相应方法的结果,这些我在前面都已研究过了。
在类主体之外实现所声明的方法。
重置三个动画框对象数组大小的三个方法:
//+------------------------------------------------------------------+ //| Reset the array size of the text animation frames | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameT(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesText(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameText *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the rectangular animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameQ(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesQuad(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameQuad *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the geometric animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameG(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesGeometry(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameGeometry *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+
所有方法彼此都雷同:
如果 CAnimation 类对象不存在,则退出该方法。 该对象没有动画。
该方法获取动画框列表内指向对应对象的指针。 循环遍历所得列表,获取下一个动画框对象的指针,将其像素数组重置为零。
从数组中恢复资源的方法:
//+------------------------------------------------------------------+ //| Restore the resource from the array | //+------------------------------------------------------------------+ bool CForm::Reset(void) { CGCnvElement::Reset(); this.ResetArrayFrameQ(); this.ResetArrayFrameT(); this.ResetArrayFrameG(); return true; } //+------------------------------------------------------------------+
首先,调用父类方法从副本数组中恢复图形资源。 然后,重置所有动画框对象的像素数组,这样我们就可以在恢复交互窗外观后,依据必要坐标和保存背景区域大小复制背景。
我们现在准备测试在交互窗上绘制正多边形。
测试
为了执行测试,我们借用上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part80\,命名为 TestDoEasyPart80.mq5。
在上一篇文章中,我们根据按键在交互窗对象上绘制造型。 我建议在此也这样做。 重新分配的关键在于,动态设置所绘制多边形的坐标和大小。 在此,我还将沿 X 轴动态更改动画框坐标和欲绘制多边形的顶点数(从 3 到 10)。
- Y – 无平滑的正多边形,
- U – 无平滑的填充多边形,
- I – 具有抗锯齿 (AA) 的正多边形,
- O – Wu 算法的正多边形,
- P – 应用两种平滑算法(平滑)的指定宽度的正多边形,
- A – 指定宽度的正多边形应用初步排序平滑(厚),
- . – 绘制一个填充区域。 事实上,这意味着用指定的颜色填充整个交互窗。
接下来,每次单击交互窗都会更改绘制画框的 X 坐标,并把绘制的多边形顶点数加 1。
将所有出现的 “TEXT_ANCHOR” 子字符串替换为 “FRAME_ANCHOR”。
在 EA 的 OnInit() 处理程序中,捕获每个所创建交互窗的外观:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the specified number of form objects list_forms.Clear(); int total=FORMS_TOTAL; for(int i=0;i<total;i++) { int y=40; if(i>0) { CForm *form_prev=list_forms.At(i-1); if(form_prev==NULL) continue; y=form_prev.BottomEdge()+10; } //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30)); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the form ID equal to the loop index and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the partial opacity for the middle form and the full one for the rest uchar opacity=(i==1 ? 250 : 255); //--- Set the form style and its color theme depending on the loop index if(i<2) { ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i; ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i; //--- Set the form style and theme form.SetFormStyle(style,theme,opacity,true,false); } //--- If this is the first (top) form if(i==0) { //--- Draw a concave field slightly shifted from the center of the form downwards form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity()); form.Done(); } //--- If this is the second form if(i==1) { //--- Draw a concave semi-transparent "tainted glass" field in the center form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200); form.Done(); } //--- If this is the third form if(i==2) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.TextOnBG(0,TextByLanguage("V-Градиент","V-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- If this is the fourth (bottom) form if(i==3) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a horizontal gradient form.Erase(array_clr,form.Opacity(),false); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.TextOnBG(0,TextByLanguage("H-Градиент","H-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } //--- Add objects to the list if(!list_forms.Add(form)) { delete form; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在 OnChartEvent() 处理程序的按键处理模块中,实现调用恢复交互窗外观,及将画框对象像素数组重置为零的方法:
//--- 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; } }
在 FigureType() 函数中,添加 “.” 键的处理:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
在 FigureProcessing() 函数中,动态化坐标数组:
//+------------------------------------------------------------------+ //| Handle the selected shape | //+------------------------------------------------------------------+ void FigureProcessing(CForm *form,const ENUM_FIGURE_TYPE figure_type) { int array_x[]; int array_y[]; switch(figure_type) {
并在所有需要将坐标数组传递给类方法的地方设置数组大小:
//--- "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
按键处理后绘制多边形的代码应替换为调用绘制正多边形的方法:
//--- "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
代码已有详细的注释。 故此处的一切都应该清晰明了。 如果您有任何疑问,请随时在评论中提问。
不要忘记 添加 “.” 键的按压处理,该键作用是用颜色填充交互窗:
//--- "." = 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; } } //+------------------------------------------------------------------+
编译 EA,并在品种图表上启动它。
启动后,按下该键,从而绘制正多边形,并用颜色填充区域:
一切都按预期工作。 然而,造型本身证明非常不均匀...... 依我的观点,应用 Wu 平滑算法的多边形外观是最好的。 而在填充时,我们可以依据指定的必要 threshold 参数来调整颜色填充的程度(阈值):
form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);
下一步是什么?
在下一篇文章中,我将继续开发动画框和交互窗对象。
以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象
DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素
DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法
DoEasy 函数库中的图形(第七十六部分):会话窗对象和预定义的颜色主题
DoEasy 函数库中的图形(第七十七部分):阴影对象类
DoEasy 函数库中的图形(第七十八部分):函数库中的动画原理 图像切片
DoEasy 函数库中的图形(第七十九部分):“动画框”对象类及其衍生对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9689


