- Descripción de recursos mediante la directiva #resource
- Uso compartido de recursos de distintos programas MQL
- Variables de recursos
- Conectar indicadores personalizados como recursos
- Creación de recursos dinámicos: ResourceCreate
- Eliminar recursos dinámicos: ResourceFree
- Leer y modificar datos de recursos: RecursoReadImage
- Guardar imágenes en un archivo: ResourceSave
- Fuentes y salida de texto a recursos gráficos
- Aplicación de recursos gráficos en trading
Guardar imágenes en un archivo: ResourceSave
La API de MQL5 permite escribir un recurso en un archivo BMP mediante la función ResourceSave. Actualmente, el marco sólo admite recursos de imagen.
bool ResourceSave(const string resource, const string filename)
Los parámetros resource y filename especifican el nombre del recurso y del archivo, respectivamente. El nombre del recurso debe empezar por «::». El nombre del archivo puede contener una ruta relativa a la carpeta MQL5/Files. Si es necesario, la función creará todos los subdirectorios intermedios. Si el archivo especificado existe, se sobrescribirá.
La función devuelve true en caso de éxito.
Para comprobar el funcionamiento de esta función conviene crear una imagen original. Tenemos exactamente la imagen adecuada para ello.
Como parte del estudio de la POO, en el capítulo Clases e interfaces iniciamos una serie de ejemplos sobre formas gráficas: desde la primera versión Shapes1.mq5 en la sección sobre Definición de clases a la última versión Shapes6.mq5 en la sección sobre Tipos anidados. Entonces, dibujar no estaba a nuestro alcance, y no fue sino en el capítulo sobre objetos gráficos que pudimos implementar la visualización en el script ObjectShapesDraw.mq5. Ahora, tras estudiar los recursos gráficos, ha llegado el momento de otra «mejora».
En la nueva versión del script ResourceShapesDraw.mq5 dibujaremos las formas. Para facilitar el análisis de los cambios con respecto a la versión anterior, mantendremos el mismo conjunto de formas: rectángulo, cuadrado, óvalo, círculo y triángulo. Esto se hace para dar un ejemplo, y no porque algo nos limite a la hora de dibujar; al contrario: existe un potencial para ampliar el conjunto de formas, efectos visuales y etiquetado. Veremos las características en algunos ejemplos, empezando por el actual. No obstante, tenga en cuenta que no es posible demostrar toda la gama de aplicaciones en el ámbito de este libro.
Una vez generadas y dibujadas las formas, guardamos el recurso resultante en un archivo.
La base de la jerarquía de clases de forma es la clase Shape que tenía un método draw.
class Shape
|
En las clases derivadas, se implementó sobre la base de objetos gráficos, con llamadas a ObjectCreate y la posterior configuración de los objetos mediante las funciones de ObjectSet. El lienzo compartido de tal dibujo era el propio gráfico.
Ahora tenemos que pintar píxeles en algún recurso compartido de acuerdo con la forma en concreto. Es deseable asignar un recurso común y métodos para modificar píxeles en él en una clase separada o, mejor, una interfaz.
Una entidad abstracta nos permitirá no establecer vínculos con el método de creación y configuración del recurso. En concreto, nuestra próxima implementación colocará el recurso en un objeto OBJ_BITMAP_LABEL (como ya hemos hecho en este capítulo), y para algunos, puede ser suficiente para generar imágenes en la memoria y guardar en el disco sin trazado (ya que a muchos operadores les gusta capturar periódicamente gráficos de los estados).
Llamemos a la interfaz Drawing.
interface Drawing
|
He aquí sólo tres de los métodos más básicos para dibujar, que son suficientes para este caso.
El método point es público (lo que permite poner un punto aparte), pero en cierto sentido, es de bajo nivel, ya que todos los demás se implementarán a través de él. Por ello, las coordenadas que contiene son reales, y el contenido del píxel es un valor ya preparado del tipo uint. Esto permitirá, si es necesario, aplicar diversos algoritmos de suavizado para que las formas no se vean escalonadas debido al pixelado. Aquí no abordaremos esta cuestión.
Teniendo en cuenta una interfaz, el método Shape::draw se convierte en el siguiente:
virtual void draw(Drawing *drawing) = 0; |
A continuación, en la clase Rectangle, es muy fácil delegar el dibujo del rectángulo a una nueva interfaz.
class Rectangle : public Shape
|
Hay que esforzarse más para dibujar una elipse.
class Ellipse : public Shape
|
Por último, para el triángulo, el renderizado se implementa como sigue:
class Triangle: public Shape
|
Pasemos ahora a la clase MyDrawing, derivada de la interfaz Drawing. Se trata de MyDrawing, que debe, guiado por llamadas a métodos de interfaz en formas, garantizar que un determinado recurso se muestre en un mapa de bits. Por lo tanto, la clase describe variables para los nombres del objeto gráfico (object) y del recurso (sheet), así como el array data de tipo uint para almacenar la imagen. Además, hemos movido el array shapes de formas, declarado anteriormente en el manejador OnStart. Dado que MyDrawing es responsable de dibujar todas las formas, es mejor gestionar su conjunto aquí.
class MyDrawing: public Drawing
|
En el constructor creamos un objeto gráfico para el tamaño de todo el gráfico y asignamos memoria para el array data. El lienzo se rellena con ceros (lo que significa «transparencia negra») o con cualquier valor que se pase en el parámetro background, tras lo cual se crea un recurso basado en él. De manera predeterminada, el nombre del recurso empieza por la letra «D» e incluye el ID del gráfico actual, pero puede especificar otra cosa.
public:
|
El código de llamada puede averiguar el nombre del recurso utilizando el método resource.
string resource() const
|
El recurso y el objeto se eliminan en el destructor.
~MyDrawing()
|
El método push rellena el array de formas.
Shape *push(Shape *shape)
|
El método draw dibuja las formas. Simplemente llama al método draw de cada forma en el bucle y luego actualiza el recurso y el gráfico.
void draw()
|
A continuación se muestran los métodos más importantes que son los métodos de la interfaz Drawing y que realmente implementan el dibujo.
Empecemos por el método point, que por ahora presentamos de forma simplificada (más adelante nos ocuparemos de las mejoras).
virtual void point(const float x1, const float y1, const uint pixel) override
|
Basándose en point, es fácil implementar el dibujo de líneas. Cuando las coordenadas de los puntos inicial y final coinciden en una de las dimensiones, utilizamos el método rect para dibujar, ya que una línea recta es un caso degenerado de un rectángulo de espesor unitario.
virtual void line(const int x1, const int y1, const int x2, const int y2, const color clr) override
|
Y he aquí el método rect:
virtual void rect(const int x1, const int y1, const int x2, const int y2, const color clr) override
|
Ahora necesitamos modificar el manejador OnStart, y el script estará listo.
En primer lugar, configuramos el gráfico (ocultamos todos los elementos). En teoría, esto no es necesario: se deja para que coincida con el script prototipo.
void OnStart()
|
A continuación, describimos el objeto de la clase MyDrawing, generamos un número predefinido de formas aleatorias (aquí todo permanece inalterado, incluido el generador addRandomShape y la macro FIGURES igual a 21), las dibujamos en el recurso y las mostramos en el objeto del gráfico.
MyDrawing raster;
|
En el ejemplo ObjectShapesDraw.mq5, iniciamos un bucle sin fin en el que movemos las piezas aleatoriamente. Repitamos este truco aquí. Aquí tendremos que añadir la clase MyDrawing ya que el array de formas se almacena dentro de ella. Escribamos un método sencillo shake.
class MyDrawing: public Drawing
|
A continuación, en OnStart, podemos utilizar el nuevo método en un bucle hasta que el usuario detenga la animación.
void OnStart()
|
En este punto, prácticamente se repite la funcionalidad del ejemplo anterior. Pero necesitamos añadir el guardado de imágenes en un archivo, así que vamos a añadir un parámetro de entrada SaveImage.
input bool SaveImage = false; |
Cuando se establece en true, compruebe el rendimiento de la función ResourceSave.
void OnStart()
|
Además, ya que estamos hablando de variables de entrada, deje que el usuario seleccione un fondo y pase el valor resultante al constructor MyDrawing.
input color BackgroundColor = clrNONE;
|
Así pues, todo está listo para la primera prueba.
Si ejecuta el script ResourceShapesDraw.mq5, el gráfico formará una imagen como la siguiente:
Mapa de bits de un recurso con un conjunto de formas aleatorias
Al comparar esta imagen con lo que vimos en el ejemplo ObjectShapesDraw.mq5 resulta que nuestra nueva forma de renderizar es algo diferente a cómo el terminal muestra los objetos. Aunque las formas y los colores son correctos, los lugares donde se superponen las formas están indicados de forma diferente.
Nuestro script pinta las formas con el color especificado, superponiéndolas unas sobre otras en el orden en que aparecen en el array. Las formas posteriores se superponen a las anteriores. El terminal, por su parte, aplica algún tipo de mezcla de colores (inversión) en los lugares de solapamiento.
Ambos métodos tienen derecho a existir, aquí no hay errores. Sin embargo, ¿es posible conseguir un efecto similar al dibujar?
Tenemos control total sobre el proceso de dibujo, por lo que se le puede aplicar cualquier efecto, no sólo el del terminal.
Además de la forma original y sencilla de dibujar, implementemos algunos modos más. Todos ellos se resumen en la enumeración COLOR_EFFECT.
enum COLOR_EFFECT
|
Añadamos una variable de entrada para seleccionar el modo.
input COLOR_EFFECT ColorEffect = PLAIN; |
Apoyemos los modos en la clase MyDrawing. En primer lugar, describamos el campo y el método correspondientes.
class MyDrawing: public Drawing
|
A continuación, mejoramos el método point.
virtual void point(const float x1, const float y1, const uint pixel) override
|
Puede probar a ejecutar el script en diferentes modos y comparar los resultados. No olvide la posibilidad de personalizar el fondo. He aquí un ejemplo de cómo queda el aclaramiento.
Imagen de formas con mezcla de colores aclarados
Para apreciar visualmente la diferencia de efectos, puede desactivar la aleatorización de colores y el movimiento de formas. La forma estándar de superponer objetos corresponde a la constante COMPLEMENT.
Como experimento final, active la opción SaveImage. En el manejador OnStart, al generar el nombre del archivo con la imagen, usamos ahora el nombre del modo actual. Necesitamos obtener una copia de la imagen del gráfico en el archivo.
...
|
Para construcciones gráficas más sofisticadas de nuestra interfaz, Drawing puede no ser suficiente. Por lo tanto, puede utilizar clases de dibujo ya preparadas suministradas con MetaTrader 5 o disponibles en la base de código mql5.com. En concreto, eche un vistazo al archivo MQL5/Include/Canvas/Canvas.mqh.