Charts interessanter machen: Hinzufügen eines Hintergrunds
Einführung
Viele Arbeitsplätze enthalten ein repräsentatives Bild, das etwas über den Benutzer aussagt. Diese Bilder machen die Arbeitsumgebung schöner und inspirierender, da die Menschen immer versuchen, die schönsten Bilder als Bildschirmhintergrund zu verwenden. Aber wenn wir die Handelsplattform öffnen, sehen wir, dass sie irgendwie langweilig ist. Alles, was wir haben, ist die grafische Darstellung von numerischen Daten.
Man kann Bilder und Fotos über einen längeren Zeitraum betrachten, ohne zu ermüden, aber einen Chart auch nur für ein paar Minuten zu betrachten, kann sehr ermüdend sein. Deshalb sollten wir es so einrichten, dass wir den Chart beobachten und analysieren können, während das Bild im Hintergrund uns motiviert und an etwas Gutes erinnert.
Planung
Zunächst einmal müssen wir festlegen, wie das gesamte Projekt ablaufen soll: Wollen wir den Hintergrund des Diagramms von Zeit zu Zeit ändern oder ein einziges Bild für die gesamte Lebensdauer des Programms verwenden, wobei für alle Charts dasselbe Bild verwendet wird? Ich möchte verschiedene Bilder auf verschiedenen Charts platzieren. Zum Beispiel etwas, das die Art des Vermögenswerts repräsentiert, mit dem ich handele, oder etwas, das andeutet, worauf ich bei diesem Vermögenswert achten sollte. Daher wird die resultierende kompilierte Datei kein internes Bild enthalten, sodass wir später jedes gewünschte Bild auswählen können.
Nun gibt es noch eine weitere Sache zu definieren: Wo sollen unsere Bilder gespeichert werden? MetaTrader 5 verfügt über eine Verzeichnisstruktur, die wir für den Zugriff auf Dinge verwenden sollten. Der Verzeichnisbaum kann nicht außerhalb dieses Rahmens verwendet werden. Wenn wir später auf Bilder zugreifen wollen, ist es wichtig zu wissen, wie diese Struktur zu verwenden ist. Da wir planen, den Speicher zu organisieren und im Laufe der Zeit zu pflegen, werden wir einen Ordner im Verzeichnis FILES erstellen und ihn WALLPAPERS nennen. So können wir auf die Bilder zugreifen, ohne den Baum zu verlassen, dessen Wurzel das MQL5-Verzeichnis ist.
Aber warum legen wir die Dateien nicht in den Ordner IMAGES? In diesem Fall müssten wir durch den Baum navigieren, was eine unnötige Aufgabe wäre, die die Logik des Programms verkompliziert. Da wir aber nach größtmöglicher Einfachheit streben, werden wir das nutzen, was MetaTrader 5 uns bietet. Die Struktur wird also wie folgt aussehen:
Danach fügen wir die Bilder wie unten gezeigt ein, d.h. wir trennen die Logobilder von den allgemeinen Hintergrundbildern. Diese Organisation sorgt für Ordnung, da viele verschiedene Bilder als Logos verwendet werden können, wenn wir mit mehreren Assets arbeiten.
Dies ist eine einfache und effiziente Lösung: Fügen Sie so viele Bilder hinzu, wie Sie benötigen, ohne den Betrieb des Programms zu beeinträchtigen. Bitte beachten Sie jetzt ein wichtiges Detail. Die Bilder sind im BITMAP-Format. Sie sollten im 24- oder 32-Bit-Format vorliegen, da diese Formate leicht zu lesen sind: MetaTrader 5 kann diese Formate standardmäßig lesen, also habe ich es so belassen. Natürlich ist es auch möglich, andere Formate zu verwenden, wenn Sie die Leseroutine so programmieren können, dass Sie am Ende ein BITMAP-Bild erhalten. Ich glaube jedoch, dass es einfacher ist, ein Bild mit einem Bildbearbeitungsprogramm in den 24-Bit- oder 32-Bit-Standard zu konvertieren, als eine separate Lesefunktion zu erstellen. Für Dateien im LOGOS-Ordner gelten die gleichen Prinzipien, allerdings mit einigen Ausnahmen, die wir gleich sehen werden.
Nachdem wir nun die Regeln definiert haben, können wir mit der Kodierung beginnen. Der Code folgt den Prinzipien der objektorientierten Programmierung (OOP), sodass Sie ihn leicht in ein Skript oder einen Indikator portieren oder sogar isolieren können, wenn Sie dies wünschen.
Schritt für Schritt
Der Code beginnt mit einigen Definitionen:
//+------------------------------------------------------------------+ enum eType {IMAGEM, LOGO, COR}; //+------------------------------------------------------------------+ input char user01 = 30; //Transparency ( 0 a 100 ) input string user02 = "WallPaper_01"; //File name input eType user03 = IMAGEM; //Chart background type //+------------------------------------------------------------------+
Hier geben wir an, was wir tun werden. Die Enumeration eType gibt an, was die Hintergrundgrafik sein wird: IMAGE, LOGO oder Farbe. Der Eintrag USER02 gibt den Namen der Datei an, die als Hintergrund verwendet werden soll, sofern in USER03 der Typ IMAGE ausgewählt wurde. USER01 gibt den Grad der Transparenz des Hintergrundbildes an, da es in manchen Fällen die Datenvisualisierung im Chart stören kann. Daher verwenden wir die Transparenz, um diesen Effekt zu minimieren. Der Transparenzwert kann zwischen 0% und 100% liegen: je höher der Wert, desto transparenter das Bild.
Die folgenden Funktionen sollten zu Ihrem Programm hinzugefügt werden:
Funktion | Parameter | Wo eine Funktion deklariert werden soll | Ergebnis |
---|---|---|---|
Init(string szName, char cView) | Dateiname und gewünschte Transparenzstufe | Als erste Funktion im OnInit-Code | Lädt die angegebene BITMAP-Datei und rendert sie mit der angegebenen Transparenz |
Init(string szName) | Nur die Datei wird benötigt | Als erste Funktion im OnInit-Code | Lädt die angegebene BITMAP-Datei ohne jegliche Transparenz |
Resize(void) | Keine Parameter erforderlich | Im Programmcode von OnChartEvent; im Ereignis CHARTEVENT_CHART_CHANGE | Angemessene Größenänderung des Bildes im Chart |
Sehen wir uns nun an, wie diese Funktionen im Hauptcode verwendet werden können, beginnend mit der unten gezeigten Klasseninitialisierung. Beachten Sie, dass der Nutzer in diesem Fall den Grad der Transparenz angeben kann. Damit der Wert korrekt ist, sollten wir ihn von 100 subtrahieren.
int OnInit() { if(user03 != COR) WallPaper.Init(user03 == IMAGE ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01)); return INIT_SUCCEEDED; }
Beachten Sie, dass das Bild nicht erscheint, wenn wir den Modus FARBE verwenden. Achten Sie aber auf den ternären Operator ?. Wenn wir ein Bild auswählen, verweist das Programm auf das Verzeichnis WALLPAPER im FILES-Baum. Wenn es sich um LOGO handelt, verweist es ebenfalls auf den entsprechenden Speicherort, aber beachten Sie, dass der Dateiname für das Logo mit dem Symbolnamen übereinstimmen muss, sonst wird ein Fehler erzeugt. Dies alles gilt für fortlaufende Serien. Wenn Sie jedoch einen Vermögenswert mit einem Verfallsdatum verwenden, müssen Sie eine kleine Funktion hinzufügen, um den Teil des Namens zu trennen, der die aktuelle Serie von der abgelaufenen Serie unterscheidet. Das Problem lässt sich lösen, indem man das Bild einfach umbenennt, sodass es den aktuellen Namen wiedergibt. Für diejenigen, die Cross-Orders verwenden, kann es interessant sein, eine separate Routine zur Anpassung des Symbolnamens einzurichten.
Die nächste Funktion, auf die Sie achten sollten, ist die folgende:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) WallPaper.Resize(); }
Alle Codes sind sehr kurz, da ich es nicht mag, Dinge zu verkomplizieren. Das erschwert nur, das System zu verbessern oder zu ändern. Versuchen Sie außerdem, es zu Ihrer Regel zu machen. Die obige Funktion garantiert, dass jede Änderung der Chart-Größe die Funktion aufruft, die die Größe des Bildes anpasst, um immer ein schönes Aussehen zu gewährleisten und das Bild vollständig zu rendern.
Unser Klassencode hat die folgenden Eigenschaften:
Funktion | Parameter | Ergebnis |
---|---|---|
MsgError(const eErr err, int fp) | Fehlertyp und Dateideskriptor | Schließt die Datei und zeigt die entsprechende Fehlermeldung an. |
MsgError(const eErr err) | Fehlertyp | Zeigt die entsprechende Fehlermeldung an. |
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) | Dateiname und Daten-Pointer | Lädt die gewünschte Datei und gibt ihre Daten im Format data[] sowie ihre Abmessungen in Pixeln zurück. |
~C_WallPaper() | Keine Parameter erforderlich | Ermöglicht das Schließen der Objektklasse. |
Init(const string szName, const char cView) | Dateiname und Transparenzstufe | Korrektes Initialisieren der gesamten Klasse. |
Init(const string szName) | Dateiname | Korrektes Initialisieren der gesamten Klasse. |
Destroy(void) | Keine Parameter erforderlich | Beendet die Klasse auf korrekte Weise. |
Resize(void) | Keine Parameter erforderlich | Korrektes Ändern der Bildgröße. |
Um ein völliges Chaos im Code zu vermeiden, habe ich die Fehlerverarbeitung in einer Funktion konzentriert, die unten gezeigt wird. Das Einzige, was sie tut, ist, eine Nachricht an den Nutzer zu senden, wenn etwas schief läuft. Das macht die Sache einfacher, denn im Falle einer Übersetzung in eine andere Sprache muss man nur die Meldungen in einer einzigen Routine ändern und nicht versuchen, jede verwendete Meldung zu finden.
bool MsgError(const eErr err, int fp = 0) { string sz0; switch(err) { case FILE_NOT_FOUND : sz0 = "File not found"; break; case FAILED_READ : sz0 = "Reading error"; break; case FAILED_ALLOC : sz0 = "Memory error"; break; case FAILED_CREATE : sz0 = "Error creating an internal resource"; break; }; MessageBox(sz0, "WARNING", MB_OK); if(fp > 0) FileClose(fp); return false; }
Die folgende Funktion liest die Datei und lädt sie in den Speicher. Die einzige Information, die wir brauchen, ist der Dateiname, während die Funktion den Rest der Daten ausfüllt. Am Ende erhalten wir die Abmessungen des Bildes und das Bild selbst, allerdings im BITMAP-Format. Es ist wichtig, dies zu beachten, denn obwohl es verschiedene Formate gibt, endet das Ergebnis immer mit BITMAP; nur die Art der Komprimierung unterscheidet das eine Format vom anderen.
bool LoadBitmap(const string szFileName, uint &data[], int &width, int &height) { struct BitmapHeader { ushort type; uint size; uint reserv; uint offbits; uint imgSSize; uint imgWidth; uint imgHeight; ushort imgPlanes; ushort imgBitCount; uint imgCompression; uint imgSizeImage; uint imgXPelsPerMeter; uint imgYPelsPerMeter; uint imgClrUsed; uint imgClrImportant; } Header; int fp; bool noAlpha, noFlip; uint imgSize; if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE) return MsgError(FILE_NOT_FOUND); if(FileReadStruct(fp, Header) != sizeof(Header)) return MsgError(FAILED_READ, fp); width = (int)Header.imgWidth; height = (int)Header.imgHeight; if(noFlip = (height < 0)) height = -height; if(Header.imgBitCount == 32) { uint tmp[]; noAlpha = true; imgSize = FileReadArray(fp, data); if(!noFlip) for(int c0 = 0; c0 < height / 2; c0++) { ArrayCopy(tmp, data, 0, width * c0, width); ArrayCopy(data, data, width * c0, width * (height - c0 - 1), width); ArrayCopy(data, tmp, width * (height - c0 - 1), 0, width); } for(uint c0 = 0; (c0 < imgSize && noAlpha); c0++) if(uchar(data[c0] >> 24) != 0) noAlpha = false; if(noAlpha) for(uint c0 = 0; c0 < imgSize; c0++) data[c0] |= 0xFF000000; } else { int byteWidth; uchar tmp[]; byteWidth = width * 3; byteWidth = (byteWidth + 3) & ~3; if(ArrayResize(data, width * height) != -1) for(int c0 = 0; c0 < height; c0++) { if(FileReadArray(fp, tmp, 0, byteWidth) != byteWidth) return MsgError(FAILED_READ, fp); else for(int j = 0, k = 0, p = width * (height - c0 - 1); j < width; j++, k+=3, p++) data[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k]; } } FileClose(fp); return true; }
Sehen Sie sich die folgende Codezeile an:
if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)
Bitte beachten Sie, dass die Dateierweiterung an dieser Stelle angegeben wird, d.h. wir geben sie nicht in dem Moment an, in dem wir angeben, was das Bild sein wird, denn wenn wir die Erweiterung angeben, erhalten wir einen "Datei nicht gefunden"-Fehler. Der Rest der Funktion ist recht einfach: zuerst liest sie den Header der Datei und prüft, ob es sich um eine 32-Bit- oder 24-Bit-BITMAP handelt; dann liest sie das Bild entsprechend, da 32-Bit-Bilder eine etwas andere interne Struktur haben als 24-Bit-Bilder.
Die nächste Funktion initialisiert alle Daten für unser Bitmap-Bild, das auf dem Bildschirm angezeigt werden soll. Bitte beachten Sie, dass wir während dieser Funktion die Bitmap-Datei in eine Programmressource umwandeln. Das ist notwendig, weil wir diese Ressource später mit einem Objekt verknüpfen werden, und genau dieses Objekt wird auf dem Bildschirm nicht als Objekt, sondern als Ressource angezeigt. Es scheint schwer zu verstehen, warum wir das so machen. Aber dieser Schritt ermöglicht es uns, mehrere Ressourcen desselben Typs zu erstellen und sie dann mit einem einzigen Objekt zu verknüpfen, das zur Anzeige von etwas verwendet wird. Wenn wir dem Programm eine einzige bestimmte Ressource hinzufügen würden, würden wir sie als interne Ressource definieren und die Datei kompilieren. In diesem Fall wäre es unmöglich, die Ressource zu ändern, ohne den Quellcode neu zu kompilieren. Durch die Erstellung einer dynamischen Ressource ist es jedoch möglich, festzulegen, welche Ressource verwendet werden soll.
bool Init(const string szName, const char cView = 100, const int iSub = 0) { double dValue = ((cView > 100 ? 100 : (cView < 0 ? 0 : cView)) * 2.55) / 255.0; m_Id = ChartID(); if(!LoadBitmap(szName, m_BMP, m_MemWidthBMP, m_MemHeightBMP)) return false; Destroy(); m_Height = m_MemHeightBMP; m_Width = m_MemWidthBMP; if(ArrayResize(m_Pixels, (m_MemSizeArr = m_Height * m_Width)) < 0) return MsgError(FAILED_ALLOC); m_szRcName = "::" + szName + (string)(GetTickCount64() + MathRand()); if(!ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) return MsgError(FAILED_CREATE); if(!ObjectCreate(m_Id, (m_szObjName = szName), OBJ_BITMAP_LABEL, iSub, 0, 0)) return MsgError(FAILED_CREATE); ObjectSetInteger(m_Id, m_szObjName, OBJPROP_XDISTANCE, 0); ObjectSetInteger(m_Id, m_szObjName, OBJPROP_YDISTANCE, 0); ObjectSetString(m_Id, m_szObjName, OBJPROP_BMPFILE, m_szRcName); ObjectSetInteger(m_Id, m_szObjName, OBJPROP_BACK, true); for(uint i = 0; i < m_MemSizeArr; i++) m_BMP[i] = (uchar(double(m_BMP[i] >> 24) * dValue) << 24) | m_BMP[i] & 0x00FFFFFF; return true; }
Das ist alles sehr schön und scheinbar praktisch, aber das Objekt selbst ist nicht in der Lage, die Ressource zu verändern. Das bedeutet, dass es nicht möglich ist, die Art und Weise zu ändern, wie eine Ressource funktioniert oder dargestellt wird, indem man sie einfach mit einem Objekt verknüpft. Das macht die Sache manchmal etwas komplizierter, denn meistens müssen wir die Art und Weise, wie die Ressource geändert werden soll, innerhalb des Objekts kodieren.
Bis zu diesem Punkt konnte ein Bild mit folgendem Code gerendert werden:
if(ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) ChartRedraw();
Die Verwendung dieses Codes garantiert jedoch nicht, dass das Bild wie erwartet dargestellt wird, abgesehen von der Tatsache, dass es die genauen Abmessungen des Charts hat. Ich empfehle Ihnen, hochauflösende Bilder zu verwenden. Große Bilder werden besser dargestellt, was die Berechnung erleichtert und Zeit bei der Verarbeitung spart, was in bestimmten Szenarien entscheidend sein kann. Aber selbst dann haben wir immer noch das Problem, dass das Bild nicht korrekt dargestellt wird, da das Objekt die Ressource nicht so verändert, dass sie den Spezifikationen des Objekts entspricht. Wir müssen also etwas tun, um eine korrekte Modellierung der Ressource zu ermöglichen, damit sie mithilfe des Objekts dargestellt werden kann. Die Mathematik im Zusammenhang mit dem Bild reicht von den einfachsten Berechnungen bis hin zu sehr komplizierten Dingen. Da die Verarbeitungszeit für uns aber genauso wichtig ist wie beim Preis-Chart, können wir es uns nicht leisten, übermäßige Berechnungen durchzuführen. Die Dinge sollten so einfach und schnell wie möglich sein. Aus diesem Grund können wir Bilder verwenden, die größer sind als der Chart, da die einzige erforderliche Berechnung die Verkleinerung der Größe ist. Schauen wir uns an, wie das funktionieren wird.
Die mathematischen Beziehungen, die im obigen Chart verwendet werden, können wie folgt ermittelt werden:
Beachten Sie, dass hier f(x) = f(y) gilt, wodurch das Bildverhältnis erhalten bleibt. Dies wird auch als "Seitenverhältnis" bezeichnet, was bedeutet, dass sich das Bild vollständig verändert. Was aber, wenn f(y) nicht von f(x) abhinge, was würde dann mit unserem Bild passieren? Nun, es würde sich überproportional verändern, also jede beliebige Form annehmen. Obwohl wir bei der Verkleinerung kein Problem haben, gilt dies nicht für die Vergrößerung: Wenn f(x) > 1,0 oder f(y) > 1,0 ist, erhalten wir einen Bildzoom, und hier beginnen die Probleme. Das erste Problem tritt in der folgenden Abbildung auf:
Dies geschieht aufgrund des Effekts, der in der folgenden Abbildung zu sehen ist. Beachten Sie, dass die weißen Flächen leere Bereiche darstellen, die im Bild erscheinen, wenn es durch den Zoom-Effekt wächst. Dies geschieht immer, wenn f(x) oder f(y) größer als eins ist, d. h. wenn wir dem roten Pfeil folgen. In der folgenden Abbildung ist f(x) = f(y) = 2,0, d. h. wir vergrößern das Bild um den Faktor 2.
Es gibt mehrere Möglichkeiten, dieses Problem zu lösen, eine davon ist die Interpolation, die stattfinden sollte, wenn ein leerer Block gefunden wird. In diesem Moment sollten wir eine Faktorisierung durchführen und die Zwischenfarbe zwischen der verwendeten Farbe berechnen, um einen Glättungseffekt zu erzielen und leere Punkte aufzufüllen. Es gibt jedoch ein Problem, das mit den Berechnungen zusammenhängt. Selbst wenn die Interpolation schnell durchgeführt wird, ist dies keine geeignete Lösung für MetaTrader 5 Charts mit Echtzeitdaten. Selbst wenn die Größenanpassung ein paar Mal während der gesamten Zeit, in der der Chart auf dem Bildschirm angezeigt wird, durchgeführt wird (da in den meisten Fällen die Größe des Charts kleiner als das Bild ist, in diesem Fall sind f(x) und f(y) gleich oder kleiner als 1,0, und die Interpolation hat keinen Effekt), aber wenn Sie an ein Bild mit einer Größe von 1920 x 1080 (FULL HD) auf demselben Bildschirm denken, erhöht die Interpolation die Verarbeitungszeit erheblich, ohne dass das Endergebnis davon profitiert.
Im Folgenden sehen Sie, wie die Interpolationsberechnung bei einem Bild mit doppelter Größe durchgeführt wird. Natürlich wird es sehr schnell sein, aber vergessen Sie nicht, dass dies in einem 32-Bit-Farbschema oder ARGB geschehen muss, wo wir 4 Bytes mit 8 Bits für die Berechnung haben. Der Grafikprozessor verfügt über Funktionen, mit denen diese Berechnungen schnell durchgeführt werden können, aber der Zugriff auf diese Funktionen über OpenCL bringt möglicherweise keinen praktischen Nutzen, da es zu einer Verzögerung bei der Dateneingabe und -ausgabe durch den Grafikprozessor kommt. Aus diesem Grund kann die Geschwindigkeit der von der GPU durchgeführten Berechnungen keinen Vorteil darstellen.
In Anbetracht dessen denke ich, dass eine etwas schlechtere Bildqualität aufgrund des Glättungseffekts keine große Sache ist, da in den meisten Fällen f(x) oder f(y) nicht höher als 2 sein wird, nur wenn ein FULL HD-Bild auf einem 4k-Bildschirm verwendet wird. In diesem Szenario ist die Glättung minimal und kaum zu sehen. Daher ziehe ich es vor, statt Punkte zu interpolieren, einen Punkt zum nächsten zu ziehen, um leere Werte schnell aufzufüllen und so die Rechenkosten auf ein Minimum zu reduzieren. Die Art und Weise, wie dies geschieht, ist unten dargestellt. Da wir die Daten einfach kopieren, können wir alle 32 Bits in einem Schritt verarbeiten, und zwar so schnell, wie es ein Grafikverarbeitungssystem liefern würde.
Hier ist also die Funktion, die eine schnelle Größenänderung des Bildes ermöglicht.
void Resize(void) { m_Height =(uint) ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS); m_Width = (uint) ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS); double fx = (m_Width * 1.0) / m_MemWidthBMP; double fy = (m_Height * 1.0) / m_MemHeightBMP; uint pyi, pyf, pxi, pxf, tmp; ArrayResize(m_Pixels, m_Height * m_Width); ArrayInitialize(m_Pixels, 0x00FFFFFF); for (uint cy = 0, y = 0; cy < m_MemHeightBMP; cy++, y += m_MemWidthBMP) { pyf = (uint)(fy * cy) * m_Width; tmp = pyi = (uint)(fy * (cy - 1)) * m_Width; for (uint x = 0; x < m_MemWidthBMP; x++) { pxf = (uint)(fx * x); pxi = (uint)(fx * (x - 1)); m_Pixels[pxf + pyf] = m_BMP[x + y]; for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y]; } for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp]; } if (ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) ChartRedraw(); }
Die Funktion hat eine verschachtelte Schleife, die innere Schleife führt die Funktion f(x) aus, die äußere Schleife - f(y). Bei der Ausführung von f(x) kann es zu leeren Bereichen kommen - dies wird in der folgenden Zeile behoben:
for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];
Wenn eine Differenz zwischen den X-Werten auftritt, wird diese in der obigen Zeile durch Kopieren des letzten Wertes des Bildes behoben. Infolgedessen kommt es zu Aliasing, aber der Rechenaufwand ist in diesen Fällen minimal, da dieses Fragment eine interne Schleife enthält, die so kurz wie möglich läuft, falls sie ausgeführt wird (was nicht immer der Fall sein wird). Wenn Sie Daten ohne diesen Aliasing-Effekt interpolieren möchten, ändern Sie einfach diese Zeile, um die oben beschriebenen Berechnungen durchzuführen.
Sobald die gesamte Zeile berechnet wurde, überprüfen Sie f(y), um leere Bereiche zu vermeiden, wenn f(y) größer als 1 ist. Dies geschieht in der folgenden Zeile:
for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];
Auch hier kommt es zu einem Aliasing, aber das kann auf die gleiche Weise behoben werden wie die Änderung des Codes in der vorherigen Zeile. Wir fügen die Breite des neuen Bildes hinzu, weil wir eine Zeile kopieren, die bereits von der Schleife bearbeitet wurde, die für die Bearbeitung von f(x) des neuen Bildes zuständig ist. Würde dies mit einem anderen Wert geschehen, würde das Bild auf seltsame Weise verzerrt werden.
Schlussfolgerung
Ich hoffe, dass diese Idee dazu beiträgt, dass Ihre Charts mehr Spaß machen und stundenlang betrachtet werden können, denn wenn das Hintergrundbild ermüdend wird, können Sie einfach ein anderes wählen, ohne etwas neu kompilieren zu müssen. Wählen Sie einfach das neue Bild aus, das als Hintergrund für Ihr Chart angezeigt werden soll.
Ein letztes Detail sei hier noch erwähnt: Wenn Sie die Klasse, die das Hintergrundbild platziert, in einem EA verwenden möchten, muss sie als Erstes in der INIT-Routine deklariert werden. Dadurch wird verhindert, dass das Hintergrundbild andere vom EA erstellte grafische Objekte überlagert.
Genießen Sie das Endergebnis in vollen Zügen, denn jetzt werden Sie noch tiefer in die Chart-Analyse eintauchen...
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10215
- 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.
anwenden
De hecho, aconteceu algo envolvendo as atualizações do MT5, que transformam o Wallpaper em algo diferente do esperado e mostrado no artigo, já que o correto que ele seja exibido no FUNDO GRÁFICO, pois na classe C_WallPaper você encontrará a seguinte linha:
Das bedeutet, dass das Objekt nicht mehr im Grundzustand ist, sondern an den Rand gedrängt wird, da das Objekt, das den Bildschirm empfängt, oder die Bitmap, die die Cliquen empfängt, eine Lösung wäre, den Status aller Objekte zu erhöhen oder den Status der Bitmap zu verringern, was in diesem Fall einfacher ist, da sich der Wert des Objekts, das den Bildschirmhintergrund empfängt, mit dem Wert des Objekts OBJPROP_ZORDER ändert, Tentei as duas soluções, mas não consegui stabilizar a coisa toda de forma a corrigir o problema, portanto, e INFELIZMENTE, o papel de parede deve descartado por hora ... Wenn Sie aufpassen, werden Sie sehen, dass das Bitmap-Bild auf dem Körper der Kerzen gezeichnet wird, was anzeigt, dass das Bitmap-Objekt im Vordergrund ist. Auch dies ist nicht das erwartete Verhalten aufgrund der obigen Codezeile, weshalb es alle Klick-Ereignisse empfängt ... 🙁
tipo mudar a imem no gráfico ao clicar no arquivo. Ich würde gerne wissen, ob das möglich ist, oder ob es nicht möglich ist und ob ich meine Nerven nicht überstrapazieren muss.
De hecho, etwas geschah mit der MT5-Updates, die das Hintergrundbild in etwas anderes als die erwartete und in dem Artikel gezeigt, da die richtige, dass es in der GRAPHIC BACKGROUND angezeigt werden, weil in der C_WallPaper Klasse finden Sie die folgende Zeile:
Das bedeutet, dass das Objekt im Hintergrund bleiben muss, seltsamerweise kommt es in den Vordergrund, deshalb beginnt das OBJEKT, das die BitMap empfängt, die Klicks zu empfangen, eine Lösung wäre, den Status aller Objekte zu erhöhen, oder zu versuchen, den Bitmap-Status zu senken, in diesem Fall wäre die zweite Lösung einfacher, Dies würde durch Ändern des Wertes der Eigenschaft OBJPROP_ZORDER in dem Objekt, das das Hintergrundbild empfängt, erreicht werden, habe ich versucht, beide Lösungen, aber ich konnte nicht stabilisieren die ganze Sache in einer Weise, um das Problem zu beheben, daher, und INFELIZMENTE, muss das Hintergrundbild für jetzt verworfen werden ... Wenn Sie aufpassen, werden Sie sehen, dass das Bitmap-Bild auf den Kerzenkörper gezeichnet wird, was anzeigt, dass das Bitmap-Objekt im Vordergrund ist. Auch dies ist nicht das erwartete Verhalten aufgrund der obigen Codezeile, da es alle Klick-Ereignisse empfängt ... 🙁
Daniel,
Ist es möglich, wenn Sie einen Timer zu setzen und führen Sie eine Funktion zu deaktivieren und aktivieren Sie die Tapete wieder oder etwas, das "bekräftigen" die Funktion, indem Sie die Tapete in den Hintergrund (zurück)?
Da ich ein GUI-Panel verwende, musste ich mich für diese Lösung entscheiden. Einige grafische Elemente müssen gelöscht und neu erstellt werden, damit sie je nach Objekttyp im Hintergrund oder im Vordergrund stehen.