Criação de interfaces gráficas dinâmicas em MQL5 por meio de interpolação bicúbica
Introdução
A personalização de gráficos de trading com elementos visuais dinâmicos pode aumentar a eficiência da análise de mercado, mas, para obter imagens com alta qualidade visual, é necessário um código bem planejado em MetaQuotes Language 5 (MQL5). Neste artigo, apresentamos uma ferramenta robusta para interfaces gráficas dinâmicas, que usa redimensionamento de imagens baseado em recursos por interpolação bicúbica para obter elementos visuais do gráfico nítidos e adaptáveis. Vamos examinar isso nas seguintes etapas:
- Visão geral das imagens gráficas dinâmicas baseadas em recursos
- Implementação em MQL5
- Testes e validação
- Conclusão
Ao final, você terá uma ferramenta confiável para transformar seus gráficos de trading com elementos gráficos profissionais controlados pelo usuário.
Visão geral das imagens gráficas dinâmicas baseadas em recursos
Nosso objetivo é criar uma ferramenta em MQL5 que incorpore e redimensione imagens em gráficos do MetaTrader 5, criando interfaces gráficas dinâmicas controladas pelo usuário. Vamos carregar uma imagem raster como recurso, redimensioná-la de acordo com as dimensões do gráfico por meio de interpolação bicúbica e posicioná-la com base em parâmetros definidos pelo usuário, como ancoragem aos cantos ou centralização dinâmica. Isso nos permitirá sobrepor elementos visuais personalizados, como logotipos ou modelos, preservando a proporção da imagem e alternando sua exibição em segundo plano ou primeiro plano. Todas as funções são otimizadas para operação em tempo real. Assim, os gráficos ficarão mais interessantes e atraentes.
Para o redimensionamento, compararemos a interpolação bicúbica com os métodos do vizinho mais próximo e bilinear. O método do vizinho mais próximo gera resultados pixelizados, enquanto o bilinear desfoca os detalhes. A seguir, mostramos uma visualização detalhada do motivo pelo qual escolhemos o método de interpolação bicúbica em vez de outros métodos.
Visualização do gráfico.

Visualização final do efeito de pixelização.

O método bicúbico, que usa uma vizinhança de 4x4 pixels e polinômios cúbicos, proporciona gradientes mais suaves e bordas mais nítidas. Escolhemos o método bicúbico por sua nitidez superior e eficiência, já que ele é ideal para o redimensionamento dinâmico de gráficos e fornece recursos visuais nítidos que ajudam a tomar decisões de trading mais eficazes. A seguir, um exemplo do resultado que buscamos usando uma imagem personalizada em MQL5.

Implementação em MQL5
Para criar o programa em MQL5, primeiro precisaremos de imagens no formato correto, o formato Bitmap (BMP), um formato de imagem raster sem compressão e com alta resolução em comparação com o formato Joint Photographic Experts Group (JPEG). Portanto, se você tiver uma imagem adequada que precise usar e ela estiver em outro formato, poderá convertê-la com conversores online gratuitos. No nosso caso, usaremos estas imagens, pois elas já estão preparadas.

Depois de obter os arquivos nos formatos adequados, precisamos movê-los para a pasta de imagens. Basta abrir o Navegador, localizar a pasta "Imagens", clicar nela com o botão direito do mouse e selecionar "Abrir pasta". Por padrão, será aberta uma pasta com dois arquivos de imagem, dólar e euro. Você pode colar ali os seus próprios arquivos de imagem. A seguir, uma ilustração visual.

Logo em seguida, já estaremos prontos para começar. A primeira coisa que faremos será adicionar a imagem 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
Aqui estabelecemos a base para incorporar e exibir nossa imagem nos gráficos. Usamos a diretiva #resource, para incluir como recurso o arquivo bitmap localizado na pasta "\Images\mql5-circuit.bmp", garantindo ao programa acesso fácil ao recurso.
Em seguida, definimos a macro "ORIGINAL_IMAGE_RESOURCE" com o valor "::Images\mql5-circuit.bmp" para criar uma referência padrão ao caminho do recurso da imagem, que usaremos para carregar os dados da imagem. Além disso, definimos a macro "CHART_IMAGE_OBJECT_NAME" como "ChartImage", que usaremos como identificador exclusivo do objeto gráfico que exibe a imagem, o que nos permite gerenciá-lo e controlar sua aparência no gráfico.
Em seguida, precisamos definir algumas variáveis globais e parâmetros de entrada que serão usados em todo o 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 as opções de posicionamento da imagem, definimos a enumeração "ENUM_ANCHOR_CORNER" com as opções "TOP_LEFT", "TOP_RIGHT", "BOTTOM_LEFT" e "BOTTOM_RIGHT" para selecionar o canto do gráfico.
Definimos os parâmetros de entrada: "LimitToOriginalSize" (true) para limitar o redimensionamento, "ImageInBackground" (true) para exibição em segundo plano ou primeiro plano, "CenterImageDynamically" (true) para posicionamento automático ou manual, "AnchorCorner" (TOP_LEFT) para selecionar o canto, além de "XOffsetFromCorner" e "YOffsetFromCorner" (100 pixels) para o deslocamento manual. Também inicializamos "scaled_resource_counter" com o valor 0 para criar nomes exclusivos para as imagens redimensionadas.
Para exibir a imagem no gráfico, usaremos uma função personalizada que reunirá toda a 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; }
Começaremos definindo a função "DisplayImageOnChart", que será usada para exibir nossa imagem no gráfico. Primeiro, declaramos o array "image_pixels" para armazenar os dados de pixels da imagem, bem como as variáveis "original_image_width" e "original_image_height" para armazenar as dimensões da imagem original. Usando a função ResourceReadImage, carregamos os dados da imagem do macro "ORIGINAL_IMAGE_RESOURCE" no array "image_pixels", registrando sua largura e altura. Se essa operação falhar, registramos uma mensagem de erro com a função Print e retornamos o valor false para indicar a falha e interromper o processamento subsequente.
Em seguida, obtemos as dimensões do gráfico para garantir que a imagem se ajuste corretamente. Usamos a função ChartGetInteger para obter a largura e a altura do gráfico, armazenando-as em "chart_pixel_width" e "chart_pixel_height" após a conversão para inteiros. Para preservar a proporção da imagem durante o redimensionamento, calculamos "image_aspect_ratio" dividindo "original_image_width" por "original_image_height", e "chart_aspect_ratio" dividindo "chart_pixel_width" por "chart_pixel_height". Esses valores orientarão nossa lógica de redimensionamento.
Depois disso, declaramos "scaled_image_width" e "scaled_image_height" para armazenar as dimensões da imagem redimensionada. Para preservar a proporção, comparamos "image_aspect_ratio" com "chart_aspect_ratio". Se a imagem for mais larga que o gráfico, ou seja, se "image_aspect_ratio" for maior que "chart_aspect_ratio", definimos "scaled_image_width" como "chart_pixel_width" e calculamos "scaled_image_height" dividindo "chart_pixel_width" por "image_aspect_ratio". Caso contrário, definimos "scaled_image_height" como "chart_pixel_height" e calculamos "scaled_image_width" multiplicando "chart_pixel_height" por "image_aspect_ratio". Isso garante que a imagem caiba no gráfico sem distorções.
Se o parâmetro de entrada "LimitToOriginalSize" estiver definido como true, limitamos o redimensionamento às dimensões originais da imagem para evitar ampliação. Usamos a função MathMin para limitar "scaled_image_width" ao valor de "original_image_width" e "scaled_image_height" ao valor de "original_image_height". Para preservar a proporção após essa limitação, verificamos se "scaled_image_width" é menor que "scaled_image_height" multiplicado por "image_aspect_ratio". Se for esse o caso, recalculamos "scaled_image_height" dividindo "scaled_image_width" por "image_aspect_ratio"; caso contrário, recalculamos "scaled_image_width" multiplicando "scaled_image_height" por "image_aspect_ratio".
Por fim, registramos as dimensões originais, do gráfico e da imagem redimensionada com a função PrintFormat. Usaremos esse registro para acompanhar as alterações dinâmicas no gráfico mostradas abaixo.

Na imagem, vemos que já verificamos as dimensões do gráfico e da imagem. Agora precisamos redimensionar dinamicamente nossa imagem para que ela se ajuste ao gráfico. Para isso, precisaremos de uma função personalizada.
//+------------------------------------------------------------------+ //| 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 }
Aqui definimos a função "ScaleImage" para redimensionar a imagem por meio de interpolação bicúbica. Declaramos o array "scaled_pixels" e usamos a função ArrayResize para definir seu tamanho como "new_width" * "new_height" pixels. Em seguida, iteramos sobre cada pixel, mapeando suas coordenadas para a imagem original usando .
Para cada um deles, chamamos a função "BicubicInterpolate" (que explicaremos abaixo) para calcular o valor do pixel e armazená-lo em "scaled_pixels" no endereço "y" * "new_width" + "x". Por fim, redimensionamos "pixels" com ArrayResize e copiamos "scaled_pixels" para ele com ArrayCopy para o processamento posterior no gráfico.
A seguir, definimos a função responsável por calcular o valor do pixel por meio de interpolação bicúbica. Primeiro, definiremos uma função para calcular a interpolação de um único componente de cor e, em seguida, poderemos usá-la para calcular a interpolação de pixels individuais.
//+------------------------------------------------------------------+ //| 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 }
Implementamos a função para executar a interpolação bicúbica de um único componente de cor durante o redimensionamento da imagem. Declaramos o array "weights_x" para armazenar os pesos de interpolação do eixo x e definimos "t" com o valor de "fractional_x", a parte fracionária da coordenada x.
Calculamos os quatro pesos em "weights_x" usando fórmulas de polinômios cúbicos para as posições x-1, x, x+1 e x+2, o que garante uma interpolação suave. Em seguida, declaramos o array "y_values" para armazenar os resultados intermediários e percorremos as quatro linhas da vizinhança de 4x4 pixels. Para cada linha "j", calculamos "y_values[j]" multiplicando os valores de "weights_x" pelos componentes de cor correspondentes do array "components" nos índices de "j 4 + 0" a "j 4 + 3", executando a interpolação ao longo do eixo x.
Depois, declaramos o array "weights_y" e definimos "t" com o valor de "fractional_y" para os pesos do eixo y, calculando-os com as mesmas fórmulas cúbicas para y-1, y, y+1 e y+2. Executamos a interpolação ao longo do eixo y, calculando "result" como uma soma ponderada de "y_values" usando . Por fim, limitamos "result" ao intervalo válido de cores [0, 255] com as funções MathMax e MathMin, garantindo que o valor final seja adequado para a exibição da cor do pixel. Também precisaremos dos componentes dos canais de cor de cada pixel, portanto criaremos uma função para extrair o canal de cor.
//+------------------------------------------------------------------+ //| 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 a função "GetArgb" para extrair os componentes de cor ARGB individuais do pixel alvo. Recebemos o valor "pixel" e as variáveis por referência "alpha", "red", "green" e "blue" para armazenar os componentes extraídos. Usando operações bit a bit, isolamos cada componente: deslocamos "pixel" 24 bits para a direita e aplicamos a máscara "0xFF" para extrair "alpha"; deslocamos 16 bits e aplicamos a máscara para "red"; deslocamos 8 bits e aplicamos a máscara para "green"; e também aplicamos a máscara aos 8 bits menos significativos para "blue". Cada resultado é convertido para uchar, garantindo que os valores permaneçam no intervalo de 0 a 255 para a representação de cor.
Usamos a máscara "0xFF" para garantir o mascaramento do byte correto (8 bits) no inteiro sem sinal de 32 bits do pixel, que representa a cor no formato ARGB, assegurando a extração exata dos valores de alfa, vermelho, verde e azul. Sem "& 0xFF", obteríamos resultados inesperados, pois os bits mais significativos de outros canais poderiam permanecer no valor. Se você estiver se perguntando o que esses símbolos representam, trata-se do número 255 em formato hexadecimal. Vamos ver.

Com essas funções definidas, agora podemos definir a função que executará a interpolação bicúbica para um único pixel, a fim de redimensionar a imagem com precisão.
//+------------------------------------------------------------------+ //| 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 }
Implementamos a função "BicubicInterpolate" para calcular a cor de um pixel individual usando interpolação bicúbica. Começamos extraindo as partes inteiras das coordenadas de entrada "x" e "y" em "x0" e "y0" e calculando suas partes fracionárias como "fractional_x" e "fractional_y". Definimos os arrays "x_indices" e "y_indices" para armazenar as coordenadas vizinhas no intervalo 4x4, iterando de -1 a 2 para calcular os índices ao redor de "x0" e "y0". Com MathMin e MathMax, limitamos esses índices aos intervalos válidos [0, "width"-1] e [0, "height"-1].
Em seguida, criamos o array "neighborhood_pixels" para armazenar os 16 pixels da vizinhança 4x4, preenchendo-o por meio da iteração sobre "y_indices" e "x_indices" para extrair os valores dos pixels do array "pixels" no endereço "y_indices[j] * width + x_indices[i]". Depois, declaramos os arrays "alpha_components", "red_components", "green_components" e "blue_components" e usamos a função "GetArgb" para extrair os valores ARGB de cada um dos 16 pixels em "neighborhood_pixels".
Para cada componente de cor, chamamos a função "BicubicInterpolateComponent" com "fractional_x" e "fractional_y", armazenando os resultados em "alpha_out", "red_out", "green_out" e "blue_out" como valores do tipo "uchar". Por fim, combinamos esses valores em um único valor de pixel usando deslocamentos de bits: "alpha_out" é deslocado 24 bits, "red_out" 16 bits, "green_out" 8 bits, retornando o "uint" resultante para uso na imagem redimensionada. Agora podemos chamar a função «ScaleImage» para que as alterações entrem em vigor.
// 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
Após chamar a função, já podemos criar um novo recurso com a imagem redimensionada.
// 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 criar e salvar a imagem redimensionada como recurso, criamos um nome de recurso exclusivo definindo a string «scaled_resource_name», combinando «::ScaledImage» com o resultado da função IntegerToString, aplicada a «scaled_resource_counter», que em seguida incrementamos para garantir unicidade nos recursos subsequentes.
Em seguida, usamos a função ResourceCreate para criar um novo recurso com "scaled_resource_name", usando o array "image_pixels", "scaled_image_width", "scaled_image_height" e especificando deslocamentos iguais a 0, a largura «scaled_image_width» e o formato COLOR_FORMAT_ARGB_NORMALIZE. Se "ResourceCreate" falhar, registramos o erro com a função Print, incluindo "scaled_resource_name", e retornamos false para indicar a falha, interrompendo o processamento subsequente.
Neste ponto, só precisamos posicionar e exibir a imagem no gráfico, então primeiro vamos definir as coordenadas de posicionamento.
// 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 } }
Quanto ao posicionamento, declaramos "x_offset" e "y_offset". Se "CenterImageDynamically" for true, centralizamos a imagem com "x_offset" igual a ("chart_pixel_width" - "scaled_image_width") / 2 e "y_offset" igual a ("chart_pixel_height" - "scaled_image_height") / 2.
Caso contrário, a instrução switch em: "TOP_LEFT" usa "XOffsetFromCorner" e "YOffsetFromCorner"; "TOP_RIGHT" define "x_offset" como "chart_pixel_width" - "scaled_image_width" - "XOffsetFromCorner"; "BOTTOM_LEFT" define "y_offset" como "chart_pixel_height" - "scaled_image_height" - "YOffsetFromCorner"; "BOTTOM_RIGHT" combina ambos. Por padrão, são usados "XOffsetFromCorner" e "YOffsetFromCorner".
Agora precisamos de uma função para posicionar a imagem nas coordenadas especificadas.
//+------------------------------------------------------------------+ //| 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 }
Implementamos a função "CreateFullChartImage" para criar e posicionar o objeto de imagem no gráfico. Primeiro, com a função ObjectFind, verificamos se já existe um objeto com o nome "object_name". Se não existir, ou seja, se o resultado for < 0, criamos um novo objeto gráfico do tipo OBJ_BITMAP_LABEL com a função ObjectCreate, especificando "object_name", OBJ_BITMAP_LABEL e as coordenadas padrão do gráfico.
Em seguida, definimos as propriedades do objeto: com ObjectSetString, associamos "resource_name" com OBJPROP_BMPFILE para a fonte da imagem; com ObjectSetInteger, definimos "OBJPROP_XSIZE" como "x_size", "OBJPROP_YSIZE" como "y_size", "OBJPROP_XDISTANCE" como "x_offset" e "OBJPROP_YDISTANCE" como "y_offset" para o tamanho e a posição; e OBJPROP_BACK como "is_background" para alternar entre exibição em segundo plano e em primeiro plano. Por fim, chamamos a função ChartRedraw para atualizar a exibição do gráfico com o objeto de imagem novo ou modificado. Em seguida, chamamos essa função para exibir a imagem.
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
Depois disso, tudo estará pronto, e poderemos chamar essa função principal no manipulador de eventos OnInit para criar a primeira imagem.
//+------------------------------------------------------------------+ //| 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 }
No manipulador de eventos OnInit, chamamos a função "DisplayImageOnChart" para exibir a imagem durante a inicialização. Se "DisplayImageOnChart" retornar false, indicando falha na exibição da imagem, registramos uma mensagem de erro e retornamos INIT_FAILED para sinalizar falha na inicialização, o que interrompe o programa. Se a exibição for bem-sucedida, retornamos INIT_SUCCEEDED para indicar que a inicialização foi concluída com sucesso, permitindo que o programa continue em execução. Após a compilação, é isso que temos.

Na imagem, vemos que conseguimos exibir a imagem original na primeira execução. No entanto, precisamos fazer com que a imagem reaja ao redimensionamento do gráfico. Para isso, precisaremos do manipulador 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 } } }
Definimos o manipulador de eventos OnChartEvent para tratar os eventos relacionados ao gráfico em nosso programa, com atenção especial ao redimensionamento do gráfico. Verificamos se "event_id" é igual a CHARTEVENT_CHART_CHANGE, o que indica uma alteração no gráfico, como uma mudança de tamanho. Se isso for verdadeiro, chamamos a função "DisplayImageOnChart" para atualizar a exibição da imagem de acordo com as novas dimensões do gráfico. Se "DisplayImageOnChart" retornar false, sinalizando falha na atualização da imagem, registramos uma mensagem de erro para alertar o usuário sobre o problema. Por fim, ao remover o programa, precisamos remover do gráfico o objeto de imagem exibido.
//+------------------------------------------------------------------+ //| 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 garantir o encerramento correto, no manipulador de eventos OnDeinit chamamos a função ObjectDelete para remover do gráfico o objeto de imagem identificado como "CHART_IMAGE_OBJECT_NAME". Isso evita que objetos residuais permaneçam após o término do programa. Observe que os recursos dinâmicos, como os criados para as imagens redimensionadas, são liberados automaticamente pelo sistema quando o EA é removido, sem exigir limpeza adicional. Ao executar o programa, obtemos o seguinte resultado.

Na imagem, vemos que criamos uma imagem específica, a definimos como recurso e a redimensionamos dinamicamente conforme as configurações do usuário, alcançando assim nosso objetivo. Agora só falta testar o programa com atenção. É disso que trataremos no próximo tópico.
Testes e validação
Realizamos todos os testes e geramos uma visualização em formato GIF (Graphical Interchange Format), mostrando que a solução é dinâmica e responde ao redimensionamento do gráfico e às entradas do usuário sem perda de qualidade.

Conclusão
Em conclusão, criamos uma ferramenta MQL5 para exibição dinâmica de imagens nos gráficos do MetaTrader 5, usando o método de interpolação bicúbica para obter recursos visuais nítidos e adaptáveis. Mostramos como implementá-la e aplicá-la ao redimensionamento e ao posicionamento de imagens sob controle do usuário. Essa ferramenta pode ser ajustada para melhorar a funcionalidade e a estética dos seus gráficos de trading.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17892
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
WebSocket para MetaTrader 5: conexões assíncronas no lado do cliente usando a API do Windows
Está chegando o novo MetaTrader 5 e MQL5
Integração de um modelo de IA a uma estratégia de trading existente em MQL5
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso