English Русский 中文 Español Deutsch 日本語
preview
Criação de interfaces gráficas dinâmicas em MQL5 por meio de interpolação bicúbica

Criação de interfaces gráficas dinâmicas em MQL5 por meio de interpolação bicúbica

MetaTrader 5Negociação |
40 0
Allan Munene Mutiiria
Allan Munene Mutiiria

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:

  1. Visão geral das imagens gráficas dinâmicas baseadas em recursos
  2. Implementação em MQL5
  3. Testes e validação
  4. 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.

GRAPHS

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

PIXELATION

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.

OVERVIEW MQL5 BICUBIC IMAGE


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.

BITMAP FILE

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.

IMAGES FOLDER IN MQL5

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.

CHART CHANGES

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.

0xFF IN HEXADECIMAL

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.

INITIAL IMAGE RUN

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.

FINAL OUTCOME

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.

MQL5 BICUBIC INTERPOLATION IMAGE RESOURCE


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

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
WebSocket para MetaTrader 5: conexões assíncronas no lado do cliente usando a API do Windows WebSocket para MetaTrader 5: conexões assíncronas no lado do cliente usando a API do Windows
Neste artigo, descreve-se em detalhe o desenvolvimento de uma biblioteca DLL personalizada, destinada a simplificar conexões assíncronas no lado do cliente pelo protocolo WebSocket para programas MetaTrader.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Integração de um modelo de IA a uma estratégia de trading existente em MQL5 Integração de um modelo de IA a uma estratégia de trading existente em MQL5
Este artigo trata da integração de um modelo de IA treinado, por exemplo, um modelo LSTM para aprendizado por reforço ou um modelo preditivo baseado em machine learning, a uma estratégia de trading existente em MQL5.