English Русский Deutsch 日本語
preview
在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面

在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面

MetaTrader 5交易 |
23 0
Allan Munene Mutiiria
Allan Munene Mutiiria

引言

利用动态视觉效果自定义交易图表可以提升我们的市场分析水平,但要实现高质量的图像渲染,需要精心构思 MQL5 程序。在本文中,我们将介绍一款强大的工具,它能够实现动态图形界面,利用资源驱动的图像缩放结合双三次插值技术,打造出清晰、灵活的图表视觉效果。我们将通过以下步骤来探索这一过程:

  1. 动态资源驱动图像图形概述
  2. MQL5实现
  3. 测试和验证
  4. 结论

最终,您将拥有一个强大的工具,可以通过专业的、用户可控的图像图形来改造您的交易图表。


动态资源驱动图像图形概述

我们的目标是构建一个 MQL5 工具,在 MetaTrader 5 图表上嵌入并缩放图像,从而创建动态的、用户可控的图形界面。我们将把位图图像作为资源加载,使用双三次插值将其缩放以适应图表尺寸,并根据用户输入(如锚定到角落或动态居中)进行定位。这将使我们能够在保持宽高比的同时叠加自定义视觉效果(如标志或图案),并支持在背景或前景显示之间切换,所有这些都经过优化,以确保实时性能。这样,图表将变得更加有趣和吸引人。

对于缩放,我们将使用双三次插值,而不是最近邻和双线性方法。最近邻插值会产生像素化的结果,而双线性插值则会模糊细节。以下是一张详细的示意图,解释了我们为何选择双三次插值而非其他方法。

图形可视化。

图表

像素化结果可视化

像素化效果

双三次插值利用 4×4 像素邻域和三次多项式,确保更平滑的渐变和更清晰的边缘。我们选择双三次插值是因为它具有卓越的清晰度和效率,非常适合在各种图表尺寸上进行动态调整,能够提供清晰的视觉效果,从而有助于提升交易决策。以下是我们期望实现的目标示例,使用的是自定义的 MQL5 图像。

MQL5 双三次图像概述


MQL5实现

要在 MQL5 中创建程序,我们首先需要确保图像采用正确的格式,即位图(BMP)格式。与JPEG格式相比,BMP 格式是未压缩的位图图像,具有更高的分辨率。因此,如果您需要使用相关图像,而它采用其他格式,可以使用免费的在线资源进行转换。至于我们,这些是我们将要使用的图像,因为它们已经准备就绪。

位图文件

获取到相应格式的文件后,我们需要将其移动到 Images 文件夹中。您可以打开导航器面板,定位到 Images 文件夹,右键点击该文件夹,然后选择"打开文件夹"。这将打开默认文件夹,其中默认包含两个美元和欧元图像文件。您可以将图像文件粘贴到该位置。以下是直观的图示。

MQL5 中的 Images 文件夹

完成这些操作后,我们就可以开始了。我们要做的第一件事是将图像作为资源添加进去。

//+------------------------------------------------------------------+
//|                   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” 选项用于选择图表角落。

我们设置 input 参数:“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” 函数,用于在图表上渲染图像。首先,我们声明 array 类型的 “image_pixels” 数组,用于存储图像的像素数据,以及 “original_image_width” 和 “original_image_height” 变量,用于保存原始图像的尺寸。使用 ResourceReadImage 函数,我们将图像数据从 “ORIGINAL_IMAGE_RESOURCE” 宏加载到 “image_pixels” 数组中,同时捕获其宽度和高度。如果该操作失败,我们通过 Print 函数记录错误信息并返回 false,表示操作失败,从而中止后续处理。

接下来,我们获取图表的尺寸,以确保图像能够适当适配。我们使用 ChartGetInteger 函数获取图表的宽度和高度,并将其转换为整数后分别存储在 “chart_pixel_width” 和 “chart_pixel_height” 中。为了在缩放时保持图像的宽高比,我们通过将 “original_image_width” 除以 “original_image_height” 计算 “image_aspect_ratio”,并将 “chart_pixel_width” 除以 “chart_pixel_height” 计算 “chart_aspect_ratio”。这两个比例将指导我们的缩放逻辑。

然后,我们声明 “scaled_image_width” 和 “scaled_image_height” 变量,用于存储缩放后图像的尺寸。为了保持宽高比,我们将 “image_aspect_ratio” 与 “chart_aspect_ratio” 进行比较。如果图像相对于图表更宽(即 “image_aspect_ratio” 大于 “chart_aspect_ratio”),我们将 “scaled_image_width” 设置为 “chart_pixel_width”,并通过将 “chart_pixel_width” 除以 “image_aspect_ratio” 计算 “scaled_image_height”。否则,我们将 “scaled_image_height” 设置为 “chart_pixel_height”,并通过将 “chart_pixel_height” 乘以 “image_aspect_ratio” 计算 “scaled_image_width”。这样能够确保图像在不发生畸变的情况下适配图表。

如果 “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_width” 除以 “image_aspect_ratio” 重新计算 “scaled_image_height”;否则,我们通过将 “scaled_image_height” 乘以 “image_aspect_ratio” 重新计算 “scaled_image_width”。

最后,我们使用 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 调整 “pixels” 的大小,并使用 ArrayCopy 将 “scaled_pixels” 复制到其中,以便进行后续的图表处理。

以下是负责通过双三次插值计算像素值的函数。让我们先定义一个函数来计算单个颜色元素的插值,然后可以将其用于计算单个像素的插值。

//+------------------------------------------------------------------+
//| 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 坐标的小数部分。

我们使用三次多项式公式计算位置 x-1、x、x+1 和 x+2 的四个权重,存储在 “weights_x” 中,从而实现平滑插值。接下来,我们声明 “y_values” array 以保存中间结果,并遍历 4×4 像素邻域的四行。对于每一行 “j”,我们通过将 “weights_x” 值与 “components” 数组中索引 “j × 4 + 0” 到 “j × 4 + 3” 处的对应颜色分量相乘,计算 “y_values[j]”,执行 x 轴方向的插值。

然后,我们声明 “weights_y” 数组,并将 “t” 设置为 “fractional_y” 用于 y 轴权重,使用相同的三次公式计算位置 y-1、y、y+1 和 y+2 的权重。我们通过使用 “weights_y” 对 “y_values” 进行加权和运算,在 y 轴方向上执行插值,计算 “result”。最后,我们使用 MathMaxMathMin 函数将 “result” 限制在有效颜色范围 [0, 255] 内,确保输出适合像素颜色表示。我们还需要为每个像素准备颜色通道分量,因此让我们设计一个颜色通道提取函数。

//+------------------------------------------------------------------+
//| 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” 掩码机制,确保从 32 位无符号整数像素中提取所需的字节(8 位),该像素以 ARGB 格式表示颜色,从而准确提取 alpha、红色、绿色或蓝色值。如果缺少 “& 0xFF”,可能会因为其他通道的高位未被清除而导致意想不到的结果。如果您对这些字符的含义感到好奇,我们讨论的是十六进制格式的数字 255。请查看这里。

十六进制中的 0xFF

有了这些函数,我们现在可以定义一个函数来对单个像素执行双三次插值,从而精确地缩放图像。

//+------------------------------------------------------------------+
//| 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” 数组来存储 4×4 邻域的坐标,从 -1 迭代到 2,计算 “x0” 和 “y0” 周围的索引。使用 MathMinMathMax 函数,我们将这些索引限制在有效范围 [0, “width”-1] 和 [0, “height”-1] 内。

然后,我们创建 “neighborhood_pixels” 数组来保存来自 4×4 邻域的 16 个像素,通过遍历 “y_indices” 和 “x_indices” 从 “pixels” 数组的 “y_indices[j] × width + x_indices[i]” 位置获取像素值来填充它。接下来,我们声明 “alpha_components”、“red_components”、“green_components” 和 “blue_components” 数组,并使用 “GetArgb” 函数提取 “neighborhood_pixels” 中 16 个像素中每一个的 ARGB 值。

对于每个颜色分量,我们调用 “BicubicInterpolateComponent” 函数,传入 “fractional_x” 和 “fractional_y”,将结果作为 “uchar” 值存储在 “alpha_out”、“red_out”、“green_out” 和 “blue_out” 中。最后,我们使用位移运算将这些分量组合成单个像素值:“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” 与应用于 “scaled_resource_counter” 的 IntegerToString 函数的结果连接起来,然后我们递增该计数器以确保后续资源的唯一性。

接下来,我们使用 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 来使图像居中。

否则,通过对 “AnchorCorner” 使用 switch 语句设置偏移量:“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,该事件表示图表修改(如调整大小)。如果为真,我们调用 “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
}

为了确保程序能够干净地退出,在 OnDeinit 事件处理程序中,我们调用 ObjectDelete 函数从图表上移除由 “CHART_IMAGE_OBJECT_NAME” 标识的图表图像对象。这样可以防止程序结束后留下任何残留对象。请注意,为缩放图像创建的动态资源在移除 Expert Advisor 时会被系统自动释放,无需额外的清理操作。当我们运行程序时,会得到以下结果。

最终结果

从图像可以看出,我们成功创建了指定的图像并将其设置为资源,按照用户定义的方式动态缩放图像,从而实现了我们的目标。现在剩下的是对程序进行彻底的测试,这将在接下来的部分中讨论。


测试和验证

我们进行了所有测试,并将其编译为一个图形交换格式 (GIF) 可视化文件,展示了该图像的动态特性,能够响应图表调整大小和用户输入,且在此过程中不会损失任何质量。

MQL5 双三次插值图像资源


结论

总之,我们构建了一个用于在 MetaTrader 5 图表上实现动态图像图形的 MQL5 工具,利用双三次插值实现了清晰、自适应的视觉效果。我们展示了如何实现并应用它来进行用户控制的图像缩放和定位。您可以定制此工具,以增强您的交易图表的功能性和美观度。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17892

交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略 MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略
本文将介绍如何在MQL5中实现午夜区间突破结合结构突破(BoS)价格行为策略自动化,并详细说明突破检测与交易执行的代码逻辑。我们为入场、止损和止盈设定了精确的风险参数。包含回测与优化方法,助力实战交易。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
MQL5 简介(第 14 部分):构建自定义指标的初学者指南(三) MQL5 简介(第 14 部分):构建自定义指标的初学者指南(三)
学习如何使用图表对象在 MQL5 中构建谐波形态指标。了解如何检测波动点、应用斐波那契回撤线以及自动识别形态。