DoEasyライブラリのグラフィックス(第78部): ライブラリのアニメーションの原則イメージスライス
内容
概念
グラフィカルインターフェイスは本質的に非静的画像の存在を意味します。表示されるデータ(たとえば、表に示されているデータ)は、時間の経過とともに変化する可能性があります。GUI要素は、さまざまな視覚効果などを使用して、ユーザの操作に反応する場合があります。
さまざまな視覚効果メソッドを作成し、ライブラリにスプライトアニメーションを操作する機能を与えます。アニメーションは、静止画像の変化する(フレームごとの)シーケンスの使用に基づいています。
CCanvasクラスを使用すると、キャンバスに画像を描画できます。描画され画像配列に保存された一連の画像から、特定のシーケンスを作成すると、これは最終的にアニメーション画像になります。ただし、後続の各画像を1つずつキャンバスに描画したとすれば、それらは単に互いに重なり合い、最終的には下の画像のように、混沌としたピクセルの山になってしまいます(ここでは、フォームオブジェクトのさまざまな場所にテキストを表示しています)。
これを回避するには、前の画像を完全に消去し、背景を再描画してその上にテキストを表示する必要があります(これは、テキストをフォームに配置してテキストアンカーメソッドを説明するときに、前の記事の1つで行いました)。このオプションは、再描画されたフォームのサイズが小さく複雑でない場合にのみ実行可能です。もう1つのオプションは、テキストを重ね合わせる背景の一部を(配列内の)メモリに保存してから、テキストを追加することです。新しい座標に再配置する場合は、配列から以前に保存した背景画像を使用して描画したテキストを上書きし(背景を復元するため)、新しい場所にテキストを描画します(テキストの移動先の背景の一部を事前に保存します) 。したがって、画像を重ね合わせる場所の背景は常にメモリに保存され、画像を変更する必要がある場合は復元されます。
これは、ライブラリで紹介するスプライトアニメーションの概念の最小要素です。
- 必要な座標で背景を保存する
- 座標を使用して画像を表示する
- 画像を再描画するときに背景を復元する
これをすべて実現するために、画像の座標とサイズを格納するための小さなクラスを作成します。これらの座標とサイズを使用して背景画像の一部を保存するメソッドは、クラスでも作成されます。さらに、配列に保存された背景を格納する2番目のメソッドが必要になります(サイズと座標は、背景を配列に保存するときにクラス変数に保存されます)。
フォームオブジェクトに対してそのようなメソッドを2つ作成するのではなく、クラスを作成するのはなぜでしょうか。ここではすべてが簡単です。テキストまたは単一のアニメーション画像のみを表示する必要がある場合は2つのメソッドで十分ですが、フォームのさまざまな場所に複数のテキストを表示する必要がある場合は、クラスの方が便利だからです。各アニメーション画像は、個別に管理できる独自のクラスインスタンスを受け取ります。
このような概念により、以前に描画した画像を背景として使用した描画が可能になります。背景と描画した画像の両方を保存し、それらを背景から削除できます。
この概念を使用して、フォームオブジェクトにさまざまなスプライトアニメーションを作成、保存、表示するためのクラスを開発します。各クラスインスタンスには、リストに動的に追加して処理できる一連の画像が含まれています。
ライブラリクラスの改善
\MQL5\Include\DoEasy\Data.mqhに新しいメッセージインデックスを追加します。
//--- CChartObjCollection MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION, // Chart collection MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, // Failed to create a new chart object MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART, // Failed to add a chart object to the collection MSG_CHART_COLLECTION_ERR_CHARTS_MAX, // Cannot open new chart. Number of open charts at maximum MSG_CHART_COLLECTION_CHART_OPENED, // Chart opened MSG_CHART_COLLECTION_CHART_CLOSED, // Chart closed MSG_CHART_COLLECTION_CHART_SYMB_CHANGED, // Chart symbol changed MSG_CHART_COLLECTION_CHART_TF_CHANGED, // Chart timeframe changed MSG_CHART_COLLECTION_CHART_SYMB_TF_CHANGED, // Chart symbol and timeframe changed //--- CGCnvElement MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY, // Error! Empty array //--- CForm MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ, // Failed to create new shadow object MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ, // Failed to create new pixel copier object MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST, // Pixel copier object with ID already present in the list MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST, // No pixel copier object with ID in the list //--- CShadowObj MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE, // Error! Image size too small or blur too extensive }; //+------------------------------------------------------------------+
また、新しく追加したインデックスに対応するメッセージテキストも追加します。
//--- CChartObjCollection {"Коллекция чартов","Chart collection"}, {"Не удалось создать новый объект-чарт","Failed to create new chart object"}, {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"}, {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"}, {"Открыт график","Open chart"}, {"Закрыт график","Closed chart"}, {"Изменён символ графика","Changed chart symbol"}, {"Изменён таймфрейм графика","Changed chart timeframe"}, {"Изменён символ и таймфрейм графика","Changed the symbol and timeframe of the chart"}, //--- CGCnvElement {"Ошибка! Пустой массив","Error! Empty array"}, //--- CForm {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"}, {"Не удалось создать новый объект для тени","Failed to create new object for shadow"}, {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"}, {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "}, {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "}, //--- CShadowObj {"Ошибка! Размер изображения очень маленький или очень большое размытие","Error! Image size is very small or very large blur"}, }; //+---------------------------------------------------------------------+
グラフィック要素オブジェクトから継承された既製のフォームオブジェクト、またはカスタムプログラムの他のGUIオブジェクトに画像やテキストを描画するため、いつでも元の形式に復元できるように、オブジェクトの初期外観を常に手元に置いておく必要があります。
もちろん、新しく再描画することもできますが、配列間のコピーははるかに高速です。
これを行うために、グラフィック要素オブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhでいくつかの変更と改善を行います。
クラスのprotectedセクションで、配列を宣言します。これには、作成直後に初期オブジェクト(その外観)のすべてのピクセルが含まれます。また、CCanvasクラスインスタンスのグラフィカルリソースを配列に保存しするメソッドも宣言します。
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_data_array[]; // Array for storing resource data copy //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Save the graphical resource to the array bool ResourceCopy(const string source); private:
少量のメモリを犠牲にして配列を別の配列にコピーするだけで、プログラムインターフェイスの任意の要素の外観を元の形式にすばやく復元できます。
クラスのprivateセクションで、最後に描画されたテキストのX座標とY座標を格納するための2つの変数を宣言します。
long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties ENUM_TEXT_ANCHOR m_text_anchor; // Current text alignment int m_text_x; // Text last X coordinate int m_text_y; // Text last Y coordinate color m_color_bg; // Element background color uchar m_opacity; // Element opacity //--- Return the index of the array the order's (1) double and (2) string properties are located at
クラスのpublicセクションで、現在のクラスインスタントへのポインタを返すメソッドを記述し、指定された配列に画像を保存するメソッドを宣言します。
//--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return false; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return itself CGCnvElement *GetObject(void) { return &this; } //--- Compare CGCnvElement objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CGCnvElement objects with each other by all properties (to search equal objects) bool IsEqual(CGCnvElement* compared_obj) const; //--- (1) Save the object to file and (2) upload the object from the file virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; } //--- Set the canvas update frequency void SetFrequency(const ulong value) { this.m_pause.SetWaitingMSC(value); } //--- Update the coordinates (shift the canvas) bool Move(const int x,const int y,const bool redraw=false); //--- Save an image to the array bool ImageCopy(const string source,uint &array[]);
クラスが自らへのポインタを戻すためのメソッドは、クラスへのポインタを以下で検討するピクセルコピークラスに渡すために必要です。一方、CCanvasインスタンスのグラフィカルリソースをコピーするメソッドは、フォームの外観をライブラリベースのプログラムの必要な配列にすばやくコピーするために必要です。
テキストを操作するメソッドのコードブロックに、最後に描画されたテキストのX座標とY座標を返す2つのメソッドを追加します。
//+------------------------------------------------------------------+ //| Methods of working with text | //+------------------------------------------------------------------+ //--- Return (1) alignment type (anchor method), the last (2) X and (3) Y text coordinate ENUM_TEXT_ANCHOR TextAnchor(void) const { return this.m_text_anchor; } int TextLastX(void) const { return this.m_text_x; } int TextLastY(void) const { return this.m_text_y; } //--- Set the current font
メソッドは、適切な変数の値を返すだけです。
これらの値が常に関連性を保つために、現在のフォントを使用してテキストを表示するメソッドの変数に、メソッド引数に渡された座標を書き込みます。
//--- Display the text in the current font void Text(int x, // X coordinate of the text anchor point int y, // Y coordinate of the text anchor point string text, // Display text const color clr, // Color const uchar opacity=255, // Opacity uint alignment=0) // Text anchoring method { this.m_text_anchor=(ENUM_TEXT_ANCHOR)alignment; this.m_text_x=x; this.m_text_y=y; this.m_canvas.TextOut(x,y,text,::ColorToARGB(clr,opacity),alignment); }
描画されたテキストには、9つのアンカーポイントを含めることができます。
たとえば、テキストアンカーポイントが右下隅(Right|Bottom)にある場合、これが開始XY座標になります。ライブラリ内のすべての初期座標は、長方形の左上隅(Left|Top)に対応するため、初期テキスト座標を使用して画像を保存すると、テキストは保存された画像の右下に配置されます。これでは、テキストが重ねられる背景の領域を正しく保存できません。
したがって、長方形の輪郭を描くテキストの座標のオフセットを計算し、その後の復元のために、背景を配列に保存する必要があります。将来のテキストの幅と高さは、テキストを描画する前に事前に計算されるため、指定する必要があるのはテキスト自体だけです。CCanvasクラスのTextSize()メソッドは、輪郭を描く長方形の幅と高さを返します。
クラスのpublicセクションで、テキスト配置メソッドに応じてX/Yオフセットを返すメソッドを宣言します。
//--- Return coordinate offsets relative to the text anchor point void TextGetShiftXY(const string text, // Text for calculating the size of its outlining rectangle const ENUM_TEXT_ANCHOR anchor,// Text anchor point, relative to which the offsets are calculated int &shift_x, // X coordinate of the rectangle upper left corner int &shift_y); // Y coordinate of the rectangle upper left corner }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+
このメソッドについては、以下で検討します。
パラメトリッククラスコンストラクタで最後に描画されたテキストの座標を初期化します。
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND); this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.SetFont("Calibri",8); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow()); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name()); // Element object name this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_ID,element_id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x); // Element's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y); // Element's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h); // Element height this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); // Element bottom border this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft()); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop()); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area } else { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name); } } //+------------------------------------------------------------------+
同様に、protectedコンストラクタで変数を初期化します。
//+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND); this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.SetFont("Calibri",8); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=NULL_COLOR; this.m_opacity=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false)) { ...
次に、上記で宣言されたメソッドの実装について考えてみましょう。
画像を配列に保存するメソッドを実装します。
//+------------------------------------------------------------------+ //| Save the image to the array | //+------------------------------------------------------------------+ bool CGCnvElement::ImageCopy(const string source,uint &array[]) { ::ResetLastError(); int w=0,h=0; if(!::ResourceReadImage(this.NameRes(),array,w,h)) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES,true); return false; } return true; } //+------------------------------------------------------------------+
メソッドは、(エラーの可能性を見つけるために)呼び出されたメソッドまたは関数の名前とグラフィカルリソースデータ(画像ピクセル)が書き込まれる配列へのリンクを受け取ります。
ResourceReadImage()関数を使用して、CCanvasクラスによって作成され、フォームの画像を含むグラフィカルリソースのデータを配列に読み込みます。リソース読み取りエラーが発生した場合は、そのことを通知し、falseを返します。すべて問題がなければ、trueを返します。 リソースに保存されているすべての画像ピクセルは、メソッドに渡された配列に書き込まれます。
以下は、グラフィカルリソースを配列に保存するメソッドです。
//+------------------------------------------------------------------+ //| Save the graphical resource to the array | //+------------------------------------------------------------------+ bool CGCnvElement::ResourceCopy(const string source) { return this.ImageCopy(DFUN,this.m_data_array); } //+------------------------------------------------------------------+
このメソッドは、上記で検討したメソッドを呼び出した結果を返します。唯一の違いは、グラフィカルリソースデータが、リンクによって渡される配列ではなく、フォームオブジェクト全体の画像のコピーを格納するために以前に宣言された特別な配列に書き込まれることです。
以下は、テキストアンカーポイントを基準にした座標オフセットを返すメソッドです。
//+------------------------------------------------------------------+ //| Return coordinate offsets relative to the text anchor point | //+------------------------------------------------------------------+ void CGCnvElement::TextGetShiftXY(const string text,const ENUM_TEXT_ANCHOR anchor,int &shift_x,int &shift_y) { int tw=0,th=0; this.TextSize(text,tw,th); switch(anchor) { case TEXT_ANCHOR_LEFT_TOP : shift_x=0; shift_y=0; break; case TEXT_ANCHOR_LEFT_CENTER : shift_x=0; shift_y=-th/2; break; case TEXT_ANCHOR_LEFT_BOTTOM : shift_x=0; shift_y=-th; break; case TEXT_ANCHOR_CENTER_TOP : shift_x=-tw/2; shift_y=0; break; case TEXT_ANCHOR_CENTER : shift_x=-tw/2; shift_y=-th/2; break; case TEXT_ANCHOR_CENTER_BOTTOM : shift_x=-tw/2; shift_y=-th; break; case TEXT_ANCHOR_RIGHT_TOP : shift_x=-tw; shift_y=0; break; case TEXT_ANCHOR_RIGHT_CENTER : shift_x=-tw; shift_y=-th/2; break; case TEXT_ANCHOR_RIGHT_BOTTOM : shift_x=-tw; shift_y=-th; break; default: shift_x=0; shift_y=0; break; } } //+------------------------------------------------------------------+
ここでは、まずメソッドに渡されるテキストのサイズを取得し(サイズは宣言された変数で設定されます)、次に、メソッドに渡されるテキストのアンカーメソッドに応じて初期テキスト座標に依存して、XおよびY座標を初期のテキスト座標に対して移動するために必要なピクセル数を計算します。
次に影オブジェクトクラスを改善します。グラフィカルリソースを読み取るためのメソッドと、グラフィカルリソースのコピーを格納できる定数配列を追加したばかりなので、過剰な変数、配列、およびコードブロックを影オブジェクトクラスから削除できます。
\MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqhファイルを改善しましょう。
ガウスぼかしメソッドから配列と不要な変数を削除します。
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/ja/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CShadowObj::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; uint res_data[]; // Array for storing graphical resource data uint res_w=this.Width(); // Graphical resource width uint res_h=this.Height(); // Graphical resource height //--- Read graphical resource data. 失敗した場合は、falseを返します。
グラフィカルリソースデータを読み取るブロックで、文字列を上記のメソッドの呼び出しで置き換えます。
//--- Read graphical resource data. If failed, return false ::ResetLastError(); if(!::ResourceReadImage(this.NameRes(),res_data,res_w,res_h)) { CMessage::OutByID(MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES); return false; } //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceCopy(DFUN)) return false;
コード全体で、削除されたres_w 変数とres_h 変数の代わりにグラフィック要素オブジェクトクラスのメソッドのWidth()メソッドとHeight()メソッドを使用します。res_data配列の代わりに、m_data_array配列を使用します。これは現在グラフィカルリソースのコピーを格納するために使用されています。
一般に、すべての改善は、不要で削除された変数をグラフィック要素オブジェクトクラスのメソッドに置き換えることに要約されています。
//+------------------------------------------------------------------+ //| Gaussian blur | //| https://www.mql5.com/ja/articles/1612#chapter4 | //+------------------------------------------------------------------+ bool CShadowObj::GaussianBlur(const uint radius) { //--- int n_nodes=(int)radius*2+1; //--- Read graphical resource data. If failed, return false if(!CGCnvElement::ResourceCopy(DFUN)) return false; //--- Check the blur amount. If the blur radius exceeds half of the width or height, return 'false' if((int)radius>=this.Width()/2 || (int)radius>=this.Height()/2) { ::Print(DFUN,CMessage::Text(MSG_SHADOW_OBJ_IMG_SMALL_BLUR_LARGE)); return false; } //--- Decompose image data from the resource into a, r, g, b color components int size=::ArraySize(this.m_data_array); //--- arrays for storing A, R, G and B color components //--- for horizontal and vertical blur uchar a_h_data[],r_h_data[],g_h_data[],b_h_data[]; uchar a_v_data[],r_v_data[],g_v_data[],b_v_data[]; //--- Change the size of component arrays according to the array size of the graphical resource data if(::ArrayResize(a_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_h_data\""); return false; } if(::ArrayResize(r_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_h_data\""); return false; } if(::ArrayResize(g_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_h_data\""); return false; } if(ArrayResize(b_h_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_h_data\""); return false; } if(::ArrayResize(a_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"a_v_data\""); return false; } if(::ArrayResize(r_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"r_v_data\""); return false; } if(::ArrayResize(g_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"g_v_data\""); return false; } if(::ArrayResize(b_v_data,size)==-1) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); ::Print(DFUN_ERR_LINE,": \"b_v_data\""); return false; } //--- Declare the array for storing blur weight ratios and, //--- if failed to get the array of weight ratios, return 'false' double weights[]; if(!this.GetQuadratureWeights(1,n_nodes,weights)) return false; //--- Set components of each image pixel to the color component arrays for(int i=0;i<size;i++) { a_h_data[i]=GETRGBA(this.m_data_array[i]); r_h_data[i]=GETRGBR(this.m_data_array[i]); g_h_data[i]=GETRGBG(this.m_data_array[i]); b_h_data[i]=GETRGBB(this.m_data_array[i]); } //--- Blur the image horizontally (along the X axis) uint XY; // Pixel coordinate in the array double a_temp=0.0,r_temp=0.0,g_temp=0.0,b_temp=0.0; int coef=0; int j=(int)radius; //--- Loop by the image width for(int Y=0;Y<this.Height();Y++) { //--- Loop by the image height for(uint X=radius;X<this.Width()-radius;X++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { a_temp+=a_h_data[XY+i]*weights[coef]; r_temp+=r_h_data[XY+i]*weights[coef]; g_temp+=g_h_data[XY+i]*weights[coef]; b_temp+=b_h_data[XY+i]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_h_data[XY]=(uchar)::round(a_temp); r_h_data[XY]=(uchar)::round(r_temp); g_h_data[XY]=(uchar)::round(g_temp); b_h_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts to the left by copying adjacent pixels for(uint x=0;x<radius;x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[Y*this.Width()+radius]; r_h_data[XY]=r_h_data[Y*this.Width()+radius]; g_h_data[XY]=g_h_data[Y*this.Width()+radius]; b_h_data[XY]=b_h_data[Y*this.Width()+radius]; } //--- Remove blur artifacts to the right by copying adjacent pixels for(int x=int(this.Width()-radius);x<this.Width();x++) { XY=Y*this.Width()+x; a_h_data[XY]=a_h_data[(Y+1)*this.Width()-radius-1]; r_h_data[XY]=r_h_data[(Y+1)*this.Width()-radius-1]; g_h_data[XY]=g_h_data[(Y+1)*this.Width()-radius-1]; b_h_data[XY]=b_h_data[(Y+1)*this.Width()-radius-1]; } } //--- Blur vertically (along the Y axis) the image already blurred horizontally int dxdy=0; //--- Loop by the image height for(int X=0;X<this.Width();X++) { //--- Loop by the image width for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; a_temp=0.0; r_temp=0.0; g_temp=0.0; b_temp=0.0; coef=0; //--- Multiply each color component by the weight ratio corresponding to the current image pixel for(int i=-1*j;i<j+1;i=i+1) { dxdy=i*(int)this.Width(); a_temp+=a_h_data[XY+dxdy]*weights[coef]; r_temp+=r_h_data[XY+dxdy]*weights[coef]; g_temp+=g_h_data[XY+dxdy]*weights[coef]; b_temp+=b_h_data[XY+dxdy]*weights[coef]; coef++; } //--- Save each rounded color component calculated according to the ratios to the component arrays a_v_data[XY]=(uchar)::round(a_temp); r_v_data[XY]=(uchar)::round(r_temp); g_v_data[XY]=(uchar)::round(g_temp); b_v_data[XY]=(uchar)::round(b_temp); } //--- Remove blur artifacts at the top by copying adjacent pixels for(uint y=0;y<radius;y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+radius*this.Width()]; r_v_data[XY]=r_v_data[X+radius*this.Width()]; g_v_data[XY]=g_v_data[X+radius*this.Width()]; b_v_data[XY]=b_v_data[X+radius*this.Width()]; } //--- Remove blur artifacts at the bottom by copying adjacent pixels for(int y=int(this.Height()-radius);y<this.Height();y++) { XY=y*this.Width()+X; a_v_data[XY]=a_v_data[X+(this.Height()-1-radius)*this.Width()]; r_v_data[XY]=r_v_data[X+(this.Height()-1-radius)*this.Width()]; g_v_data[XY]=g_v_data[X+(this.Height()-1-radius)*this.Width()]; b_v_data[XY]=b_v_data[X+(this.Height()-1-radius)*this.Width()]; } } //--- Set the twice blurred (horizontally and vertically) image pixels to the graphical resource data array for(int i=0;i<size;i++) this.m_data_array[i]=ARGB(a_v_data[i],r_v_data[i],g_v_data[i],b_v_data[i]); //--- Display the image pixels on the canvas in a loop by the image height and width from the graphical resource data array for(int X=0;X<this.Width();X++) { for(uint Y=radius;Y<this.Height()-radius;Y++) { XY=Y*this.Width()+X; this.m_canvas.PixelSet(X,Y,this.m_data_array[XY]); } } //--- Done return true; } //+------------------------------------------------------------------+
これで、クラスを開発する準備が整いました。そのオブジェクトを使用すると、キャンバス上の任意のグラフィック要素の描画を管理できるため、後で新しい描画が重ねられた画像の背景を簡単に復元できます。さらに、これにより、スプライトアニメーションを操作するためのクラスを作成できるようになります。
画像の一部をコピーして貼り付けるためのクラス
フォームオブジェクトクラスは、継承階層内の最小オブジェクトであり、アニメーションを操作できるようにする必要があります。
画像の一部を保存および復元するためのクラスは小さいため、フォームオブジェクトクラスファイル\MQL5\Include\DoEasy\Objects\Graph\Form.mqhに直接配置します。クラスに「ピクセルコピー(pixel copier)」という名前を付けます。これは、その目的を明確に説明しています。
各ピクセルコピークラスオブジェクトには、オブジェクトが処理している画像を定義できるカスタムIDが必要です。必要なクラスオブジェクトをIDで参照して、アニメーション化された各オブジェクトを個別に処理できます。たとえば、3つの画像を同時に管理および変更する必要があり、そのうちの2つがテキストで1つが画像で、画像ごとにコピーオブジェクトを作成する場合、「text1 = ID0、 text2 = ID1、image = ID2」のように異なるIDを割り当てる必要があります—。この場合、各オブジェクトは、それを操作するための残りのすべてのパラメータを格納します。
- 画像が重ね合わされる背景の一部を格納するピクセルの配列
- 画像が重ね合わされる背景の長方形領域の左上隅のX座標とY座標
- 長方形の領域の幅と高さ
- 計算された領域の幅と高さ
長方形がピクセルを保存する必要のあるフォームの領域を超えた場合に、長方形のコピー領域の幅と高さを正確に知るために、計算された幅と高さが必要です。さらに、背景を復元するには、実際にコピーされた長方形の背景領域の幅と高さを再計算する必要はなくなり、オブジェクト変数に格納されている計算済みの値を使用するだけです。
クラスのprivateセクションで、グラフィック要素オブジェクトクラスへのポインタ(これを新しく作成したピクセルコピークラスオブジェクトに渡して、フォームのデータを使用できるようにします。このフォームで、コピーオブジェクトのインスタンスを作成します)、保存および復元する必要があるフォーム画像の一部を格納する配列、および上記のすべての変数を宣言します。
//+------------------------------------------------------------------+ //| Form.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GCnvElement.mqh" #include "ShadowObj.mqh" //+------------------------------------------------------------------+ //| Pixel copier class | //+------------------------------------------------------------------+ class CPixelCopier : public CObject { private: CGCnvElement *m_element; // Pointer to the graphical element uint m_array[]; // Pixel array int m_id; // ID int m_x; // X coordinate of the upper left corner int m_y; // Y coordinate of the upper left corner int m_w; // Copied image width int m_h; // Copied image height int m_wr; // Calculated copied image width int m_hr; // Calculated copied image height public:
クラスのpublicセクションに、 2つのコピーオブジェクトを比較するメソッド、オブジェクトプロパティを設定するメソッドと受け取るメソッド、デフォルトクラスコンストラクタとパラメトリックコンストラクタ、背景部分を保存および復元するための2つのメソッドを宣言します。
public: //--- Compare CPixelCopier objects by a specified property (to sort the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CPixelCopier *obj_compared=node; return(mode==0 ? (this.ID()>obj_compared.ID() ? 1 : this.ID()<obj_compared.ID() ? -1 : 0) : WRONG_VALUE); } //--- Set the properties void SetID(const int id) { this.m_id=id; } void SetCoordX(const int value) { this.m_x=value; } void SetCoordY(const int value) { this.m_y=value; } void SetWidth(const int value) { this.m_w=value; } void SetHeight(const int value) { this.m_h=value; } //--- Get the properties int ID(void) const { return this.m_id; } int CoordX(void) const { return this.m_x; } int CoordY(void) const { return this.m_y; } int Width(void) const { return this.m_w; } int Height(void) const { return this.m_h; } int WidthReal(void) const { return this.m_wr; } int HeightReal(void) const { return this.m_hr; } //--- Copy the part or the entire image to the array bool CopyImgDataToArray(const uint x_coord,const uint y_coord,uint width,uint height); //--- Copy the part or the entire image from the array to the canvas bool CopyImgDataToCanvas(const int x_coord,const int y_coord); //--- Constructors CPixelCopier (void){;} CPixelCopier (const int id, const int x, const int y, const int w, const int h, CGCnvElement *element) : m_id(id), m_x(x),m_y(y),m_w(w),m_wr(w),m_h(h),m_hr(h) { this.m_element=element; } ~CPixelCopier (void){;} }; //+------------------------------------------------------------------+
メソッドについて詳しく見ていきましょう。
以下は、2つのコピーオブジェクトを比較するメソッドです。
//--- Compare CPixelCopier objects by a specified property (to sort the list by an object property) virtual int Compare(const CObject *node,const int mode=0) const { const CPixelCopier *obj_compared=node; return(mode==0 ? (this.ID()>obj_compared.ID() ? 1 : this.ID()<obj_compared.ID() ? -1 : 0) : WRONG_VALUE); }
ここでは、他のライブラリクラスと同様に、すべてが標準です。比較モード(mode)が0(デフォルト)に等しい場合、現在のオブジェクトのIDと、メソッドに渡されるポインターであるオブジェクトのIDが比較されます。現在のオブジェクトIDがより大きい場合は1、より小さい場合は-1、等しい場合は-0が返されます。他のすべての場合(mode != 0の場合)、-1が返されます。現在、このメソッドはオブジェクトIDのみを比較できます。
パラメトリッククラスコンストラクタの初期化リストでは、引数で渡された値はすべてのクラスメンバ変数に割り当てられますが、クラス本体ではポインタ値はグラフィック要素オブジェクトクラスを指す変数に割り当てられます。 ポインタ値はまた引数で渡されます。
CPixelCopier (const int id, const int x, const int y, const int w, const int h, CGCnvElement *element) : m_id(id), m_x(x),m_y(y),m_w(w),m_wr(w),m_h(h),m_hr(h) { this.m_element=element; }
これで、新しく作成されたコピーオブジェクトは、どのオブジェクトがそれを作成したかを「認識」し、そのメソッドとパラメータにアクセスできるようになります。
以下は、画像の一部または全部を配列にコピーするメソッドです。
//+------------------------------------------------------------------+ //| Copy part or all of the image to the array | //+------------------------------------------------------------------+ bool CPixelCopier::CopyImgDataToArray(const uint x_coord,const uint y_coord,uint width,uint height) { //--- Assign coordinate values, passed to the method, to the variables int x1=(int)x_coord; int y1=(int)y_coord; //--- If X coordinates goes beyond the form on the right or Y coordinate goes beyond the form at the bottom, //--- there is nothing to copy, the copied area is outside the form. Return 'false' if(x1>this.m_element.Width()-1 || y1>this.m_element.Height()-1) return false; //--- Assign the width and height values of the copied area to the variables //--- If the passed width and height are equal to zero, assign the form width and height to them this.m_wr=int(width==0 ? this.m_element.Width() : width); this.m_hr=int(height==0 ? this.m_element.Height() : height); //--- If X and Y coordinates are equal to zero (the upper left corner of the form), as well as the width and height are equal to the form width and height, //--- the copied area is equal to the entire form area. Copy the entire form (returning it from the method) using the ImageCopy() method if(x1==0 && y1==0 && this.m_wr==this.m_element.Width() && this.m_hr==this.m_element.Height()) return this.m_element.ImageCopy(DFUN,this.m_array); //--- Calculate the right X coordinate and lower Y coordinate of the rectangle area int x2=int(x1+this.m_wr-1); int y2=int(y1+this.m_hr-1); //--- If the calculated X coordinate goes beyond the form, the right edge of the form will be used as the coordinate if(x2>=this.m_element.Width()-1) x2=this.m_element.Width()-1; //--- If the calculated Y coordinate goes beyond the form, the bottom edge of the form will be used as the coordinate if(y2>=this.m_element.Height()-1) y2=this.m_element.Height()-1; //--- Calculate the copied width and height this.m_wr=x2-x1+1; this.m_hr=y2-y1+1; //--- Define the necessary size of the array, which is to store all image pixels with calculated width and height int size=this.m_wr*this.m_hr; //--- If failed to set the array size, inform of that and return 'false' if(::ArrayResize(this.m_array,size)!=size) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE,true); return false; } //--- Set the index in the array for recording the image pixel int n=0; //--- In a loop by the calculated height of the copied area, starting from the specified Y coordinate for(int y=y1;y<y1+this.m_hr;y++) { //--- in a loop by the calculated width of the copied area, starting from the specified X coordinate for(int x=x1;x<x1+this.m_wr;x++) { //--- Copy the next image pixel to the array and increase the array index this.m_array[n]=this.m_element.GetCanvasObj().PixelGet(x,y); n++; } } //--- Successful - return 'true' return true; } //+------------------------------------------------------------------+
各メソッド文字列は、コードで詳細に説明されています。つまり、コピーされた領域の初期座標がフォームの外側にある場合、コピーするものはないので、falseを返します。コピーされた領域の初期座標がフォームの座標と一致し、コピーされた領域の幅と高さがゼロに等しいか、フォームの幅と高さに一致する場合、フォーム画像全体がコピーされます。画像の一部のみを保存する必要がある場合は、最初にコピーされた幅と高さを計算して、フォームを超えないようにし、コピーされた領域にあるすべてのフォーム画像ピクセルをコピーします。
以下は、画像の一部または全体を配列からキャンバスにコピーするメソッドです。
//+------------------------------------------------------------------+ //| Copy the part or the entire image from the array to the canvas | //+------------------------------------------------------------------+ bool CPixelCopier::CopyImgDataToCanvas(const int x_coord,const int y_coord) { //--- If the array of saved pixels is empty, inform of that and return 'false' int size=::ArraySize(this.m_array); if(size==0) { CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,true); return false; } //--- Set the index of the array for reading the image pixel int n=0; //--- In a loop by the previously calculated height of the copied area, starting from the specified Y coordinate for(int y=y_coord;y<y_coord+this.m_hr;y++) { //--- in a loop by the previously calculated width of the copied area, starting from the specified X coordinate for(int x=x_coord;x<x_coord+this.m_wr;x++) { //--- Restore the next image pixel from the array and increase the array index this.m_element.GetCanvasObj().PixelSet(x,y,this.m_array[n]); n++; } } return true; } //+------------------------------------------------------------------+
メソッドのロジックについては、コードコメントでも詳しく説明されています。 画像の一部を保存するメソッドとは異なり、コピーされた領域の座標とサイズを計算する必要はありません。これらはすべて、最初のメソッド操作後にクラス変数に保存されるためです。ここでは、復元された領域の各行を高さのループでピクセルごとにキャンバスにコピーするだけで、前のメソッドで保存された画像の一部を復元できます。
次に、フォームオブジェクトクラスから新しく作成されたクラスへのアクセスを調整します。
必要な数のコピーオブジェクトを動的に作成するので、フォームオブジェクトクラスでそのようなオブジェクトのリストを宣言する必要があります。新しく作成された各コピーオブジェクトがリストに追加され、そこから必要なオブジェクトへのポインタを取得して操作できるようになります。
クラスのprivateセクションで次のリストを宣言します。
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CArrayObj m_list_pc_obj; // List of pixel copier objects CShadowObj *m_shadow_obj; // Pointer to the shadow object color m_color_frame; // Form frame color int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom
同様のIDを持つ複数のコピーオブジェクトを持つことはできないため、指定されたIDを持つリスト内のオブジェクトプレゼンスフラグを返すメソッドが必要です。メソッドを宣言します。
//--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the flag indicating the presence of the copier object with the specified ID in the list bool IsPresentPC(const int id); public:
クラスのpublicセクションで、現在のフォームオブジェクトへのポインタを返すメソッドとコピーオブジェクトのリストを返すメソッドを記述します。
public: //--- Constructors CForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h); CForm(const string name, const int x, const int y, const int w, const int h); CForm() { this.Initialize(); } //--- Destructor ~CForm(); //--- Supported form properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return (1) itself, the list of (2) attached objects, (3) pixel copier objects and (4) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list_elements; } CArrayObj *GetListPC(void) { return &this.m_list_pc_obj; } CGCnvElement *GetShadowObj(void) { return this.m_shadow_obj; }
Next, declare the method creating a new image pixel copier object:
//--- Create a new pixel copier object CPixelCopier *CreateNewPixelCopier(const int id,const int x_coord,const int y_coord,const int width,const int height); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=4);
オブジェクトプロパティへの簡略化されたアクセスのメソッドを持つコードブロックの前に、画像ピクセルを操作するためのコードブロックを追加します。
//+------------------------------------------------------------------+ //| Methods of working with image pixels | //+------------------------------------------------------------------+ //--- Return the pixel copier object by ID CPixelCopier *GetPixelCopier(const int id); //--- Copy the part or the entire image to the array bool ImageCopy(const int id,const uint x_coord,const uint y_coord,uint &width,uint &height); //--- Copy the part or the entire image from the array to the canvas bool ImagePaste(const int id,const uint x_coord,const uint y_coord); //+------------------------------------------------------------------+
宣言されたメソッドをクラス本体の外側に実装します。
以下は、リスト内の指定されたIDを持つコピーオブジェクトの存在を示すフラグを返すメソッドです。
//+------------------------------------------------------------------+ //| Return the flag indicating the presence | //| of the copier object with the specified ID in the list | //+------------------------------------------------------------------+ bool CForm::IsPresentPC(const int id) { for(int i=0;i<this.m_list_pc_obj.Total();i++) { CPixelCopier *pc=this.m_list_pc_obj.At(i); if(pc==NULL) continue; if(pc.ID()==id) return true; } return false; } //+------------------------------------------------------------------+
ここでは、コピーオブジェクトのリストに対する単純なループで次のオブジェクトを取得します。そのIDがメソッドに渡されたものと等しい場合は、trueを返します。ループが完了したら、falseを返します。
以下は、新しい画像ピクセルコピーオブジェクトを作成するメソッドです。
//+------------------------------------------------------------------+ //| Create a new pixel copier object | //+------------------------------------------------------------------+ CPixelCopier *CForm::CreateNewPixelCopier(const int id,const int x_coord,const int y_coord,const int width,const int height) { //--- If the object with such an ID is already present, inform of that in the journal and return NULL if(this.IsPresentPC(id)) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST),(string)id); return NULL; } //--- Create a new copier object with the specified parameters CPixelCopier *pc=new CPixelCopier(id,x_coord,y_coord,width,height,CGCnvElement::GetObject()); //--- If failed to create an object, inform of that and return NULL if(pc==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ)); return NULL; } //--- If failed to add the created object to the list, inform of that, remove the object and return NULL if(!this.m_list_pc_obj.Add(pc)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST)," ID: ",id); delete pc; return NULL; } //--- Return the pointer to a newly created object return pc; } //+------------------------------------------------------------------+
メソッドロジック全体は、コードへのコメントで説明されています。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。
以下は、IDでピクセルコピーオブジェクトへのポインタを返すメソッドです。
//+------------------------------------------------------------------+ //| Return the pixel copier object by ID | //+------------------------------------------------------------------+ CPixelCopier *CForm::GetPixelCopier(const int id) { for(int i=0;i<this.m_list_pc_obj.Total();i++) { CPixelCopier *pc=m_list_pc_obj.At(i); if(pc==NULL) continue; if(pc.ID()==id) return pc; } return NULL; } //+------------------------------------------------------------------+
ここではすべてが簡単です。コピーのオブジェクトリストに対するループで、次のオブジェクトへのポインタを取得します。IDが必要なIDと一致する場合は、ポインタを返します。ループが完了した場合は指定されたIDのオブジェクトがリストに見つからないので、NULLを返します。
以下は、画像の一部または全部を配列にコピーするメソッドです。
//+------------------------------------------------------------------+ //| Copy part or all of the image to the array | //+------------------------------------------------------------------+ bool CForm::ImageCopy(const int id,const uint x_coord,const uint y_coord,uint &width,uint &height) { CPixelCopier *pc=this.GetPixelCopier(id); if(pc==NULL) { pc=this.CreateNewPixelCopier(id,x_coord,y_coord,width,height); if(pc==NULL) return false; } return pc.CopyImgDataToArray(x_coord,y_coord,width,height); } //+------------------------------------------------------------------+
ここでは、IDでコピーオブジェクトへのポインタを取得します。オブジェクトが見つからない場合は、そのことを通知してfalseを返します。オブジェクトへのポインタが正常に受信された場合は、上記で検討したコピーオブジェクトクラスのCopyImgDataToArray()メソッドの結果を返します。
以下は、画像の一部または全体を配列からキャンバスにコピーするメソッドです。
//+------------------------------------------------------------------+ //| Copy the part or the entire image from the array to the canvas | //+------------------------------------------------------------------+ bool CForm::ImagePaste(const int id,const uint x_coord,const uint y_coord) { CPixelCopier *pc=this.GetPixelCopier(id); if(pc==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST),(string)id); return false; } return pc.CopyImgDataToCanvas(x_coord,y_coord); } //+------------------------------------------------------------------+
メソッドのロジックは、領域を配列に保存せず、代わりに配列から復元することを除いて、上記で検討したものと同じです。
これで、画像ピクセルコピーオブジェクトの動作をテストする準備が整いました。
検証
ピクセルコピーオブジェクトが正しく機能することを確認しましょう。記事の冒頭にあるGIF画像は、形状オブジェクトの背景に対して描画された後続の各画像が、以前に描画された画像にどのように重ね合わされるかを明確に示しています。次に、ピクセルコピーを使用して、テキストを重ね合わせる背景を最初に保存する必要があります。新しいテキストを描画する(描画されたテキストを視覚的に再配置する)前に、まずテキストが描画される背景を復元し(テキストを上書き)、新しい座標を使用して画像の一部を保存し、そこに次のテキストを表示します。これは、異なるアンカーポイントを持ち、テキストアンカーポイントに対応するフォーム側に表示される9つの表示テキストのそれぞれに対して行われます。これにより、テキストの下に保存された画像パーツの座標オフセットの計算の妥当性を確認できます。
テストを実行するには、前の記事のEAを使用して、\MQL5\Experts\TestDoEasy\Part78\にTestDoEasyPart78.mq5として保存します。
EAはチャートに3つのフォームを表示します。縦のグラデーションで塗りつぶされた背景は、一番下のフォームで描画されます。ここでは、さらに4番目のフォームを描画し、水平方向のグラデーション塗りつぶしを実装します。テストされたテキストは、このフォームに表示されます。
EAグローバル変数の領域で、 4つのフォームオブジェクトを作成する必要があることを示します。
//+------------------------------------------------------------------+ //| TestDoEasyPart78.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <Arrays\ArrayObj.mqh> #include <DoEasy\Services\Select.mqh> #include <DoEasy\Objects\Graph\Form.mqh> //--- defines #define FORMS_TOTAL (4) // Number of created forms //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpUseColorBG = INPUT_YES; // Use chart background color to calculate shadow color sinput color InpColorForm3 = clrCadetBlue; // Third form shadow color (if not background color) //--- global variables CArrayObj list_forms; color array_clr[]; //+------------------------------------------------------------------+
b0>OnInit() ハンドラで、前のフォームの座標に応じて新しいフォームの座標を計算します。後続の各フォームを作成した後、チャート全体を再描画する必要はありません。したがって、指定された文字列のフォーム更新メソッドにremoveを渡します(以前は、trueを明示的に渡しました)。この値は、最後のフォームを作成した後、4番目のフォームを作成するための新しいコードブロックで最後に渡されます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables ArrayResize(array_clr,2); array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the specified number of form objects list_forms.Clear(); int total=FORMS_TOTAL; for(int i=0;i<total;i++) { int y=40; if(i>0) { CForm *form_prev=list_forms.At(i-1); if(form_prev==NULL) continue; y=form_prev.BottomEdge()+10; } //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+(string)(i+1),300,y,100,(i<2 ? 70 : 30)); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the form ID equal to the loop index and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the partial opacity for the middle form and the full one for the rest uchar opacity=(i==1 ? 250 : 255); //--- Set the form style and its color theme depending on the loop index if(i<2) { ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i; ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i; //--- Set the form style and theme form.SetFormStyle(style,theme,opacity,true,false); } //--- If this is the first (top) form if(i==0) { //--- Draw a concave field slightly shifted from the center of the form downwards form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity()); form.Update(); } //--- If this is the second form if(i==1) { //--- Draw a concave semi-transparent "tainted glass" field in the center form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200); form.Update(); } //--- If this is the third form if(i==2) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity()); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); //--- Display the text describing the gradient type and update the form form.Text(form.Width()/2,form.Height()/2,TextByLanguage("V-Градиент","V-Gradient"),C'211,233,149',255,TEXT_ANCHOR_CENTER); form.Update(); } //--- If this is the fourth (bottom - tested) form if(i==3) { //--- Set the opacity of 200 form.SetOpacity(200); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(true); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a horizontal gradient form.Erase(array_clr,form.Opacity(),false); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); //--- Display the text describing the gradient type and update the form //--- Specify the text parameters (text coordinates in the center of the form) and the anchor point (located at the center as well) string text=TextByLanguage("H-Градиент","H-Gradient"); int text_x=form.Width()/2; int text_y=form.Height()/2; ENUM_TEXT_ANCHOR anchor=TEXT_ANCHOR_CENTER; //--- Find out the width and height of the outlining text rectangle (to be used as the size of the saved area) int text_w=0,text_h=0; form.TextSize(text,text_w,text_h); //--- Calculate coordinate offsets for the saved area depending on the text anchor point int shift_x=0,shift_y=0; form.TextGetShiftXY(text,anchor,shift_x,shift_y); //--- If a background area with calculated coordinates and size under the future text is successfully saved if(form.ImageCopy(0,text_x+shift_x,text_y+shift_y,text_w,text_h)) { //--- Draw the text and update the form together with redrawing a chart form.Text(text_x,text_y,text,C'211,233,149',255,anchor); form.Update(true); } } //--- Add objects to the list if(!list_forms.Add(form)) { delete form; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
新しいフォーム作成コード文字列にはそれぞれ、ここに詳細なコメントが付いています。フォームを作成した後、その上にテキストを描画する前に、テキストを配置する背景領域を保存する必要があります。その後、別のハンドラで、最初にテキストを上書きしてフォームの背景を復元し、同じ方法で背景を保持した新しい場所にテキストを表示し、テキストが新しい座標に移動するたびに復元します。
これはすべて、新しいコードブロックのOnChartEvent()ハンドラで行われます。
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If clicking on an object if(id==CHARTEVENT_OBJECT_CLICK) { //--- If the clicked object belongs to the EA if(StringFind(sparam,MQLInfoString(MQL_PROGRAM_NAME))==0) { //--- Get the object ID from it int form_id=(int)StringToInteger(StringSubstr(sparam,StringLen(sparam)-1))-1; //--- Find this form object in the loop by all forms created in the EA for(int i=0;i<list_forms.Total();i++) { CForm *form=list_forms.At(i); if(form==NULL) continue; //--- If the clicked object has the ID of 3 and the form has the same ID if(form_id==3 && form.ID()==3) { //--- Set the text parameters string text=TextByLanguage("H-Градиент","H-Gradient"); //--- Get the size of the future text int text_w=0,text_h=0; form.TextSize(text,text_w,text_h); //--- Get the anchor point of the last drawn text (this is the form which contained the last drawn text) ENUM_TEXT_ANCHOR anchor=form.TextAnchor(); //--- Get the coordinates of the last drawn text int text_x=form.TextLastX(); int text_y=form.TextLastY(); //--- Calculate the coordinate offset of the saved rectangle area depending on the text anchor point int shift_x=0,shift_y=0; form.TextGetShiftXY(text,anchor,shift_x,shift_y); //--- Set the text anchor initial point (0 = LEFT_TOP) out of nine possible ones static int n=0; //--- If the previously copied form background image is successfully restored when creating the form object in OnInit() if(form.ImagePaste(0,text_x+shift_x,text_y+shift_y)) { //--- Depending on the n variable, set the new text anchor point switch(n) { case 0 : anchor=TEXT_ANCHOR_LEFT_TOP; text_x=1; text_y=1; break; case 1 : anchor=TEXT_ANCHOR_CENTER_TOP; text_x=form.Width()/2; text_y=1; break; case 2 : anchor=TEXT_ANCHOR_RIGHT_TOP; text_x=form.Width()-2; text_y=1; break; case 3 : anchor=TEXT_ANCHOR_LEFT_CENTER; text_x=1; text_y=form.Height()/2; break; case 4 : anchor=TEXT_ANCHOR_CENTER; text_x=form.Width()/2; text_y=form.Height()/2; break; case 5 : anchor=TEXT_ANCHOR_RIGHT_CENTER; text_x=form.Width()-2; text_y=form.Height()/2; break; case 6 : anchor=TEXT_ANCHOR_LEFT_BOTTOM; text_x=1; text_y=form.Height()-2; break; case 7 : anchor=TEXT_ANCHOR_CENTER_BOTTOM;text_x=form.Width()/2; text_y=form.Height()-2; break; case 8 : anchor=TEXT_ANCHOR_RIGHT_BOTTOM; text_x=form.Width()-2; text_y=form.Height()-2; break; default: anchor=TEXT_ANCHOR_CENTER; text_x=form.Width()/2; text_y=form.Height()/2; break; } //--- According to the new anchor point, get the new offsets of the saved area coordinates form.TextGetShiftXY(text,anchor,shift_x,shift_y); //--- If the background area is successfully saved at new coordinates if(form.ImageCopy(0,text_x+shift_x,text_y+shift_y,text_w,text_h)) { //--- Draw the text in new coordinates and update the form form.Text(text_x,text_y,text,C'211,233,149',255,anchor); form.Update(); } //--- Increase the object click counter (and also the pointer to the text anchor point), //--- and if the value exceeds 8, reset the value to zero (from 0 to 8 = nine anchor points) n++; if(n>8) n=0; } } } } } } //+------------------------------------------------------------------+
コードコメントで詳細な説明を見つけてください。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。
EAをコンパイルし、チャート上で起動します。
下部のフォームをクリックして、すべてが意図したとおりに機能することを確認しましょう。
次の段階
次の記事では、ライブラリでアニメーションコンセプトの開発を続け、スプライトアニメーションの作業を開始します。
ライブラリの現在のバージョンのすべてのファイルは、テストおよびダウンロードできるように、MQL5のテストEAファイルと一緒に以下に添付されています。
質問や提案はコメント欄にお願いします。
**連載のこれまでの記事:
DoEasyライブラリのグラフィックス(第73部): グラフィック要素のフォームオブジェクト
DoEasyライブラリのグラフィックス(第74部): CCanvasクラスを使用した基本的グラフィック要素
DoEasyライブラリのグラフィックス(第75部): 基本的なグラフィック要素でプリミティブとテキストを処理するメソッド
DoEasyライブラリのグラフィックス(第76部): フォームオブジェクトと事前定義されたカラースキーム
DoEasyライブラリのグラフィックス(第77部): 影オブジェクトクラス
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/9612
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索