English Deutsch 日本語
preview
Создание динамических графических интерфейсов на MQL5 через бикубическую интерполяцию

Создание динамических графических интерфейсов на MQL5 через бикубическую интерполяцию

MetaTrader 5Трейдинг |
519 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

Пользовательская настройка торговых графиков с помощью динамических визуальных элементов может повысить эффективность анализа рынков, но для достижения высококачественной визуализации изображений требуется хорошо продуманное программирование на языке MetaQuotes Language 5 (MQL5). В настоящей статье мы представляем мощный инструмент, поддерживающий динамические графические интерфейсы, использующий ресурсоориентированное масштабирование изображений методом бикубической интерполяции для получения четких и адаптируемых визуальных элементов графика. Мы рассмотрим этот процесс на следующих этапах:

  1. Обзор ресурсоориентированных динамических графических изображений
  2. Реализация на MQL5
  3. Тестирование и проверка
  4. Заключение

В итоге у вас будет надежный инструмент для преобразования ваших торговых графиков с помощью управляемой пользователем профессиональной графики.


Обзор ресурсоориентированных динамических графических изображений

Мы стремимся создать инструмент на MQL5, который встраивает и масштабирует изображения в графики MetaTrader 5, создавая динамичные управляемые пользователем графические интерфейсы. Мы загрузим растровое изображение в качестве ресурса, масштабируем его в соответствии с размерами графика с помощью бикубической интерполяции и расположим его на основе таких пользовательских данных, как привязка к углам или динамическое центрирование. Это позволит нам накладывать пользовательские визуальные элементы — логотипы или модели — при сохранении соотношения сторон и переключении отображения фона или переднего плана. Все функции оптимизированы для работы в режиме реального времени. Таким образом, графики будут более интересными и привлекательными.

Для масштабирования мы будем использовать бикубическую интерполяцию по ближайшему соседу и билинейные методы. Ближайший сосед создает пикселизированные результаты, а билинейный размывает детали. Вот подробная визуализация того, почему мы выбрали метод бикубической интерполяции вместо других методов.

Визуализация графика.

GRAPHS

Итоговая визуализация пикселизации.

PIXELATION

Бикубический метод, использующий соседство 4x4 пикселя и кубические полиномы, обеспечивает более плавные градиенты и более четкие края. Мы выбрали бикубический формат из-за его превосходной четкости и эффективности, он идеально подходит для динамического изменения размера графиков и обеспечивает четкие визуальные эффекты, помогающие принимать более эффективные торговые решения. Вот пример того, к чему мы стремимся, используя пользовательское изображение MQL5.

OVERVIEW MQL5 BICUBIC IMAGE


Реализация на MQL5

Для создания программы на языке MQL5 нам сначала понадобятся изображения в корректном формате — формате Bitmap (BMP), представляющем собой несжатые растровые изображения с высоким разрешением по сравнению с форматом Joint Photographic Experts Group (JPEG). Итак, если у вас есть подходящее изображение, которое вам нужно использовать, и оно находится в каком-либо другом формате, можно конвертировать его с помощью бесплатных онлайн-ресурсов. Что касается нас, то мы будем использовать эти изображения, поскольку они уже подготовлены.

BITMAP FILE

Получив файлы в соответствующих форматах, нам необходимо переместить их в папку с изображениями. Можно просто открыть Навигатор, найти папку «Изображения», щелкнуть по ней правой кнопкой мыши и выбрать «Открыть папку». По умолчанию откроется папка с двумя файлами изображений доллара и евро. Туда можно вставить свои файлы изображений. Вот наглядная иллюстрация.

IMAGES FOLDER IN MQL5

Сразу после этого мы готовы приступить к работе. Первое, что мы сделаем - добавим изображение в качестве ресурса.

//+------------------------------------------------------------------+
//|                   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

Здесь мы закладываем основу для встраивания и отображения нашего изображения на диаграммах. Мы используем директиву #resource,  чтобы включить в качестве ресурса расположенный в папке "\Images\mql5-circuit.bmp» растровый файл, гарантируя нашей программе легкий доступ к нему.

Затем определяем макрос "ORIGINAL_IMAGE_RESOURCE" со значением "::Images\mql5-circuit.bmp", чтобы создать стандартную ссылку на путь к ресурсу изображения, который мы будем использовать для загрузки данных изображения. Кроме того, определяем макрос "CHART_IMAGE_OBJECT_NAME" как "ChartImage", который мы будем использовать в качестве уникального идентификатора для объекта диаграммы, отображающего изображение, что позволяет нам управлять им, контролируя его внешний вид на графике.

Следующее, что нам понадобится, - это определить некоторые глобальные переменные и входные данные, которые будут использоваться во всей программе.

// 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

Чтобы настроить пользовательские элементы управления для размещения изображений, определяем перечисление «ENUM_ANCHOR_CORNER» с параметрами «TOP_LEFT», «TOP_RIGHT», «BOTTOM_LEFT» и «BOTTOM_RIGHT» для выбора угла диаграммы.

Задаем входные параметры: «LimitToOriginalSize» (true) для ограничения масштабирования, «ImageInBackground» (true) для отображения фона/переднего плана, «CenterImageDynamically» (true) для автоматического или ручного позиционирования, «AnchorCorner» (TOP_LEFT) для выбора угла, а также «XOffsetFromCorner» и «YOffsetFromCorner» (100 пикселей) для ручного смещения. Мы также инициализируем «scaled_resource_counter» значением 0 для уникальных имен масштабированных изображений.

Для отображения изображения на диаграмме будем использовать пользовательскую функцию, которая будет содержать всю логику.

//+------------------------------------------------------------------+
//| 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;

}

Начнем с определения функции «DisplayImageOnChart», которая будет использоваться для отображения нашего изображения на диаграмме. Начинаем с объявления массива «image_pixels» для хранения пиксельных данных изображения, а также переменных «original_image_width» и «original_image_height» для хранения размеров исходного изображения. Используя функцию ResourceReadImage , загружаем данные изображения из макроса «ORIGINAL_IMAGE_RESOURCE» в массив «image_pixels», фиксируя его ширину и высоту. Если эта операция завершается неудачей, регистрируем сообщение об ошибке с помощью функции Print  и возвращаем значение false, чтобы указать на сбой и остановить дальнейшую обработку.

Затем получаем размеры диаграммы, чтобы убедиться, что изображение подходит по размеру. Используем функцию ChartGetInteger  для получения ширины и высоты диаграммы, сохраняя их в «chart_pixel_width» и «chart_pixel_height» после приведения к целым числам. Чтобы сохранить пропорции изображения при масштабировании, вычисляем «image_aspect_ratio» путем деления «original_image_width» на «original_image_height», а «chart_aspect_ratio» — путем деления «chart_pixel_width» на «chart_pixel_height». Эти соотношения будут определять нашу логику масштабирования.

Затем объявляем «scaled_image_width» и «scaled_image_height» для хранения размеров масштабированного изображения. Для сохранения соотношения сторон сравниваем «image_aspect_ratio» с «chart_aspect_ratio». Если изображение шире диаграммы (т. е. «image_aspect_ratio» превышает «chart_aspect_ratio»), устанавливаем «scaled_image_width» на «chart_pixel_width» и вычисляем «scaled_image_height» путем деления «chart_pixel_width» на «image_aspect_ratio». В противном случае устанавливаем «scaled_image_height» на «chart_pixel_height» и вычисляем «scaled_image_width» путем умножения «chart_pixel_height» на «image_aspect_ratio». Это гарантирует, что изображение будет помещаться на диаграмму без искажений.

Если входящее значение «LimitToOriginalSize» равно true, для предотвращения увеличения масштаба ограничиваем масштабирование исходными размерами изображения. Используем функцию MathMin , чтобы ограничить «scaled_image_width» значением «original_image_width» и «scaled_image_height» значением «original_image_height». Чтобы сохранить соотношение сторон после этого ограничения, проверяем, меньше ли значение «scaled_image_width» значения «scaled_image_height», умноженного на «image_aspect_ratio». Если это так, пересчитываем «scaled_image_height» путем деления «scaled_image_width» на «image_aspect_ratio»; в противном случае мы пересчитываем «scaled_image_width» путем умножения «scaled_image_height» на «image_aspect_ratio».

Наконец, регистрируем исходные, графические и масштабированные размеры с помощью функции PrintFormat . Будем использовать её для отслеживания динамических изменений на диаграмме, показанной ниже.

CHART CHANGES

На изображении видно, что мы уже ознакомились с диаграммой и размерами изображения. Теперь нам нужно динамически масштабировать наше изображение, чтобы оно поместилось на диаграмме. Для достижения этой цели нам понадобится пользовательская функция.

//+------------------------------------------------------------------+
//| 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
}

Здесь мы определяем функцию «ScaleImage» для изменения размера изображения с помощью бикубической интерполяции. Объявляем массив «scaled_pixels» и используем функцию ArrayResize , чтобы задать его размер «new_width» * «new_height» пикселей. Циклично проходим по каждому пикселю, сопоставляя координаты с исходным изображением с помощью «original_x» и «original_y».

Для каждого из них вызываем функцию «BicubicInterpolate» (которую объясним ниже), чтобы вычислить значение в пикселях и сохранить его в «scaled_pixels» по адресу «y» * «new_width» + «x». Наконец, изменяем размер «пикселей» с помощью ArrayResize  и копируем в него «scaled_pixels» с помощью ArrayCopy  для дальнейшей обработки диаграммы.

Вот функция, отвечающая за вычисление значения в пикселях посредством бикубической интерполяции. Сначала определим функцию для вычисления интерполяции для одноцветного элемента, затем сможем использовать ее для вычисления интерполяции для отдельных пикселей.

//+------------------------------------------------------------------+
//| 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
}

Здесь мы реализуем функцию «BicubicInterpolateComponent» для выполнения бикубической интерполяции в отношении одноцветного компонента в процессе масштабирования изображения. Объявляем массив «weights_x» для хранения интерполяционных весов для оси x и устанавливаем «t» в значение «fractional_x», дробную часть координаты x.

Вычисляем четыре веса в «weights_x» с помощью формул кубических полиномов для позиций x-1, x, x+1 и x+2, что обеспечивает плавную интерполяцию. Затем объявляем массив «y_values» для хранения промежуточных результатов и перебираем четыре ряда по соседству размером 4x4 пикселя. Для каждого ряда «j» вычисляем «y_values[j]» путем умножения значений «weights_x» на соответствующие цветовые компоненты из массива «components» в индексах от «j * 4 + 0» до «j * 4 + 3», выполняя интерполяцию по оси x.

Затем объявляем массив «weights_y» и устанавливаем «t» в значение «fractional_y» для весов по оси y, вычисляя их по тем же кубическим формулам для y-1, y, y+1 и y+2. Выполняем интерполяцию по оси y, вычисляя «результат» как взвешенную сумму «y_values» с использованием «weights_y». Наконец, ограничиваем «результат» допустимым диапазоном цветов [0, 255] с помощью функций MathMax  и MathMin , гарантируя, что итог подходит для отображения цвета пикселей. Нам также понадобятся компоненты цветового канала для каждого пикселя, поэтому создаём функцию для извлечения цветового канала.

//+------------------------------------------------------------------+
//| 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
}

Реализуем функцию «GetArgb» для извлечения отдельных цветовых компонентов ARGB из целевого пикселя. Берем значение "pixel" и ссылочные переменные "alpha", "red", "green" и "blue" для хранения извлеченных компонентов. Используя побитовые операции, изолируем каждый компонент: сдвигаем "pixel" вправо на 24 бита и маскируем с помощью «0xFF» для извлечения "alpha"; сдвигаем на 16 бит и маскируем для "red"; сдвигаем на 8 бит и маскируем для "green"; а также маскируем младшие 8 бит для "blue". Каждый результат преобразуется в uchar, гарантируя, что значения соответствуют диапазону 0–255 для представления цвета.

Мы использовали систему «0xFF», чтобы гарантировать маскировку нужного байта (8 бит) из 32-битного беззнакового целого пикселя, представляющего цвет в формате ARGB, что гарантирует точное извлечение значений альфа, красного, зеленого или синего. Без «& 0xFF» мы получили бы неожиданные результаты, поскольку могли бы остаться старшие биты из других каналов. Если вам интересно, что это за символы, то речь идет о числе 255 в шестнадцатеричном формате. Давайте посмотрим.

0xFF IN HEXADECIMAL

Вооружившись этими функциями, мы теперь можем определить функцию для выполнения бикубической интерполяции для одного пикселя, чтобы точно масштабировать изображение.

//+------------------------------------------------------------------+
//| 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
}

Здесь мы реализуем функцию «BicubicInterpolate» для вычисления цвета отдельного пикселя с использованием процесса бикубической интерполяции. Начнем с извлечения целочисленных частей входных координат «x» и «y» в «x0» и «y0» и вычисляем их дробные части как «fractional_x» и «fractional_y». Определяем массивы «x_indices» и «y_indices» для хранения соседних координат в диапазоне 4x4, выполняя итерации от -1 до 2 для вычисления индексов вокруг «x0» и «y0». С помощью MathMin  и MathMax мы ограничиваем их допустимыми диапазонами [0, "width"-1] и [0, "height"-1].

Затем создаем массив «neighborhood_pixels» для хранения 16 пикселей из соседства 4x4, заполняя его путем итерации по «y_indices» и «x_indices» для извлечения значений пикселей из массива «pixels» по адресу «y_indices[j] * width + x_indices[i]». Далее объявляем массивы «alpha_components», «red_components», «green_components» и «blue_components» и используем функцию «GetArgb» для извлечения значений ARGB для каждого из 16 пикселей в «neighborhood_pixels».

Для каждого компонента цвета вызываем функцию «BicubicInterpolateComponent» с «fractional_x» и «fractional_y», сохраняя результаты в «alpha_out», «red_out», «green_out» и «blue_out» как значения «uchar». Наконец, объединяем их в одно значение пикселя, используя побитовые сдвиги: «alpha_out» сдвигается на 24 бита, «red_out» — на 16 бит, «green_out» — на 8 бит, а также «blue_out», возвращая полученный «uint» для использования в масштабированном изображении. Теперь мы можем вызвать функцию «ScaleImage», чтобы изменения вступили в силу.

// 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

После вызова функции теперь можно создать новый ресурс с масштабированным изображением.

// 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
}

Чтобы создать и сохранить масштабированное изображение как ресурс, мы создаем уникальное имя ресурса, определяя строку «scaled_resource_name», объединяя «::ScaledImage» с результатом функции IntegerToString , примененной к «scaled_resource_counter», который мы затем увеличиваем, чтобы гарантировать уникальность для последующих ресурсов.

Далее используем функцию ResourceCreate  для создания нового ресурса с «scaled_resource_name», используя массив «image_pixels», «scaled_image_width», «scaled_image_height» и указывая смещения 0, ширину «scaled_image_width» и формат COLOR_FORMAT_ARGB_NORMALIZE . Если «ResourceCreate» завершается неудачей, регистрируем ошибку с помощью функции Print , включая «scaled_resource_name», и возвращаем false, чтобы указать на сбой, останавливая дальнейшую обработку.

Если мы передадим сюда, нам просто нужно расположить и визуализировать изображение на диаграмме, поэтому давайте сначала определим координаты позиционирования.

// 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
   }
}

Что касается позиционирования, объявляем «x_offset» и «y_offset». Если «CenterImageDynamically» равно true, мы центрируем изображение со смещением «x_offset» как («chart_pixel_width» - «scaled_image_width») / 2 и смещением «y_offset» как («chart_pixel_height» - «scaled_image_height») / 2.

В противном случае оператор-переключатель switch на «AnchorCorner» задает смещения: «TOP_LEFT» использует «XOffsetFromCorner» и «YOffsetFromCorner»; «TOP_RIGHT» задает «x_offset» равным «chart_pixel_width» - «scaled_image_width» - «XOffsetFromCorner»; «BOTTOM_LEFT» задает «y_offset» равным «chart_pixel_height» - «scaled_image_height» - «YOffsetFromCorner»; «BOTTOM_RIGHT» объединяет оба. По умолчанию используются «XOffsetFromCorner» и «YOffsetFromCorner».

Теперь нам нужна функция для позиционирования изображения по заданным координатам.

//+------------------------------------------------------------------+
//| 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
}

Здесь мы реализуем функцию «CreateFullChartImage» для создания и размещения объекта изображения на диаграмме. Сначала с помощью функции ObjectFind  проверяем, существует ли объект с именем «object_name». Если это не так (результат < 0), создаем новый объект с графической меткой с помощью функции ObjectCreate , указывая «object_name», OBJ_BITMAP_LABEL и координаты диаграммы по умолчанию.

Затем устанавливаем свойства объекта: с помощью ObjectSetString связываем «resource_name» с OBJPROP_BMPFILE  для источника изображения; с помощью ObjectSetInteger устанавливаем «OBJPROP_XSIZE» на «x_size», «OBJPROP_YSIZE» на «y_size», «OBJPROP_XDISTANCE» на «x_offset» и «OBJPROP_YDISTANCE» на «y_offset» для размера и положения; а OBJPROP_BACK  на «is_background» для переключения между фоновым отображением или отображением на переднем плане. Наконец, вызываем функцию ChartRedraw  для обновления отображения диаграммы с использованием нового или измененного объекта изображения. Затем вызываем эту функцию для визуализации изображения.

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

После вызова функции все готово, и теперь, чтобы создать первое изображение, мы можем вызвать эту основную функцию в обработчике событий OnInit .

//+------------------------------------------------------------------+
//| 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
}

В обработчике событий OnInit  вызываем функцию «DisplayImageOnChart» для визуализации изображения во время инициализации. Если «DisplayImageOnChart» возвращает значение false, что указывает на сбой при отображении изображения, регистрируем сообщение об ошибке и возвращаем INIT_FAILED , чтобы сообщить о сбое инициализации, что приводит к остановке программы. Если отображение прошло успешно, возвращаем INIT_SUCCEEDED , чтобы указать, что инициализация была успешно завершена, что позволяет программе продолжить работу. По итогам компиляции вот что мы имеем.

INITIAL IMAGE RUN

На изображении видно, что мы можем отобразить исходное изображение при первом запуске. Однако нам необходимо сделать так, чтобы изображение реагировало на изменение размеров диаграммы. Таким образом, нам понадобится обработчик событий 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
      }
   }
}

Здесь мы определяем обработчик событий OnChartEvent  для обработки событий, связанных с диаграммой в нашей программе, уделяя особое внимание изменению размера диаграммы. Мы проверяем, равен ли «event_id» CHARTEVENT_CHART_CHANGE, что указывает на изменение диаграммы, например изменение размера. Если значение равно true, вызываем функцию «DisplayImageOnChart», чтобы обновить отображение изображения в соответствии с новыми размерами диаграммы. Если «DisplayImageOnChart» возвращает значение false, сигнализируя о сбое обновления изображения, мы регистрируем сообщение об ошибке, чтобы предупредить пользователя о проблеме. Наконец, при удалении программы нужно удалить все ресурсы, которые мы отобразили на диаграмме.

//+------------------------------------------------------------------+
//| 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
}

Чтобы обеспечить корректный выход, для удаления из диаграммы объекта изображения диаграммы, идентифицированного как «CHART_IMAGE_OBJECT_NAME», в обработчике событий OnDeinit  вызываем функцию ObjectDelete . Это предотвращает появление остаточных объектов после завершения программы. Обратите внимание, что динамические ресурсы, например, созданные для масштабированных изображений, автоматически освобождаются системой при удалении советника, не требуя дополнительной очистки. При запуске программы мы получаем следующий результат.

FINAL OUTCOME

На изображении видно, что мы создаем определенное изображение, устанавливаем его в качестве ресурса и динамически масштабируем его по указанию пользователя, достигая тем самым нашей цели. Теперь осталось только тщательно протестировать программу. Об этом пойдет речь в следующей теме ниже.


Тестирование и проверка

Мы провели все тесты и скомпилировали её в визуализацию в формате GIF (Graphical Interchange Format), показывающую, что она динамична и реагирует на изменение размеров диаграммы и ввод данных пользователем, не теряя при этом качества в процессе.

MQL5 BICUBIC INTERPOLATION IMAGE RESOURCE


Заключение

В заключение отметим, что нами создан инструмент MQL5 для динамической графики изображений на диаграммах MetaTrader 5 с помощью метода бикубической интерполяции для получения четких, адаптивных визуальных эффектов. Мы показали, как реализовывать и применять его для масштабирования и позиционирования изображений под контролем пользователя. Этот инструмент можно настроить для улучшения функциональности и эстетики ваших торговых графиков.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17892

Прикрепленные файлы |
Алгоритм конкурентного обучения — Competitive Learning Algorithm (CLA) Алгоритм конкурентного обучения — Competitive Learning Algorithm (CLA)
В статье представлен алгоритм конкурентного обучения (Competitive Learning Algorithm, CLA) — новый метаэвристический метод оптимизации, основанный на моделировании образовательного процесса. Алгоритм организует популяцию решений в виде классов со студентами и учителями, где агенты обучаются через три механизма: следование за лучшим в классе, использование личного опыта и обмен знаниями между классами.
Новый подход к пользовательским критериям при оптимизациях (Часть 1): Примеры функций активации Новый подход к пользовательским критериям при оптимизациях (Часть 1): Примеры функций активации
Это первая из серии статей, посвященных математическим аспектам создания пользовательских критериев с особым акцентом на нелинейных функциях, применяемых в нейросетях, MQL5-коде для реализации, а также на использования целевых и корректирующих смещений.
Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки
В этой статье мы создадим кнопки для фильтров валютных пар, уровней важности, временных фильтров и функцию отмены для улучшения управления панелью. Кнопки будут запрограммированы на динамическую реакцию на действия пользователя, обеспечивая бесперебойное взаимодействие. Мы также автоматизируем их поведение, чтобы отражать изменения в реальном времени на панели. Это повысит общую функциональность, мобильность и оперативность панели.
Нейросети в трейдинге: Сквозная многомерная модель прогнозирования временных рядов (Окончание) Нейросети в трейдинге: Сквозная многомерная модель прогнозирования временных рядов (Окончание)
Представляем вашему вниманию заключительную часть цикла, посвящённого GinAR — нейросетевому фреймворку для прогнозирования временных рядов. В этой статье мы анализируем результаты тестирования модели на новых данных и оцениваем её устойчивость в условиях реального рынка.