English Русский 中文 Deutsch 日本語 Português
preview
Usando la clase CCanvas en las aplicaciones MQL

Usando la clase CCanvas en las aplicaciones MQL

MetaTrader 5Ejemplos | 26 abril 2022, 16:27
460 2
Mihail Matkovskij
Mihail Matkovskij

El presente artículo abarca la estructura de la clase CCanvas y su uso en aplicaciones MQL. Para consolidar los conocimientos adquiridos en este artículo, ofreceremos ejemplos prácticos.


Gráficos en las aplicaciones

Para trabajar con gráficos en cualquier aplicación hay que tener acceso a los datos de los píxeles en la pantalla. Si existe este acceso, podremos cambiar los colores de los píxeles, y crear todo tipo de elementos para la interfaz de usuario, como campos de entrada, botones, paneles, ventanas y otros elementos o imágenes de salida de archivos, además obtener los datos de píxeles y procesarlos. Este mecanismo se suele organizar en una clase que incluya la palabra Canvas en su nombre. En los lenguajes de programación del tipo C, suele ser CCanvas o Canvas, dependiendo de cómo esté escrito el código. De esta forma, llegamos a la conclusión de que cualquier aplicación necesita un Canvas o lienzo para acceder a los gráficos.


Canvas en las aplicaciones MQL

En las aplicaciones MQL, esta herramienta se representa como la clase CCanvas, que contiene la matriz de píxeles, los métodos para modificar dicha matriz y el mecanismo para darle salida a esta al gráfico del terminal. Uno de los objetos gráficos, OBJ_BITMAP u OBJ_BITMAP_LABEL, se utiliza como mecanismo de salida. El objeto gráfico usado toma los datos del recurso gráfico, que a su vez se carga con los datos de la matriz de píxeles.

Con la clase CCanvas, podemos implementar todas las características de la interfaz de escritorio. Cualquier idea que se nos ocurra. Además de las interfaces de usuario, con CCanvas podemos dibujar búferes de indicador, barras con los precios de apertura y cierre, mínimos y máximos y mucho más... Sin embargo, no hablaremos de ello en este artículo, ese tema se tratará con más detalle en futuros materiales.


Analizando CCanvas

Bueno, ya hemos llegado a la parte más interesante: el análisis de CCanvas. La correcta comprensión de su estructura determinará la asimilación posterior de esta herramienta. Vamos a echar un vistazo a la estructura de CCanvas en el esquema.

Analizando CCanvas

Según el esquema, podemos ver que la clase CCanvas se divide en dos secciones principales: datos y métodos.

Vamos a analizar los datos:

  • Datos de píxeles. Los datos de píxeles suponen una matriz de píxeles m_pixels que almacena los píxeles procesados por los métodos de dibujado, así como los datos m_width y m_height: la anchura y la altura de la imagen, respectivamente. Los parámetros m_width y m_height son muy importantes para trabajar con la matriz m_pixels de los métodos de dibujado, así como para transmitir la matriz m_pixels al recurso gráfico, en el método Update.
  • Datos de la fuente para mostrar el texto. Consta de los campos m_fontname, m_fontsize, m_fontflags y m_fontangle : el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo del texto, respectivamente. Estos datos son necesarios para establecer y obtener las propiedades de la fuente (usando los métodos de lectura/escritura de las propiedades de la fuente ) antes de llamar al método TextOut.
  • Datos para el dibujado de líneas. Consta de dos campos: m_style (el estilo de la línea) y m_style_idx (el índice de bits actual en la plantilla de estilo de línea). Son necesarios para los métodos de dibujado.
  • Datos por defecto. Solo hay un campo: m_default_colors, la matriz de colores por defecto. No se utiliza en los métodos de la clase CCanvas, pero puede resultar útil en otras funciones o métodos de las clases descendientes de CCanvas como paleta de colores.
  • Datos para interactuar con el gráfico. Son los siguientes datos: m_chart_id, m_objname, m_objtype, m_rcname y m_format, el identificador del objeto gráfico, el nombre del objeto gráfico, el tipo del objeto gráfico, el nombre del recurso gráfico y el formato del píxel, respectivamente. Sirven para trabajar con los siguientes métodos: Destroy, Update, Resize. En concreto, el campo m_format es necesario para trabajar con el método TextOut.


Vamos a analizar los métodos:

  • Métodos para crear/adjuntar/borrar. Esta categoría contiene diferentes métodos que trabajan con el objeto gráfico. Hablamos de los métodos para crear objetos gráficos: CrearMapaDeBits y CrearEtiquetaDeBits. Estos se encargan de crear un objeto gráfico y el recurso gráfico asociado, que a su vez muestra la imagen en el gráfico. Métodos de fijación: Attach. Si el gráfico tiene un objeto gráfico OBJ_BITMAP_LABEL con o sin recurso gráfico adjunto, CCanvas trabajará con él de la misma forma que con el objeto gráfico creado. Basta con adjuntarlo mediante el método Attach correspondiente. El método de eliminación se representa como Destroy. Elimina el objeto gráfico, libera el búfer de la matriz m_pixels y elimina el recurso gráfico vinculado al objeto gráfico. Por consiguiente, para que CCanvas funcione correctamente en las aplicaciones, siempre hay que llamar al método Destroy al terminar, porque CCanvas no elimina automáticamente su objeto gráfico y su recurso gráfico.
  • Métodos para cargar imágenes desde un archivo. Aquí se presentan los siguientes métodos. El método estático LoadBitmap, capaz de cargar una imagen desde un archivo *.bmp a cualquier matriz uint que se le transmita con una dirección como parámetro, conservando el tamaño de la imagen resultante en las variables width y height, también transmitidas a este método con una dirección como parámetro. Otro método, LoadFromFile, carga una imagen desde un archivo *.bmp en la matriz m_pixels, estableciendo las dimensiones m_width y m_height de la imagen. El formato de píxel m_format debe ser COLOR_FORMAT_ARGB_RAW.
  • Métodos de lectura de las propiedades de los objetos del gráfico. Se componen de ChartObjectName, ResourceName, Width y Height, y retornan el nombre del objeto gráfico, el nombre del recurso gráfico, la anchura y la altura, respectivamente. Estos métodos ofrecen al usuario solo algunos datos para interactuar con el gráfico, como m_objname, m_rcname, y los datos de la imagen m_width y m_height.
  • Métodos para leer/escribir las propiedades de las fuentes del texto mostrado. Vamos a analizar primero los métodos de escritura: FontNameSet, FontSizeSet, FontFlagsSet y FontAngleSet. Estos métodos establecen el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo del texto mostrado, respectivamente. Ahora veremos los métodos de lectura FontSizeGet, FontFlagsGet y FontAngleGet, que retornan el tamaño de la fuente, los atributos de la fuente y el ángulo del texto mostrado, respectivamente. También tenemos los métodos para obtener/establecer las propiedades de la fuente que retornan/configuran a la vez todas las propiedades de la fuente. Método para establecer las propiedades: FontSet, establece el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo de inclinación del texto mostrado. Método para obtener propiedades: FontGet, retorna el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo de inclinación del texto mostrado. 
  • Métodos para leer/escribir el estilo de dibujado de las líneas. Para la lectura, se usa el método LineStyleGet. Para la escritura, el método LineStyleSet. El estilo de las líneas se necesita para trabajar con los métodos de dibujado de las primitivas gráficas LineAA, PolylineAA, PolygonAA, TriangleAA, CircleAA, EllipseAA, LineWu, PolylineWu, PolygonWu, TriangleWu, CircleWu, EllipseWu, LineThickVertical, LineThickHorizontal, LineThick, PolylineThick, PolygonThick.
  • Métodos de dibujado en la matriz de píxeles. La clase CCanvas tiene bastantes métodos para dibujar primitivas gráficas usando diferentes algoritmos, lo cual nos permite crear gráficos complejos utilizando técnicas avanzadas de antialiasing, como el algoritmo de Wu y las curvas de Bézier. Veamos estos métodos. Las primitivas simples sin suavizados son: LineVertical, LineHorizontal, Line, PolylinePolygon, Rectangle, Triangle, Circle, Ellipse, Arc y Pie. Los métodos dibujan las siguientes primitivas: línea vertical, línea horizontal, línea arbitraria, línea discontinua, polígono, rectángulo, triángulo, círculo, elipse, arco y sector de elipse pintado, respectivamente. Las primitivas coloreadas son: FillRectangle, FillTriangle, FillPolygon, FillCircle, FillEllipse y Fill. Estos métodos dibujan: un rectángulo, un triángulo, un polígono, un círculo, una elipse y un área a pintar, respectivamente. Los métodos para dibujar primitivas con antialiasing (AA) son: PixelSetAA, LíneaAA, PolilíneaAA, PolígonoAA, TriánguloAA, CírculoAA y ElipseAA. Estos pintan el píxel y muestran primitivas tales como: línea arbitraria, polilínea, polígono, triángulo, círculo y elipse, respectivamente. Los métodos para dibujar primitivas con el algoritmo de Wu (Wu's) son: LineWu, PolylineWu, PolygonWu, TriangleWu, CircleWu y EllipseWu. Estos dibujan primitivas tales como: línea arbitraria, polilínea, polígono, triángulo, círculo y elipse, respectivamente. Los métodos para dibujar primitivas con antialiasing prefiltrado y grosor de línea ajustable son: LineThickVertical, LineThickHorizontal, LineThick, PolylineThick y PolygonThick. Estos dibujan las siguientes primitivas: línea vertical, línea horizontal, línea arbitraria, polígono y línea discontinua, respectivamente. Los métodos para dibujar primitivas suavizadas con los métodos de Bézier son: PolylineSmooth y PolygonSmooth. Estos dibujan las primitivas como una línea suavizada y un polígono suavizado, respectivamente. Además de los métodos mencionados, a esta categoría también pertenecen el método TextOut (encargado de mostrar el texto), porque también modifica los valores del color en la matriz de píxeles, aunque en el código fuente de la clase CCanvas se refiere a los métodos para trabajar con el texto.
  • Métodos para transmitir una imagen para la muestra en el gráfico. Hay dos métodos que pertenecen a esta categoría. El método Update, que transmite una matriz de píxeles m_pixels al recurso gráfico vinculado al objeto gráfico que muestra en él una imagen. Como recordatorio, la matriz m_pixels se modifica usando los métodos de dibujado de la matriz de píxeles descritos anteriormente. El segundo método es Resize, que modifica las dimensiones de la matriz m_pixels (el tamaño de la imagen) y también la transmite al recurso gráfico.
  • Servicios. Como servicios, existen dos métodos en CCanvas: GetDefaultColor, que retorna los colores predefinidos, y TransparentLevelSet, que cambia la transparencia de la imagen modificando los valores del canal alfa en la matriz m_pixels.
  • Otros ajustes. Aquí solo hay un método, FilterFunction, encargado de ajustar el filtro para el antialiasing, que establece un filtro para todos los métodos de dibujado que contienen AA en sus nombres.

La clase CCanvas tiene campos y métodos en el ámbito private que no vamos a analizar dentro de este artículo, ya que son internos y no pueden ser utilizados en los métodos redefinidos de las clases descendientes de CCanvas. No obstante, el lector podrá estudiarlas en el código fuente del módulo Canvas.mqh en el MetaEditor.


Secuencia de operaciones al usar CCanvas en aplicaciones MQL

Utilizando como base la información anterior, podemos determinar la secuencia general de acciones para trabajar con la clase CCanvas al utilizar esta en cualquier aplicación MQL. Veamos qué acciones debemos realizar para que una imagen aparezca en un gráfico.

  • Crear o adjuntar un objeto gráfico (OBJ_BITMAP u OBJ_BITMAP_LABEL) o fijar a un objeto OBJ_BITMAP_LABEL existente.
  • Especificar los parámetros de la fuente y los estilos de dibujado de las primitivas
  • Realizar el dibujado en la matriz m_pixels usando los métodos correspondientes
  • Actualizar el recurso del objeto gráfico (OBJ_BITMAP o OBJ_BITMAP_LABEL)

Como resultado, aparecerá en el gráfico un objeto con las construcciones gráficas o el texto mostrados. Vamos a considerar todas estas acciones con más detalle.

Crear un objeto gráfico y adjuntarlo a un objeto gráfico existente

Para que la aplicación MQL pueda mostrar imágenes en el gráfico, el objeto deberá ser creado sobre la base de la clase CCanvas o la clase heredera de CCanvas. Después de crear el objeto CCanvas, podemos empezar a crear el objeto gráfico OBJ_BITMAP o OBJ_BITMAP_LABEL. O bien adjuntar un OBJ_BITMAP_LABEL existente al objeto CCanvas creado.

CCanvas tiene dos métodos, CrearBitmap y CrearBitmapLabel, para crear un objeto gráfico, cada uno de ellos con su propia opción de sobrecarga para facilitar su uso.

bool  CreateBitmap(const long chart_id, const int subwin, const string name, const datetime time, const double price, const int width, const int height, ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmap(const string name, const datetime time, const double price, const int width, const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmapLabel(const long chart_id, const int subwin, const string name, const int x, const int y, const int width, const int height, ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

bool  CreateBitmapLabel(const string name,const int x,const int y, const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);


El método CreateBitmap crea un mapa de bits (tipo de objeto OBJ_BITMAP) cuyas coordenadas se establecen como tiempo y precio en el gráfico de un instrumento comercial; asimismo, contiene los siguientes parámetros:

  • chart_id - identificador del gráfico (0 - gráfico actual)
  • window - número de subventanas del gráfico (0 - ventana principal)
  • name - nombre del objeto gráfico que se crea en el gráfico
  • time - coordenada de tiempo del objeto gráfico en el gráfico
  • price - coordenada del precio del objeto gráfico en el gráfico
  • width - anchura del objeto gráfico en el gráfico
  • height - altura del objeto gráfico en el gráfico

La otra versión del método CreateBitmap es un método sobrecargado que llama a CreateBitmap con chart_id y window igual a 0 (se corresponden con el gráfico actual y la ventana principal).

//+------------------------------------------------------------------+
//| Create object on chart with attached dynamic resource            |
//+------------------------------------------------------------------+
bool CCanvas::CreateBitmap(const string name,const datetime time,const double price,
                           const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   return(CreateBitmap(0,0,name,time,price,width,height,clrfmt));
  }

El método CrearBitmapLabel contiene los mismos parámetros que CreateBitmap, salvo "time" y "price". En su lugar, vemos x e y.

Parámetros:

  • chart_id – identificador del gráfico (0 – gráfico actual)
  • window – número de subventana del gráfico (0 - ventana principal)
  • name - nombre del objeto gráfico que se crea en el gráfico
  • x - coordenada en el eje X del objeto gráfico en el gráfico
  • y - coordenada en el eje Y del objeto gráfico en el gráfico
  • width - anchura de la imagen del objeto gráfico que se crea.
  • height - altura de la imagen del objeto gráfico que se crea.
  • clrfmt - formato de color de los píxeles de la imagen del objeto gráfico que se crea.

Otra variante del método CrearBitmapLabel es un método sobrecargado que llama a CrearBitmapLabel con chart_id y window igual a 0 (igual que en CreateBitmap).

//+------------------------------------------------------------------+
//| Create object on chart with attached dynamic resource            |
//+------------------------------------------------------------------+
bool CCanvas::CreateBitmapLabel(const string name,const int x,const int y,
                                const int width,const int height,ENUM_COLOR_FORMAT clrfmt)
  {
   return(CreateBitmapLabel(0,0,name,x,y,width,height,clrfmt));
  }

Si el gráfico tiene un objeto OBJ_BITMAP_LABEL, podemos adjuntarlo a CCanvas usando el método Attach. Como resultado, el objeto gráfico interactuará con el objeto CCanvas de la misma forma que el objeto gráfico creado con el método CreateBitmap o CreateBitmapLabel.

virtual bool  Attach(const long chart_id,const string objname,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);

virtual bool  Attach(const long chart_id,const string objname,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA);


Este método fija CCanvas a un objeto gráfico OBJ_BITMAP_LABEL existente.

Parámetros:

  • chart_id – identificador del gráfico (0 – gráfico actual)
  • objname – nombre del objeto gráfico adjunto
  • width – anchura de la imagen del objeto gráfico adjunto
  • height - altura de la imagen del objeto gráfico adjunto.
  • clrfmt – formato de color de los píxeles de la imagen del objeto gráfico adjunto

La primera variante del método Attach asume que el objeto gráfico no tiene ningún recurso gráfico, y lo crea automáticamente usando como base los parámetros objname, width, height y clrfmt, rellenando todos los datos que necesitamos para seguir trabajando con el objeto gráfico adjunto.

La segunda variante asume la existencia del recurso gráfico y simplemente lee los datos de los píxeles de la imagen en la matriz m_pixels, rellenando también todos los datos necesarios para seguir trabajando con el objeto gráfico adjunto.

Métodos para establecer y obtener los parámetros de la fuente, así como métodos para establecer y obtener el estilo de dibujado de las líneas

Después de crear un objeto gráfico usando el método CreateBitmap o CreateBitmapLabel o de adjuntarlo mediante el método Attach, deberemos establecer los parámetros de la fuente y el estilo de las primitivas de dibujado para obtener la imagen deseada como resultado final. Los parámetros de la fuente se establecen con la ayuda de los métodos siguientes.

Métodos sencillos para establecer las propiedades de las fuentes:

bool FontNameSet(string name);  // Set the font name

bool FontSizeSet(int size);     // Set the font size

bool FontFlagsSet(uint flags);  // Set the font attributes

bool FontAngleSet(uint angle);  // Set the font slope angle


Métodos simples para leer las propiedades de la fuente:

string FontNameGet(void) const;   // Return the font name

int    FontSizeGet(void) const;   // Return the font size

uint   FontFlagsGet(void) const;  // Return the font attributes

uint   FontAngleGet(void) const;  // Return the font slope angle


Método para establecer todas las propiedades de la fuente

bool FontSet(const string name,const int size,const uint flags=0,const uint angle=0); // Set the font properties

Parámetros:

  • name - nombre de la fuente
  • size - tamaño de la fuente
  • flags - atributos de la fuente
  • angle - ángulo de inclinación de la fuente

Como podemos ver en el listado, este método establece el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo del texto según los valores de las variables name, size, flags y angle, respectivamente, que se le transmiten como parámetros.

Método para obtener todas las propiedades de las fuentes

void FontGet(string &name,int &size,uint &flags,uint &angle); // Get font properties

Parámetros:

  • name - nombre de la fuente
  • size - tamaño de la fuente
  • flags - atributos de la fuente
  • angle - ángulo de inclinación de la fuente

Como podmeos ver en el listado, este método escribe el nombre de la fuente, el tamaño de la fuente, los atributos de la fuente y el ángulo del texto en las variables correspondientes name, size, flags y angle que se transmiten como parámetros por dirección.

Métodos de lectura y escritura del estilo de las líneas de las construcciones gráficas:

uint LineStyleGet(void) const;       // Return the specified line drawing style

void LineStyleSet(const uint style); // Set the line drawing style


    Parámetros:

    • style - estilo de dibujado de la líneas

    Métodos de dibujado y método de muestra del texto

    Empezaremos con el método de muestra del texto, porque solo es uno, aunque haya muchos métodos para dibujar primitivas gráficas en CCanvas.

    Muestra del texto

    void TextOut(int x,int y,string text,const uint clr,uint alignment=0); // Display text to the m_pixels array

    Parámetros:

    • x - coordenada del eje X del texto mostrado
    • y - coordenada del eje Y del texto mostrado
    • text - texto mostrado
    • clr - color del texto mostrado
    • alignment - método de vinculación del texto mostrado

    Como podemos ver en el listado, el método de muestra el texto text, según las coordenadas x, y, con el color clr y el método de vinculación del texto alignment.

    Cambio de píxeles

    En CCanvas, los píxeles ubicados en la matriz m_pixels pueden ser cambiados, o sus valores pueden ser obtenidos usando como base las coordenadas dadas.

    uint PixelGet(const int x,const int y) const;          // Return the pixel color value according to x and y coordinates
    
    void PixelSet(const int x,const int y,const uint clr); // Change the pixel color value according to x and y coordinates
    
    
    

    Parámetros:

    • x - coordenada del eje X del píxel
    • y - coordenada del eje Y del píxel
    • clr - color del píxel

    Dibujado de primitivas gráficas

    Como hay muchos métodos para dibujar primitivas en CCanvas, discutiremos solo los más básicos dentro de este artículo. Los demás métodos de la clase CCanvas se describen con todo detalle en la documentación.

    Línea vertical

    void LineVertical(int x,int y1,int y2,const uint clr); // Draw a vertical line according to specified coordinates and color

    Parámetros:

    • x - coordenada de la línea en el eje X
    • y1 - coordenada del primer punto en el eje Y
    • y2 - coordenada del segundo punto en el eje Y
    • clr - color de la línea

    Línea horizontal

    void LineHorizontal(int x1,int x2,int y,const uint clr); // Draw a horizontal line according to specified coordinates and color

    Parámetros:

    • x1 - coordenada del primer punto en el eje X
    • x2 - coordenada del segundo punto en el eje X
    • y - coordenada de la línea en el eje Y
    • clr - color de la línea

    Línea aleatoria

    void Line(int x1,int y1,int x2,int y2,const uint clr); // Draw a freehand line according to specified coordinates and color

    Parámetros:

    • x1 - coordenada del primer punto en el eje X
    • y1 - coordenada del primer punto en el eje
    • x2 - coordenada del segundo punto en el eje
    • y2 - coordenada del segundo punto en el eje Y
    • clr - color de la línea

    Línea quebrada

    void Polyline(int &x[],int &y[],const uint clr); // Draw a polyline according to specified coordinates and color

    Parámetros:

    • x - matriz con las coordenadas de los puntos en el eje X
    • y - matriz  con las coordenadas de los puntos en el eje Y
    • clr - color de la línea

    Polígono

    void Polygon(int &x[],int &y[],const uint clr); // Draw a polygon according to specified coordinates and color

    Parámetros:

    • x - matriz con las coordenadas de los puntos en el eje X
    • y - matriz  coordenadas de los puntos en el eje Y
    • clr - color del polígono

    Rectángulo

    void Rectangle(int x1,int y1,int x2,int y2,const uint clr); // Draw a rectangle according to specified coordinates and color

    Parámetros:

    • x1 - coordenada del primer punto en el eje X
    • y1 - coordenada del primer punto en el eje Y
    • x2 - coordenada del seguno punto en el eje X
    • y2 - coordenada del seguno punto en el eje Y
    • clr - color del rectángulo 

    Triángulo

    void Triangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr); // Draw a triangle according to specified coordinates and color

    Parámetros:

    • x1 - coordenada del primer punto en el eje X
    • y1 - coordenada del primer punto en el eje Y
    • x2 - coordenada del seguno punto en el eje X
    • y2 - coordenada del seguno punto en el eje Y
    • x3 - coordenada del tercer punto en el eje X
    • y3 - coordenada del tercer punto en el eje Y
    • clr - color del rectángulo 

    Circunferencia

    void Circle(int x,int y,int r,const uint clr); // Draw a circle according to specified coordinates, radius and color

    Parámetros:

    • x - coordenada en el eje X
    • y - coordenada en el eje Y
    • r - radio de la circunferencia
    • clr - color de la circunferencia

    Elipse

    void Ellipse(int x1,int y1,int x2,int y2,const uint clr); // Draw an ellipse according to specified coordinates and color

    Parámetros:

    • x1 - coordenada del primer punto en el eje X
    • y1 - coordenada del primer punto en el eje Y
    • x2 - coordenada del seguno punto en el eje X
    • y2 - coordenada del seguno punto en el eje Y
    • clr - color del rectángulo 

    Las descripciones de otros métodos, como Arc y Pie, son demasiado extensas como para describirlas en este artículo. Por ello, le recomendamos que consulte los enlaces anteriores para ver las descripciones de estos métodos en la documentación.

    Los métodos anteriores producen primitivas simples con una anchura de línea de 1 píxel y el estilo de línea más común STYLE_SOLID. Si el lector está satisfecho con estos gráficos sencillos, podrá utilizarlos,

    pero si quiere un estilo de línea diferente a STYLE_SOLID, le recomendamos que elija alguno de los siguientes métodos.

    //--- Methods for drawing primitives with smoothing using antialiasing
        
    void PixelSetAA(const double x,const double y,const uint clr);
        
    void LineAA(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX);
        
    void PolylineAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX);
        
    void PolygonAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX);
        
    void TriangleAA(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style=UINT_MAX);
        
    void CircleAA(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX);
        
    void EllipseAA(const double x1,const double y1,const double x2,const double y2,const uint clr,const uint style=UINT_MAX);
        
    //--- Methods for drawing primitives with smoothing using Wu's algorithm
        
    void LineWu(int x1,int y1,int x2,int y2,const uint clr,const uint style=UINT_MAX);
        
    void PolylineWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX);
        
    void PolygonWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX);
        
    void TriangleWu(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style=UINT_MAX);
        
    void CircleWu(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX);
        
    void EllipseWu(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX);
    

    Todos estos métodos son similares a los métodos simples anteriores, pero tienen un parámetro adicional style que se puede elegir de la enumeración ENUM_LINE_STYLE. En el listado, está resaltado en amarillo. Si no se especifica el parámetro  style (igual a UINT_MAX), el método llamado usará el valor m_style establecido con el método LineStyleSet, que se puede obtener con el método LineStyleGet.

    Como los lectores atentos habrán notado, los métodos descritos anteriormente tienen la capacidad de establecer el estilo de las líneas, pero no ofrecen la posibilidad de cambiar su grosor. Por lo tanto, para dibujar primitivas con un grosor de línea determinado, existen los siguientes métodos.

    //--- Methods for drawing primitives with preliminarily set antialiasing filter
    
    void LineThickVertical(const int x,const int y1,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void LineThickHorizontal(const int x1,const int x2,const int y,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void LineThick(const int x1,const int y1,const int x2,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void PolylineThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    
    void PolygonThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style);
    

    En estos métodos, al igual que en los métodos comentados anteriormente, podemos establecer el estilo de línea style. También se añaden nuevos parámetros, como size, que establece el grosor de las líneas y end_style, que indica el estilo final de las líneas, y que podemos seleccionar de la lista ENUM_LINE_END.

    También en CCanvas hay métodos para dibujar primitivas suavizadas usando el método Bézier. Después, para mejorar la calidad de la imagen final, las primitivas resultantes se procesan con un algoritmo de antialiasing de mapa de bits. Así, en lugar de usar las líneas rectas de los métodos anteriores, las primitivas constarán de curvas de Bézier. Veamos estos métodos.

    //--- Methods for drawing a smoothed polyline and smoothed polygon
    
    void PolylineSmooth(const int &x[],const int &y[],const uint clr,const int size,
                        ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND,
                        double tension=0.5,double step=10);
    
    void PolygonSmooth(int &x[],int &y[],const uint clr,const int size,
                       ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND,
                       double tension=0.5,double step=10);
    

    Aquí, además de las matrices que ya conocemos con las coordenadas de los puntos x e y de la primitiva, junto con el color clr y el grosor de la línea size, el estilo de las líneas style y el estilo de finalización de la línea end_style, tenemos dos parámetros adicionales: tension, el valor del parámetro de suavizado, y step, el salto de aproximación.

    Además de los métodos mencionados anteriormente para dibujar primitivas formadas por líneas, CCanvas tiene métodos para dibujar primitivas coloreadas.

    //--- Methods of drawing filled primitives
    
    void FillRectangle(int x1,int y1,int x2,int y2,const uint clr);
    
    void FillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr);
    
    void FillPolygon(int &x[],int &y[],const uint clr);
    
    void FillCircle(int x,int y,int r,const uint clr);
    
    void FillEllipse(int x1,int y1,int x2,int y2,const uint clr);
    
    void Fill(int x,int y,const uint clr);
    
    void Fill(int x,int y,const uint clr,const uint threshould);
    

    Estos métodos son simples y no contienen algoritmos de suavizado. La asignación de los parámetros es similar a la de los anteriores métodos que hemos analizado para crear primitivas similares a partir de líneas.


    Formato del píxeles y componentes de color

    Vamos a regresar a los métodos anteriores y prestar atención al formato de píxeles en los métodos CreateBitmap y CreateBitmapLabel, que está controlado por el parámetro clrfmt. Este parámetro establece el formato de píxeles para el recurso gráfico creado que luego se vinculará con el objeto gráfico que muestra la imagen. El formato de píxeles que asignemos al recurso gráfico al crear Canvas mediante los métodos CreateBitmap o CreateBitmapLabel determinará el método que usa el terminal para procesar la imagen al mostrarla en el gráfico. Para entender qué son los métodos de procesamiento de imágenes, echaremos un vistazo a ENUM_COLOR_FORMAT. Aquí podemos seleccionar uno de los tres valores de las constantes.

    ENUM_COLOR_FORMAT

    • COLOR_FORMAT_XRGB_NOALPHA - formato XRGB (el canal alfa se ignora)
    • COLOR_FORMAT_ARGB_RAW -  formato "a secas" de ARGB (los componentes de color no son procesados por el terminal)
    • COLOR_FORMAT_ARGB_NORMALIZE - formato ARGB (los componentes de color no son procesados por el terminal)

    Ahora vamos a analizar el orden de representación de los componentes de color en estos formatos.

    Formato del píxeles y componentes de color

    En el esquema mostrado, podemos ver la ubicación de los componentes de color en los bytes de las celdas de la matriz m_pixels, donde R es el canal rojo, G es el canal verde, B es el canal azul, A es el canal alfa y X es el byte no utilizado. El tipo de datos de píxeles es uint, 4 bytes. Un byte por canal. Un byte puede almacenar números del 0 al 255. Cambiando los valores de los bytes, estableceremos el color del píxel. Por ejemplo, si establecemos en el canal R la cifra 255, y en los canales G y B ponemos 0, obtendremos el color rojo. Si escribimos el valor 255 en el canal G y ponemos los otros canales a 0, obtendremos verde. Si ponemos el canal B a 255, y el resto a 0, obtendremos el color azul. Ajustando los canales RGB en diferentes proporciones, podremos obtener cualquier color. Estableciendo el canal alfa entre 0 (completamente invisible, transparente) y 255 (completamente visible, opaco), podremos ajustar la opacidad de los píxeles, creando la sensación de que los píxeles de una imagen se superponen a los de otra ya dibujada en el gráfico. Esto se puede hacer correctamente con COLOR_FORMAT_ARGB_NORMALIZE. Ahora, vamos a pasar directamente a los colores y a ver la paleta que contiene estos.

    Paleta de colores

    Aquí podemos ver claramente cómo los cambios en la proporción de los niveles de cada canal influye en el color final. Los colores están en el formato RGB y HEX. El formato RGB  ya lo hemos visto, pero HEX necesita ciertas aclaraciones, especialmente para los principiantes que quieren aprender a manejar CCanvas. Vamos a ver la notación hexadecimal, que contiene números del 0 al 15, y a compararla con la notación decimal, que contiene números del 0 al 9. Para estos números, el sistema decimal tiene los caracteres: 0, 1, 2, 3, 4, 5, 6, 7, 8 y 9. Obviamente, muchos principiantes dudarán cómo escribir números mayores que 9 en notación hexadecimal. Aquí es donde acuden en nuestra ayuda los caracteres del alfabeto latino de la A a la F. Lo que obtenemos es un conjunto de números hexadecimales: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Ahora resulta fácil entender cómo es el byte en el que estamos escribiendo el valor del canal: de formato hexadecimal o HEX. En los lenguajes de programación de tipo C, HEX se escribe como 0x[valor]. Así, el rango de un byte se verá como valores de 0x00 a 0xFF.


    Ejemplos de creación de gráficos con CCanvas

    Volviendo al tema del formato de píxeles, vamos a ver un sencillo ejemplo de aplicación que usa CCanvas, y también experimentaremos con diferentes valores de formato de píxeles intentando establecer los valores del color y del canal alfa de una imagen en diferentes formatos de píxeles. Empezaremos con el formato más universal COLOR_FORMAT_ARGB_NORMALIZE. Con dicho valor construiremos nuestra primera aplicación..

    Una aplicación sencilla con CCanvas

    Vamos a crear un script y a ponerle el nombre Erase.mq5.

    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(ColorToARGB(clrWhite, 255));
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    En la primera línea vemos la conexión del módulo Canvas.mqh. Ahora podemos crear un objeto, una instancia de la clase CCanvas, para seguir trabajando, cosa que implementamos en el ejemplo.

    CCanvas  canvas;

    A continuación, creamos el propio Canvas con el parámetro clrfmt: COLOR_FORMAT_ARGB_NORMALIZE.

     canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);

    Y lo rellanamos con la ayuda del método Erase.

     canvas.Erase(ColorToARGB(clrWhite, 255));

    Después de crear Canvas, llamamos al método Update.

    canvas.Update(true);

    Con el parámetro redraw: true. Este parámetro podemos dejarlo en blanco, ya que está establecido por defecto, pero le sugerimos establecerlo para mayor claridad. Luego esperamos 6 segundos, para ver el resultado de la aplicación en el gráfico.

    Sleep(6000); 

    A continuación, llamamos al método Destroy para liberar la memoria utilizada por el recurso canvas y el objeto canvas.

    canvas.Destroy();

    El script completa su trabajo. Podemos ver el resultado en la siguiente imagen.

    Erase

    Aquí vemos un rectángulo coloreado que podemos usar como base o fondo para dibujar gráficos más complejos.

    Formato de píxeles y métodos de superposición

    Vamos a tomar el ejemplo de Erase.mq5 que acabamos de comentar e intentar establecer el color directamente, sin la función ColorToARGB. Para ello, copiaremos el código completo del ejemplo y crearemos un script basado en él, llamado ARGB_NORMALIZE.mq5. Ahora vamos a establecer el color seleccionando uno de los colores de nuestra paleta. Por ejemplo, clrPaleGreen. A continuación, tomamos su valor en formato HEX y añadimos el valor del canal alfa de 0xFF a la izquierda.

    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(0xFF98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    En el listado, los componentes RGB están resaltados en verde, mientras que el componente A está resaltado en gris. Ejecutamos el script y miramos lo que sucede.

    COLOR_FORMAT_ARGB_NORMALIZE cambio de color

    Podemos ver cómo ha cambiado el color de la imagen. Ahora vamos a intentar cambiar la transparencia. Establecemos el valor del canal alfa en 0xCC.

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(0xCC98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    Y miramos el resultado.

    Cambio de transparencia en el formato COLOR_FORMAT_ARGB_NORMALIZE

    En la imagen, podemos ver que la zona coloreada se ha vuelto semitransparente.

    Cambiamos el formato de los píxeles a COLOR_FORMAT_ARGB_RAW y retornamos la opacidad total a la imagen. Para ello, crearemos un ejemplo aparte, ARGB_RAW.mq5.

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
        canvas.Erase(0xFF98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    Vamos a ejecutar el ejemplo y a ver el resultado.

    Imagen de la opacidad en el formato COLOR_FORMAT_ARGB_RAW

    Vemos que el resultado no es diferente del ejemplo con COLOR_FORMAT_ARGB_NORMALIZE.

    Establecemos el canal alfa en 0xFB y cambiamos el color de fondo del gráfico a blanco.

    void OnStart()
     {
        ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite);
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
        canvas.Erase(0xFB98FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    Como resultado, podemos ver solo un pequeño cambio y ninguna diferencia en el formato de píxeles COLOR_FORMAT_ARGB_NORMALIZE.

    Cambio de la opacidad en el formato COLOR_FORMAT_ARGB_RAW

    No obstante, merece la pena reducir el valor del canal alfa en una unidad,

    canvas.Erase(0xFA98FB98);

    ya que enseguida notamos un cambio bastante significativo en la imagen final.

    Cambio de la imagen con una pequeña modificación de la opacidad en el formato COLOR_FORMAT_ARGB_RAW

    Ahora vamos a comprobar cómo se comporta la imagen cuando la transparencia está totalmente modificada. Para ello, crearemos un nuevo ejemplo y lo llamaremos ARGB_RAW-2.mq5 Copiamos el código del ejemplo anterior y realizamos algunos pequeños cambios para que el valor del canal alfa pueda cambiar de 255 a 0 en el intervalo especificado.

    void OnStart()
     {
      ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite);
      CCanvas canvas;
      canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(0xFF98FB98);
      for(int a = 255; a >= 0; a--)
       {
        canvas.TransparentLevelSet((uchar)a);
        canvas.Update(true);
        Sleep(100);
       }
      canvas.Destroy();
     }
    

    Como podemos ver en el listado, hemos añadido a la aplicación un ciclo for que cambia la transparencia (variable a del ciclo) llamando al método TrancparentLevelSet, y cambiando la transparencia de toda la imagen.

    canvas.TransparentLevelSet((uchar)a);

    A continuación, llamamos al método Update, que ya conocemos.

    canvas.Update(true);

    Después, la función Sleep hace que el ciclo espere 100 milisegundos (para que el usuario tenga tiempo de ver el cambio de transparencia).

    Sleep(100);

    A continuación, la aplicación elimina Canvas

    canvas.Destroy();

    y finaliza su funcionamiento. Podemos ver el resultado en la animación GIF.

    Cambio completo de la opacidad en el formato COLOR_FORMAT_ARGB_RAW

    Por este ejemplo, y por todos los ejemplos que usan COLOR_FORMAT_ARGB_RAW, podemos ver que una imagen con un valor de canal alfa de 255 se imprimirá correctamente. Pero en cuanto reducimos este valor, la imagen empieza a mostrarse distorsionada, porque el formato no normaliza los canales RGB, que pueden desbordarse y crear así artefactos y distorsiones, ya que los valores desbordados por encima de 255 simplemente se recortan. No obstante, la imagen se muestra más rápido con este formato que con COLOR_FORMAT_ARGB_NORMALIZE.

    Volvamos a nuestro ejemplo con el uso del formato de píxeles COLOR_FORMAT_ARGB_RAW ARGB_RAW.mq5. Vamos a crear un script llamado XRGB_NOALPHA.mq5 y a copiar el código de ARGB_RAW.mq5 en él, estableciendo el formato de píxeles como COLOR_FORMAT_XRGB_NOALPHA y estableciendo como cero el valor del canal alfa en el método Erase. 

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_XRGB_NOALPHA);
        canvas.Erase(0x0098FB98);
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    Ejecutamos el script y miramos el resultado.

    Superposición del color en el formato COLOR_FORMAT_XRGB_NOALPHA

    Podemos ver que el resultado no difiere de los ejemplos con los formatos COLOR_FORMAT_ARGB_NORMALIZE y COLOR_FORMAT_ARGB_RAW, con el valor máximo del canal alfa (0xFF) en el método Erase. Así vemos que en este formato el valor del canal alfa se ignora completamente y la imagen simlemente  se superpone sobre la imagen del gráfico. 

    Muestra del texto

    Sabemos que CCanvas tiene un método para mostrar el texto, TextOut. Vamos a intentar mostrar el texto. Para ello, crearemos un script y lo llamaremos TextOut.mq5. Tomaremos como base el ejemplo Erase.mq5, que hemos visto anteriormente, copiando su código. Vamos a añadir un método para establecer los parámetros de la fuente FontSet y establecer los valores necesarios, el nombre de la fuente "Calibri" y el tamaño de la fuente -210.

    Para establecer el tamaño de la fuente en píxeles,tanto size en los métodos FontSet como FontSizeSet se multiplican por -10. Así, para un tamaño de fuente de 21 píxeles, size sería -210.

    Los demás parámetros se dejan por defecto. A continuación, añadimos el método TextOut con el parámetro text: "Text". Las demás líneas que necesitamos para que este ejemplo funcione permanecerán en el código copiado.

    void OnStart()
     {
        CCanvas canvas;
        canvas.CreateBitmapLabel("canvas", 15, 30, 300, 250, COLOR_FORMAT_ARGB_NORMALIZE);
        canvas.Erase(ColorToARGB(clrWhite, 255));
        canvas.FontSet("Calibri", -210);
        canvas.TextOut(15, 15, "Text", ColorToARGB(clrLightSlateGray, 255));
        canvas.Update(true);
        Sleep(6000);
        canvas.Destroy();
     }
    

    Veamos el resultado del funcionamiento del script.

    Mostrando el texto con el método TextOut

    Podemos ver que el texto ha aparecido en la imagen. 

    Vamos a cambiar nuestro ejemplo para que la imagen surja exactamente en el centro del gráfico y se parezca a un objeto Label (OBJ_LABEL). Para ello, crearemos un nuevo ejemplo basado en este script y lo llamaremos TextOut-2.mq5.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_NORMALIZE);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }
    

    En el listado podemos ver que el texto mostrado se escribe en la variable text (porque se accede a ella dos veces en el código del script).

    string text = "Text";

    Las variables width y height se declaran para mantener el tamaño del texto mostrado.

    int width, height;

    Asimismo, declaramos dos constantes: textXDist y textYDist, en las que se guarda la sangría del texto mostrado respecto al borde derecho y superior de la imagen, respectivamente.

    const int textXDist = 10, textYDist = 5;

    A continuación, declaramos las dos variables canvasX y canvasY para almacenar los resultados de los cálculos de las coordenadas de la imagen.

    int canvasX, canvasY;

    Después, llamamos al método FontSet, que define los parámetros de la fuente (para fijarlos de antemano).

    canvas.FontSet("Calibri", -210);

    Luego usamos el método TextSize para determinar el tamaño del texto a mostrar en la pantalla (para saber qué tamaño debe tener la imagen) y lo almacenamos en las variables width y height.

    canvas.TextSize(text, width, height);

    A continuación, calculamos las coordenadas de la imagen que servirán para mostrar la imagen en el centro del gráfico.

    canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
    canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
    

    Después creamos la imagen usando el método CreateBitmapLabel, donde se establecen las coordenadas calculadas previamente.

    canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_NORMALIZE);
    

    A continuación, la imagen se rellena a color con la ayuda del método Erase.

    canvas.Erase(ColorToARGB(clrWhite, 255));

    Acto seguido, mostramos el texto (text) utilizando el método TextOut con las coordenadas previamente establecidas textXDist y textYDist.

    canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));

    Después van unas líneas que ya nos resultan familiares, y que no requieren explicación. Vamos a ejecutar el script y ver el resultado.

    Ejemplo de etiqueta de texto

    En la imagen, vemos un objeto que parece una etiqueta de texto, pero que carece de fondo transparente. También podemos establecer en nuestro formato de píxeles actual COLOR_FORMAT_ARGB_NORMALIZE, pero lo haremos de otra forma. Estableceremos el formato de píxeles rápido COLOR_FORMAT_ARGB_RAW en Canvas porque no necesitamos la mezcla de colores en este caso, y los colores con un valor de canal alfa de 0 y 255 se emitirán en este formato sin distorsión. Además, un color con un valor de canal alfa igual a 0 no afectará en absoluto a la imagen final. Vamos a copiar el script LabelExample.mq5 basado en el ejemplo anterior.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.FontSet("Calibri", -210);
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }
    

    No hemos cambiado el valor del canal alfa para asegurarnos de lo que hemos mencionado antes (que un valor del canal alfa igual a 255 recoloreará completamente los píxeles del gráfico).

    Comprobación del formato COLOR_FORMAT_ARGB_RAW

    Ahora establecemos el valor del canal alfa en 0.

    void OnStart()
     {
      string text = "Text";
      int width, height;
      const int textXDist = 10, textYDist = 5;
      int canvasX, canvasY;
      CCanvas canvas;
      
      canvas.FontSet("Calibri", -210);
      canvas.TextSize(text, width, height);
      
      canvasX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) / 2 - width / 2;
      canvasY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0) / 2 - height / 2;
      
      canvas.CreateBitmapLabel("canvas", canvasX, canvasY,
                               width + textXDist * 2, height + textYDist * 2,
                               COLOR_FORMAT_ARGB_RAW);
      canvas.Erase(ColorToARGB(clrWhite, 0));
      canvas.FontSet("Calibri", -210);
      canvas.TextOut(textXDist, textYDist, text, ColorToARGB(clrLightSlateGray, 255));
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }
    

    Iniciamos el script y miramos el resultado.

    Análogo de Label

    Vemos un análogo de la etiqueta de texto (OBJ_LABEL). Esto nos permite mostrar un texto encima de una imagen sin recolorearla completamente, lo cual se usa mucho en todo tipo de UI para indicar los elementos de control, y simplemente para mostrar información en forma de texto.

    Dibujando primitivas simples sin antialiasing

    Para entender cómo dibujar primitivas en CCanvas, vamos a analizar el ejemplo DrawPrimitives.mq5.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.LineHorizontal(point, w - point, h - point, color_);
      canvas.LineVertical(point, point, h - point, color_);
      canvas.Line(point * 2, point * 13, point * 8, point * 9, color_);
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.Polyline(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.Polygon(pgX, pgY, color_);
      canvas.Rectangle(point * 2, point * 5, point * 7, point, color_);
      canvas.Triangle(point * 2, point * 11, point * 2, point * 6, point * 7, point * 6, color_);
      canvas.Circle(point * 10, point * 3, point * 2, color_);
      canvas.Ellipse(point * 8, point * 9, point * 12, point * 6, color_);
      canvas.Arc(point * 15, point * 2, point * 2, point, 45.0 * M_PI / 180, 180.0 * M_PI / 180, color_);
      canvas.Pie(point * 16, point * 3, point * 2, point, 180.0 * M_PI / 180, 402.5 * M_PI / 180, color_, fillColor);
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }
    

    Resulta más cómodo dibujar todas las primitivas en celdas; asimismo, también es muy cómodo establecer sus coordenadas y otros parámetros de la misma manera. Así que hemos decidido simplificar la tarea dividiendo la imagen de CCanvas en partes iguales en anchura y altura, para poder establecer las coordenadas en las celdas y luego convertirlas a píxeles multiplicando las coordenadas en las celdas por el tamaño de una celda. Para ello, hemos tomado las dimensiones del gráfico como el tamaño de la imagen CCanvas y hemos dividido el tamaño más pequeño entre 15.

      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
    

    En el listado, hemos resaltado este código en amarillo. Conociendo ahora las dimensiones de una sola celda (variable point), podemos convertir las coordenadas dadas en las celdas a sus valores naturales en píxeles. Antes de crear este ejemplo, hemos dibujado la imagen del gráfico en celdas y hemos usado estas para escribir las coordenadas de todas las primitivas en el script.

    int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {14, 12, 13, 11, 12, 10};
    int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {9, 7, 5, 5, 7, 9};
    

    Aquí, hemos puesto las coordenadas para la línea quebrada (Polyline) y para el polígono (Polygon) en matrices. Luego las hemos convertido en píxeles iterando las matrices una por una, y hemos dibujado la línea quebrada y el polígono.

      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.Polyline(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.Polygon(pgX, pgY, color_);
    

    El código para llamar a los métodos de las primitivas de dibujado está resaltado en amarillo. El resto de las primitivas se muestran de la forma siguiente.

      canvas.Polygon(pgX, pgY, color_);
      canvas.Rectangle(point * 2, point * 5, point * 7, point, color_);
      canvas.Triangle(point * 2, point * 11, point * 2, point * 6, point * 7, point * 6, color_);
      canvas.Circle(point * 10, point * 3, point * 2, color_);
      canvas.Ellipse(point * 8, point * 9, point * 12, point * 6, color_);
      canvas.Arc(point * 15, point * 2, point * 2, point, 45.0 * M_PI / 180, 180.0 * M_PI / 180, color_);
      canvas.Pie(point * 16, point * 3, point * 2, point, 180.0 * M_PI / 180, 402.5 * M_PI / 180, color_, fillColor);
    

    Como podemos ver en el listado, todas las coordenadas se multiplican por point para ser convertidas a píxeles. Veamos el resultado del script.

    Primitivas simples

    Podemos ver primitivas con un estilo de dibujado de línea simple (STYLE_SOLID) que no se puede cambiar.

    Dibujando primitivas con antialiasing y estilo de línea cambiante

    Para hacer que el estilo de las líneas sea cambiante, vamos a utilizar métodos con algoritmo de filtrado antialiasing (AA). Vamos a crear un script llamado DrawPrimitivesAA.mq5 y a copiar allí el código del ejemplo anterior. Todos los métodos que existen en la clase CCanvas con el prefijo AA (LineAA, PolylineAA, PolygonAA, TriangleAA, CircleAA y EllipseAA) los dejaremos, confiriéndoles el aspecto correspondiente. Los métodos restantes se eliminarán. Como tenemos menos primitivas que en el ejemplo anterior, vamos a cambiar su disposición.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.LineStyleSet(lineStyle);
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      canvas.PolylineAA(plX, plY, color_);
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      canvas.PolygonAA(pgX, pgY, color_);
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_);
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);
      canvas.Update(true);
      Sleep(6000);
      canvas.Destroy();
     }
    

    Hemos resaltado las líneas modificadas en amarillo. Ahora vamos a ejecutar el script y ver el resultado.

    Primitivas con antialiasing

    En la imagen, vemos el resultado de los métodos de antialiasing. Si nos fijamos bien en las líneas, estas parecen más suaves en comparación con el resultado del ejemplo anterior, con primitivas simples.

    Hemos visto el resultado de los métodos de dibujado que usan el antialiasing, pero el estilo de las líneas sigue siendo STYLE_SOLID. Vamos a corregir esto creando un script llamado DrawPrimitivesAA-2.mq5, insertando el código del ejemplo anterior. Para ello, insertaremos el parámetro sleep como parámetro input que establecerá un retardo después de cambiar el estilo de las líneas y primitivas a mostrar.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    

    Vamos a sacar todos los métodos de dibujado a una macro.

    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineAA(plX, plY, color_);                                                         \
      canvas.PolygonAA(pgX, pgY, color_);                                                          \
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);
    

    Entonces, modificaremos de forma secuencial el estilo de dibujado de las líneas usando el método LineStyleSet y las primitivas a mostrar. Veamos el ejemplo resultante.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineAA(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineAA(plX, plY, color_);                                                         \
      canvas.PolygonAA(pgX, pgY, color_);                                                          \
      canvas.TriangleAA(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleAA(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseAA(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      //ENUM_LINE_STYLE lineStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
      
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DOT);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DASH);
      drawPrimitives
      canvas.LineStyleSet(STYLE_DASHDOTDOT);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }
    

    En el fragmento de código resaltado podemos ver cómo se cambia el estilo y cómo se llaman los métodos de dibujado. Veamos el resultado del script.

    Cambio de estilo de dibujado de las líneas (AA)

    En esta animación GIF podemos ver cómo cambia el estilo de las líneas de las primitivas con el intervalo sleep dado.

    Ahora proponemos al lector crear un ejemplo de dibujado de primitivas usando el algoritmo de Wu. Para ello, crearemos el script DrawPrimitivesWu.mq5, basado en el ejemplo anterior. La combinación de caracteres AA la sustituiremos por Wu, comentando además las líneas como se muestra en el listado.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                               \
      canvas.LineWu(point * 2, point * 12, point * 8, point * 8, color_);                          \
      canvas.PolylineWu(plX, plY, color_);                                                         \
      canvas.PolygonWu(pgX, pgY, color_);                                                          \
      canvas.TriangleWu(point * 2, point * 9, point * 2, point * 4, point * 7, point * 4, color_); \
      canvas.CircleWu(point * 16, point * 11, point * 2, color_);                                  \
      canvas.EllipseWu(point * 8, point * 4, point * 12, point * 7, color_);                       \
      canvas.Update(true);                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {15, 14, 15, 17, 18, 17}, pgY[] = {7, 5, 3, 3, 5, 7};
      //ENUM_LINE_STYLE lineStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
    
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      //canvas.LineStyleSet(STYLE_DOT);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASH);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASHDOTDOT);
      //drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }
    

    Todos los métodos añadidos están resaltados en amarillo. Vamos a ejecutar el ejemplo y ver el resultado.

    Primitivas con alisado de Wu

    Podemos ver que la calidad de dibujado es aún mejor, gracias al suavizado con el algoritmo de Wu. Ahora vamos a descomentar las líneas de nuestro script y ejecutarlo de nuevo.

    Cambio de estilo de dibujado de las líneas (Wu)

    Podemos ver que los estilos de las líneas cambian exactamente como en el ejemplo anterior, con la diferencia de que la calidad del suavizado de la imagen de las primitivas es mayor.

    Dibujando primitivas con diferentes anchuras de línea

    Vamos a crear un script basado en el ejemplo anterior: lo llamaremos DrawPrimitivesThick.mq5. Para ello, sustituiremos los métodos existentes para el dibujado de primitivas por los métodos con el prefijo "Thick" y eliminaremos los métodos que no tengan análogos como se muestra en el listado. Y, al igual que en el ejemplo anterior, comentaremos las líneas innecesarias.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                                      \
      canvas.LineThickVertical(point, point, h - point, color_, size, lineStyle, endStyle);               \
      canvas.LineThickHorizontal(point, w - point, h - point, color_, size, lineStyle, endStyle);         \
      canvas.LineThick(point * 2, point * 12, point * 8, point * 8, color_, size, lineStyle, endStyle);   \
      canvas.PolylineThick(plX, plY, color_, size, lineStyle, endStyle);                                  \
      canvas.PolygonThick(pgX, pgY, color_, size, lineStyle, endStyle);                                   \
      canvas.Update(true);                                                                                \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {17, 18, 17, 15, 14, 15, 17}, pgY[] = {7, 5, 3, 3, 5, 7, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_ROUND;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      //canvas.LineStyleSet(STYLE_DOT);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASH);
      //drawPrimitives
      //canvas.LineStyleSet(STYLE_DASHDOTDOT);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }
    

    En el listado, los métodos añadidos están resaltados en amarillo. Como los lectores atentos ya habrán notado, los métodos tienen dos parámetros adicionales: size, la anchura de la línea, y end_style, el estilo de finalización de la línea. Ejecutamos el script y miramos el resultado.

    Muestra de primitivas (Thick)

    En la imagen vemos primitivas con líneas gruesas, cuyo grosor se puede modificar como hemos mencionado anteriormente. Vamos a crear un nuevo script basado en el anterior y a llamarlo DrawPrimitivesThick-2.mq5. Lo utilizaremos para ver todas las combinaciones posibles de anchura de línea, estilos de línea y estilos de finalización de línea. Para ello, descomentaremos las líneas comentadas y las añadiremos a la macro method1, donde cambiaremos el estilo de finalización de la línea y llamaremos a la macro method0 después de cada cambio. En la macro method0 llamaremos a los métodos de dibujado de primitivas. En la macro drawPrimitives cambiaremos el estilo de la línea y llamaremos a method1 tras cada cambio de estilo de línea. Llamamos a la macro drawPrimitives en un ciclo donde cambiamos el grosor de las líneas dentro de un rango determinado.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    input int beginSize = 1;
    input int endSize = 4;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives lineStyle = STYLE_SOLID; \
      method1                                       \
      lineStyle = STYLE_DOT;                        \
      method1                                       \
      lineStyle = STYLE_DASH;                       \
      method1                                       \
      lineStyle = STYLE_DASHDOTDOT;                 \
      method1 
      
    #define method1 endStyle = LINE_END_ROUND; \
      method0                                  \
      endStyle = LINE_END_BUTT;                \
      method0                                  \
      endStyle = LINE_END_SQUARE;              \
      method0
    
    #define method0 canvas.Erase(ColorToARGB(clrWhite, 255));                                                                                                      \
      canvas.LineThickVertical(point, point, h - point, color_, size, lineStyle, endStyle);                                                                        \
      canvas.LineThickHorizontal(point, w - point, h - point, color_, size, lineStyle, endStyle);                                                                  \
      canvas.LineThick(point * 2, point * 12, point * 8, point * 8, color_, size, lineStyle, endStyle);                                                            \
      canvas.PolylineThick(plX, plY, color_, size, lineStyle, endStyle);                                                                                           \
      canvas.PolygonThick(pgX, pgY, color_, size, lineStyle, endStyle);                                                                                            \
      canvas.TextOut(point * 2, point, "Size: " + (string)size + "; Style: " + EnumToString(lineStyle) + "; End Style: " + EnumToString(endStyle) + ";", color_);  \
      canvas.Update(true);                                                                                                                                         \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {6, 8, 9, 11, 12, 14}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {17, 18, 17, 15, 14, 15, 17}, pgY[] = {7, 5, 3, 3, 5, 7, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_ROUND;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      for (int i = beginSize; i <= endSize; i++) {
        size = i;
        drawPrimitives
      }
      
      Sleep(6000);
      canvas.Destroy();
     }
    //+------------------------------------------------------------------+
    

    Todos los cambios en el código (en comparación con el ejemplo anterior) están resaltados en amarillo. El script resultante adopta el intervalo de grosor de la línea a partir de los parámetros de entrada beginSize y endSize, y luego itera, usando macros, todas las combinaciones posibles de estilo de línea y estilo de finalización. El resultado es una enumeración con todos los posibles parámetros de líneas de primitivas, cuyas combinaciones podemos ver al ejecutar el script. La siguiente animación GIF muestra el resultado del funcionamiento del script.

    Primitivas con diferentes parámetros

    En la animación podemos ver cómo cambia el grosor de la línea, el estilo de la línea y el estilo de finalización de la línea. Todos estos parámetros se muestran en Canvas, y también podemos verlos.

    Dibujado de primitivas suavizadas con el algoritmo de Bézier con anchura variable de líneas

    Vamos a tomar uno de nuestros ejemplos anteriores, DrawPrimitivesThick.mq5, y a crear un script basado en él. Lo llamaremos DrawPrimitivesSmooth.mq5. En el script, sustituimos los métodos PolylineThick y PolygonThick por PolylineSmooth PolygonSmooth. A continuación, desplazamos las coordenadas de la línea quebrada y el polígono 2 celdas hacia la izquierda para que las primitivas se dibujen aproximadamente en el centro.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};
    

    En el listado, resaltamos los valores modificados. El resultado debería ser el siguiente script.

    #property script_show_inputs
    
    //--- input parameters
    input int sleep = 1000;
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #define drawPrimitives canvas.Erase(ColorToARGB(clrWhite, 255));                                      \
      canvas.PolylineSmooth(plX, plY, color_, size, lineStyle, endStyle);                                 \
      canvas.PolygonSmooth(pgX, pgY, color_, size, lineStyle, endStyle);                                  \
      canvas.Update(true);                                                                                \
      Sleep(sleep);
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
      int size = 3;
      ENUM_LINE_END endStyle = LINE_END_BUTT;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.LineStyleSet(STYLE_SOLID);
      drawPrimitives
      
      Sleep(6000);
      canvas.Destroy();
     }
    

    Vamos a ejecutar el ejemplo obtenido y a ver el resultado.

    Primitivas según el algoritmo de Bézier (Smooth)

    En la imagen, podemos ver una línea quebrada y un polígono que se construyen con curvas de Bézier. Los métodos PolylineSmooth PolygonSmooth son los parámetros tension (suavizado) y step (salto de aproximación) según los cuales se dibujan las curvas de Bézier. Vamos a crear un ejemplo en el que estos parámetros se modifiquen a intervalos determinados, mostrando además el resultado de estos cambios en la pantalla. Tomaremos el ejemplo DrawPrimitivesThick-2.mq5, y basándonos en él, crearemos el script DrawPrimitivesSmooth-2.mq5. Para evitar un análisis demasiado prolongado del proceso de cambio del código, iremos directamente al resultado.

    #property script_show_inputs
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    
    //--- input parameters
    input int sleep = 1000;
    input int lineSize = 3;
    input ENUM_LINE_STYLE lineStyle = STYLE_SOLID;
    input ENUM_LINE_END lineEndStyle = LINE_END_BUTT;
    input double minTension = 0.0;
    input double stepTension = 0.1;
    input double maxTension = 1.0;
    input double minStep = 1.0;
    input double stepStep = 5.0;
    input double maxStep = 21.0;
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint color_ = ColorToARGB(clrDodgerBlue, 255);
      uint fillColor = ColorToARGB(clrRed, 255);
      int plX[] = {3, 5, 6, 8, 9, 11}, plY[] = {13, 11, 12, 10, 11, 9};
      int pgX[] = {14, 15, 14, 12, 11, 12}, pgY[] = {7, 5, 3, 3, 5, 7};
      ENUM_LINE_STYLE style = lineStyle;
      int size = lineSize;
      ENUM_LINE_END endStyle = lineEndStyle;
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)plX.Size(); i++)
        plX[i] *= point;
      for (int i = 0; i < (int)plY.Size(); i++)
        plY[i] *= point;
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      for (double tension = minTension; tension <= maxTension; tension += stepTension)
        for (double step = minStep; step <= maxStep; step += stepStep)
         {
           canvas.Erase(ColorToARGB(clrWhite, 255));
           canvas.PolylineSmooth(plX, plY, color_, size, style, endStyle, tension, step);
           canvas.PolygonSmooth(pgX, pgY, color_, size, style, endStyle, tension, step);
           canvas.TextOut(point * 2, point, "Size: " + (string)size + "; Style: " + EnumToString(style) + "; End Style: " + EnumToString(endStyle) + ";", color_);
           canvas.TextOut(point * 2, point * 2, "Tension: " + DoubleToString(tension, 2) + ";" + " Step: " + DoubleToString(step, 2) + ";", color_);
           canvas.Update(true);
           Sleep(sleep);
         }
      
      canvas.Destroy();
     }
    

    Podemos ver que el código se ha cambiado casi por completo. Entre los parámetros de entrada aparecían los siguientes ajustes: lineSize, el tamaño de la línea; lineStyle, el estilo de la línea, y lineEndStyle , el estilo de finalización de la línea (que ahora no participan en el ciclo). Asimismo, hemos añadido los parámetros input para establecer los parámetros de dibujado de las curvas de BézierminTensionmaxTensionstepTension (intervalo y salto de los valores del parámetro de suavizado tension), así como minStepmaxStep stepStep (intervalo y salto de los valores del salto de aproximación step). A continuación, estos parámetros funcionarán en un ciclo, estableciendo todas las combinaciones posibles de los parámetros tension y step, según los parámetros stepTension y stepStep establecidos, respectivamente. Vamos a ejecutar el ejemplo y a ver lo que obtenemos como resultado.

    Primitivas con diferentes parámetros

    En esta animación GIF, vemos varias primitivas con parámetros de antialiasing variables, cuyos valores se muestran en la imagen, en la segunda línea.

    Dibujando primitivas coloreadas

    Al desarrollar aplicaciones que dibujan gráficos, hay veces que queremos dibujar sobre una primitiva en particular. En la clase CCanvac hay métodos con el prefijo Fill para estos casos; se encargan de rellenar las primitivas correspondientes con un color uniforme. Vamos a tomar nuestro ejemplo anterior, DrawPrimitivesWu.mq5, y a crear el script DrawPrimitivesFill.mq5 basándonos en él; necesitaremos que las primitivas tengan todas las coordenadas, y solo nos quedará sustituir los métodos correspondientes y especificar el color de rellenado. Vamos a sustituir los métodos FillPolygonFillTriangleFillCircle FillEllipse. El método Fill lo analizaremos más adelante (cuando veamos el rellenado). Echemos un vistazo al código resultante.

    void OnStart()
     {
      CCanvas canvas;
      int w, h;
      int minSize, point;
      uint fillColor = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.FillPolygon(pgX, pgY, fillColor);
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, fillColor); 
      canvas.FillCircle(point * 13, point * 12, point * 2, fillColor);
      canvas.FillEllipse(point * 11, point * 3, point * 15, point * 6, fillColor);
      canvas.Update(true);                                                                         
      
      Sleep(6000);
      canvas.Destroy();
     }
    

    Todos los cambios y las líneas añadidas se han resaltado en amarillo. Ejecutamos el script y miramos el resultado.

    Primitivas coloreadas (Fill) 

    En la imagen, podemos ver primitivas rellenadas con un color uniforme sobre las que podemos dibujar primitivas normales como un trazo, o primitivas de estilo de línea variable, como hemos comentado anteriormente.

    Rellenado

    Cualquiera que haya utilizado la herramienta "Rellenado" en un editor gráfico probablemente pueda adivinar de qué vamos a hablar. Ahora es el momento de ver el método Fill que hemos mencionado antes, y que hace exactamente eso: rellenar un área de una imagen de un determinado color con otro color. Vamos a crear un nuevo ejemplo en forma de indicador (dado que procesará los eventos del gráfico (OnChartEvent) en lugar de un script). Mucha gente se preguntará: ¿por qué necesitamos en este caso procesar los eventos del gráfico? Vamos a crear un ejemplo en el que podamos seleccionar el color de rellenado y, clicando en cualquier punto de la imagen, rellenar este con el color seleccionado. Creamos un nuevo indicador y lo llamamos Filling.mq5. Vamos a ver el código del indicador.

    #property indicator_chart_window
    #property indicator_plots 0
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    #include <Canvas\Canvas.mqh>
    #include <ChartObjects\ChartObjectsTxtControls.mqh>
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    CCanvas canvas;
    CChartObjectButton colors[14];
    CChartObjectButton * oldSelected = NULL;
    uint fillColor = 0;
    
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
     {
      int w, h;
      int minSize, point;
      uint Color1 = ColorToARGB(clrDodgerBlue, 255);
      uint Color2 = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
    
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));
      canvas.Polygon(pgX, pgY, Color1);
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, Color2);
      canvas.FillCircle(point * 13, point * 12, point * 2, Color2);
      canvas.Ellipse(point * 11, point * 3, point * 15, point * 6, Color1);
      canvas.Update(true); 
      
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
      
      for (int i = 0; i < (int)colors.Size(); i++)
       {
        colors[i].Create(0, "color-"+(string)i, 0, (int)((i + 2.8) * point), point, 21, 21);
        colors[i].SetInteger(OBJPROP_BGCOLOR, ColorToARGB(CCanvas::GetDefaultColor(i), 0));
       }
      
      if ((oldSelected = GetPointer(colors[(int)colors.Size()-1])) == NULL)
        return(INIT_FAILED); 
      oldSelected.State(1);  
      fillColor = ColorToARGB((color)oldSelected.GetInteger(OBJPROP_BGCOLOR), 255);
      
      return(INIT_SUCCEEDED);
     }
     
    //+------------------------------------------------------------------+
    //| Custom indicator deinitialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
     {
      canvas.Destroy();
     }
     
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const int begin,
                    const double &price[])
     {
    //---
    //--- return value of prev_calculated for next call
      return(rates_total);
     }
    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
     {
      uint mouseState = (uint)sparam;
      int x = (int)lparam, y = (int)dparam;
      CChartObjectButton * colorBtn;
      int left, right, bottom, top;
      
      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          for (int i = 0; i < (int)colors.Size(); i++) 
           {
            if ((colorBtn = GetPointer(colors[i])) == NULL)
              return;
            left = colorBtn.X_Distance();
            top = colorBtn.Y_Distance();
            right = left + colorBtn.X_Size() - 1;
            bottom = top + colorBtn.Y_Size() - 1;
            if (x >= left && x <= right && y >= top && y <= bottom) {
              fillColor = ColorToARGB((color)colorBtn.GetInteger(OBJPROP_BGCOLOR), 255);
              if (oldSelected == NULL)
                return;
              oldSelected.State(0);
              oldSelected = GetPointer(colorBtn);
              ChartRedraw();
              return;
            }
           }
          canvas.Fill(x, y, fillColor);
          canvas.Update(true);
         }
        
     }
    

    ¿Cómo funciona el ejemplo? En primer lugar, la variable canvas es accesible globalmente en todo el indicador.

    CCanvas canvas;

    Para seleccionar el color hay una matriz de 14 botones, y al clicar en uno de ellos, se definirá el color de rellenado.

    CChartObjectButton colors[14];

    A continuación, declaramos el puntero oldSelected.

    CChartObjectButton * oldSelected = NULL;

    La siguiente variable se usa para mantener el botón pulsado, para retornarlo a su posición original cuando se selecciona otro color, y para mantener el color de rellenado.

    uint fillColor = 0;

    A continuación, en el manejador OnInit, podemos ver el código ya conocido para crear un lienzo y dibujar las primitivas en él.

    int OnInit()
     {
      int w, h;
      int minSize, point;
      uint Color1 = ColorToARGB(clrDodgerBlue, 255);
      uint Color2 = ColorToARGB(clrRed, 255);
      int pgX[] = {4, 5, 7, 8, 7, 5}, pgY[] = {12, 10, 10, 12, 14, 14};
      w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
      h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
      minSize = (int)fmin(w, h);
      point = minSize / 15;
      canvas.CreateBitmapLabel("canvas", 0, 0, w, h, COLOR_FORMAT_XRGB_NOALPHA);
      
      canvas.FontSet("Calibri", -120);
      
      for (int i = 0; i < (int)pgX.Size(); i++)
        pgX[i] *= point;
      for (int i = 0; i < (int)pgY.Size(); i++)
        pgY[i] *= point;
    
      canvas.Erase(ColorToARGB(clrWhite, 255));                                                                                                                 
      canvas.Polygon(pgX, pgY, Color1);                                                          
      canvas.FillTriangle(point * 4, point * 8, point * 4, point * 3, point * 9, point * 3, Color2); 
      canvas.FillCircle(point * 13, point * 12, point * 2, Color2);                                  
      canvas.Ellipse(point * 11, point * 3, point * 15, point * 6, Color1);                     
      canvas.Update(true);
    ...
    

    A continuación, se activa el procesamiento de los eventos del ratón.

    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    Después, se crean los botones de los diferentes colores (para poder cambiar el color de rellenado).

      for (int i = 0; i < (int)colors.Size(); i++)
       {
        colors[i].Create(0, "color-"+(string)i, 0, (int)((i + 2.8) * point), point, 21, 21);
        colors[i].SetInteger(OBJPROP_BGCOLOR, ColorToARGB(CCanvas::GetDefaultColor(i), 0));
       }
    

    Sugerimos a los lectores que presten atención a la procedencia de los colores de los botones. En el código, este fragmento está resaltado en amarillo. Vemos el método estático CCanvas::GetDefaultColor. Fijando el parámetro i a partir de 0, podemos obtener la paleta de colores, cosa que hacemos en este ejemplo.

    A continuación, inicializamos un enlace para retornar el botón oldSelected a su estado original.

      if ((oldSelected = GetPointer(colors[(int)colors.Size()-1])) == NULL)
        return(INIT_FAILED); 
      oldSelected.State(1);
    

    E inicializamos el color de rellenado fillColor.

    fillColor = ColorToARGB((color)oldSelected.GetInteger(OBJPROP_BGCOLOR), 255);

    Después se realiza el procesamiento de los eventos del gráfico en OnChartEvent. En él se declaran las variables para obtener y almacenar los parámetros del ratón.

    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
     {
      uint mouseState = (uint)sparam;
      int x = (int)lparam, y = (int)dparam;
    ...
    

    A continuación, se declara una variable, el puntero para almacenar el botón pulsado,

    CChartObjectButton * colorBtn;

    que se determina por la entrada del cursor en las coordenadas de los lados del botón al pulsar el botón izquierdo del ratón. Las coordenadas de los lados se guardan en las siguientes variables.

    int left, right, bottom, top;

    A continuación, monitoreamos el evento CHARTEVENT_MOUSE_MOVE y pulsamos el botón izquierdo del ratón.

      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          ...
         }
    

    Este es el código donde se implementa la selección de colores y el rellenado de la imagen. Aquí, el ciclo itera todos los botones de la matriz de colores, detectando qué botón ha pulsado el usuario, guardando el color de este botón en la variable fillColor y retornando el botón previamente pulsado oldSelected a su posición original. Entonces tiene lugar la salida del manejador (return), porque se ha clicado en el botón y no en la imagen a rellenar. Si clicamos en la imagen, y no en uno de los botones colors, el control se transferirá más allá y la imagen se rellenará usando el método Fill con el color fillColor seleccionado con la siguiente actualización de la imagen (método Update).

      if (id == CHARTEVENT_MOUSE_MOVE) 
        if ((mouseState & 1) == 1) 
         {
          for (int i = 0; i < (int)colors.Size(); i++) 
           {
            if ((colorBtn = GetPointer(colors[i])) == NULL)
              return;
            left = colorBtn.X_Distance();
            top = colorBtn.Y_Distance();
            right = left + colorBtn.X_Size() - 1;
            bottom = top + colorBtn.Y_Size() - 1;
            if (x >= left && x <= right && y >= top && y <= bottom) {
              fillColor = ColorToARGB((color)colorBtn.GetInteger(OBJPROP_BGCOLOR), 255);
              if (oldSelected == NULL)
                return;
              oldSelected.State(0);
              oldSelected = GetPointer(colorBtn);
              ChartRedraw();
              return;
            }
           }
          canvas.Fill(x, y, fillColor);
          canvas.Update(true);
         }
    

    Ahora sugerimos al lector que ejecute el ejemplo resultante y pruebe a realizar un rellenado.

    Rellenado en CCanvas

    Como podemos ver en la animación GIF de abajo, hemos creado con la ayuda de CCanvas un rellenado que funciona de forma similar a la herramienta de rellenado de los editores gráficos.


    Conclusión

    En este artículo, hemos visto la estructura de la clase CCanvas, hemos analizado sus métodos y los hemos revisado en un orden que permite al usuario comprender al completo los principios básicos de la clase. Los ejemplos que hemos revisado explican claramente cómo trabajar con CCanvas, confirmando el material analizado y aclarando aspectos que resultan difíciles de entender al leer la parte teórica del artículo.


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

    Archivos adjuntos |
    MQL5.zip (21.43 KB)
    Ştefan Dascălu
    Ştefan Dascălu | 24 abr. 2022 en 06:39
    Muchas gracias por este útil artículo
    Facundo Laje
    Facundo Laje | 23 ago. 2022 en 00:09

    ¡Gracias por compartir este artículo! me gustaría aprender más sobre canvas, ¡mucho proyecto en mente!

    Saludos

    Aprendiendo a diseñar un sistema de trading con las Bandas de Bollinger Aprendiendo a diseñar un sistema de trading con las Bandas de Bollinger
    En este artículo, hablaremos sobre las Bandas de Bollinger, uno de los indicadores más populares en el mundo del trading. Asimismo, trataremos el análisis técnico y veremos cómo diseñar un sistema de trading algorítmico basado en el indicador de las Bandas de Bollinger.
    Plantilla para proyectar el MVC y posibilidades de uso (Parte 2): Esquema de interacción entre los tres componentes Plantilla para proyectar el MVC y posibilidades de uso (Parte 2): Esquema de interacción entre los tres componentes
    Este artículo continúa y completa el tema planteado en el último artículo: la plantilla MVC en los programas MQL. En este artículo, veremos un posible esquema de interacción entre estos tres componentes.
    Analizando por qué fallan los asesores expertos Analizando por qué fallan los asesores expertos
    En este artículo, ofrecemos un análisis de los datos de divisas para entender mejor por qué los asesores expertos pueden tener un buen rendimiento en algunos intervalos y un mal rendimiento en otros.
    Indicadores multiples en un gráfico (Parte 04): Comenzando con el EA Indicadores multiples en un gráfico (Parte 04): Comenzando con el EA
    Es la primera vez que se actualiza la funcionalidad del sistema de indicadores. En el artículo anterior Múltiples indicadores en un gráfico expliqué el código base para poder utilizar más de un indicador dentro de una subventana, pero lo que se presentó era sólo la base inicial de un sistema mucho mayor.