Создание динамических графических интерфейсов на MQL5 через бикубическую интерполяцию
Введение
Пользовательская настройка торговых графиков с помощью динамических визуальных элементов может повысить эффективность анализа рынков, но для достижения высококачественной визуализации изображений требуется хорошо продуманное программирование на языке MetaQuotes Language 5 (MQL5). В настоящей статье мы представляем мощный инструмент, поддерживающий динамические графические интерфейсы, использующий ресурсоориентированное масштабирование изображений методом бикубической интерполяции для получения четких и адаптируемых визуальных элементов графика. Мы рассмотрим этот процесс на следующих этапах:
- Обзор ресурсоориентированных динамических графических изображений
- Реализация на MQL5
- Тестирование и проверка
- Заключение
В итоге у вас будет надежный инструмент для преобразования ваших торговых графиков с помощью управляемой пользователем профессиональной графики.
Обзор ресурсоориентированных динамических графических изображений
Мы стремимся создать инструмент на MQL5, который встраивает и масштабирует изображения в графики MetaTrader 5, создавая динамичные управляемые пользователем графические интерфейсы. Мы загрузим растровое изображение в качестве ресурса, масштабируем его в соответствии с размерами графика с помощью бикубической интерполяции и расположим его на основе таких пользовательских данных, как привязка к углам или динамическое центрирование. Это позволит нам накладывать пользовательские визуальные элементы — логотипы или модели — при сохранении соотношения сторон и переключении отображения фона или переднего плана. Все функции оптимизированы для работы в режиме реального времени. Таким образом, графики будут более интересными и привлекательными.
Для масштабирования мы будем использовать бикубическую интерполяцию по ближайшему соседу и билинейные методы. Ближайший сосед создает пикселизированные результаты, а билинейный размывает детали. Вот подробная визуализация того, почему мы выбрали метод бикубической интерполяции вместо других методов.
Визуализация графика.

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

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

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

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

Сразу после этого мы готовы приступить к работе. Первое, что мы сделаем - добавим изображение в качестве ресурса.
//+------------------------------------------------------------------+ //| 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 . Будем использовать её для отслеживания динамических изменений на диаграмме, показанной ниже.

На изображении видно, что мы уже ознакомились с диаграммой и размерами изображения. Теперь нам нужно динамически масштабировать наше изображение, чтобы оно поместилось на диаграмме. Для достижения этой цели нам понадобится пользовательская функция.
//+------------------------------------------------------------------+ //| 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 в шестнадцатеричном формате. Давайте посмотрим.

Вооружившись этими функциями, мы теперь можем определить функцию для выполнения бикубической интерполяции для одного пикселя, чтобы точно масштабировать изображение.
//+------------------------------------------------------------------+ //| 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 , чтобы указать, что инициализация была успешно завершена, что позволяет программе продолжить работу. По итогам компиляции вот что мы имеем.

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

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

Заключение
В заключение отметим, что нами создан инструмент MQL5 для динамической графики изображений на диаграммах MetaTrader 5 с помощью метода бикубической интерполяции для получения четких, адаптивных визуальных эффектов. Мы показали, как реализовывать и применять его для масштабирования и позиционирования изображений под контролем пользователя. Этот инструмент можно настроить для улучшения функциональности и эстетики ваших торговых графиков.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17892
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Алгоритм конкурентного обучения — Competitive Learning Algorithm (CLA)
Новый подход к пользовательским критериям при оптимизациях (Часть 1): Примеры функций активации
Трейдинг с экономическим календарем MQL5 (Часть 5): Добавление в панель адаптивных элементов управления и кнопок сортировки
Нейросети в трейдинге: Сквозная многомерная модель прогнозирования временных рядов (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования