
Erstellen von dynamischen MQL5-Grafikschnittstellen durch ressourcengesteuerte Bildskalierung mit bikubischer Interpolation auf Handelscharts
Einführung
Das Anpassen von Handelscharts mit dynamischen Grafiken kann die Art und Weise, wie wir Märkte analysieren, verbessern, aber das Erreichen einer hochwertigen Bildwiedergabe erfordert eine gut durchdachte MetaQuotes Language 5 (MQL5) Programmierung. In diesem Artikel stellen wir ein leistungsfähiges Tool vor, das dynamische grafische Oberflächen ermöglicht, indem es ressourcengesteuerte Bildskalierung mit bikubische Interpolation für scharfe, anpassungsfähige Charts nutzt. Wir werden den Prozess anhand dieser Schritte untersuchen:
- Überblick über dynamische ressourcengesteuerte Bildgrafiken
- MQL5-Implementierung
- Tests und Validierung
- Schlussfolgerung
Am Ende des Kurses verfügen Sie über ein robustes Werkzeug, mit dem Sie Ihre Handelscharte mit professionellen, nutzergesteuerten Bildgrafiken versehen können.
Überblick über dynamische ressourcengesteuerte Bildgrafiken
Unser Ziel ist es, ein MQL5-Tool zu entwickeln, das Bilder in die Charts des MetaTrader 5 einbettet und skaliert und so dynamische, nutzergesteuerte grafische Schnittstellen schafft. Wir laden ein Bitmap-Bild als Ressource, skalieren es mit Hilfe der bikubische Interpolation, um es an die Abmessungen des Charts anzupassen, und positionieren es auf der Grundlage von Nutzereingaben wie der Verankerung an den Ecken oder der dynamischen Zentrierung. So können wir nutzerdefinierte Grafiken - Logos oder Muster - überlagern und dabei die Seitenverhältnisse beibehalten und die Anzeige von Hintergrund und Vordergrund umschalten, alles optimiert für Echtzeitleistung. Auf diese Weise werden die Charts interessanter und ansprechender.
Zur Skalierung verwenden wir die bikubische Interpolation über den nächsten Nachbarn und bilineare Methoden. Der nächste Nachbar erzeugt verpixelte Ergebnisse, und die bilineare Methode verwischt Details. Hier finden Sie eine detaillierte Visualisierung, warum wir uns für die bikubische Interpolation entschieden haben.
Die Visualisierung der Grafik
Visualisierung der resultierenden Verpixelung.
Bikubisch, das eine 4x4-Pixel-Nachbarschaft und kubische Polynome nutzt, sorgt für glattere Verläufe und schärfere Kanten. Wir haben uns für die bikubische Darstellung entschieden, weil sie besonders klar und effizient ist. Sie eignet sich ideal für die dynamische Größenanpassung von Charts und liefert gestochen scharfe Bilder, die Handelsentscheidungen erleichtern. Hier ist ein Beispiel dafür, was wir mit einem nutzerdefinierten MQL5-Bild erreichen wollen.
MQL5-Implementierung
Um das Programm in MQL5 zu erstellen, müssen wir zunächst die Bilder im richtigen Format haben, nämlich im Bitmap (BMP)-Format, bei dem es sich um unkomprimierte Bitmap-Bilder mit hoher Auflösung handelt, verglichen mit der Joint Photographic Experts Group (JPEG). Wenn Sie also ein relevantes Bild in einem anderen Format benötigen, können Sie es mit kostenlosen Online-Ressourcen konvertieren. Was uns betrifft, so sind dies die Bilder, die wir verwenden werden, da wir sie bereits vorbereitet haben.
Nachdem wir die Dateien in den entsprechenden Formaten erhalten haben, müssen wir sie in den Ordner „images“ verschieben. Sie können einfach den Navigator öffnen, den Ordner der Bilder suchen, mit der rechten Maustaste darauf klicken und „Ordner öffnen“ wählen. Dadurch wird der Standardordner mit zwei Dollar- und Euro-Bilddateien standardmäßig geöffnet. Dort können Sie Ihre Bilddateien einfügen. Hier ist eine visuelle Veranschaulichung.
Wenn das erledigt ist, können wir loslegen. Als erstes werden wir das Bild als Ressource hinzufügen.
//+------------------------------------------------------------------+ //| 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
Hier schaffen wir die Grundlage für die Einbettung und Darstellung unseres Bildes auf den Charts. Wir verwenden die Direktive #resource, um eine Bitmap-Datei mit dem Namen „\Images\mql5-circuit.bmp“ als Ressource einzubinden und sicherzustellen, dass sie für unser Programm leicht zugänglich ist.
Anschließend definieren wir das Makro „ORIGINAL_IMAGE_RESOURCE“, das auf „::Images\mql5-circuit.bmp“ gesetzt wird, um einen standardisierten Verweis auf den Ressourcenpfad des Bildes zu erstellen, den wir zum Laden der Bilddaten verwenden werden. Zusätzlich definieren wir das Makro „CHART_IMAGE_OBJECT_NAME“ als „ChartImage“, das wir als eindeutigen Bezeichner für das Chartobjekt, das das Bild anzeigt, verwenden werden, um es zu verwalten, indem wir sein Aussehen im Chart steuern.
Als Nächstes müssen wir einige globale Variablen und Eingaben definieren, die wir im gesamten Programm verwenden werden.
// 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
Um die Nutzersteuerung für die Bildplatzierung zu konfigurieren, definieren wir die Enumeration „ENUM_ANCHOR_CORNER“ mit den Optionen „TOP_LEFT“, „TOP_RIGHT“, „BOTTOM_LEFT“ und „BOTTOM_RIGHT“ zur Auswahl einer Chart-Ecke.
Wir legen folgende Eingabeparameter fest: “LimitToOriginalSize“ (true) zur Begrenzung der Skalierung, „ImageInBackground“ (true) für die Anzeige im Hintergrund/Vordergrund, „CenterImageDynamically“ (true) für die automatische oder manuelle Positionierung, „AnchorCorner“ (TOP_LEFT) für die Auswahl der Ecken und „XOffsetFromCorner“ und „YOffsetFromCorner“ (100 Pixel) für manuelle Offsets. Außerdem initialisieren wir „scaled_resource_counter“ auf 0 für eindeutige skalierte Bildnamen.
Um das Bild im Chart anzuzeigen, verwenden wir eine nutzerdefinierte Funktion, die die gesamte Logik enthält.
//+------------------------------------------------------------------+ //| 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; }
Wir beginnen mit der Definition der Funktion „DisplayImageOnChart“, mit der wir unser Bild auf dem Chart darstellen werden. Zunächst deklarieren wir das Array „image_pixels“, das die Pixeldaten des Bildes enthält, und die Variablen „original_image_width“ und „original_image_height“, die die Abmessungen des Originalbildes speichern. Mit der Funktion ResourceReadImage laden wir die Bilddaten aus dem Makro „ORIGINAL_IMAGE_RESOURCE“ in das Array „image_pixels“, wobei wir die Breite und Höhe erfassen. Wenn dieser Vorgang fehlschlägt, wird mit der Funktion Print eine Fehlermeldung protokolliert und false zurückgegeben, um den Fehler anzuzeigen und die weitere Verarbeitung zu stoppen.
Als Nächstes werden die Abmessungen des Charts abgerufen, um sicherzustellen, dass das Bild in die richtige Größe passt. Wir verwenden die Funktion ChartGetInteger, um die Breite und Höhe des Charts zu erhalten und speichern sie in „chart_pixel_width“ und „chart_pixel_height“, nachdem wir sie in Ganzzahlen umgewandelt haben. Um die Proportionen des Bildes bei der Skalierung beizubehalten, berechnen wir das „image_aspect_ratio“, indem wir „original_image_width“ durch „original_image_height“ dividieren, und das „chart_aspect_ratio“, indem wir „chart_pixel_width“ durch „chart_pixel_height“ dividieren. Diese Verhältnisse werden unsere Skalierungslogik leiten.
Wir deklarieren dann „scaled_image_width“ und „scaled_image_height“, um die Abmessungen des skalierten Bildes zu speichern. Um das Seitenverhältnis beizubehalten, vergleichen wir das „image_aspect_ratio“ mit dem „chart_aspect_ratio“. Wenn das Bild im Verhältnis zum Chart breiter ist (d. h., „image_aspect_ratio“ ist größer als „chart_aspect_ratio“), wird „scaled_image_width“ auf „chart_pixel_width“ gesetzt und „scaled_image_height“ durch Division von „chart_pixel_width“ durch „image_aspect_ratio“ berechnet. Andernfalls setzen wir „scaled_image_height“ auf „chart_pixel_height“ und berechnen „scaled_image_width“ durch Multiplikation von „chart_pixel_height“ mit „image_aspect_ratio“. Dadurch wird sichergestellt, dass das Bild ohne Verzerrung in das Chart passt.
Wenn die Eingabe „LimitToOriginalSize“ wahr ist, beschränken wir die Skalierung auf die ursprünglichen Bildabmessungen, um eine Hochskalierung zu verhindern. Wir verwenden die MathMin-Funktion, um „scaled_image_width“ auf „original_image_width“ und „scaled_image_height“ auf „original_image_height“ zu begrenzen. Um das Seitenverhältnis nach dieser Einschränkung beizubehalten, wird geprüft, ob „scaled_image_width“ kleiner ist als „scaled_image_height“ multipliziert mit „image_aspect_ratio“. Wenn dies der Fall ist, wird „scaled_image_height“ neu berechnet, indem „scaled_image_width“ durch „image_aspect_ratio“ dividiert wird; andernfalls wird „scaled_image_width“ durch Multiplikation von „scaled_image_height“ mit „image_aspect_ratio“ neu berechnet.
Schließlich protokollieren wir die Original-, Chart- und skalierten Abmessungen mit der Funktion PrintFormat. Damit können wir dynamische Änderungen im Chart verfolgen, wie unten gezeigt.
Aus dem Bild können wir ersehen, dass wir bereits die Abmessungen des Charts und des Bildes gelesen haben. Jetzt müssen wir unser Bild dynamisch skalieren, damit es in das Chart passt. Um dies zu erreichen, benötigen wir eine nutzerdefinierte Funktion.
//+------------------------------------------------------------------+ //| 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 }
Hier definieren wir die Funktion „ScaleImage“ zur Größenänderung eines Bildes durch bikubische Interpolation. Wir deklarieren das Array „scaled_pixels“ und verwenden die Funktion ArrayResize, um es für „new_width“ * „new_height“ Pixel zu vergrößern. Wir gehen in einer Schleife durch jedes Pixel und ordnen die Koordinaten dem Originalbild mit „original_x“ und „original_y“ zu.
Für jedes Pixel rufen wir die Funktion „BicubicInterpolate“ auf, die wir weiter unten erklären werden, um den Pixelwert zu berechnen und ihn in „scaled_pixels“ bei „y“ * „new_width“ + „x“ zu speichern. Schließlich ändern wir die Größe von „pixels“ mit ArrayResize und kopieren „scaled_pixels“ mit ArrayCopy für die weitere Bearbeitung des Charts hinein.
Dies ist die Funktion, die für die Berechnung des Pixelwertes durch bikubische Interpolation zuständig ist. Definieren wir zunächst eine Funktion zur Berechnung der Interpolation für ein einzelnes Farbelement, dann können wir sie zur Berechnung der Interpolation für einzelne Pixel verwenden.
//+------------------------------------------------------------------+ //| 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 }
Hier implementieren wir die Funktion „BicubicInterpolateComponent“, um eine bikubische Interpolation für eine einzelne Farbkomponente in unserem Bildskalierungsprozess durchzuführen. Wir deklarieren das Array „weights_x“, um Interpolationsgewichte für die x-Achse zu speichern, und setzen „t“ auf „fractional_x“, den gebrochenen Teil der x-Koordinate.
Wir berechnen vier Gewichte in „weights_x“ mit Hilfe von kubischen Polynomformeln für die Positionen x-1, x, x+1 und x+2, was eine reibungslose Interpolation ermöglicht. Als Nächstes deklarieren wir das Array „y_values“, um Zwischenergebnisse zu speichern, und iterieren über vier Zeilen einer 4x4-Pixel-Nachbarschaft. Für jede Zeile „j“ wird „y_values[j]“ durch Multiplikation der „weights_x“-Werte mit den entsprechenden Farbkomponenten aus dem Array „components“ bei den Indizes „j * 4 + 0“ bis „j * 4 + 3“ berechnet, wobei eine Interpolation auf der x-Achse erfolgt.
Dann deklarieren wir das Array „weights_y“ und setzen „t“ auf „fractional_y“ für die Gewichte der y-Achse und berechnen sie mit denselben kubischen Formeln für y-1, y, y+1 und y+2. Wir interpolieren auf der y-Achse, indem wir „result“ als gewichtete Summe der „y_values“ mit „weights_y“ berechnen. Schließlich wird das „Ergebnis“ mit den Funktionen MathMax und MathMin auf den gültigen Farbbereich [0, 255] begrenzt, um sicherzustellen, dass die Ausgabe für die Pixelfarbdarstellung geeignet ist. Wir benötigen auch Farbkanalkomponenten für jedes Pixel, also brauchen wir eine Funktion für die Extraktion der Farbkanäle.
//+------------------------------------------------------------------+ //| 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 }
Wir implementieren die Funktion „GetArgb“, um einzelne ARGB-Farbkomponenten aus einem bestimmten Pixel zu extrahieren. Wir nehmen einen „Pixel“-Wert und referenzieren die Variablen „Alpha“, „Rot“, „Grün“ und „Blau“, um die extrahierten Komponenten zu speichern. Mit bitweisen Operationen isolieren wir jede Komponente: Wir verschieben „Pixel“ um 24 Bits nach rechts und maskieren mit 0xFF, um „Alpha“ zu extrahieren; verschieben um 16 Bits und maskieren für „Rot“; verschieben um 8 Bits und maskieren für „Grün“; und maskieren die untersten 8 Bits für „Blau“. Jedes Ergebnis wird in uchar umgewandelt, um sicherzustellen, dass die Werte in den Bereich 0-255 für die Farbdarstellung passen.
Wir haben das System „0xFF“ verwendet, um sicherzustellen, dass das gewünschte Byte (8 Bits) von einem 32-Bit-Pixel ohne Vorzeichen maskiert wird, das eine Farbe im ARGB-Format darstellt, wodurch eine genaue Extraktion der Alpha-, Rot-, Grün- oder Blauwerte gewährleistet wird. Ohne „& 0xFF“ würden wir unerwartete Ergebnisse erhalten, weil die höheren Bits von anderen Kanälen übrig bleiben könnten. Wenn Sie sich fragen, welche Zeichen das sind, so handelt es sich um 255 im Hexadezimalformat. Schauen Sie hier nach.
Mit diesen Funktionen können wir nun eine Funktion definieren, die die bikubische Interpolation für ein einzelnes Pixel durchführt, um das Bild präzise zu skalieren.
//+------------------------------------------------------------------+ //| 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 }
Hier implementieren wir die Funktion „BicubicInterpolate“, um die Farbe eines einzelnen Pixels mit Hilfe des bikubischen Interpolationsverfahrens zu berechnen. Wir beginnen mit der Extraktion der ganzzahligen Teile der Eingabekoordinaten „x“ und „y“ in „x0“ und „y0“ und berechnen ihre gebrochenen Teile als „fractional_x“ und „fractional_y“. Wir definieren die Arrays „x_indices“ und „y_indices“, um die Koordinaten einer 4x4-Nachbarschaft zu speichern, wobei wir von -1 bis 2 iterieren, um Indizes um „x0“ und „y0“ zu berechnen. Mithilfe von MathMin und MathMax werden diese auf gültige Bereiche [0, „Breite“-1] und [0, „Höhe“-1] festgelegt.
Dann erstellen wir das Array „neighborhood_pixels“, um 16 Pixel aus der 4x4-Nachbarschaft zu speichern, und füllen es durch eine Iteration über „y_indices“ und „x_indices“, um Pixelwerte aus dem Array „pixels“ bei „y_indices[j] * width + x_indices[i]“ zu holen. Als Nächstes deklarieren wir die Arrays „alpha_components“, „red_components“, „green_components“ und „blue_components“ und verwenden die Funktion „GetArgb“, um ARGB-Werte für jedes der 16 Pixel in „neighborhood_pixels“ zu extrahieren.
Für jede Farbkomponente rufen wir die Funktion „BicubicInterpolateComponent“ mit „fractional_x“ und „fractional_y“ auf und speichern die Ergebnisse in „alpha_out“, „red_out“, „green_out“ und „blue_out“ als „uchar“ Werte. Schließlich kombinieren wir diese zu einem einzigen Pixelwert, indem wir sie bitweise verschieben: „alpha_out“ um 24 Bits, „red_out“ um 16 Bits, „green_out“ um 8 Bits und „blue_out“ um den resultierenden „uint“ zur Verwendung im skalierten Bild. Jetzt können wir die Funktion „ScaleImage“ aufrufen, damit sie wirksam wird.
// 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
Nach dem Aufruf der Funktion können wir nun eine neue Ressource mit dem skalierten Bild erstellen.
// 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 }
Um ein skaliertes Bild als Ressource zu erzeugen und zu speichern, erstellen wir einen eindeutigen Ressourcennamen, indem wir die Zeichenkette „scaled_resource_name“ definieren und „::ScaledImage“ mit dem Ergebnis der Funktion IntegerToString, die auf „scaled_resource_counter“ angewendet wird, verketten, das wir dann inkrementieren, um die Eindeutigkeit für nachfolgende Ressourcen sicherzustellen.
Als Nächstes verwenden wir die Funktion ResourceCreate, um eine neue Ressource mit dem Namen „scaled_resource_name“ zu erstellen. Dabei verwenden wir das Array „image_pixels“, „scaled_image_width“, „scaled_image_height“ und geben einen Offset von 0, eine Breite von „scaled_image_width“ und das Format COLOR_FORMAT_ARGB_NORMALIZE an. Wenn „ResourceCreate“ fehlschlägt, protokollieren wir einen Fehler mit Print, einschließlich „scaled_resource_name“, und geben false zurück, um den Fehler anzuzeigen und die weitere Verarbeitung zu stoppen.
Wenn wir hier passen, müssen wir nur das Bild auf dem Chart positionieren und visualisieren, also definieren wir zuerst die Positionierungskoordinaten.
// 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 } }
Für die Positionierung geben wir „x_offset“ und „y_offset“ an. Wenn „CenterImageDynamically“ true ist, wird das Bild mit „x_offset“ als (“chart_pixel_width“ - „scaled_image_width“) / 2 und „y_offset“ als (“chart_pixel_height“ - „scaled_image_height“) / 2 zentriert.
Andernfalls setzen wir mit switch auf „AnchorCorner“ die Abstände: “TOP_LEFT“ verwendet „XOffsetFromCorner“ und „YOffsetFromCorner“; „TOP_RIGHT“ setzt „x_offset“ auf „chart_pixel_width“ - „scaled_image_width“ - „XOffsetFromCorner“; „BOTTOM_LEFT“ setzt „y_offset“ auf „chart_pixel_height“ - „scaled_image_height“ - „YOffsetFromCorner“; „BOTTOM_RIGHT“ kombiniert beide. Standardmäßig werden „XOffsetFromCorner“ und „YOffsetFromCorner“ verwendet.
Wir brauchen nun eine Funktion, die das Bild über die definierten Koordinaten positioniert.
//+------------------------------------------------------------------+ //| 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 }
Hier implementieren wir die Funktion „CreateFullChartImage“, um ein Bildobjekt auf dem Chart zu erstellen und zu positionieren. Zunächst wird mit der Funktion ObjectFind geprüft, ob das Objekt mit dem Namen „object_name“ existiert. Ist dies nicht der Fall (Ergebnis < 0), wird mit der Funktion ObjectCreate ein neues Bitmap-Objekt erstellt, wobei „object_name“, OBJ_BITMAP_LABEL und die Standardkoordinaten des Charts angegeben werden.
Dann setzen wir die Eigenschaften des Objekts: Mit ObjectSetString verknüpfen wir „resource_name“ mit OBJPROP_BMPFILE für die Bildquelle, mit ObjectSetInteger setzen wir „OBJPROP_XSIZE“ auf „x_size“, „OBJPROP_YSIZE“ auf „y_size“, „OBJPROP_XDISTANCE“ auf „x_offset“ und „OBJPROP_YDISTANCE“ auf „y_offset“ für Größe und Position und OBJPROP_BACK auf „is_background“, um zwischen Hintergrund- und Vordergrundanzeige umzuschalten. Schließlich rufen wir die Funktion ChartRedraw auf, um die Chartanzeige mit dem neuen oder geänderten Bildobjekt zu aktualisieren. Anschließend rufen wir diese Funktion auf, um das Bild zu visualisieren.
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
Nachdem wir die Funktion aufgerufen haben, sind wir fertig und können diese Hauptfunktion nun in OnInit aufrufen, um das erste Bild zu erstellen.
//+------------------------------------------------------------------+ //| 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 }
Im OnInit-Ereignis-Handler rufen wir die Funktion „DisplayImageOnChart“ auf, um das Bild während der Initialisierung zu rendern. Wenn „DisplayImageOnChart“ den Wert false zurückgibt, was bedeutet, dass das Bild nicht angezeigt werden kann, wird eine Fehlermeldung protokolliert und INIT_FAILED zurückgegeben, um zu signalisieren, dass die Initialisierung fehlgeschlagen ist und das Programm angehalten wird. Wenn die Anzeige erfolgreich ist, geben wir INIT_SUCCEEDED zurück, um anzuzeigen, dass die Initialisierung erfolgreich abgeschlossen wurde und das Programm fortgesetzt werden kann. Die folgende Zusammenstellung zeigt, was wir haben.
Aus dem Bild geht hervor, dass wir das Ressourcenbild beim ersten Durchlauf zuordnen können. Wir müssen jedoch dafür sorgen, dass das Bild reagiert, wenn sich die Abmessungen des Charts ändern. Daher benötigen wir die Ereignisbehandlung durch 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 } } }
Hier definieren wir den OnChartEvent-Ereignishandler, um Ereignisse im Zusammenhang mit Charts in unserem Programm zu behandeln, wobei wir uns speziell auf die Größenänderung von Charts konzentrieren. Wir prüfen, ob „event_id“ gleich CHARTEVENT_CHART_CHANGE ist, was auf eine Änderung des Charts, z. B. eine Größenänderung, hinweist. Wenn „true“, rufen wir die Funktion „DisplayImageOnChart“ auf, um die Anzeige des Bildes zu aktualisieren, damit es den neuen Chartabmessungen entspricht. Wenn „DisplayImageOnChart“ den Wert „false“ zurückgibt, was bedeutet, dass das Bild nicht aktualisiert werden konnte, wird eine Fehlermeldung protokolliert, um den Nutzer auf das Problem hinzuweisen. Schließlich müssen wir alle Ressourcen entfernen, die wir im Chart zugeordnet haben, wenn wir das Programm entfernen.
//+------------------------------------------------------------------+ //| 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 }
Um eine saubere Beendigung zu gewährleisten, rufen wir im OnDeinit die Funktion ObjectDelete auf, um das durch „CHART_IMAGE_OBJECT_NAME“ identifizierte Chartobjekt aus dem Chart zu entfernen. Dadurch wird verhindert, dass nach Beendigung des Programms Restobjekte übrig bleiben. Beachten Sie, dass dynamische Ressourcen, wie z. B. solche, die für skalierte Bilder erstellt wurden, automatisch vom System freigegeben werden, wenn der Expert Advisor entfernt wird, und keine zusätzliche Bereinigung erforderlich ist. Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis.
Anhand des Bildes können wir sehen, dass wir das definierte Bild erstellen, es als Ressource festlegen und es dynamisch nach den Vorgaben des Nutzers skalieren, wodurch wir unser Ziel erreichen. Jetzt muss das Programm nur noch gründlich getestet werden, und das wird im nächsten Punkt behandelt.
Tests und Validierung
Wir haben alle Tests durchgeführt und sie in einer Visualisierung als Graphical Interchange Format (GIF) zusammengestellt, die zeigt, dass sie dynamisch ist und auf Größenänderungen des Charts und Nutzereingaben reagiert, ohne dabei an Qualität zu verlieren.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir ein MQL5-Tool für dynamische Bildgrafiken auf MetaTrader 5-Charts entwickelt haben, das bicubic interpolation für scharfe, anpassungsfähige Grafiken verwendet. Wir haben gezeigt, wie man sie für die nutzergesteuerte Skalierung und Positionierung von Bildern implementiert und anwendet. Sie können dieses Tool anpassen, um die Funktionalität und Ästhetik Ihrer Trading-Charts zu verbessern.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17892
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.