Gráficos na biblioteca DoEasy (Parte 80): classe do objeto quadro de animação geométrica
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Classe para o objeto de quadro de animação geométrica
- Teste
- O que vem agora?
Ideia
Continuamos a trabalhar com as classes para desenhar formas na tela. Já criamos classes que permitem desenhar um quadro de animação numa determinada área da tela e, ao mesmo tempo, manter o fundo no qual a imagem é colocada, para restaurar mais tarde o fundo ao excluir ou alterar a imagem. A partir dos quadros criados anteriormente, será possível compor sequências de animação para alterar quadros rapidamente. Além disso, um quadro em si também permite fazer animações dentro de seu espaço.
Hoje vamos otimizar um pouco os códigos criados anteriormente para essas classes. Para fazer isso, vamos aderir à ideia de que, se houver seções de código repetitivas, toda a sua lógica pode (e deve) ser plasmada numa função/método individual e usar sua chamada. Isso tornará o código mais legível e reduzirá seu tamanho.
Além dessa otimização, criaremos uma classe de objeto de quadro de animação geométrica. O que isso significa?
Já temos métodos suficientes para construir polígonos diferentes, assim, se precisarmos desenhar um polígono regular, será muito mais fácil usar geometria do que calcular manualmente as coordenadas dos vértices (depois, podemos adicionar outras formas geométricas, as coordenadas dos vértices que podem ser calculados por meio de fórmulas, em vez de defini-las manualmente).
Parafraseando a Wikipedia:
Polígono regular: um polígono é regular se tiver todos os seus lados e ângulos iguais (equilátero e equiângulo, respectivamente), sejam eles internos ou externos.
Octógono regular
Todo polígono regular pode ser inscrito numa circunferência. Tal circunferência é chamada de circunscrita, porque tangencia todos os vértices do polígono, ficando externa a ele.
Circunferência circunscrita
Também existe outra circunferência chamada de inscrita, pois tangencia todas as arestas do polígono, ficando interna a ele.
Circunferência inscrita
Não consideraremos estes polígonos, com exceção de um quadrado que conterá um círculo no qual, por sua vez, desenharemos um polígono regular.
Assim, teremos um retângulo (mais precisamente, um quadrado) com um círculo dentro que, por sua vez, conterá um polígono regular. O quadrado será o quadro da animação - as coordenadas do canto superior esquerdo e o tamanho (comprimento) de seus lados. Na circunferência - cujo diâmetro circunferência será igual ao comprimento do lado do quadrado do quadro da animação - traçaremos um polígono cujos vértices estarão sobre a linha do círculo.
Ou seja, para criar um polígono regular, não precisaremos criar matrizes de coordenadas de forma independente, bastará indicar o número necessário de vértices, as coordenadas do canto superior esquerdo e o comprimento dos lados do quadrado .
Aprimorando as classes da biblioteca
No arquivo \MQL5\Include\DoEasy\Data.mqh incluímos o índice da nova mensagem:
//--- CGCnvElement MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY, // Error! Empty array MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH, // Error! Array-copy of the resource does not match the original //--- CForm
e o texto da mensagem correspondente ao índice recém-adicionado:
//--- CGCnvElement {"Ошибка! Пустой массив","Error! Empty array"}, {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"}, //--- CForm
Como o alinhamento (ângulo de âncora) dos quadros de animação agora está vinculado não apenas aos quadros de texto das animações, mas também a todos os outros - retangulares, geométricos - que criaremos hoje e todos os outros novos que faremos no futuro, decidimos alterar ligeiramente o nome da enumeração e suas constantes, de modo que não fiquem vinculadas ao texto, senão aos quadros.
No arquivo \MQL5\Include\DoEasy\Defines.mqh na enumeração de ângulos de âncora substituímos a entrada do texto "TEXT" por "FRAME":
//+------------------------------------------------------------------+ //| Data for handling graphical elements | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of anchoring methods | //| (horizontal and vertical text alignment) | //+------------------------------------------------------------------+ enum ENUM_FRAME_ANCHOR { FRAME_ANCHOR_LEFT_TOP = 0, // Frame anchor point at the upper left corner of the bounding rectangle FRAME_ANCHOR_CENTER_TOP = 1, // Frame anchor point at the top center side of the bounding rectangle FRAME_ANCHOR_RIGHT_TOP = 2, // Frame anchor point at the upper right corner of the bounding rectangle FRAME_ANCHOR_LEFT_CENTER = 4, // Frame anchor point at the center of the left side of the bounding rectangle FRAME_ANCHOR_CENTER = 5, // Frame anchor point at the center of the bounding rectangle FRAME_ANCHOR_RIGHT_CENTER = 6, // Frame anchor point at the center of the right side of the bounding rectangle FRAME_ANCHOR_LEFT_BOTTOM = 8, // Frame anchor point at the lower left corner of the bounding rectangle FRAME_ANCHOR_CENTER_BOTTOM = 9, // Frame anchor point at the bottom center side of the bounding rectangle FRAME_ANCHOR_RIGHT_BOTTOM = 10, // Frame anchor point at the lower right corner of the bounding rectangle }; //+------------------------------------------------------------------+
À enumerando de tipos de quadros de animação adicionamos um novo tipo, o quadro de animações de formas geométricas:
//+------------------------------------------------------------------+ //| Data for working with graphical element animation | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of animation frame types | //+------------------------------------------------------------------+ enum ENUM_ANIMATION_FRAME_TYPE { ANIMATION_FRAME_TYPE_TEXT, // Text animation frame ANIMATION_FRAME_TYPE_QUAD, // Rectangular animation frame ANIMATION_FRAME_TYPE_GEOMETRY, // Square animation frame of geometric shapes }; //+------------------------------------------------------------------+
e na lista de tipos de formas a serem desenhadas adicionamos uma área sombreada que esquecemos de fazer em artigos anteriores:
//+------------------------------------------------------------------+ //| List of drawn shape types | //+------------------------------------------------------------------+ enum ENUM_FIGURE_TYPE { FIGURE_TYPE_PIXEL, // Pixel FIGURE_TYPE_PIXEL_AA, // Pixel with antialiasing FIGURE_TYPE_LINE_VERTICAL, // Vertical line FIGURE_TYPE_LINE_VERTICAL_THICK, // a Vertical segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_LINE_HORIZONTAL, // Horizontal line FIGURE_TYPE_LINE_HORIZONTAL_THICK, // Horizontal segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_LINE, // Arbitrary line FIGURE_TYPE_LINE_AA, // Line with antialiasing FIGURE_TYPE_LINE_WU, // Line with WU smoothing FIGURE_TYPE_LINE_THICK, // Segment of a freehand line having a specified width using antialiasing algorithm FIGURE_TYPE_POLYLINE, // Polyline FIGURE_TYPE_POLYLINE_AA, // Polyline with antialiasing FIGURE_TYPE_POLYLINE_WU, // Polyline with WU smoothing FIGURE_TYPE_POLYLINE_SMOOTH, // Polyline with a specified width using two smoothing algorithms FIGURE_TYPE_POLYLINE_THICK, // Polyline with a specified width using a smoothing algorithm FIGURE_TYPE_POLYGON, // Polygon FIGURE_TYPE_POLYGON_FILL, // Filled polygon FIGURE_TYPE_POLYGON_AA, // Polygon with antialiasing FIGURE_TYPE_POLYGON_WU, // Polygon with WU smoothing FIGURE_TYPE_POLYGON_SMOOTH, // Polygon with a specified width using two smoothing algorithms FIGURE_TYPE_POLYGON_THICK, // Polygon with a specified width using a smoothing algorithm FIGURE_TYPE_RECTANGLE, // Rectangle FIGURE_TYPE_RECTANGLE_FILL, // Filled rectangle FIGURE_TYPE_CIRCLE, // Circle FIGURE_TYPE_CIRCLE_FILL, // Filled circle FIGURE_TYPE_CIRCLE_AA, // Circle with antialiasing FIGURE_TYPE_CIRCLE_WU, // Circle with WU smoothing FIGURE_TYPE_TRIANGLE, // Triangle FIGURE_TYPE_TRIANGLE_FILL, // Filled triangle FIGURE_TYPE_TRIANGLE_AA, // Triangle with antialiasing FIGURE_TYPE_TRIANGLE_WU, // Triangle with WU smoothing FIGURE_TYPE_ELLIPSE, // Ellipse FIGURE_TYPE_ELLIPSE_FILL, // Filled ellipse FIGURE_TYPE_ELLIPSE_AA, // Ellipse with antialiasing FIGURE_TYPE_ELLIPSE_WU, // Ellipse with WU smoothing FIGURE_TYPE_ARC, // Ellipse arc FIGURE_TYPE_PIE, // Ellipse sector FIGURE_TYPE_FILL, // Filled area }; //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh substituímos o nome da matriz de armazenamento de cópia do recurso gráfico por outro mais "significativo" - eu mesmo estava confuso com os nomes, ao tentar definir qual matriz era destinada para armazenar uma cópia da forma inicial -, e removemos da seção privada a classe do método que salva numa matriz o recurso gráfico:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Save the graphical resource to the array bool ResourceCopy(const string source); private:
Vamos substituir na listagem da classe (ou melhor, em todos os arquivos da biblioteca) as ocorrências das linhas "TEXT_ANCHOR" pelas linhas "FRAME_ANCHOR". Para encontrar todas as ocorrências em todos os arquivos da biblioteca, basta pressionar a combinação de teclas Shift+Ctrl+H e, na janela que se abre, inserir os seguintes critérios de pesquisa e substituição:
Naturalmente, no campo "Folder:" o caminho deve ser especificado com base na localização do seu editor.
Na seção pública da classe, declaramos os métodos para salvar numa matriz um recurso gráfico e restaurá-lo a partir dela, e escrevemos métodos para atualizar a tela e um método que retorna o tamanho da matriz de cópia de recurso gráfico:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CANV_ELEMENT_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)];} string GetProperty(ENUM_CANV_ELEMENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)];} //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return false; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return itself CGCnvElement *GetObject(void) { return &this; } //--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CGCnvElement objects with each other by all properties (to search equal objects) bool IsEqual(CGCnvElement* compared_obj) const; //--- (1) Save the object to file and (2) upload the object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- (1) Save the graphical resource to the array and (2) restore the resource from the array bool ResourceStamp(const string source); virtual bool Reset(void); //--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; } //--- Set the canvas update frequency void SetFrequency(const ulong value) { this.m_pause.SetWaitingMSC(value); } //--- Update the canvas void CanvasUpdate(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Return the size of the graphical resource copy array uint DuplicateResArraySize(void) { return ::ArraySize(this.m_duplicate_res); } //--- Update the coordinates (shift the canvas) bool Move(const int x,const int y,const bool redraw=false); //--- Save an image to the array bool ImageCopy(const string source,uint &array[]); //--- Change the lightness of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorLightness(const uint clr,const double change_value); color ChangeColorLightness(const color colour,const double change_value); //--- Change the saturation of (1) ARGB and (2) COLOR by a specified amount uint ChangeColorSaturation(const uint clr,const double change_value); color ChangeColorSaturation(const color colour,const double change_value); protected:
O antigo método ResourceCopy() agora é chamado ResourceStamp():
//+------------------------------------------------------------------+ //| Save the graphical resource to the array | //+------------------------------------------------------------------+ bool CGCnvElement::ResourceStamp(const string source) { return this.ImageCopy(DFUN,this.m_duplicate_res); } //+------------------------------------------------------------------+
Método que restaura um recurso gráfico a partir de uma matriz:
//+------------------------------------------------------------------+ //| Restore the graphical resource from the array | //+------------------------------------------------------------------+ bool CGCnvElement::Reset(void) { //--- Get the size of the graphical resource copy array int size=::ArraySize(this.m_duplicate_res); //--- If the array is empty, inform of that and return 'false' if(size==0) { CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY); return false; } //--- If the size of the graphical resource copy array does not match the size of the graphical resource, //--- inform of that in the journal and return 'false' if(this.m_canvas.Width()*this.m_canvas.Height()!=size) { CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH); return false; } //--- Set the index of the array for setting the image pixel int n=0; //--- In the loop by the resource height, for(int y=0;y<this.m_canvas.Height();y++) { //--- in the loop by the resource width for(int x=0;x<this.m_canvas.Width();x++) { //--- Restore the next image pixel from the array and increase the array index this.m_canvas.PixelSet(x,y,this.m_duplicate_res[n]); n++; } } //--- Update the data on the canvas and return 'true' this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Resumindo, verificamos o tamanho da matriz de cópia do recurso e, se estiver vazio ou se o tamanho da cópia não corresponder ao original, imprimiremos o erro no log e saímos do método. Em seguida, copiamos para a tela todos os dados da matriz de cópia pixel por pixel.
Como alteramos o nome da matriz de cópia de recursos e o método que salva o recurso gráfico nela, precisamos fazer correções no arquivo de classe de objeto sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.
Correções de bugs só são para o método GaussianBlur():
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/en/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CShadowObj::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceStamp(DFUN)) return false; //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2) { ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE)); return false; } //--- Decompose image data from the resource into a, r, g, b color components int size=::ArraySize(this.m_duplicate_res); //--- arrays for storing A, R, G and B color components //--- for horizontal and vertical blur uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[]; uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[]; //--- Change the size of component arrays according to the array size of the graphical resource data if(::ArrayResize(a_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_h_data\""); return false; } if(::ArrayResize(r_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_h_data\""); return false; } if(::ArrayResize(g_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_h_data\""); return false; } if(ArrayResize(b_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_h_data\""); return false; } if(::ArrayResize(a_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_v_data\""); return false; } if(::ArrayResize(r_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_v_data\""); return false; } if(::ArrayResize(g_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_v_data\""); return false; } if(::ArrayResize(b_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_v_data\""); return false; } //--- Declare the array for storing blur weight ratios and, //--- if failed to get the array of weight ratios, return 'false' double weights[]; if(!this.GetQuadratureWeights(1,n_nodes,weights)) return false; //--- Set components of each image pixel to the color component arrays for(int i=0;i<size;i++) { a_h_data[i]=GETRGBA(this.m_duplicate_res[i]); r_h_data[i]=GETRGBR(this.m_duplicate_res[i]); g_h_data[i]=GETRGBG(this.m_duplicate_res[i]); b_h_data[i]=GETRGBB(this.m_duplicate_res[i]); } //--- Blur the image horizontally (along the X axis) uint XY; // Pixel coordinate in the array double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0; int coef=0; int j=(int)radius; //--- Loop by the image width for(int Y=0;Y<this.Height();Y++) { //--- Loop by the image height for(uint X=radius;X<this.Width()-radius;X++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { a_temp+=a_h_data[XY+i]*weights[coef]; r_temp+=r_h_data[XY+i]*weights[coef]; g_temp+=g_h_data[XY+i]*weights[coef]; b_temp+=b_h_data[XY+i]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_h_data[XY]=(uchar)::round(a_temp); r_h_data[XY]=(uchar)::round(r_temp); g_h_data[XY]=(uchar)::round(g_temp); b_h_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts to the left by copying adjacent pixels for(uint x=0;x<radius;x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[Y*this.Width()+radius]; r_h_data[XY]=r_h_data[Y*this.Width()+radius]; g_h_data[XY]=g_h_data[Y*this.Width()+radius]; b_h_data[XY]=b_h_data[Y*this.Width()+radius]; } //--- Remove blur artifacts to the right by copying adjacent pixels for(int x=int(this.Width()-radius);x<this.Width();x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1]; r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1]; g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1]; b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1]; } } //--- Blur vertically (along the Y axis) the image already blurred horizontally int dxdy=0; //--- Loop by the image height for(int X=0;X<this.Width();X++) { //--- Loop by the image width for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { dxdy=i*(int)this.Width(); a_temp+=a_h_data[XY+dxdy]*weights[coef]; r_temp+=r_h_data[XY+dxdy]*weights[coef]; g_temp+=g_h_data[XY+dxdy]*weights[coef]; b_temp+=b_h_data[XY+dxdy]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_v_data[XY]=(uchar)::round(a_temp); r_v_data[XY]=(uchar)::round(r_temp); g_v_data[XY]=(uchar)::round(g_temp); b_v_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts at the top by copying adjacent pixels for(uint y=0;y<radius;y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+radius*this.Width()]; r_v_data[XY]=r_v_data[X+radius*this.Width()]; g_v_data[XY]=g_v_data[X+radius*this.Width()]; b_v_data[XY]=b_v_data[X+radius*this.Width()]; } //--- Remove blur artifacts at the bottom by copying adjacent pixels for(int y=int(this.Height()-radius);y<this.Height();y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()]; r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()]; g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()]; b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()]; } } //--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array for(int i=0;i<size;i++) this.m_duplicate_res[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]); //--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array for(int X=0;X<this.Width();X++) { for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; this.m_canvas.PixelSet(X,Y,this.m_duplicate_res[XY]); } } //--- Done return true; } //+------------------------------------------------------------------+
Vamos modificar a classe do objeto-quadro de animação no arquivo \MQL5\Include\DoEasy\Objects\Graph\Animations\Frame.mqh.
Na seção protegida da classe, declaramos um método para escrever os valores das coordenadas e do deslocamento do retângulo de contorno como passados, para seu posterior uso, e escrevemos o método virtual para salvamento e restauração do fundo sob a imagem:
//+------------------------------------------------------------------+ //| Single animation frame class | //+------------------------------------------------------------------+ class CFrame : public CPixelCopier { protected: ENUM_ANIMATION_FRAME_TYPE m_frame_figure_type; // Type of the figure drawn by the frame ENUM_FRAME_ANCHOR m_anchor_last; // Last frame anchor point double m_x_last; // X coordinate of the upper left corner of the last frame double m_y_last; // Y coordinate of the upper left corner of the last frame int m_shift_x_prev; // Offset of the X coordinate of the last frame upper left corner int m_shift_y_prev; // Offset of the Y coordinate of the last frame upper left corner //--- Set the coordinates and offset of the outlining rectangle as the previous ones void SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP); //--- Save and restore the background under the image virtual bool SaveRestoreBG(void) { return false; } public:
Todos esses métodos são o resultado da otimização do código dos métodos para desenhar formas nas classes que escrevemos nos artigos anteriores.
Aqui o método virtual simplesmente retorna false e deve ser implementado nas classes herdeiros (se sua implementação em todas as classes herdeiras for a mesma, faremos esse método não virtual e apenas nesta classe). Veremos o método SetLastParams() um pouco mais tarde.
Na seção pública da classe, escrevemos o método para zerar uma matriz de pixels:
public: //--- Reset the pixel array void ResetArray(void) { ::ArrayResize(this.m_array,0); } //--- Return the last (1) anchor point, (2) X and (3) Y coordinate, //--- previous offset by (4) X and (5) Y, (6) type of the figure drawn by the frame ENUM_FRAME_ANCHOR LastAnchor(void) const { return this.m_anchor_last; } double LastX(void) const { return this.m_x_last; } double LastY(void) const { return this.m_y_last; } int LastShiftX(void) const { return this.m_shift_x_prev; } int LastShiftY(void) const { return this.m_shift_y_prev; } ENUM_ANIMATION_FRAME_TYPE FrameFigureType(void) const { return this.m_frame_figure_type; } //--- Default constructor CFrame(); protected:
O método simplesmente define o tamanho da matriz de pixels como zero. Isso é necessário para processar corretamente o redimensionamento do retângulo de contorno, pois, no método que salva o fundo para sua posterior restauração, o tamanho é verificado - se for zero, o fundo será salvo, caso contrário considera-se-á que o fundo foi previamente salvo com os valores corretos das coordenadas e tamanhos da área salva. Assim, se alterarmos a figura que está sendo desenhada, a matriz deve ser redefinida para zero. Caso contrário, o fundo sob a nova figura não será salvo e, logo, um fundo completamente diferente será restaurado de uma área completamente diferente (salva anteriormente - antes da alteração do tamanho, das coordenadas e da aparência da figura que está sendo desenhada).
Na seção protegida da classe, declaramos o construtor para uma classe - quadro de animação de formas geométricas, que criaremos e testaremos hoje:
protected: //--- Text frame constructor CFrame(const int id, const int x, const int y, const string text, CGCnvElement *element); //--- Rectangular frame constructor CFrame(const int id, const int x, const int y, const int w, const int h, CGCnvElement *element); //--- Geometric frame constructor CFrame(const int id, const int x, const int y, const int len, CGCnvElement *element); }; //+------------------------------------------------------------------+
Ao construtor da classe, por analogia com as outras classes herdadas criadas anteriormente, passaremos o identificador do objeto, as coordenadas X e Y do canto superior esquerdo do quadro, o comprimento das laterais do quadro quadrado e o ponteiro para o elemento gráfico a partir do qual é criado o novo objeto.
Implementação do construtor do objeto-quadro de animação geométrica:
//+------------------------------------------------------------------+ //| Geometric frame constructor | //+------------------------------------------------------------------+ CFrame::CFrame(const int id,const int x,const int y,const int len,CGCnvElement *element) : CPixelCopier(id,x,y,len,len,element) { this.m_frame_figure_type=ANIMATION_FRAME_TYPE_GEOMETRY; this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_x_last=x; this.m_y_last=y; this.m_shift_x_prev=0; this.m_shift_y_prev=0; } //+------------------------------------------------------------------+
Na lista de inicialização, ao construtor da classe pai passamos todos os parâmetros necessários, e no corpo da classe escrevemos o tipo de forma como ANIMATION_FRAME_TYPE_GEOMETRY, tipo esse adicionado por nós hoje à lista de tipos de quadro de animação. O restante dos parâmetros são inicializados de forma semelhante aos construtores anteriormente considerados das classes de texto e animação retangular.
Método que registra as coordenadas e deslocamento do retângulo de contorno como passados:
//+------------------------------------------------------------------+ //| Set the coordinates and the offset | //| of the outlining rectangle as the previous ones | //+------------------------------------------------------------------+ void CFrame::SetLastParams(const double quad_x,const double quad_y,const int shift_x,const int shift_y,const ENUM_FRAME_ANCHOR anchor=FRAME_ANCHOR_LEFT_TOP) { this.m_anchor_last=anchor; this.m_x_last=quad_x; this.m_y_last=quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; } //+------------------------------------------------------------------+
A este método foi passado um trecho de código recorrente a partir dos métodos de desenho de formas com salvamento e restauração do fundo da forma, que consideramos em artigos anteriores.
Modificamos as classes herdeiros da classe CFrame.
Abrimos o arquivo da classe da animação retangular \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameQuad.mqh e fazemos as alterações necessárias.
Na seção privada da classe declaramos duas variáveis para armazenar os deslocamentos das coordenadas do retângulo de contorno e declaramos um método virtual para salvar e restaurar o fundo sob a imagem:
//+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameQuad : public CFrame { private: double m_quad_x; // X coordinate of the rectangle enclosing the shape double m_quad_y; // Y coordinate of the rectangle enclosing the shape uint m_quad_width; // Width of the rectangle enclosing the shape uint m_quad_height; // Height of the rectangle enclosing the shape int m_shift_x; // Offset of the X coordinate of the rectangle enclosing the shape int m_shift_y; // Offset of the Y coordinate of the rectangle enclosing the shape //--- Save and restore the background under the image virtual bool SaveRestoreBG(void); public:
Na seção pública da classe, iremos complementar a implementação do construtor paramétrico - agora em seu corpo serão inicializadas todas as variáveis de classe (anteriormente não eram inicializadas, o que não é correto):
public: //--- Constructors CFrameQuad() {;} CFrameQuad(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element) { this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_quad_x=0; this.m_quad_y=0; this.m_quad_width=0; this.m_quad_height=0; this.m_shift_x=0; this.m_shift_y=0; }
Vamos ver quais foram nossos métodos de desenho com salvamento/restauração do plano de fundo usando o exemplo do método de desenho de ponto:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Calculate coordinate offsets for the saved area depending on the anchor point int shift_x=0,shift_y=0; this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,TEXT_ANCHOR_LEFT_TOP,shift_x,shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- If a background area with calculated coordinates and size under the future image is successfully saved if(!CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+shift_x),int(this.m_quad_y+shift_y),this.m_quad_width,this.m_quad_height)) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.m_element.Update(redraw); this.m_anchor_last=TEXT_ANCHOR_LEFT_TOP; this.m_x_last=this.m_quad_x; this.m_y_last=this.m_quad_y; this.m_shift_x_prev=shift_x; this.m_shift_y_prev=shift_y; return true; } //+------------------------------------------------------------------+
Agora podemos substituir as seções de código destacadas pelos novos métodos escritos hoje. Assim fica este método agora:
//+------------------------------------------------------------------+ //| Set the color of the dot with the specified coordinates | //+------------------------------------------------------------------+ bool CFrameQuad::SetPixelOnBG(const int x,const int y,const color clr,const uchar opacity=255,const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_quad_x=x; this.m_quad_y=y; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=1; this.m_quad_height=1; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.SetPixel(x,y,clr,opacity); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Como se pode ver, a substituição das seções indicadas do código pela chamada de novos métodos reduziu significativamente o código e o tornou mais legível. Mudanças idênticas foram feitas em todos os métodos de desenho de formas com salvamento e restauração do plano de fundo. Como existem muitos métodos e fizemos as mesmas alterações em todos eles, não analisaremos suas listas inteiras - você pode se familiarizar com elas nos arquivos anexados ao artigo.
Vamos apenas nos deter nos métodos para desenhar elipses. Como você lembra, no último artigo não desenhamos elipses, pois podia acontecer uma potencial divisão por zero no CCanvas. Isso acontece se as mesmas coordenadas x1 e x2 ou y1 e y2 do retângulo no qual a elipse é desenhada forem passadas para o método. Por isso, neste caso, precisamos corrigir os valores das mesmas coordenadas se forem iguais:
//+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseAAOnBG(const double x1, // X coordinate of the first point defining the ellipse const double y1, // Y coordinate of the first point defining the ellipse const double x2, // X coordinate of the second point defining the ellipse const double y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseAA(xn1,yn1,xn2,yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Draw an ellipse using two points while applying | //| Wu algorithm | //+------------------------------------------------------------------+ bool CFrameQuad::DrawEllipseWuOnBG(const int x1, // X coordinate of the first point defining the ellipse const int y1, // Y coordinate of the first point defining the ellipse const int x2, // X coordinate of the second point defining the ellipse const int y2, // Y coordinate of the second point defining the ellipse const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Get the minimum and maximum coordinates double xn1=::fmin(x1,x2); double xn2=::fmax(x1,x2); double yn1=::fmin(y1,y2); double yn2=::fmax(y1,y2); if(xn2==xn1) xn2=xn1+0.1; if(yn2==yn1) yn2=yn1+0.1; //--- Set the coordinates of the outlining rectangle this.m_quad_x=xn1-1; this.m_quad_y=yn1-1; //--- Set the width and height of the image outlining the rectangle (to be used as the size of the saved area) this.m_quad_width=int(::ceil((xn2-xn1)+1))+2; this.m_quad_height=int(::ceil((yn2-yn1)+1))+2; //--- Adjust the width and height of the outlining rectangle if(this.m_quad_width<3) this.m_quad_width=3; if(this.m_quad_height<3) this.m_quad_height=3; //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw the shape and update the element this.m_element.DrawEllipseWu((int)xn1,(int)yn1,(int)xn2,(int)yn2,clr,opacity,style); this.SetLastParams(this.m_quad_x,this.m_quad_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Método para preservar e restaurar o fundo sob a imagem:
//+------------------------------------------------------------------+ //| Save and restore the background under the image | //+------------------------------------------------------------------+ bool CFrameQuad::SaveRestoreBG(void) { //--- Calculate coordinate offsets for the saved area depending on the anchor point this.m_element.GetShiftXYbySize(this.m_quad_width,this.m_quad_height,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- Return the result of saving the background area with the calculated coordinates and size under the future image return CPixelCopier::CopyImgDataToArray(int(this.m_quad_x+this.m_shift_x),int(this.m_quad_y+this.m_shift_y),this.m_quad_width,this.m_quad_height); } //+------------------------------------------------------------------+
Um bloco de código foi transferido ao método recorrente desde métodos para desenhar formas com salvamento e restauração do plano de fundo.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\Animations\FrameText.mqh temos mudanças mínimas - simplesmente em dois lugares do código substituímos a linha "ENUM_TEXT_ANCHOR" por "ENUM_FRAME_ANCHOR":
//+------------------------------------------------------------------+ //| Single text animation frame class | //+------------------------------------------------------------------+ class CFrameText : public CFrame { private: public: //--- Display the text on the background while saving and restoring the background bool TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false); //--- Constructors CFrameText() {;} CFrameText(const int id,CGCnvElement *element) : CFrame(id,0,0,"",element) {} }; //+--------------------------------------------------------------------------------------+ //| Display the text on the background, while saving and restoring the background | //+--------------------------------------------------------------------------------------+ bool CFrameText::TextOnBG(const string text,const int x,const int y,const ENUM_FRAME_ANCHOR anchor,const color clr,const uchar opacity,bool redraw=false) {
Classe para o objeto de quadro de animação geométrica
A classe destinada ao objeto de quadro de animação geométrica, por sua lógica, não será muito diferente de seus dois predecessoras - objetos de quadros de animação retangulares e de texto. Precisaremos apenas criar um método que calcule as coordenadas dos vértices do polígono na circunferência, dependendo do seu número de vértices:
Fórmulas para calcular as coordenadas cartesianas de um polígono regular:
Sejam xc e yc as coordenadas do centro; R, o raio de um círculo circunscrito em torno de um polígono regular; ϕ0, a coordenada angular do primeiro vértice em relação ao centro. Depois as coordenadas cartesianas dos vértices de um polígono regular são determinados pelas fórmulas:
onde i toma valores de 0 a n−1
Na pasta \MQL5\Include\DoEasy\Objects\Graph\Animations\ criamos o novo arquivo FrameGeometry.mqh da classe CFrameGeometry.
No arquivo deve ser integrado o arquivo da classe do objeto-quadro de animação, e a classe deve ser herdada deste último:
//+------------------------------------------------------------------+ //| FrameGeometry.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Frame.mqh" //+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameGeometry : public CFrame { }
Vejamos a definição geral do corpo da classe: na seção privada são declarados todas as variáveis da classe e o método virtual para salvar e restaurar o fundo sob a imagem (nós vimos o método acima no contexto da classe de objeto de animação retangular, ele simplesmente é uma transferência de blocos de código recorrentes desde métodos de desenho de figuras estudados em artigos anteriores). Além disso, nessa mesma seção é declarado um método para calcular as coordenadas de um polígono regular.
Já na seção pública da classe estão localizados os construtores (padrão e paramétricos) e métodos para desenhar polígonos regulares - simples, preenchidos e com suavização:
//+------------------------------------------------------------------+ //| Class of a single rectangular animation frame | //+------------------------------------------------------------------+ class CFrameGeometry : public CFrame { private: double m_square_x; // X coordinate of the square enclosing the shape double m_square_y; // Y coordinate of the square enclosing the shape uint m_square_length; // Length of the sides of the square enclosing the shape int m_shift_x; // Offset of the X coordinate of the square enclosing the shape int m_shift_y; // Offset of the Y coordinate of the square enclosing the shape int m_array_x[]; // Array of shape X coordinates int m_array_y[]; // Array of shape Y coordinates //--- Save and restore the background under the image virtual bool SaveRestoreBG(void); //--- Calculate coordinates of the regular polygon built in a circumscribed circle inscribed in a square void CoordsNgon(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left square angle the circle will be inscribed into const int coord_y, // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon const int len, // Square sides length const double angle); // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center) public: //--- Constructors CFrameGeometry() {;} CFrameGeometry(const int id,CGCnvElement *element) : CFrame(id,0,0,0,0,element) { ::ArrayResize(this.m_array_x,0); ::ArrayResize(this.m_array_y,0); this.m_anchor_last=FRAME_ANCHOR_LEFT_TOP; this.m_square_x=0; this.m_square_y=0; this.m_square_length=0; this.m_shift_x=0; this.m_shift_y=0; } //--- Destructor ~CFrameGeometry() { ::ArrayFree(this.m_array_x); ::ArrayFree(this.m_array_y); } //+------------------------------------------------------------------+ //| Methods of drawing regular polygons | //+------------------------------------------------------------------+ //--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false); // Chart redraw flag //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false); // Chart redraw flag //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND); // line ends style }; //+------------------------------------------------------------------+
Vejamos a implementação de alguns dos métodos de classe.
Método para desenhar um polígono regular:
//+------------------------------------------------------------------+ //| Draw a regular polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonOnBG(const int N, const int coord_x, const int coord_y, const int len, const double angle, const color clr, const uchar opacity=255, const bool redraw=false) { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygon(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Como se pode ver, este método difere de outros semelhantes das classes anteriores (classe de quadro de animação retangular) no seguinte: neste caso, não são transferidas as matrizes - previamente preparadas - das coordenadas dos vértices do polígono, mas, sim, o número de vértices do polígono e de coordenadas do canto superior esquerdo do quadrado do quadro em que o polígono será desenhado, e já no próprio método é chamado um método que calcula as coordenadas dos vértices do polígono pelo número de vértices, coordenadas, o raio da circunferência e o ângulo de rotação, e neste último método são preenchidas as matrizes das coordenadas dos vértices X e Y. Em seguida, o polígono correspondente ao método é desenhado usando a classe CCanvas.
Vejamos uma comparação, este mesmo método desenha um polígono preenchido:
//+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false) // Chart redraw flag { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
A diferença em relação ao primeiro método está apenas na chamada do método de desenho de polígono preenchido.
Os outros métodos são quase idênticos aos dois acima, com exceção de algumas peculiaridades quanto ao cálculo de coordenadas do retângulo de contorno para desenho de um polígono com uma determinada espessura de linha, uma vez que no cálculo das coordenadas e dimensões do retângulo de contorno deve ser considerado o valor da espessura da linha desenhada.
Outros métodos para desenhar polígonos regulares:
//+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonFillOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false) // Chart redraw flag { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonFill(this.m_array_x,this.m_array_y,clr,opacity); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonAAOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonAA(this.m_array_x,this.m_array_y,clr,opacity,style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| Wu algorithm | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonWuOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonWu(this.m_array_x,this.m_array_y,clr,opacity,style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon of a specified width | //| using two smoothing algorithms in series. | //| First, individual segments are smoothed based on Bezier curves. | //| Then, to improve the rendering quality, | //| a raster smoothing algorithm is applied | //| made of these segments. | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonSmoothOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-1; this.m_square_y=coord_y-1; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonSmooth(this.m_array_x,this.m_array_y,size,clr,opacity,tension,step,style,end_style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+ //| Draw a regular polygon with a specified width using | //| a smoothing algorithm with the preliminary sorting | //+------------------------------------------------------------------+ bool CFrameGeometry::DrawNgonThickOnBG(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { //--- Calculate the adjustment of the outlining rectangle coordinates depending on the line size int correct=int(::ceil((double)size/2.0))+1; //--- Set the coordinates of the outlining rectangle this.m_square_x=coord_x-correct; this.m_square_y=coord_y-correct; //--- Set the width and height of a square frame (to be used as the size of the saved area) this.m_square_length=len+correct*2; //--- Calculate the polygon coordinates on the circle this.CoordsNgon(N,coord_x,coord_y,len,angle); //--- Restore the previously saved background and save the new one if(!this.SaveRestoreBG()) return false; //--- Draw a polygon inscribed in a circle and update the element this.m_element.DrawPolygonThick(this.m_array_x,this.m_array_y,size,clr,opacity,style,end_style); this.SetLastParams(this.m_square_x,this.m_square_y,this.m_shift_x,this.m_shift_y); this.m_element.Update(redraw); return true; } //+------------------------------------------------------------------+
Método virtual que salva e restaura o fundo sob a imagem:
//+------------------------------------------------------------------+ //| Save and restore the background under the image | //+------------------------------------------------------------------+ bool CFrameGeometry::SaveRestoreBG(void) { //--- Calculate coordinate offsets for the saved area depending on the anchor point this.m_element.GetShiftXYbySize(this.m_square_length,this.m_square_length,FRAME_ANCHOR_LEFT_TOP,this.m_shift_x,this.m_shift_y); //--- If the pixel array is not empty, the background under the image has already been saved - //--- restore the previously saved background (by the previous coordinates and offsets) if(::ArraySize(this.m_array)>0) { if(!CPixelCopier::CopyImgDataToCanvas(int(this.m_x_last+this.m_shift_x_prev),int(this.m_y_last+this.m_shift_y_prev))) return false; } //--- Return the result of saving the background area with the calculated coordinates and size under the future image return CPixelCopier::CopyImgDataToArray(int(this.m_square_x+this.m_shift_x),int(this.m_square_y+this.m_shift_y),this.m_square_length,this.m_square_length); } //+------------------------------------------------------------------+
Este é um bloco de código recorrente transferido a partir dos métodos de desenho de forma vistos no último artigo.
Método para calcular as coordenadas de um polígono regular circunscrito:
//+------------------------------------------------------------------+ //| Calculate the coordinates of the regular polygon | //+------------------------------------------------------------------+ void CFrameGeometry::CoordsNgon(const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left square angle the circle will be inscribed into const int coord_y, // Y coordinate of the upper-left square angle whose inscribed circle is used to build a polygon const int len, // Length of the sides of the square a polygon is to be inscribed into const double angle) // Polygon rotation angle (the polygon is built from the point 0 to the right of the circle center) { //--- If there are less than three sides, there will be three int n=(N<3 ? 3 : N); //--- Set the size of coordinate arrays according to the number of vertices ::ArrayResize(this.m_array_x,n); ::ArrayResize(this.m_array_y,n); //--- Calculate the radius of the circumscribed circle double R=(double)len/2.0; //--- X and Y coordinates of the circle center double xc=coord_x+R; double yc=coord_y+R; //--- Calculate the polygon inclination angle in degrees double grad=angle*M_PI/180.0; //--- In the loop by the number of vertices, calculate the coordinates of each next polygon vertex for(int i=0; i<n; i++) { //--- Angle of the current polygon vertex with the rotation in degrees double a=2.0*M_PI*i/n+grad; //--- X and Y coordinates of the current polygon vertex double xi=xc+R*::cos(a); double yi=yc+R*::sin(a); //--- Set the current coordinates to the arrays this.m_array_x[i]=int(::floor(xi)); this.m_array_y[i]=int(::floor(yi)); } } //+------------------------------------------------------------------+
A lógica do método é descrita em detalhes nos comentários ao código, eis as fórmulas de cálculo das coordenadas cartesianas do polígono:
Vamos deixar o método para que cada um de nós analise individualmente. Acho que não deveria suscitar perguntas. Em qualquer caso, você pode colocar qualquer questão na discussão do artigo.
A classe de objeto-quadro de animação geométrica agora está completa.
Agora precisamos dar acesso a ela a partir de um programa externo e gerar a capacidade de criar objetos rapidamente.
Todos os objetos recém-criados de quadros de animação são armazenados em listas próprias na classe CAnimations.
Realizamos as alterações necessárias no arquivo da classe \MQL5\Include\DoEasy\Objects\Graph\Animations\Animations.mqh.
Ao arquivo da classe anexamos o arquivo destinado à recém-criada classe do objeto de quadro de animação geométrica e na seção privada da classe declaramos a lista, em que todos os objetos recém-criados desta classe serão armazenados:
//+------------------------------------------------------------------+ //| Animations.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "FrameText.mqh" #include "FrameQuad.mqh" #include "FrameGeometry.mqh" //+------------------------------------------------------------------+ //| Pixel copier class | //+------------------------------------------------------------------+ class CAnimations : public CObject { private: CGCnvElement *m_element; // Pointer to the graphical element CArrayObj m_list_frames_text; // List of text animation frames CArrayObj m_list_frames_quad; // List of rectangular animation frames CArrayObj m_list_frames_geom; // List of geometric shape animations frames //--- Return the flag indicating the presence of the frame object with the specified ID in the list bool IsPresentFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return or create a new animation frame object CFrame *GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new); public:
Na seção pública da classe declaramos um método para criar um novo objeto de quadro de animação geométrica e escrevemos um método que retorna um ponteiro para a lista desses objetos:
public: CAnimations(CGCnvElement *element); CAnimations(){;} //--- Create a new (1) rectangular, (2) text and geometric animation frame object CFrame *CreateNewFrameText(const int id); CFrame *CreateNewFrameQuad(const int id); CFrame *CreateNewFrameGeometry(const int id); //--- Return the animation frame objects by ID CFrame *GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id); //--- Return the list of (1) text, (2) rectangular and (3) geometric shape animation frames CArrayObj *GetListFramesText(void) { return &this.m_list_frames_text; } CArrayObj *GetListFramesQuad(void) { return &this.m_list_frames_quad; } CArrayObj *GetListFramesGeometry(void) { return &this.m_list_frames_geom; }
A seguir, declaramos métodos para desenhar polígonos regulares:
//+------------------------------------------------------------------+ //| Methods of drawing regular polygons | //+------------------------------------------------------------------+ //--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false); // Chart redraw flag //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false); // Chart redraw flag //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX); // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND);// Line style is one of the ENUM_LINE_END enumeration's values //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND); // line ends style }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+
Todas as ocorrências da string "ENUM_TEXT_ANCHOR" na listagem de classes devem ser substituídas pela string "ENUM_FRAME_ANCHOR".
No método que retorna um objeto-quadro de animação por tipo e identificador, incluiremos o processamento de novo tipo de objeto-quadro de animação:
//+------------------------------------------------------------------+ //| Return the animation frame objects by type and ID | //+------------------------------------------------------------------+ CFrame *CAnimations::GetFrame(const ENUM_ANIMATION_FRAME_TYPE frame_type,const int id) { //--- Declare the pointer to the animation frame object CFrame *frame=NULL; //--- Depending on the necessary object type, receive their number in the appropriate list int total= ( frame_type==ANIMATION_FRAME_TYPE_TEXT ? this.m_list_frames_text.Total() : frame_type==ANIMATION_FRAME_TYPE_QUAD ? this.m_list_frames_quad.Total() : frame_type==ANIMATION_FRAME_TYPE_GEOMETRY ? this.m_list_frames_geom.Total() : 0 ); //--- Get the next object in the loop ... for(int i=0;i<total;i++) { //--- ... by the list corresponding to the animation frame type switch(frame_type) { case ANIMATION_FRAME_TYPE_TEXT : frame=this.m_list_frames_text.At(i); break; case ANIMATION_FRAME_TYPE_QUAD : frame=this.m_list_frames_quad.At(i); break; case ANIMATION_FRAME_TYPE_GEOMETRY : frame=this.m_list_frames_geom.At(i); break; default: break; } //--- if failed to get the pointer, move on to the next one if(frame==NULL) continue; //--- If the object ID correspond to the required one, //--- return the pointer to the detected object if(frame.ID()==id) return frame; } //--- Nothing is found - return NULL return NULL; } //+------------------------------------------------------------------+
Método que cria um novo objeto-quadro de animação geométrica:
//+------------------------------------------------------------------+ //| Create a new geometric animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::CreateNewFrameGeometry(const int id) { //--- If the object with such an ID is already present, inform of that in the journal and return NULL if(this.IsPresentFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id)) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_FRAME_ALREADY_IN_LIST),(string)id); return NULL; } //--- Create a new geometric animation frame object with the specified ID CFrame *frame=new CFrameGeometry(id,this.m_element); //--- If failed to create an object, inform of that and return NULL if(frame==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_FRAME)); return NULL; } //--- If failed to add the created object to the list, inform of that, remove the object and return NULL if(!this.m_list_frames_geom.Add(frame)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id); delete frame; return NULL; } //--- Return the pointer to a newly created object return frame; } //+------------------------------------------------------------------+
A lógica do método é totalmente descrita nos comentários do código.
No método que retorna ou cria um novo objeto-quadro de animação, incluímos o processamento de novo tipo de quadro de animação:
//+------------------------------------------------------------------+ //| Return or create a new animation frame object | //+------------------------------------------------------------------+ CFrame *CAnimations::GetOrCreateFrame(const string source,const int id,const ENUM_ANIMATION_FRAME_TYPE frame_type,const bool create_new) { //--- Declare null pointers to objects CFrameQuad *frame_q=NULL; CFrameText *frame_t=NULL; CFrameGeometry *frame_g=NULL; //--- Depending on the required object type switch(frame_type) { //--- If this is a text animation frame, case ANIMATION_FRAME_TYPE_TEXT : //--- get the pointer to an object with a specified ID frame_t=this.GetFrame(ANIMATION_FRAME_TYPE_TEXT,id); //--- If the pointer is obtained, return it if(frame_t!=NULL) return frame_t; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new text animation frame object (pointer to the created object) return this.CreateNewFrameText(id); //--- If this is a rectangular animation frame case ANIMATION_FRAME_TYPE_QUAD : //--- get the pointer to an object with a specified ID frame_q=this.GetFrame(ANIMATION_FRAME_TYPE_QUAD,id); //--- If the pointer is obtained, return it if(frame_q!=NULL) return frame_q; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new rectangular animation frame object (pointer to the created object) return this.CreateNewFrameQuad(id); //--- If this is a geometric animation frame case ANIMATION_FRAME_TYPE_GEOMETRY : //--- get the pointer to an object with a specified ID frame_g=this.GetFrame(ANIMATION_FRAME_TYPE_GEOMETRY,id); //--- If the pointer is obtained, return it if(frame_g!=NULL) return frame_g; //--- If the flag of creating a new object is not set, report an error and return NULL if(!create_new) { ::Print(source,CMessage::Text(MSG_FORM_OBJECT_FRAME_NOT_EXIST_LIST),(string)id); return NULL; } //--- Return the result of creating a new geometric animation frame object (pointer to the created object) return this.CreateNewFrameGeometry(id); //--- In the remaining cases, return NULL default: return NULL; } } //+------------------------------------------------------------------+
Aqui toda a lógica é descrita nos comentários ao código.
No final da listagem da classes, incluímos a implementação de métodos para desenhar polígonos regulares:
//+------------------------------------------------------------------+ //| Draw a regular polygon without smoothing | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw); } //+------------------------------------------------------------------+ //| Draw a regular filled polygon | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonFillOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw); } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| AntiAliasing algorithm | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonAAOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style); } //+------------------------------------------------------------------+ //| Draw a regular polygon using | //| Wu algorithm | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonWuOnBG(N,coord_x,coord_y,len,angle,clr,opacity,redraw,style); } //+------------------------------------------------------------------+ //| Draw a regular polygon of a specified width | //| using two smoothing algorithms in series. | //| First, individual segments are smoothed based on Bezier curves. | //| Then, to improve the rendering quality, | //| a raster smoothing algorithm is applied | //| to the polygon made of these segments. | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonSmoothOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,redraw,style,end_style); } //+------------------------------------------------------------------+ //| Draw a regular polygon with a specified width using | //| a smoothing algorithm with the preliminary sorting | //+------------------------------------------------------------------+ bool CAnimations::DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { CFrameGeometry *frame=this.GetOrCreateFrame(DFUN,id,ANIMATION_FRAME_TYPE_GEOMETRY,create_new); if(frame==NULL) return false; return frame.DrawNgonThickOnBG(N,coord_x,coord_y,len,angle,size,clr,opacity,redraw,style,end_style); } //+------------------------------------------------------------------+
Visto que a lógica de todos esses métodos é absolutamente idêntica, daremos uma olhada no exemplo do último método.
Como se pode ver, basta obtermos desde a lista um objeto-quadro de animação geométrica já criado ou, se não houver nenhum nela e se definido o sinalizador de criação de novo objeto, basta criamos um, já se o objeto não puder ser obtido ou criado, retornamos false.
De outra forma, retornamos o resultado da chamada do método da classe - que tem o mesmo nome - do objeto-quadro da animação geométrica recuperado da lista ou recém-criado.
Agora modificaremos a classe do objeto-forma no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Todas as ocorrências da linha "ENUM_TEXT_ANCHOR" na listagem da classe devem ser substituídas por "ENUM_FRAME_ANCHOR".
Na seção privada da classe declararemos os métodos que permitirão redefinir para zero os tamanhos das matrizes de pixels de três classes de quadros de animação:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Return the name of the dependent object
Precisamos disso para o correto funcionamento dos métodos de salvamento/restauração do fundo da forma onde as figuras são desenhadas (vimos isso acima).
Na seção pública da classe escrevemos um método para definir a aparência da forma e declaramos um método virtual que restaura um recurso gráfico desde uma matriz:
//--- Draw an embossed (concave) field void DrawFieldStamp(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Field width const int height, // Field height const color colour, // Field color const uchar opacity); // Field opacity //--- Capture the appearance of the created form void Done(void) { CGCnvElement::CanvasUpdate(false); CGCnvElement::ResourceStamp(DFUN); } //--- Restore the resource from the array virtual bool Reset(void); //+------------------------------------------------------------------+ //| Methods of working with image pixels | //+------------------------------------------------------------------+
Por que precisamos de um método para definir a aparência da forma?
Vejamos que se, por exemplo, criássemos uma forma e desenhássemos nela todos os elementos inalteráveis necessários, agora precisaríamos copiar a nova aparência da forma na matriz de cópia do recurso gráfico, para que, se necessário, pudéssemos retornar a aparência original da forma. Afinal, tudo o que desenhamos na forma e todas as mudanças são exibidos no recurso gráfico, portanto para que não tenhamos que redesenhar toda a forma, basta armazenarmos numa matriz especial uma cópia da forma inicial e restaurarmos a aparência original a partir desta matriz - método Reset().
Na seção pública da classe, escreveremos métodos para desenhar polígonos regulares:
//--- Draw a regular polygon without smoothing bool DrawNgonOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { return(this.m_animations!=NULL ? this.m_animations.DrawNgonOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false); } //--- Draw a regular filled polygon bool DrawNgonFillOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false) // Chart redraw flag { return(this.m_animations!=NULL ? this.m_animations.DrawNgonFillOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw) : false); } //--- Draw a regular polygon using AntiAliasing algorithm bool DrawNgonAAOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { return(this.m_animations!=NULL ? this.m_animations.DrawNgonAAOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false); } //--- Draw a regular polygon using Wu algorithm bool DrawNgonWuOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=UINT_MAX) // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value { return(this.m_animations!=NULL ? this.m_animations.DrawNgonWuOnBG(id,N,coord_x,coord_y,len,angle,clr,opacity,create_new,redraw,style) : false); } //--- Draw a regular polygon with a specified width consecutively using two smoothing algorithms. //--- First, individual segments are smoothed based on Bezier curves. //--- Then, the raster smoothing algorithm is applied to the polygon built from these segments to improve the rendering quality. bool DrawNgonSmoothOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // Line width const color clr, // Color const uchar opacity=255, // Opacity const double tension=0.5, // Smoothing parameter value const double step=10, // Approximation step const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const ENUM_LINE_STYLE style=STYLE_SOLID, // Line style is one of the ENUM_LINE_STYLE enumeration's values or a custom value const ENUM_LINE_END end_style=LINE_END_ROUND)// Line style is one of the ENUM_LINE_END enumeration's values { return(this.m_animations!=NULL ? this.m_animations.DrawNgonSmoothOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,tension,step,create_new,redraw,style,end_style) : false); } //--- Draw a regular polygon having a specified width using smoothing algorithm with the preliminary filtration bool DrawNgonThickOnBG(const int id, // Frame ID const int N, // Number of polygon vertices const int coord_x, // X coordinate of the upper-left frame angle const int coord_y, // Y coordinate of the upper-left frame angle const int len, // Frame sides length const double angle, // Polygon rotation angle const int size, // line width const color clr, // Color const uchar opacity=255, // Opacity const bool create_new=true, // New object creation flag const bool redraw=false, // Chart redraw flag const uint style=STYLE_SOLID, // line style ENUM_LINE_END end_style=LINE_END_ROUND) // line ends style { return(this.m_animations!=NULL ? this.m_animations.DrawNgonThickOnBG(id,N,coord_x,coord_y,len,angle,size,clr,opacity,create_new,redraw,style,end_style) : false); }
Todos os métodos são idênticos e retornam o resultado da chamada dos métodos em questão da instância da classe CAnimations discutidos acima.
Fora do corpo da classe, vamos escrever a implementação dos métodos declarados.
Três métodos para redefinir os tamanhos de matrizes de três objetos-quadros de animação:
//+------------------------------------------------------------------+ //| Reset the array size of the text animation frames | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameT(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesText(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameText *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the rectangular animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameQ(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesQuad(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameQuad *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+ //| Reset the size of the geometric animation frame array | //+------------------------------------------------------------------+ void CForm::ResetArrayFrameG(void) { if(this.m_animations==NULL) return; CArrayObj *list=this.m_animations.GetListFramesGeometry(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CFrameGeometry *frame=list.At(i); if(frame==NULL) continue; frame.ResetArray(); } } //+------------------------------------------------------------------+
Todos os métodos são idênticos:
Se o objeto da classe CAnimation não existir, saímos do método, uma vez que este não possuirá animações.
Obtemos um ponteiro para a lista de quadros de animação correspondentes ao método. Num loop ao longo da lista resultante obtemos um ponteiro para o próximo objeto-quadro de animação e redefinimos sua matriz de pixels para zero.
Método que restaura um recurso desde uma matriz:
//+------------------------------------------------------------------+ //| Restore the resource from the array | //+------------------------------------------------------------------+ bool CForm::Reset(void) { CGCnvElement::Reset(); this.ResetArrayFrameQ(); this.ResetArrayFrameT(); this.ResetArrayFrameG(); return true; } //+------------------------------------------------------------------+
Inicialmente, chamamos o método da classe pai que restaura o recurso gráfico desde a matriz de cópia e, em seguida, zeramos as matrizes de pixels de todos os objetos-quadros de animação, para que depois de restaurar a aparência da forma, possamos copiar novamente o fundo com as coordenadas desejadas e o tamanho da área de fundo salva.
Então, estamos prontos para testar o desenho de polígonos regulares numa forma.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part80\ com o novo nome TestDoEasyPart80.mq5.
Como vamos testar? Se você se lembra, no último artigo desenhamos figuras num objeto-forma usando o teclado. Faremos o mesmo hoje. Só que desta vez remapearemos os botões que, quando eram pressionados, desenhavam polígonos com atribuição dinâmica de coordenadas e tamanhos. Hoje também alteraremos dinamicamente as coordenadas do quadro de animação ao longo do eixo X e o número de vértices do polígono desenhado (de 3 a 10).
- Ao pressionar a tecla "Y" será desenhado um polígono regular não suavizado,
- Ao pressionar a tecla "U" será desenhado um polígono preenchido não suavizado,
- Ao pressionar a tecla "I" será desenhado um polígono regular suavizado pelo algoritmo AntiAlliasing (AA),
- Ao pressionar a tecla "O" será desenhado um polígono regular suavizado pelo algoritmo de Xiaolin Wu,
- Ao pressionar a tecla "P" será desenhado um polígono regular de uma determinada espessura suavizado por dois algoritmos de suavização (Smooth),
- Ao pressionar a tecla "A" será desenhado um polígono regular de uma determinada espessura usando suavização com pré-filtrado (Thick),
- À tecla "." atribuiremos o desenho da área preenchida - será preenchida toda a forma com a cor especificada.
Além disso, com cada clique na forma, a coordenada X do quadro desenhado mudará e o número de vértices do polígono desenhado aumentará em 1.
Alteramos todas as ocorrências da substring "TEXT_ANCHOR" por "FRAME_ANCHOR".
No manipulador OnInit() do Expert Advisor, após a criação de cada forma, fixamos sua aparência:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the specified number of form objects list_forms.Clear(); int total=FORMS_TOTAL; for(int i=0;i<total;i++) { int y=40; if(i>0) { CForm *form_prev=list_forms.At(i-1); if(form_prev==NULL) continue; y=form_prev.BottomEdge()+10; } //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30)); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the form ID equal to the loop index and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the partial opacity for the middle form and the full one for the rest uchar opacity=(i==1 ? 250 : 255); //--- Set the form style and its color theme depending on the loop index if(i<2) { ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i; ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i; //--- Set the form style and theme form.SetFormStyle(style,theme,opacity,true,false); } //--- If this is the first (top) form if(i==0) { //--- Draw a concave field slightly shifted from the center of the form downwards form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity()); form.Done(); } //--- If this is the second form if(i==1) { //--- Draw a concave semi-transparent "tainted glass" field in the center form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200); form.Done(); } //--- If this is the third form if(i==2) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.TextOnBG(0,TextByLanguage("V-Градиент","V-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- If this is the fourth (bottom) form if(i==3) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a horizontal gradient form.Erase(array_clr,form.Opacity(),false); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.TextOnBG(0,TextByLanguage("H-Градиент","H-Gradient"),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } //--- Add objects to the list if(!list_forms.Add(form)) { delete form; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
No bloco de processamento de teclas do manipulador OnChartEvent() incluímos a chamada do método de restauração de aparência da forma e redefinição - para zero - das matrizes de pixels dos objetos-quadros:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { //--- Get a drawn shape type depending on a pressed key figure_type=FigureType(lparam); //--- If the shape type has changed if(figure_type!=figure_type_prev) { //--- Get the text of the drawn shape type description figure=FigureTypeDescription(figure_type); //--- In the loop by all forms, for(int i=0;i<list_forms.Total();i++) { //--- get the pointer to the next form object CForm *form=list_forms.At(i); if(form==NULL) continue; //--- If the form ID is 2, if(form.ID()==2) { //--- Reset all coordinate shifts to zero, restore the form background and display the text describing the drawn shape type nx1=ny1=nx2=ny2=nx3=ny3=nx4=ny4=nx5=ny5=0; form.Reset(); form.TextOnBG(0,figure,form.TextLastX(),form.TextLastY(),form.TextAnchor(),C'211,233,149',255,false,true); } } //--- Write the new shape type figure_type_prev=figure_type; } }
Na função FigureType() incluímos o processamento da tecla "." :
//+------------------------------------------------------------------+ //| Return the shape depending on the pressed key | //+------------------------------------------------------------------+ ENUM_FIGURE_TYPE FigureType(const long key_code) { switch((int)key_code) { //--- "1" = Dot case 49 : return FIGURE_TYPE_PIXEL; //--- "2" = Dot with AntiAlliasing case 50 : return FIGURE_TYPE_PIXEL_AA; //--- "3" = Vertical line case 51 : return FIGURE_TYPE_LINE_VERTICAL; //--- "4" = Vertical segment of a freehand line having a specified width using a smoothing algorithm case 52 : return FIGURE_TYPE_LINE_VERTICAL_THICK; //--- "5" = Horizontal line case 53 : return FIGURE_TYPE_LINE_HORIZONTAL; //--- "6" = Horizontal segment of a freehand line having a specified width using a smoothing algorithm case 54 : return FIGURE_TYPE_LINE_HORIZONTAL_THICK; //--- "7" = Freehand line case 55 : return FIGURE_TYPE_LINE; //--- "8" = Line with AntiAlliasing case 56 : return FIGURE_TYPE_LINE_AA; //--- "9" = Line with WU case 57 : return FIGURE_TYPE_LINE_WU; //--- "0" = Segment of a freehand line having a specified width using a smoothing algorithm case 48 : return FIGURE_TYPE_LINE_THICK; //--- "q" = Polyline case 81 : return FIGURE_TYPE_POLYLINE; //--- "w" = Polyline with AntiAlliasing case 87 : return FIGURE_TYPE_POLYLINE_AA; //--- "e" = Polyline with WU case 69 : return FIGURE_TYPE_POLYLINE_WU; //--- "r" = Polyline with a specified width using two smoothing algorithms case 82 : return FIGURE_TYPE_POLYLINE_SMOOTH; //--- "t" = Polyline with a specified width using a smoothing algorithm case 84 : return FIGURE_TYPE_POLYLINE_THICK; //--- "y" = Polygon case 89 : return FIGURE_TYPE_POLYGON; //--- "u" = Filled polygon case 85 : return FIGURE_TYPE_POLYGON_FILL; //--- "i" = Polygon with AntiAlliasing case 73 : return FIGURE_TYPE_POLYGON_AA; //--- "o" = Polygon with WU case 79 : return FIGURE_TYPE_POLYGON_WU; //--- "p" = Polygon with a specified width using two smoothing algorithms case 80 : return FIGURE_TYPE_POLYGON_SMOOTH; //--- "a" = Polygon with a specified width using a smoothing algorithm case 65 : return FIGURE_TYPE_POLYGON_THICK; //--- "s" = Rectangle case 83 : return FIGURE_TYPE_RECTANGLE; //--- "d" = Filled rectangle case 68 : return FIGURE_TYPE_RECTANGLE_FILL; //--- "f" = Circle case 70 : return FIGURE_TYPE_CIRCLE; //--- "g" = Filled circle case 71 : return FIGURE_TYPE_CIRCLE_FILL; //--- "h" = Circle with AntiAlliasing case 72 : return FIGURE_TYPE_CIRCLE_AA; //--- "j" = Circle with WU case 74 : return FIGURE_TYPE_CIRCLE_WU; //--- "k" = Triangle case 75 : return FIGURE_TYPE_TRIANGLE; //--- "l" = Filled triangle case 76 : return FIGURE_TYPE_TRIANGLE_FILL; //--- "z" = Triangle with AntiAlliasing case 90 : return FIGURE_TYPE_TRIANGLE_AA; //--- "x" = Triangle with WU case 88 : return FIGURE_TYPE_TRIANGLE_WU; //--- "c" = Ellipse case 67 : return FIGURE_TYPE_ELLIPSE; //--- "v" = Filled ellipse case 86 : return FIGURE_TYPE_ELLIPSE_FILL; //--- "b" = Ellipse with AntiAlliasing case 66 : return FIGURE_TYPE_ELLIPSE_AA; //--- "n" = Ellipse with WU case 78 : return FIGURE_TYPE_ELLIPSE_WU; //--- "m" = Ellipse arc case 77 : return FIGURE_TYPE_ARC; //--- "," = Ellipse sector case 188 : return FIGURE_TYPE_PIE; //--- "." = Filled area case 190 : return FIGURE_TYPE_FILL; //--- Default = Dot default : return FIGURE_TYPE_PIXEL; } } //+------------------------------------------------------------------+
Na função FigureProcessing() tornamos as matrizes de coordenadas dinâmicas:
//+------------------------------------------------------------------+ //| Handle the selected shape | //+------------------------------------------------------------------+ void FigureProcessing(CForm *form,const ENUM_FIGURE_TYPE figure_type) { int array_x[]; int array_y[]; switch(figure_type) {
e, onde necessário transferir aos métodos de classe as matrizes de coordenadas, definiremos os tamanhos dessas matrizes:
//--- "q" = Polyline case FIGURE_TYPE_POLYLINE : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "w" = Polyline with AntiAlliasing case FIGURE_TYPE_POLYLINE_AA : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "e" = Polyline with WU case FIGURE_TYPE_POLYLINE_WU : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "r" = Polyline with a specified width using two smoothing algorithms case FIGURE_TYPE_POLYLINE_SMOOTH : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
...
//--- "t" = Polyline with a specified width using a smoothing algorithm case FIGURE_TYPE_POLYLINE_THICK : coordX1=START_X+nx1; coordY1=START_Y+ny1; coordX2=coordX1+nx2*8; coordY2=coordY1; coordX3=coordX2; coordY3=coordY2+ny3*2; coordX4=coordX1; coordY4=coordY3; coordX5=coordX1; coordY5=coordY1; //--- Fill in the arrays with coordinate values ArrayResize(array_x,5); ArrayResize(array_y,5); array_x[0]=coordX1; array_x[1]=coordX2; array_x[2]=coordX3; array_x[3]=coordX4; array_x[4]=coordX5; array_y[0]=coordY1; array_y[1]=coordY2; array_y[2]=coordY3; array_y[3]=coordY4; array_y[4]=coordY5; //--- check x1 and y1 coordinates for being outside the form
Agora nos lugares onde tínhamos códigos para processamento das teclas para desenho de polígonos chamaremos métodos para desenhar polígonos regulares:
//--- "y" = Polygon case FIGURE_TYPE_POLYGON : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrAliceBlue); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "u" = Filled polygon case FIGURE_TYPE_POLYGON_FILL : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonFillOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCoral); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "i" = Polygon with AntiAlliasing case FIGURE_TYPE_POLYGON_AA : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonAAOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightCyan); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "o" = Polygon with WU case FIGURE_TYPE_POLYGON_WU : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonWuOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,clrLightGoldenrod); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "p" = Polygon with a specified width using two smoothing algorithms case FIGURE_TYPE_POLYGON_SMOOTH : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonSmoothOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,3,clrLightGreen,255,0.5,10.0,true,false,STYLE_SOLID,LINE_END_BUTT); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "a" = Polygon with a specified width using a smoothing algorithm case FIGURE_TYPE_POLYGON_THICK : coordX1=START_X+nx1; // X coordinate coordY1=START_Y; // Y coordinate coordX2=3+nx2*4; // Length of square sides coordY2=3+ny2; // Number of faces coordX3=0; // Rotation angle //--- check the square side length for exceeding the double form height if(coordX2>form.Height()*2) { nx2=0; coordX2=3; } //--- check the x1 coordinate for exceeding the form boundaries if(coordX1>form.Width()-1) { nx1=0; coordX1=-coordX2; } //--- check the number of faces for exceeding 10 if(coordY2>16) { ny2=0; coordY2=3; } //--- check the rotation angle for exceeding 360 degrees if(coordX3>360) { nx3=0; coordX3=0; } //--- Draw a shape form.DrawNgonThickOnBG(0,coordY2,coordX1,coordY1,coordX2,(double)coordX3,5,clrLightSalmon,255,true,false,STYLE_SOLID,LINE_END_BUTT); nx1++; ny1++; nx2++; ny2++; nx3++; break; //--- "s" = Rectangle
Todo o código é comentado e deve estar claro o suficiente. Em qualquer caso, você pode colocar qualquer questão na discussão do artigo.
Não vamos esquecer no final da função adicionar o processamento da tecla "." para preencher a forma com a cor:
//--- "." = Filled area case FIGURE_TYPE_FILL : coordX1=START_X+nx1; coordY1=START_Y+ny1; form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10); break; //--- Default = Nothing default : break; } } //+------------------------------------------------------------------+
Compilamos o EA e o executamos na gráfico do símbolo.
Depois de iniciar, pressionamos as teclas às quais atribuímos o desenho de polígonos regulares e, ao mesmo tempo, preenchemos a área com cor:
Bem, tudo funciona como planejado. Uma ressalva é que as figuras não resultaram muito uniformes. A mais bem-sucedida, em minha opinião, é o polígono com utilização do algoritmo de suavização Wu. Ao preencher, podemos ajustar o grau (limiar) de preenchimento com cor, indicando o valor desejado para o parâmetro threshould:
form.FillOnBG(0,coordX1,coordY1,clrLightSteelBlue,255,10);
O que vem agora?
No próximo artigo, continuaremos a trabalhar com animações e com o objeto-forma.
Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo por conta própria.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
*Artigos desta série:
Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico
Gráficos na biblioteca DoEasy (Parte 74): elemento gráfico básico baseado na classe CCanvas
Gráficos na biblioteca DoEasy (Parte 75): métodos para trabalhar com primitivas e texto num elemento gráfico básico
Gráficos na biblioteca DoEasy (Parte 76): objeto Forma e temas de cores predefinidos
Gráficos na biblioteca DoEasy (Parte 77): classe do objeto Sombra
Gráficos na Biblioteca DoEasy (Parte 78): princípios de animação dentro da biblioteca. Corte de imagens
Gráficos na biblioteca DoEasy (Parte 79): classe para o objeto quadro-de-animação e seus objetos herdeiros
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/9689
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso