Creación de interfaces gráficas dinámicas MQL5 mediante el escalado de imágenes basado en recursos con interpolación bicúbica en gráficos de trading
Introducción
Personalizar los gráficos bursátiles con elementos visuales dinámicos puede mejorar nuestra forma de analizar los mercados, pero para conseguir una representación de imágenes de alta calidad es necesario un programación bien pensada en MetaQuotes Language 5 (MQL5). En este artículo presentamos una potente herramienta que permite crear interfaces gráficas dinámicas, aprovechando el escalado de imágenes basado en recursos con interpolación bicúbica para obtener gráficos nítidos y adaptables. Exploraremos el proceso a través de estos pasos:
- Descripción general de los gráficos de imágenes dinámicos basados en recursos
- Implementación en MQL5
- Pruebas y validación
- Conclusión
Al final, tendrás una herramienta sólida para transformar tus gráficos comerciales con gráficos de imágenes profesionales controlados por el usuario.
Descripción general de los gráficos de imágenes dinámicos basados en recursos
Nuestro objetivo es crear una herramienta MQL5 que incruste y escale imágenes en los gráficos de MetaTrader 5, creando interfaces gráficas dinámicas y controladas por el usuario. Cargaremos una imagen bitmap como recurso, la escalaremos para ajustarla a las dimensiones del gráfico utilizando interpolación bicúbica y la posicionaremos en función de las entradas del usuario, como el anclaje a las esquinas o el centrado dinámico. Esto nos permitirá superponer elementos visuales personalizados (logotipos o patrones) mientras mantenemos las relaciones de aspecto y alternamos la visualización entre el fondo y el primer plano, todo optimizado para un rendimiento en tiempo real. De esta manera, los gráficos serán más interesantes y atractivos.
Para escalar, utilizaremos interpolación bicúbica sobre el vecino más cercano y métodos bilineales. El vecino más cercano produce resultados pixelados y desenfoca los detalles. Aquí hay una visualización detallada de por qué elegimos la interpolación bicúbica en lugar de los otros métodos.
La visualización gráfica.

Visualización de pixelación resultante.

Bicúbico, aprovechando un vecindario de píxeles 4x4 y polinomios cúbicos, garantiza gradientes más suaves y bordes más nítidos. Elegimos bicúbico por su claridad y eficiencia superiores, ideal para cambiar el tamaño de forma dinámica en distintos tamaños de gráficos, ofreciendo imágenes nítidas que mejoran las decisiones comerciales. A continuación se muestra un ejemplo de lo que pretendemos, utilizando una imagen MQL5 personalizada.

Implementación en MQL5
Para crear el programa en MQL5, primero necesitaremos tener las imágenes en el formato correcto, que es Bitmap (BMP), que son imágenes de mapa de bits sin comprimir y con alta resolución, en comparación con el formato Joint Photographic Experts Group (JPEG). Por lo tanto, si tienes una imagen relevante que necesitas utilizar y está en cualquier otro formato, puedes convertirla utilizando recursos gratuitos en línea. En cuanto a nosotros, estas son las imágenes que utilizaremos, ya que las tenemos preparadas.

Después de obtener los archivos en los formatos respectivos, debemos moverlos a la carpeta de imágenes. Puede simplemente abrir el Navegador, ubicar la carpeta Imágenes, hacer clic derecho sobre ella y seleccionar "Abrir carpeta". Esto abrirá la carpeta predeterminada con dos archivos de imágenes en dólares y euros de forma predeterminada. Puedes pegar tus archivos de imagen allí. A continuación se muestra una ilustración visual.

Una vez hecho esto, ya estamos listos para empezar. Lo primero que haremos será agregar la imagen como recurso.
//+------------------------------------------------------------------+ //| Image Resource Cubic Interpolation Scaling.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #resource "\\Images\\mql5-circuit.bmp" //--- Include the image file as a resource #define ORIGINAL_IMAGE_RESOURCE "::Images\\mql5-circuit.bmp" //--- Define the resource path for the original image #define CHART_IMAGE_OBJECT_NAME "ChartImage" //--- Define the name of the chart image object
Aquí establecemos las bases para incrustar y mostrar nuestra imagen en los gráficos. Utilizamos la directiva #resource para incorporar un archivo de mapa de bits ubicado en «\Images\mql5-circuit.bmp» como recurso, asegurándonos de que esté fácilmente disponible para que nuestro programa pueda acceder a él.
A continuación, definimos la macro «ORIGINAL_IMAGE_RESOURCE», establecida en «::Images\mql5-circuit.bmp», para crear una referencia estandarizada a la ruta del recurso de la imagen, que utilizaremos para cargar los datos de la imagen. Además, definimos la macro «CHART_IMAGE_OBJECT_NAME» como «ChartImage», que utilizaremos como identificador único para el objeto gráfico que muestra la imagen, lo que nos permitirá gestionarlo controlando su apariencia en el gráfico.
Lo siguiente que necesitaremos es definir algunas variables globales y entradas que utilizaremos a lo largo del programa.
// Enum for selecting anchor corner enum ENUM_ANCHOR_CORNER { TOP_LEFT = 0, // Top-Left TOP_RIGHT = 1, // Top-Right BOTTOM_LEFT = 2, // Bottom-Left BOTTOM_RIGHT = 3 // Bottom-Right }; // Input parameters for user customization input bool LimitToOriginalSize = true; // Image scaling limited to original size input bool ImageInBackground = true; // Image displayed in background (true) or foreground (false) input bool CenterImageDynamically = true; // Image centered dynamically (true) or positioned manually (false) input ENUM_ANCHOR_CORNER AnchorCorner = TOP_LEFT; // Anchor corner for manual positioning input int XOffsetFromCorner = 100; // x-offset in pixels from the chosen corner input int YOffsetFromCorner = 100; // y-offset in pixels from the chosen corner // Counter for generating unique resource names for scaled images int scaled_resource_counter = 0; //--- Initialize a counter for creating unique resource names
Para configurar los controles de usuario para la colocación de imágenes, definimos la «ENUM_ANCHOR_CORNER» enumeración con las opciones «TOP_LEFT», «TOP_RIGHT», «BOTTOM_LEFT» y «BOTTOM_RIGHT» para seleccionar una esquina del gráfico.
Establecemos los parámetros de entrada: « LimitToOriginalSize» (verdadero) para limitar el escalado, «ImageInBackground» (verdadero) para la visualización del fondo/primer plano, «CenterImageDynamically» (verdadero) para el posicionamiento automático o manual, «AnchorCorner» (TOP_LEFT) para la selección de la esquina y «XOffsetFromCorner» e «YOffsetFromCorner» (100 píxeles) para los desplazamientos manuales. También inicializamos «scaled_resource_counter» a 0 para nombres de imágenes escaladas únicos.
Para mostrar la imagen en el gráfico, utilizaremos una función personalizada que albergará toda la lógica.
//+------------------------------------------------------------------+ //| Display the image on the chart | //+------------------------------------------------------------------+ bool DisplayImageOnChart() { // Load the original image from the resource uint image_pixels[]; //--- Declare an array to store image pixel data uint original_image_width, original_image_height; //--- Declare variables for original image dimensions if (!ResourceReadImage(ORIGINAL_IMAGE_RESOURCE, image_pixels, original_image_width, original_image_height)) { //--- Read the image resource into the pixel array Print("Error: Failed to read original image data from resource."); //--- Log an error if reading the image fails return false; //--- Return false to indicate failure } // Get chart dimensions int chart_pixel_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Retrieve the chart width in pixels int chart_pixel_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Retrieve the chart height in pixels // Calculate scaled dimensions while preserving aspect ratio double image_aspect_ratio = (double)original_image_width / original_image_height; //--- Calculate the aspect ratio of the original image double chart_aspect_ratio = (double)chart_pixel_width / chart_pixel_height; //--- Calculate the aspect ratio of the chart int scaled_image_width, scaled_image_height; //--- Declare variables for scaled image dimensions if (image_aspect_ratio > chart_aspect_ratio) { //--- Check if the image is wider relative to the chart scaled_image_width = chart_pixel_width; //--- Set scaled width to match chart width scaled_image_height = (int)(chart_pixel_width / image_aspect_ratio); //--- Calculate scaled height to maintain aspect ratio } else { scaled_image_height = chart_pixel_height; //--- Set scaled height to match chart height scaled_image_width = (int)(chart_pixel_height * image_aspect_ratio); //--- Calculate scaled width to maintain aspect ratio } // Limit scaling to original size if enabled if (LimitToOriginalSize) { //--- Check if the user has enabled limiting to original size scaled_image_width = MathMin(scaled_image_width, (int)original_image_width); //--- Restrict width to original width scaled_image_height = MathMin(scaled_image_height, (int)original_image_height); //--- Restrict height to original height // Recalculate one dimension to maintain aspect ratio if (scaled_image_width < scaled_image_height * image_aspect_ratio) { //--- Check if width is the limiting factor scaled_image_height = (int)(scaled_image_width / image_aspect_ratio); //--- Adjust height to maintain aspect ratio } else { scaled_image_width = (int)(scaled_image_height * image_aspect_ratio); //--- Adjust width to maintain aspect ratio } } // Log dimensions for debugging PrintFormat( "Original: %dx%d, Chart: %dx%d, Scaled: %dx%d", original_image_width, original_image_height, chart_pixel_width, chart_pixel_height, scaled_image_width, scaled_image_height ); //--- Log the original, chart, and scaled dimensions for debugging return true; }
Comenzamos definiendo la función «DisplayImageOnChart», que utilizaremos para representar nuestra imagen en el gráfico. Comenzamos declarando la matriz «image_pixels» para almacenar los datos de píxeles de la imagen y las variables «original_image_width» y «original_image_height» para almacenar las dimensiones de la imagen original. Mediante la función ResourceReadImage, cargamos los datos de la imagen desde la macro «ORIGINAL_IMAGE_RESOURCE» en la matriz «image_pixels», capturando su anchura y altura. Si esta operación falla, registramos un mensaje de error con la función Print y devolvemos false para indicar el fallo, deteniendo el procesamiento posterior.
A continuación, recuperamos las dimensiones del gráfico para asegurarnos de que la imagen se ajuste correctamente. Utilizamos la función ChartGetInteger para obtener el ancho y el alto del gráfico, y los almacenamos en «chart_pixel_width» y «chart_pixel_height» después de convertirlos a números enteros. Para mantener las proporciones de la imagen durante el escalado, calculamos la "image_aspect_ratio" dividiendo "original_image_width" por "original_image_height" y la "chart_aspect_ratio" dividiendo "chart_pixel_width" por "chart_pixel_height". Estas proporciones guiarán nuestra lógica de escalamiento.
Luego declaramos "scaled_image_width" y "scaled_image_height" para almacenar las dimensiones de la imagen escalada. Para preservar la relación de aspecto, comparamos la "image_aspect_ratio" con la "chart_aspect_ratio". Si la imagen es más ancha en relación con el gráfico (es decir, "image_aspect_ratio" excede "chart_aspect_ratio"), establecemos "scaled_image_width" en "chart_pixel_width" y calculamos "scaled_image_height" dividiendo "chart_pixel_width" por "image_aspect_ratio". De lo contrario, establecemos "scaled_image_height" en "chart_pixel_height" y calculamos "scaled_image_width" multiplicando "chart_pixel_height" por "image_aspect_ratio". Esto garantiza que la imagen se ajuste al gráfico sin distorsión.
Si la entrada "LimitToOriginalSize" es verdadera, restringimos la escala a las dimensiones de la imagen original para evitar el aumento de escala. Nosotros usamos elMatemáticasMinfunción para limitar "scaled_image_width" a "original_image_width" y "scaled_image_height" a "original_image_height". Para mantener la relación de aspecto después de esta restricción, verificamos si "scaled_image_width" es menor que "scaled_image_height" multiplicado por "image_aspect_ratio". Si es así, recalculamos "scaled_image_height" dividiendo "scaled_image_width" por "image_aspect_ratio"; de lo contrario, recalculamos "scaled_image_width" multiplicando "scaled_image_height" por "image_aspect_ratio".
Por último, registramos las dimensiones originales, del gráfico y escaladas utilizando la función PrintFormat. Usaremos esto para rastrear cambios dinámicos en el gráfico como se muestra a continuación.

Desde la imagen podemos ver que ya leímos el gráfico y las dimensiones de la imagen. Lo que necesitamos hacer ahora es escalar nuestra imagen dinámicamente para que encaje en el gráfico. Necesitaremos una función personalizada para ayudar a lograrlo.
//+------------------------------------------------------------------+ //| Scale the image using bicubic interpolation | //+------------------------------------------------------------------+ void ScaleImage( uint &pixels[], int original_width, int original_height, int new_width, int new_height ) { uint scaled_pixels[]; //--- Declare an array for scaled pixel data ArrayResize(scaled_pixels, new_width * new_height); //--- Resize the array to fit the scaled image for (int y = 0; y < new_height; y++) { //--- Iterate over each row of the scaled image for (int x = 0; x < new_width; x++) { //--- Iterate over each column of the scaled image // Map to original image coordinates double original_x = (double)x * original_width / new_width; //--- Calculate the corresponding x-coordinate in the original image double original_y = (double)y * original_height / new_height; //--- Calculate the corresponding y-coordinate in the original image // Apply bicubic interpolation uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate the pixel value scaled_pixels[y * new_width + x] = pixel; //--- Store the interpolated pixel in the scaled array } } ArrayResize(pixels, new_width * new_height); //--- Resize the original pixel array to the new dimensions ArrayCopy(pixels, scaled_pixels); //--- Copy the scaled pixels back to the original array }
Aquí, definimos la función "ScaleImage" para cambiar el tamaño de una imagen utilizando interpolación bicúbica. Declaramos la matriz «scaled_pixels» y utilizamos la función ArrayResize para ajustar su tamaño a «new_width» * «new_height» píxeles. Recorremos cada píxel, asignando coordenadas a la imagen original con «original_x» y «original_y».
Para cada uno, llamamos a la función «BicubicInterpolate», que explicaremos más adelante, para calcular el valor del píxel, almacenándolo en «scaled_pixels» en «y» * «new_width» + «x». Por último, cambiamos el tamaño de «pixels» con ArrayResize y copiamos «scaled_pixels» en él utilizando ArrayCopy para su posterior procesamiento en el gráfico.
Aquí está la función responsable de calcular el valor del píxel a través de la interpolación bicúbica. Primero definamos una función para calcular la interpolación de un solo elemento de color, luego podemos usarla para calcular la interpolación de píxeles individuales.
//+------------------------------------------------------------------+ //| Perform bicubic interpolation for a single color component | //+------------------------------------------------------------------+ double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) { // Calculate cubic interpolation weights for x double weights_x[4]; //--- Declare an array for x interpolation weights double t = fractional_x; //--- Store the fractional x value weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate weight for x-1 weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate weight for x weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate weight for x+1 weights_x[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate weight for x+2 // Interpolate in x for each y double y_values[4]; //--- Declare an array for intermediate y values for (int j = 0; j < 4; j++) { //--- Iterate over rows of the neighborhood y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] + weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Perform interpolation in x for each y } // Calculate cubic interpolation weights for y double weights_y[4]; //--- Declare an array for y interpolation weights t = fractional_y; //--- Store the fractional y value weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate weight for y-1 weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate weight for y weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate weight for y+1 weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate weight for y+2 // Interpolate in y double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] + weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Perform interpolation in y to get the final value // Clamp the result to valid color range [0, 255] return MathMax(0, MathMin(255, result)); //--- Clamp the interpolated value to the valid color range }
Aquí, implementamos la función "BicubicInterpolateComponent" para realizar una interpolación bicúbica para un solo componente de color en nuestro proceso de escalado de imágenes. Declaramos la matriz "weights_x" para almacenar los pesos de interpolación para el eje x y establecemos "t" en "fractional_x", la parte fraccionaria de la coordenada x.
Calculamos cuatro pesos en "weights_x" utilizando fórmulas polinomiales cúbicas para las posiciones x-1, x, x+1 y x+2, lo que permite una interpolación suave. A continuación, declaramos la matriz «y_values» array para almacenar los resultados intermedios y iteramos sobre cuatro filas de un vecindario de píxeles de 4x4. Para cada fila «j», calculamos «y_values[j]» multiplicando los valores «weights_x» por los componentes de color correspondientes de la matriz «components» en los índices «j * 4 + 0» a «j * 4 + 3», realizando una interpolación del eje x.
A continuación, declaramos la matriz «weights_y» y establecemos «t» en «fractional_y» para los pesos del eje y, calculándolos con las mismas fórmulas cúbicas para y-1, y, y+1 y y+2. Interpolamos en el eje y calculando el "result" como la suma ponderada de "y_values" utilizando "weights_y". Finalmente, fijamos "result" al rango de color válido [0, 255] usando MathMax y MathMin funciones que garantizan que la salida sea adecuada para la representación del color de los píxeles. También necesitaremos componentes del canal de color para cada píxel, así que tengamos una función para realizar la extracción del canal de color.
//+------------------------------------------------------------------+ //| Extract ARGB components from a pixel | //+------------------------------------------------------------------+ void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) { alpha = (uchar)((pixel >> 24) & 0xFF); //--- Extract the alpha channel from the pixel red = (uchar)((pixel >> 16) & 0xFF); //--- Extract the red channel from the pixel green = (uchar)((pixel >> 8) & 0xFF); //--- Extract the green channel from the pixel blue = (uchar)(pixel & 0xFF); //--- Extract the blue channel from the pixel }
Implementamos la función «GetArgb» para extraer componentes de color ARGB individuales de un píxel específico. Tomamos el valor de "pixel" y hacemos referencia a las variables "alpha", "red", "green" y "blue" para almacenar los componentes extraídos. Mediante operaciones bit a bit, aislamos cada componente: desplazamos «pixel» 24 bits a la derecha y lo enmascaramos con «0xFF» para extraer «alpha»; lo desplazamos 16 bits y lo enmascaramos para «red»; lo desplazamos 8 bits y lo enmascaramos para «green»; y enmascaramos los 8 bits más bajos para «blue». Cada resultado se convierte a uchar, lo que garantiza que los valores se ajusten al rango de 0 a 255 para la representación del color.
Utilizamos el sistema «0xFF» para garantizar que enmascara el byte deseado (8 bits) de un píxel entero sin signo de 32 bits, que representa un color en formato ARGB, lo que garantiza la extracción precisa de los valores alfa, rojo, verde o azul. Sin «& 0xFF», obtendríamos resultados inesperados porque podrían permanecer los bits más altos de otros canales. Si te estás preguntando qué son los caracteres, solo estamos hablando de 255 en formato hexadecimal. Echa un vistazo aquí.

Armados con estas funciones, ahora podemos definir una función para realizar la interpolación bicúbica de un solo píxel para escalar la imagen con precisión.
//+------------------------------------------------------------------+ //| Perform bicubic interpolation for a single pixel | //+------------------------------------------------------------------+ uint BicubicInterpolate( uint &pixels[], int width, int height, double x, double y ) { // Get integer and fractional parts int x0 = (int)x; //--- Extract the integer part of the x-coordinate int y0 = (int)y; //--- Extract the integer part of the y-coordinate double fractional_x = x - x0; //--- Calculate the fractional part of the x-coordinate double fractional_y = y - y0; //--- Calculate the fractional part of the y-coordinate // Define 4x4 neighborhood int x_indices[4], y_indices[4]; //--- Declare arrays for x and y indices for (int i = -1; i <= 2; i++) { //--- Iterate over the 4x4 neighborhood x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Calculate clamped x-index y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Calculate clamped y-index } // Get 16 pixels in the 4x4 neighborhood uint neighborhood_pixels[16]; //--- Declare an array for the 4x4 neighborhood pixels for (int j = 0; j < 4; j++) { //--- Iterate over rows of the neighborhood for (int i = 0; i < 4; i++) { //--- Iterate over columns of the neighborhood neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store the pixel value } } // Extract ARGB components uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare arrays for ARGB components for (int i = 0; i < 16; i++) { //--- Iterate over the neighborhood pixels GetArgb( neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i] ); //--- Extract ARGB components for each pixel } // Perform bicubic interpolation for each component uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate the alpha component uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate the red component uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate the green component uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate the blue component // Combine components into a single pixel return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine ARGB components into a single pixel value }
Aquí, implementamos la función "BicubicInterpolate" para calcular el color de un solo píxel utilizando el proceso de interpolación bicúbica. Comenzamos extrayendo las partes enteras de las coordenadas de entrada "x" e "y" en "x0" e "y0", y calculamos sus partes fraccionarias como "fractional_x" y "fractional_y". Definimos las matrices "x_indices" e "y_indices" para almacenar las coordenadas de un vecindario de 4x4, iterando de -1 a 2 para calcular índices alrededor de "x0" e "y0". Utilizando MathMin y MathMax, los limitamos a los rangos válidos [0, "width"-1] and [0, "height"-1].
Luego creamos la matriz "neighborhood_pixels" para contener 16 píxeles del vecindario de 4x4, y la rellenamos iterando sobre "y_indices" y "x_indices" para obtener los valores de píxeles de la matriz "pixels" en "y_indices[j] * ancho + x_indices[i]". A continuación, declaramos las matrices "alpha_components", "red_components", "green_components" y "blue_components" y usamos la función "GetArgb" para extraer valores ARGB para cada uno de los 16 píxeles en "neighborhood_pixels".
Para cada componente de color, llamamos a la función "BicubicInterpolateComponent" con "fractional_x" y "fractional_y", almacenando los resultados en "alpha_out", "red_out", "green_out" y "blue_out" como valores "uchar". Finalmente, los combinamos en un único valor de píxel mediante desplazamientos bit a bit: "alpha_out" desplazado 24 bits, "red_out" 16 bits, "green_out" 8 bits y "blue_out", devolviendo el "uint" resultante para usar en la imagen escalada. Ahora podemos llamar a la función "ScaleImage" para que tenga efecto.
// Scale the image using bicubic interpolation ScaleImage(image_pixels, original_image_width, original_image_height, scaled_image_width, scaled_image_height); //--- Scale the image to the calculated dimensions
Después de llamar a la función, ahora podemos crear un nuevo recurso con la imagen escalada.
// Create a unique resource name for the scaled image string scaled_resource_name = "::ScaledImage" + IntegerToString(scaled_resource_counter++); //--- Generate a unique resource name using the counter // Create a new resource with the scaled image if (!ResourceCreate( scaled_resource_name, image_pixels, scaled_image_width, scaled_image_height, 0, 0, scaled_image_width, COLOR_FORMAT_ARGB_NORMALIZE )) { //--- Create a new resource for the scaled image Print("Error: Failed to create resource for scaled image: ", scaled_resource_name); //--- Log an error if resource creation fails return false; //--- Return false to indicate failure }
Para generar y almacenar una imagen escalada como recurso, creamos un nombre de recurso único definiendo la cadena «scaled_resource_name», concatenando «::ScaledImage» con el resultado de la función IntegerToString aplicada a «scaled_resource_counter», que luego incrementamos para garantizar la unicidad de los recursos posteriores.
A continuación, utilizamos la función ResourceCreate para crear un nuevo recurso con «scaled_resource_name», utilizando la matriz «image_pixels», «scaled_image_width», "scaled_image_height" y especificando desplazamientos de 0, un ancho de "scaled_image_width" y el formato COLOR_FORMAT_ARGB_NORMALIZE. Si «ResourceCreate» falla, registramos un error con la función Print, incluyendo «scaled_resource_name», y devolvemos false para indicar el fallo, deteniendo el procesamiento posterior.
Si pasamos por aquí, solo tenemos que posicionar y visualizar la imagen en el gráfico, así que primero definamos las coordenadas de posicionamiento.
// Determine image position based on user input int x_offset, y_offset; //--- Declare variables for x and y offsets if (CenterImageDynamically) { //--- Check if the user wants to center the image dynamically x_offset = (chart_pixel_width - scaled_image_width) / 2; //--- Calculate horizontal offset to center the image y_offset = (chart_pixel_height - scaled_image_height) / 2; //--- Calculate vertical offset to center the image } else { // Set base position based on chosen anchor corner switch (AnchorCorner) { //--- Select the anchor corner based on user input case TOP_LEFT: //--- Handle Top-Left corner x_offset = XOffsetFromCorner; //--- Use user-defined x-offset from top-left y_offset = YOffsetFromCorner; //--- Use user-defined y-offset from top-left break; case TOP_RIGHT: //--- Handle Top-Right corner x_offset = chart_pixel_width - scaled_image_width - XOffsetFromCorner; //--- Calculate x-offset from right edge y_offset = YOffsetFromCorner; //--- Use user-defined y-offset from top break; case BOTTOM_LEFT: //--- Handle Bottom-Left corner x_offset = XOffsetFromCorner; //--- Use user-defined x-offset from left y_offset = chart_pixel_height - scaled_image_height - YOffsetFromCorner; //--- Calculate y-offset from bottom break; case BOTTOM_RIGHT: //--- Handle Bottom-Right corner x_offset = chart_pixel_width - scaled_image_width - XOffsetFromCorner; //--- Calculate x-offset from right edge y_offset = chart_pixel_height - scaled_image_height - YOffsetFromCorner; //--- Calculate y-offset from bottom break; default: //--- Handle unexpected case x_offset = XOffsetFromCorner; //--- Default to top-left x-offset y_offset = YOffsetFromCorner; //--- Default to top-left y-offset } }
En cuanto al posicionamiento, declaramos «x_offset» y «y_offset». Si «CenterImageDynamically» es verdadero, centramos la imagen con «x_offset» como («chart_pixel_width» - «scaled_image_width») / 2 y «y_offset» como («chart_pixel_height» - «scaled_image_height») / 2.
De lo contrario, un switch en «AnchorCorner» establece los desplazamientos: «TOP_LEFT» utiliza «XOffsetFromCorner» y «YOffsetFromCorner»; «TOP_RIGHT» establece «x_offset» en «chart_pixel_width» - «scaled_image_width» - «XOffsetFromCorner»; «BOTTOM_LEFT» establece «y_offset» en «chart_pixel_height» - «scaled_image_height» - «YOffsetFromCorner»; «BOTTOM_RIGHT» combina ambos. Por defecto se utiliza «XOffsetFromCorner» y «YOffsetFromCorner».
Ahora necesitamos una función para posicionar la imagen mediante las coordenadas definidas.
//+------------------------------------------------------------------+ //| Create and position the chart image object | //+------------------------------------------------------------------+ void CreateFullChartImage( string object_name, string resource_name, int x_size, int y_size, int x_offset, int y_offset, bool is_background ) { // Create the bitmap label object if it doesn't exist if (ObjectFind(0, object_name) < 0) { //--- Check if the object already exists ObjectCreate(0, object_name, OBJ_BITMAP_LABEL, 0, 0, 0); //--- Create a new bitmap label object } // Set object properties ObjectSetString(0, object_name, OBJPROP_BMPFILE, resource_name); //--- Set the resource file for the bitmap ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Set the width of the bitmap ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Set the height of the bitmap ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_offset); //--- Set the horizontal position of the bitmap ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_offset); //--- Set the vertical position of the bitmap ObjectSetInteger(0, object_name, OBJPROP_BACK, is_background); //--- Set whether the bitmap is in the background based on input // Redraw the chart to update the display ChartRedraw(0); //--- Redraw the chart to reflect changes }
Aquí, implementamos la función "CreateFullChartImage" para crear y posicionar un objeto de imagen en el gráfico. Primero comprobamos si el objeto denominado «object_name» existe utilizando la función ObjectFind. Si no es así (result < 0), creamos un nuevo objeto de etiqueta de mapa de bits con la función ObjectCreate, especificando «object_name», OBJ_BITMAP_LABEL y las coordenadas predeterminadas del gráfico.
A continuación, configuramos las propiedades del objeto: utilizando ObjectSetString, vinculamos «resource_name» a OBJPROP_BMPFILE para la fuente de la imagen; con ObjectSetInteger, configuramos «OBJPROP_XSIZE» como «x_size», «OBJPROP_YSIZE» a «y_size», «OBJPROP_XDISTANCE» a «x_offset» y «OBJPROP_YDISTANCE» a «y_offset» para el tamaño y la posición; y OBJPROP_BACK a «is_background» para alternar entre la visualización en primer plano y en segundo plano. Por último, llamamos a la función ChartRedraw para actualizar la visualización del gráfico con el objeto de imagen nuevo o modificado. A continuación, llamamos a esta función para visualizar la imagen.
CreateFullChartImage(
CHART_IMAGE_OBJECT_NAME, scaled_resource_name,
scaled_image_width, scaled_image_height,
x_offset, y_offset, ImageInBackground
); //--- Create and position the chart image object, using user-specified background setting
Después de llamar a la función, ya estamos listos y podemos llamar a esta función principal ahora en el controlador de eventos OnInit para crear la primera imagen.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Display the image on the chart during initialization if (!DisplayImageOnChart()) { //--- Attempt to display the image on the chart Print("Error: Failed to display the initial image."); //--- Log an error if image display fails return (INIT_FAILED); //--- Return failure status if initialization fails } return (INIT_SUCCEEDED); //--- Return success status if initialization succeeds }
En el controlador de eventos OnInit, llamamos a la función «DisplayImageOnChart» para renderizar la imagen durante la inicialización. Si «DisplayImageOnChart» devuelve falso, lo que indica un error al mostrar la imagen, registramos un mensaje de error y devolvemos INIT_FAILED para indicar que la inicialización ha fallado, deteniendo el programa. Si la visualización se realiza correctamente, devolvemos INIT_SUCCEEDED para indicar que la inicialización se ha completado con éxito, lo que permite que el programa continúe. Tras la compilación, esto es lo que tenemos.

Desde la imagen, podemos ver que podemos mapear la imagen del recurso en la ejecución inicial. Sin embargo, necesitamos hacer que la imagen responda cuando cambian las dimensiones del gráfico. Por lo tanto, necesitaremos el controlador de eventos OnChartEvent.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent( const int event_id, // Event ID const long &long_param, // Long type event parameter const double &double_param, // Double type event parameter const string &string_param // String type event parameter ) { // Handle chart resize events to update the image if (event_id == CHARTEVENT_CHART_CHANGE) { //--- Check if the event is a chart change (e.g., resize) if (!DisplayImageOnChart()) { //--- Attempt to update the image on the chart Print("Error: Failed to update image on chart resize."); //--- Log an error if image update fails } } }
Aquí definimos el controlador de eventos OnChartEvent para gestionar los eventos relacionados con los gráficos en nuestro programa, centrándonos específicamente en el cambio de tamaño de los gráficos. Comprobamos si «event_id» es igual a CHARTEVENT_CHART_CHANGE, lo que indica una modificación del gráfico, como un cambio de tamaño. Si es cierto, llamamos a la función «DisplayImageOnChart» para actualizar la visualización de la imagen y que coincida con las nuevas dimensiones del gráfico. Si «DisplayImageOnChart» devuelve falso, lo que indica un error al actualizar la imagen, registramos un mensaje de error para alertar al usuario del problema. Por último, debemos eliminar todos los recursos que hemos asignado en el gráfico cuando eliminemos el programa.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up by deleting the chart image object ObjectDelete(0, CHART_IMAGE_OBJECT_NAME); //--- Remove the chart image object during deinitialization // Dynamic resources are automatically freed when the EA is removed //--- Note that dynamic resources are automatically released }
Para garantizar una salida limpia, en el controlador de eventos OnDeinit, llamamos a la función ObjectDelete para eliminar del gráfico el objeto de imagen identificado por «CHART_IMAGE_OBJECT_NAME». Esto evita que queden objetos residuales después de que finalice el programa. Tenga en cuenta que los recursos dinámicos, como los creados para imágenes escaladas, son liberados automáticamente por el sistema cuando se elimina el Asesor Experto, sin necesidad de realizar ninguna limpieza adicional. Cuando ejecutamos el programa, obtenemos el siguiente resultado.

En la imagen podemos ver que creamos la imagen definida, la establecemos como recurso y la escalamos dinámicamente según lo definido por el usuario, logrando así nuestro objetivo. Ahora solo queda probar el programa a fondo, lo cual se trata en el siguiente tema.
Pruebas y validación
Realizamos todas las pruebas y las recopilamos en una visualización en formato GIF (Graphical Interchange Format) que muestra que es dinámica y responde al cambio de tamaño de los gráficos y a las entradas del usuario, sin perder calidad en el proceso.

Conclusión
En conclusión, hemos creado una herramienta MQL5 para gráficos de imágenes dinámicas en los gráficos de MetaTrader 5, utilizando interpolación bicúbica para obtener imágenes nítidas y adaptables. Hemos mostrado cómo implementarlo y aplicarlo para el escalado y posicionamiento de imágenes controlado por el usuario. Puede personalizar esta herramienta para mejorar la funcionalidad y la estética de sus gráficos bursátiles.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17892
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Del básico al intermedio: Indicador (V)
Simulación de mercado (Parte 20): Iniciando el SQL (III)
Visión por computadora para el trading (Parte 1): Creamos una funcionalidad básica sencilla
Del básico al intermedio: Herencia
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso